Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/opencode/src/cli/cmd/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion packages/opencode/src/cli/cmd/tui/context/sdk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
},
})
53 changes: 53 additions & 0 deletions packages/opencode/src/server/routes/tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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)
},
)
Expand Down
Loading