diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index fc2394779e..29836b908f 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -49,7 +49,6 @@ import { ToastProvider, useToast } from "./ui/toast" import { ExitProvider, useExit } from "./context/exit" import { Session as SessionApi } from "@/session" import { TuiEvent } from "./event" -import { Bus } from "@/bus" import { KVProvider, useKV } from "./context/kv" import { Provider } from "@/provider/provider" import { ArgsProvider, useArgs, type Args } from "./context/args" @@ -881,14 +880,24 @@ function App(props: { onSnapshot?: () => Promise }) { sdk.event.on(TuiEvent.RendererSuspendRequest.type, async (evt) => { renderer.suspend() renderer.currentRenderBuffer.clear() - await Bus.publish(TuiEvent.RendererSuspendAck, { token: evt.properties.token }) + await sdk.client.tui.publish({ + body: { + type: TuiEvent.RendererSuspendAck.type, + properties: { token: evt.properties.token }, + }, + }) }) sdk.event.on(TuiEvent.RendererResumeRequest.type, async (evt) => { renderer.currentRenderBuffer.clear() renderer.resume() renderer.requestRender() - await Bus.publish(TuiEvent.RendererResumeAck, { token: evt.properties.token }) + await sdk.client.tui.publish({ + body: { + type: TuiEvent.RendererResumeAck.type, + properties: { token: evt.properties.token }, + }, + }) }) return ( diff --git a/packages/opencode/src/terminal/control.ts b/packages/opencode/src/terminal/control.ts index 1657303664..c89b3f933e 100644 --- a/packages/opencode/src/terminal/control.ts +++ b/packages/opencode/src/terminal/control.ts @@ -1,6 +1,7 @@ import { Bus } from "@/bus" import { TuiEvent } from "@/cli/cmd/tui/event" import { Identifier } from "@/id/id" +import { Instance } from "@/project/instance" import { Log } from "@/util/log" const log = Log.create({ service: "terminal-control" }) @@ -23,7 +24,15 @@ export namespace TerminalControl { readonly timer: NodeJS.Timeout } - const pendingAcks = new Map() + interface State { + pendingAcks: Map + subscriptionsInitialized: boolean + } + + const state = Instance.state(() => ({ + pendingAcks: new Map(), + subscriptionsInitialized: false, + })) function isTuiMode(): boolean { return process.env.OPENCODE_TUI === "1" @@ -36,17 +45,17 @@ export namespace TerminalControl { function waitForAck(token: string, timeoutMs: number, operation: "suspend" | "resume"): Promise { return new Promise((resolve) => { const timer = setTimeout(() => { - pendingAcks.delete(token) + state().pendingAcks.delete(token) log.warn(`${operation}: timeout waiting for TUI ack`, { token, timeoutMs }) resolve() }, timeoutMs) - pendingAcks.set(token, { + state().pendingAcks.set(token, { token, timer, resolve: () => { clearTimeout(timer) - pendingAcks.delete(token) + state().pendingAcks.delete(token) log.debug(`${operation}: received ack`, { token }) resolve() }, @@ -55,10 +64,10 @@ export namespace TerminalControl { } function cleanupPendingAck(token: string): void { - const handler = pendingAcks.get(token) + const handler = state().pendingAcks.get(token) if (handler) { clearTimeout(handler.timer) - pendingAcks.delete(token) + state().pendingAcks.delete(token) } } @@ -101,14 +110,14 @@ export namespace TerminalControl { } function handleAck(token: string): void { - const handler = pendingAcks.get(token) + const handler = state().pendingAcks.get(token) handler?.resolve() } - let subscriptionsInitialized = false function ensureSubscriptions(): void { - if (subscriptionsInitialized) return - subscriptionsInitialized = true + const s = state() + if (s.subscriptionsInitialized) return + s.subscriptionsInitialized = true Bus.subscribe(TuiEvent.RendererSuspendAck, (event) => { handleAck(event.properties.token) @@ -118,6 +127,6 @@ export namespace TerminalControl { handleAck(event.properties.token) }) - log.debug("Bus subscriptions initialized") + log.debug("Bus subscriptions initialized", { directory: Instance.directory }) } }