From fa1dd21579aaf4c0a50633457298969246a13acb Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 5 May 2026 16:31:00 -0400 Subject: [PATCH 1/8] fix(core): filter only connected workspaces in dialog --- .../tui/component/dialog-workspace-create.tsx | 30 ++++++++++----- .../cmd/tui/dialog-workspace-create.test.ts | 38 +++++++++++++++++++ 2 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index e372c59b996b..37e88c5e4c57 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -33,6 +33,22 @@ export type WorkspaceSelection = type WorkspaceSelectValue = WorkspaceSelection | { type: "existing-list" } type ExistingWorkspaceSelectValue = { workspace: Workspace } +export function recentConnectedWorkspaces(input: { + sessions: readonly { workspaceID?: string; time: { updated: number } }[] + get: (workspaceID: string) => WorkspaceInfo | undefined + status: (workspaceID: string) => string | undefined + limit?: number +}) { + return input.sessions + .toSorted((a, b) => b.time.updated - a.time.updated) + .flatMap((session) => { + const workspace = session.workspaceID ? input.get(session.workspaceID) : undefined + return workspace && input.status(workspace.id) === "connected" ? [workspace] : [] + }) + .filter((workspace, index, list) => list.findIndex((item) => item.id === workspace.id) === index) + .slice(0, input.limit ?? 3) +} + async function loadWorkspaceAdapters(input: { sdk: ReturnType sync: ReturnType @@ -125,15 +141,11 @@ export function DialogWorkspaceSelect(props: { const options = createMemo[]>(() => { const list = adapters() if (!list) return [] - const recent = sync.data.session - .toSorted((a, b) => b.time.updated - a.time.updated) - .flatMap((session) => (session.workspaceID ? [session.workspaceID] : [])) - .filter((workspaceID, index, list) => list.indexOf(workspaceID) === index) - .flatMap((workspaceID) => { - const workspace = project.workspace.get(workspaceID) - return workspace && project.workspace.status(workspace.id) === "connected" ? [workspace] : [] - }) - .slice(0, 3) + const recent = recentConnectedWorkspaces({ + sessions: sync.data.session, + get: project.workspace.get, + status: project.workspace.status, + }) return [ ...list.map((adapter) => ({ title: adapter.name, diff --git a/packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts b/packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts new file mode 100644 index 000000000000..8f6bc51b0dd6 --- /dev/null +++ b/packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, test } from "bun:test" +import { recentConnectedWorkspaces } from "../../../../src/cli/cmd/tui/component/dialog-workspace-create" + +describe("recentConnectedWorkspaces", () => { + test("returns unique connected workspaces after filtering missing and inactive entries", () => { + const workspaces = [ + { id: "wrk_a", name: "alpha" }, + { id: "wrk_b", name: "beta" }, + { id: "wrk_c", name: "gamma" }, + { id: "wrk_d", name: "delta" }, + { id: "wrk_e", name: "epsilon" }, + ] + const status = { + wrk_a: "connected", + wrk_b: "disconnected", + wrk_c: "error", + wrk_d: "connected", + wrk_e: "connected", + } as const + + expect( + recentConnectedWorkspaces({ + sessions: [ + { time: { updated: 900 } }, + { workspaceID: "wrk_b", time: { updated: 800 } }, + { workspaceID: "wrk_a", time: { updated: 700 } }, + { workspaceID: "wrk_a", time: { updated: 600 } }, + { workspaceID: "wrk_missing", time: { updated: 500 } }, + { workspaceID: "wrk_c", time: { updated: 400 } }, + { workspaceID: "wrk_d", time: { updated: 300 } }, + { workspaceID: "wrk_e", time: { updated: 200 } }, + ], + get: (workspaceID) => workspaces.find((workspace) => workspace.id === workspaceID), + status: (workspaceID) => status[workspaceID as keyof typeof status], + }).map((workspace) => workspace.id), + ).toEqual(["wrk_a", "wrk_d", "wrk_e"]) + }) +}) From e55c52aa7fc9768c94bba02473c65d509ba3d666 Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 5 May 2026 16:36:58 -0400 Subject: [PATCH 2/8] hide more if there are no more --- .../tui/component/dialog-workspace-create.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index 37e88c5e4c57..73d25a3eb203 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -39,14 +39,16 @@ export function recentConnectedWorkspaces( status: (workspaceID: string) => string | undefined limit?: number }) { - return input.sessions + const workspaces = input.sessions .toSorted((a, b) => b.time.updated - a.time.updated) .flatMap((session) => { const workspace = session.workspaceID ? input.get(session.workspaceID) : undefined return workspace && input.status(workspace.id) === "connected" ? [workspace] : [] }) .filter((workspace, index, list) => list.findIndex((item) => item.id === workspace.id) === index) - .slice(0, input.limit ?? 3) + const recent = workspaces.slice(0, input.limit ?? 3) + + return { recent, hasMore: recent.length < workspaces.length } } async function loadWorkspaceAdapters(input: { @@ -141,7 +143,7 @@ export function DialogWorkspaceSelect(props: { const options = createMemo[]>(() => { const list = adapters() if (!list) return [] - const recent = recentConnectedWorkspaces({ + const { recent, hasMore } = recentConnectedWorkspaces({ sessions: sync.data.session, get: project.workspace.get, status: project.workspace.status, @@ -170,13 +172,15 @@ export function DialogWorkspaceSelect(props: { }, category: "Choose workspace", })), - { - title: "View all workspaces", - value: { type: "existing-list" as const }, - description: "Choose from all workspaces", - category: "Choose workspace", - }, - ] + hasMore + ? { + title: "View all workspaces", + value: { type: "existing-list" as const }, + description: "Choose from all workspaces", + category: "Choose workspace", + } + : null, + ].filter(Boolean) }) if (!adapters()) return null From 181f6c5e4bbb5c342df1841937dfaf62de54250d Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 5 May 2026 16:41:30 -0400 Subject: [PATCH 3/8] fix test --- .../cmd/tui/dialog-workspace-create.test.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts b/packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts index 8f6bc51b0dd6..7d051923f69a 100644 --- a/packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts +++ b/packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts @@ -18,21 +18,21 @@ describe("recentConnectedWorkspaces", () => { wrk_e: "connected", } as const - expect( - recentConnectedWorkspaces({ - sessions: [ - { time: { updated: 900 } }, - { workspaceID: "wrk_b", time: { updated: 800 } }, - { workspaceID: "wrk_a", time: { updated: 700 } }, - { workspaceID: "wrk_a", time: { updated: 600 } }, - { workspaceID: "wrk_missing", time: { updated: 500 } }, - { workspaceID: "wrk_c", time: { updated: 400 } }, - { workspaceID: "wrk_d", time: { updated: 300 } }, - { workspaceID: "wrk_e", time: { updated: 200 } }, - ], - get: (workspaceID) => workspaces.find((workspace) => workspace.id === workspaceID), - status: (workspaceID) => status[workspaceID as keyof typeof status], - }).map((workspace) => workspace.id), - ).toEqual(["wrk_a", "wrk_d", "wrk_e"]) + const { recent } = recentConnectedWorkspaces({ + sessions: [ + { time: { updated: 900 } }, + { workspaceID: "wrk_b", time: { updated: 800 } }, + { workspaceID: "wrk_a", time: { updated: 700 } }, + { workspaceID: "wrk_a", time: { updated: 600 } }, + { workspaceID: "wrk_missing", time: { updated: 500 } }, + { workspaceID: "wrk_c", time: { updated: 400 } }, + { workspaceID: "wrk_d", time: { updated: 300 } }, + { workspaceID: "wrk_e", time: { updated: 200 } }, + ], + get: (workspaceID) => workspaces.find((workspace) => workspace.id === workspaceID), + status: (workspaceID) => status[workspaceID as keyof typeof status], + }) + + expect(recent.map((workspace) => workspace.id)).toEqual(["wrk_a", "wrk_d", "wrk_e"]) }) }) From c1b05c95a7beaae9c9eae9a198767f35321420ac Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 5 May 2026 16:43:25 -0400 Subject: [PATCH 4/8] fix type --- .../tui/component/dialog-workspace-create.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index 73d25a3eb203..355f308242bd 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -172,15 +172,17 @@ export function DialogWorkspaceSelect(props: { }, category: "Choose workspace", })), - hasMore - ? { - title: "View all workspaces", - value: { type: "existing-list" as const }, - description: "Choose from all workspaces", - category: "Choose workspace", - } - : null, - ].filter(Boolean) + ...(hasMore + ? [ + { + title: "View all workspaces", + value: { type: "existing-list" as const }, + description: "Choose from all workspaces", + category: "Choose workspace", + }, + ] + : []), + ] }) if (!adapters()) return null From a221a11a2a99836ce63b5ab8691e1999d31b0166 Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 5 May 2026 16:49:50 -0400 Subject: [PATCH 5/8] fix warping from workspace to local project --- .../cli/cmd/tui/component/dialog-workspace-create.tsx | 2 +- packages/sdk/openapi.json | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index 355f308242bd..768f4c2875c3 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -95,7 +95,7 @@ export async function warpWorkspaceSession(input: { }): Promise { const result = await input.sdk.client.experimental.workspace .warp({ - id: input.workspaceID ?? undefined, + id: input.workspaceID, sessionID: input.sessionID, }) .catch(() => undefined) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index fea9dd5a958b..007da602690f 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -8405,7 +8405,14 @@ "type": "object", "properties": { "id": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] }, "sessionID": { "type": "string" From b446e6b03ee9a7cec5250322241bb87447c160f3 Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 5 May 2026 16:54:02 -0400 Subject: [PATCH 6/8] add plant watering placeholder --- packages/opencode/src/cli/cmd/tui/routes/home.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index 43a52082beb5..07f5041acafc 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -13,7 +13,7 @@ import { useEditorContext } from "@tui/context/editor" let once = false const placeholder = { - normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"], + normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests", "go water your plants"], shell: ["ls -la", "git status", "pwd"], } From 94d8a9d6e57c08f6d16228957aea90c6a3c9c459 Mon Sep 17 00:00:00 2001 From: James Long Date: Wed, 6 May 2026 10:09:43 -0400 Subject: [PATCH 7/8] inject synthetic reminder when warping --- .../tui/component/dialog-workspace-create.tsx | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index 768f4c2875c3..157ca2058231 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -51,6 +51,10 @@ export function recentConnectedWorkspaces( return { recent, hasMore: recent.length < workspaces.length } } +export function warpReminderText(dir: string) { + return `The user has changed the current working directory to "${dir}". This is still the same project but at a possibly new location; take this into account when working with any files from now on.` +} + async function loadWorkspaceAdapters(input: { sdk: ReturnType sync: ReturnType @@ -111,10 +115,30 @@ export async function warpWorkspaceSession(input: { await input.sync.bootstrap({ fatal: false }).catch(() => undefined) + const dir = input.project.instance.directory() || input.sync.path.directory + if (dir) { + await input.sdk.client.session + .promptAsync({ + sessionID: input.sessionID, + workspace: input.workspaceID ?? undefined, + noReply: true, + parts: [ + { + type: "text", + text: warpReminderText(dir), + synthetic: true, + }, + ], + }) + .catch(() => undefined) + } + await Promise.all([input.project.workspace.sync(), input.sync.session.refresh()]) - input.done?.() - if (input.done) return true + if (input.done) { + input.done() + return true + } input.dialog.clear() return true } From f9804e8358af867039b81b18c67f2e1f1ae1f725 Mon Sep 17 00:00:00 2001 From: James Long Date: Wed, 6 May 2026 10:10:42 -0400 Subject: [PATCH 8/8] revert change from demo --- packages/opencode/src/cli/cmd/tui/routes/home.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index 07f5041acafc..43a52082beb5 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -13,7 +13,7 @@ import { useEditorContext } from "@tui/context/editor" let once = false const placeholder = { - normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests", "go water your plants"], + normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"], shell: ["ls -la", "git status", "pwd"], }