From d0e3e32c6fc50d46287ab2b83458eec3f3f6e831 Mon Sep 17 00:00:00 2001 From: qer Date: Sun, 28 Jun 2026 00:48:31 +0800 Subject: [PATCH] backup: full worktree implementation --- apps/kimi-web/src/App.vue | 119 +++- apps/kimi-web/src/api/daemon/client.ts | 54 ++ apps/kimi-web/src/api/daemon/eventReducer.ts | 5 +- apps/kimi-web/src/api/daemon/mappers.ts | 26 + apps/kimi-web/src/api/daemon/wire.ts | 15 + apps/kimi-web/src/api/types.ts | 36 ++ apps/kimi-web/src/components/Sidebar.vue | 499 +++++------------ .../kimi-web/src/components/WorktreeBoard.vue | 361 +++++++++++++ .../kimi-web/src/components/WorktreeGroup.vue | 288 ++++++++++ .../src/components/chat/OpenInMenu.vue | 3 + .../components/dialogs/NewWorktreeDialog.vue | 249 +++++++++ .../dialogs/WorktreeCompleteDialog.vue | 196 +++++++ .../src/components/dialogs/WorktreeDialog.vue | 433 +++++++++++++++ .../composables/client/useWorkspaceState.ts | 88 ++- .../src/composables/useKimiWebClient.ts | 308 ++++++++++- apps/kimi-web/src/i18n/locales/en/worktree.ts | 42 ++ apps/kimi-web/src/i18n/locales/index.ts | 4 + apps/kimi-web/src/i18n/locales/zh/worktree.ts | 41 ++ apps/kimi-web/src/lib/branchColor.ts | 22 + apps/kimi-web/src/lib/snapshotSync.ts | 35 ++ apps/kimi-web/src/lib/worktreeGroups.ts | 56 ++ apps/kimi-web/src/types.ts | 55 ++ apps/kimi-web/test/lib-logic.test.ts | 168 ++++++ packages/agent-core/src/services/index.ts | 9 + .../src/services/worktree/worktree.ts | 63 +++ .../src/services/worktree/worktreeService.ts | 508 ++++++++++++++++++ .../test/services/worktree-service.test.ts | 127 +++++ packages/protocol/src/error-codes.ts | 6 + packages/protocol/src/events.ts | 11 + packages/protocol/src/index.ts | 2 + packages/protocol/src/rest/worktree.ts | 80 +++ packages/protocol/src/worktree.ts | 38 ++ .../server/src/routes/registerApiV1Routes.ts | 5 + packages/server/src/routes/worktrees.ts | 209 +++++++ .../services/gateway/wsBroadcastService.ts | 3 +- snapshot-sync-logic.html | 278 ++++++++++ worktree-board-layouts.html | 233 ++++++++ worktree-marker-options.html | 300 +++++++++++ worktree-sidebar.html | 143 +++++ worktree-ux-mockups.html | 260 +++++++++ 40 files changed, 4954 insertions(+), 424 deletions(-) create mode 100644 apps/kimi-web/src/components/WorktreeBoard.vue create mode 100644 apps/kimi-web/src/components/WorktreeGroup.vue create mode 100644 apps/kimi-web/src/components/dialogs/NewWorktreeDialog.vue create mode 100644 apps/kimi-web/src/components/dialogs/WorktreeCompleteDialog.vue create mode 100644 apps/kimi-web/src/components/dialogs/WorktreeDialog.vue create mode 100644 apps/kimi-web/src/i18n/locales/en/worktree.ts create mode 100644 apps/kimi-web/src/i18n/locales/zh/worktree.ts create mode 100644 apps/kimi-web/src/lib/branchColor.ts create mode 100644 apps/kimi-web/src/lib/snapshotSync.ts create mode 100644 apps/kimi-web/src/lib/worktreeGroups.ts create mode 100644 packages/agent-core/src/services/worktree/worktree.ts create mode 100644 packages/agent-core/src/services/worktree/worktreeService.ts create mode 100644 packages/agent-core/test/services/worktree-service.test.ts create mode 100644 packages/protocol/src/rest/worktree.ts create mode 100644 packages/protocol/src/worktree.ts create mode 100644 packages/server/src/routes/worktrees.ts create mode 100644 snapshot-sync-logic.html create mode 100644 worktree-board-layouts.html create mode 100644 worktree-marker-options.html create mode 100644 worktree-sidebar.html create mode 100644 worktree-ux-mockups.html diff --git a/apps/kimi-web/src/App.vue b/apps/kimi-web/src/App.vue index 176910afa..cc9bbb79a 100644 --- a/apps/kimi-web/src/App.vue +++ b/apps/kimi-web/src/App.vue @@ -17,6 +17,9 @@ import NewSessionDialog from './components/dialogs/NewSessionDialog.vue'; import SettingsDialog from './components/settings/SettingsDialog.vue'; import SessionsDialog from './components/dialogs/SessionsDialog.vue'; import AddWorkspaceDialog from './components/dialogs/AddWorkspaceDialog.vue'; +import WorktreeBoard from './components/WorktreeBoard.vue'; +import WorktreeCompleteDialog from './components/dialogs/WorktreeCompleteDialog.vue'; +import NewWorktreeDialog from './components/dialogs/NewWorktreeDialog.vue'; import StatusPanel from './components/chat/StatusPanel.vue'; import WarningToasts from './components/WarningToasts.vue'; import MobileTopBar from './components/mobile/MobileTopBar.vue'; @@ -34,6 +37,7 @@ import { useFilePreview, type DetailTarget } from './composables/useFilePreview' import { useDetailPanel } from './composables/useDetailPanel'; import { useIsMobile } from './composables/useIsMobile'; import type { AppConfig, ThinkingLevel } from './api/types'; +import type { BoardSection, CompleteWorktreeTarget, WorkspaceView } from './types'; const client = useKimiWebClient(); provide('resolveImage', client.resolveImageUrl); @@ -214,6 +218,19 @@ const showSessions = ref(false); const showAddWorkspace = ref(false); const showStatusPanel = ref(false); const showSettings = ref(false); +const mainView = ref<'chat' | 'board'>('chat'); +const boardLoading = ref(false); +// Global board: one section per git workspace, each carrying its worktrees and +// sessions. Recomputed reactively as worktrees load or change. +const boardSections = computed(() => + client.workspacesView.value + .filter((ws) => ws.isGitRepo === true) + .map((ws) => ({ + workspace: ws, + worktrees: client.worktreesByWorkspace.value[ws.id] ?? [], + sessions: client.workspaceGroups.value.find((g) => g.workspace.id === ws.id)?.sessions ?? [], + })), +); type SubmitPayload = { text: string; @@ -235,6 +252,7 @@ const anyOverlayOpen = computed(() => showStatusPanel.value || showSettings.value || showOnboarding.value || + showNewWorktree.value || showMobileSwitcher.value || showMobileSettings.value, ); @@ -500,6 +518,63 @@ function handleCreateSessionInWorkspace(workspaceId: string): void { function openPr(url: string): void { if (url) window.open(url, '_blank', 'noopener'); } + +// Sidebar worktree button (per-workspace) also opens the global board. +function handleManageWorktrees(_workspace: WorkspaceView): void { + void openGlobalBoard(); +} + +// Open the global worktree board: load worktrees for every git workspace, then +// show the board. Per-worktree loads are independent and best-effort. +async function openGlobalBoard(): Promise { + mainView.value = 'board'; + boardLoading.value = true; + try { + const gitWs = client.workspacesView.value.filter((w) => w.isGitRepo === true); + await Promise.allSettled(gitWs.map((w) => client.loadWorktrees(w.id))); + } finally { + boardLoading.value = false; + } +} + +// Board "+ session" on a column: open a draft session scoped to that worktree, +// then return to the chat so the user can type the first message. +function handleOpenWorktreeFromBoard(workspaceId: string, path: string): void { + client.openWorkspaceDraft(workspaceId, path); + mainView.value = 'chat'; +} + +// Sidebar "+ session" on a worktree group: open a draft session scoped to that +// worktree checkout. The chat is already showing, so no view switch is needed. +function handleNewSessionInWorktree(workspaceId: string, path: string): void { + client.openWorkspaceDraft(workspaceId, path); +} + +// Open a worktree folder in an external application (Cursor, VS Code, Finder, etc.). +function handleOpenWorktreeInApp(workspaceId: string, path: string, appId: string): void { + void client.openWorktreeInApp(workspaceId, appId, path); +} + +// "Complete" a worktree — opens a confirmation dialog; the dialog itself calls +// removeWorktree and reports errors, then closes on success. +const completeTarget = ref(null); + +function handleCompleteWorktree(target: CompleteWorktreeTarget): void { + completeTarget.value = target; +} + +// Quick-create a worktree and jump straight into a draft session in it. +const showNewWorktree = ref(false); + +function handleOpenNewWorktree(): void { + showNewWorktree.value = true; +} + +function handleNewWorktreeCreated(workspaceId: string, path: string): void { + showNewWorktree.value = false; + mainView.value = 'chat'; + client.openWorkspaceDraft(workspaceId, path); +}