diff --git a/.changeset/fix-web-add-workspace-invalid-path.md b/.changeset/fix-web-add-workspace-invalid-path.md new file mode 100644 index 000000000..db33a2a52 --- /dev/null +++ b/.changeset/fix-web-add-workspace-invalid-path.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Fix adding a workspace by path in the web UI failing silently when the daemon rejects the path; it now shows an error instead of a broken workspace. diff --git a/apps/kimi-web/src/App.vue b/apps/kimi-web/src/App.vue index 8b43e4936..f4b687197 100644 --- a/apps/kimi-web/src/App.vue +++ b/apps/kimi-web/src/App.vue @@ -244,6 +244,10 @@ type SubmitPayload = { attachments: { fileId: string; kind: 'image' | 'video' }[]; }; const pendingWorkspaceSubmit = ref(null); +// Inline error shown inside the add-workspace picker after the daemon rejects +// a path. Kept separate from the global toast so the feedback is visible above +// the picker's backdrop and persists until the user retries or closes. +const addWorkspaceError = ref(null); // Any of these modal/overlay layers, when open, owns Escape. The global // capture-phase handler must NOT close a background side panel out from under an @@ -483,8 +487,17 @@ async function handleSubmit(payload: SubmitPayload): Promise { } async function handleAddWorkspace(root: string): Promise { + addWorkspaceError.value = null; + const added = await client.addWorkspaceByPath(root); + // Keep the picker open (and the pending submission intact) when the daemon + // rejects the path so the user can retry with a valid one. The error is shown + // inline in the picker. Closing via Escape goes through handleCloseAddWorkspace, + // which drops the pending prompt. + if (!added) { + addWorkspaceError.value = t('workspace.addFailed'); + return; + } showAddWorkspace.value = false; - await client.addWorkspaceByPath(root); const pending = pendingWorkspaceSubmit.value; pendingWorkspaceSubmit.value = null; const wsId = client.activeWorkspaceId.value; @@ -495,6 +508,7 @@ async function handleAddWorkspace(root: string): Promise { function handleCloseAddWorkspace(): void { pendingWorkspaceSubmit.value = null; + addWorkspaceError.value = null; showAddWorkspace.value = false; } @@ -881,6 +895,7 @@ function openPr(url: string): void { :browse-fs="client.browseFs" :get-fs-home="client.getFsHome" :default-path="client.visibleWorkspace.value?.root ?? client.status.value.cwd" + :error="addWorkspaceError" @add="handleAddWorkspace($event)" @close="handleCloseAddWorkspace" /> diff --git a/apps/kimi-web/src/api/daemon/client.ts b/apps/kimi-web/src/api/daemon/client.ts index c855d7c0d..f47bdb3ba 100644 --- a/apps/kimi-web/src/api/daemon/client.ts +++ b/apps/kimi-web/src/api/daemon/client.ts @@ -973,8 +973,8 @@ export class DaemonKimiWebApi implements KimiWebApi { /** * Register a workspace by folder path. - * PRESUMED — POST /api/v1/workspaces { root, name? }. On error this throws so - * the composable can fall back to a locally-derived workspace from the path. + * PRESUMED — POST /api/v1/workspaces { root, name? }. Throws on error (e.g. + * path not found) so the caller can surface it to the user. */ async addWorkspace(input: { root: string; name?: string }): Promise { const body: Record = { root: input.root }; diff --git a/apps/kimi-web/src/components/dialogs/AddWorkspaceDialog.vue b/apps/kimi-web/src/components/dialogs/AddWorkspaceDialog.vue index 53291ddd1..1eb6598f9 100644 --- a/apps/kimi-web/src/components/dialogs/AddWorkspaceDialog.vue +++ b/apps/kimi-web/src/components/dialogs/AddWorkspaceDialog.vue @@ -16,6 +16,8 @@ const props = defineProps<{ getFsHome: () => Promise<{ home: string; recentRoots: string[] }>; /** Where the browser opens by default — the path kimi-web is working in. */ defaultPath?: string; + /** Inline error from a failed add attempt (e.g. daemon rejected the path). */ + error?: string | null; }>(); const emit = defineEmits<{ @@ -336,6 +338,10 @@ onUnmounted(() => { + + +