diff --git a/packages/tui/src/component/dialog-open.tsx b/packages/tui/src/component/dialog-open.tsx index 1926dabbc4f..2102ea99ec5 100644 --- a/packages/tui/src/component/dialog-open.tsx +++ b/packages/tui/src/component/dialog-open.tsx @@ -51,6 +51,17 @@ export function DialogOpen() { .catch(() => [] as SessionInfo[]), { initialValue: [] }, ) + const [matched] = createResource( + () => { + const value = filter().trim() + return /^ses_[0-9A-Za-z]{26}$/.test(value) ? value : undefined + }, + (sessionID) => + client.api.session + .get({ sessionID }) + .then((session) => (session.id === sessionID ? session : undefined)) + .catch(() => undefined), + ) const openTabs = createMemo( () => new Set(sessionTabs.enabled() ? sessionTabs.tabs().map((tab) => tab.sessionID) : []), @@ -60,7 +71,8 @@ export function DialogOpen() { ) const sessions = createMemo(() => { const seen = new Set() - return [...data.session.list(), ...fetched()] + const match = matched() + return [...data.session.list(), ...fetched(), ...(match ? [match] : [])] .filter((session) => { if (session.parentID || seen.has(session.id)) return false seen.add(session.id) @@ -87,6 +99,7 @@ export function DialogOpen() { data.session.family(session.id).some((id) => data.session.status(id) === "running") return { title: withTimestampedFallback(session), + searchText: session.id, value: { type: "session", sessionID: session.id } as OpenTarget, category: "Sessions", footer: `${name ? `${Locale.truncate(name, 20)} ยท ` : ""}${timeAgo(session.time.updated)}`, diff --git a/packages/tui/test/cli/tui/dialog-open.test.tsx b/packages/tui/test/cli/tui/dialog-open.test.tsx index bcdce8342fb..cdd1c1f5c6b 100644 --- a/packages/tui/test/cli/tui/dialog-open.test.tsx +++ b/packages/tui/test/cli/tui/dialog-open.test.tsx @@ -54,6 +54,40 @@ test("selecting an unhydrated session preserves its location", async () => { } }) +test("finds and opens an exact session ID outside the recent list", async () => { + const sessionID = "ses_04a7a3d82ffeIphUJgd3SnEqiv" + const remote = { directory: "/tmp/opencode/archive", workspaceID: "ws_archive" } + const fixture = await renderOpen((url) => { + if (url.pathname === "/api/session") return json({ data: [], cursor: {} }) + if (url.pathname !== `/api/session/${sessionID}`) return undefined + return json({ + data: { + id: sessionID, + projectID: "proj_archive", + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + time: { created: 1, updated: 2 }, + title: "TUI plugin slot API v2", + location: remote, + }, + }) + }) + + try { + await fixture.app.waitForFrame((frame) => frame.includes("Search sessions and projects")) + await fixture.app.mockInput.typeText(sessionID) + await fixture.app.waitForFrame((frame) => frame.includes("TUI plugin slot API v2")) + + fixture.app.mockInput.pressEnter() + await fixture.app.waitFor(() => fixture.route.data.type === "session") + + expect(fixture.route.data).toEqual({ type: "session", sessionID }) + expect(fixture.location.ref).toEqual(remote) + } finally { + fixture.dispose() + } +}) + test("shows the current project and opens its root", async () => { const root = "/tmp/opencode/project" const subfolder = `${root}/packages/tui`