From e4218d98479461698cb126558e0e0190df4691cf Mon Sep 17 00:00:00 2001 From: "DESKTOP-SB818KQ\\js" Date: Tue, 3 Mar 2026 07:17:02 +0900 Subject: [PATCH 1/2] feat(opencode): add GET/POST /tui/active-session endpoint Track which session the TUI is currently viewing and expose it via HTTP API. The TUI reports route changes to the server, and external clients can query the active session. Fixes #15759 --- packages/opencode/src/cli/cmd/tui/app.tsx | 12 +++++ .../opencode/src/cli/cmd/tui/context/sdk.tsx | 15 +++++- packages/opencode/src/server/routes/tui.ts | 53 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 97c910a47d4b..6f53124dafb4 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -261,6 +261,18 @@ function App() { console.log(JSON.stringify(route.data)) }) + // Report active session to server whenever route changes + createEffect(() => { + const sessionID = route.data.type === "session" ? route.data.sessionID : null + sdk + .fetch(sdk.url + "/tui/active-session", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ sessionID }), + }) + .catch(() => {}) + }) + // Update terminal window title based on current route and session createEffect(() => { if (!terminalTitleEnabled() || Flag.OPENCODE_DISABLE_TERMINAL_TITLE) return diff --git a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx index 7fa7e05c3d25..c54e5b444bd6 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx @@ -96,6 +96,19 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ if (timer) clearTimeout(timer) }) - return { client: sdk, event: emitter, url: props.url } + const raw = props.fetch ?? fetch + const wrapped = ((input: RequestInfo | URL, init?: RequestInit) => { + const merged = new Headers(props.headers as HeadersInit) + if (init?.headers) { + const h = new Headers(init.headers as HeadersInit) + h.forEach((v, k) => { + merged.set(k, v) + }) + } + if (props.directory && !merged.has("x-opencode-directory")) merged.set("x-opencode-directory", props.directory) + return raw(input as RequestInfo, { ...init, headers: merged }) + }) as typeof fetch + + return { client: sdk, event: emitter, url: props.url, fetch: wrapped } }, }) diff --git a/packages/opencode/src/server/routes/tui.ts b/packages/opencode/src/server/routes/tui.ts index 8650a0cccf76..afc3afb67701 100644 --- a/packages/opencode/src/server/routes/tui.ts +++ b/packages/opencode/src/server/routes/tui.ts @@ -8,6 +8,8 @@ import { AsyncQueue } from "../../util/queue" import { errors } from "../error" import { lazy } from "../../util/lazy" +let activeSessionID: string | undefined + const TuiRequest = z.object({ path: z.string(), body: z.any(), @@ -372,6 +374,57 @@ export const TuiRoutes = lazy(() => const { sessionID } = c.req.valid("json") await Session.get(sessionID) await Bus.publish(TuiEvent.SessionSelect, { sessionID }) + activeSessionID = sessionID + return c.json(true) + }, + ) + .get( + "/active-session", + describeRoute({ + summary: "Get active session", + description: "Get the session currently being viewed in the TUI.", + operationId: "tui.activeSession.get", + responses: { + 200: { + description: "Currently active session, or null if on home screen", + content: { + "application/json": { + schema: resolver(Session.Info.nullable()), + }, + }, + }, + ...errors(404), + }, + }), + async (c) => { + if (!activeSessionID) return c.json(null) + const session = await Session.get(activeSessionID) + return c.json(session) + }, + ) + .post( + "/active-session", + describeRoute({ + summary: "Report active session", + description: "Report which session the TUI is currently viewing.", + operationId: "tui.activeSession.set", + responses: { + 200: { + description: "Active session updated", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + ...errors(400, 404), + }, + }), + validator("json", z.object({ sessionID: z.string().nullable() })), + async (c) => { + const { sessionID } = c.req.valid("json") + if (sessionID !== null) await Session.get(sessionID) + activeSessionID = sessionID ?? undefined return c.json(true) }, ) From afdfdacb1593572af74034ff0de516592ef25ae5 Mon Sep 17 00:00:00 2001 From: "DESKTOP-SB818KQ\\js" Date: Tue, 3 Mar 2026 08:41:54 +0900 Subject: [PATCH 2/2] ci: retry e2e tests