From 7b1554d3370158b648bc58ebe5b0832c3b18c9b6 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 1 Oct 2025 23:55:12 -0400 Subject: [PATCH] tui: move server to worker for better isolation and lifecycle management --- .../src/cli/cmd/tui/context/helper.tsx | 9 ++++--- .../src/cli/cmd/tui/context/keybind.tsx | 5 ---- .../opencode/src/cli/cmd/tui/context/sdk.tsx | 10 ++----- packages/opencode/src/cli/cmd/tui/tui.tsx | 27 ++++++++++++++++--- packages/opencode/src/cli/cmd/tui/worker.ts | 19 +++++++++++++ 5 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/worker.ts diff --git a/packages/opencode/src/cli/cmd/tui/context/helper.tsx b/packages/opencode/src/cli/cmd/tui/context/helper.tsx index f2ed591cb..6be88e775 100644 --- a/packages/opencode/src/cli/cmd/tui/context/helper.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/helper.tsx @@ -1,11 +1,14 @@ import { createContext, Show, useContext, type ParentProps } from "solid-js" -export function createSimpleContext(input: { name: string; init: () => T }) { +export function createSimpleContext>(input: { + name: string + init: ((input: Props) => T) | (() => T) +}) { const ctx = createContext() return { - provider: (props: ParentProps) => { - const init = input.init() + provider: (props: ParentProps) => { + const init = input.init(props) return ( // @ts-expect-error diff --git a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx index 4d801e248..1b26a397f 100644 --- a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx @@ -62,11 +62,6 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex leader(false) }) } - - if (result.match("app_exit", evt)) { - await Instance.disposeAll() - renderer.destroy() - } }) const result = { diff --git a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx index 7ad10a5e3..0b04e3756 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx @@ -1,17 +1,11 @@ import { createOpencodeClient } from "@opencode-ai/sdk" -import { Server } from "@/server/server" import { createSimpleContext } from "./helper" export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ name: "SDK", - init: () => { + init: (props: { url: string }) => { const client = createOpencodeClient({ - baseUrl: "http://localhost:4096", - // @ts-ignore - fetch: async (r) => { - // @ts-ignore - return Server.App().fetch(r) - }, + baseUrl: props.url, }) return client }, diff --git a/packages/opencode/src/cli/cmd/tui/tui.tsx b/packages/opencode/src/cli/cmd/tui/tui.tsx index bff4fe2d5..8b1912951 100644 --- a/packages/opencode/src/cli/cmd/tui/tui.tsx +++ b/packages/opencode/src/cli/cmd/tui/tui.tsx @@ -72,18 +72,34 @@ export const TuiCommand = cmd({ directory: process.cwd(), fn: () => Config.get(), }) + + const worker = new Worker(import.meta.resolve("./worker.ts")) + worker.onerror = console.log + worker.onmessageerror = console.log + const url = await new Promise((resolve) => { + worker.onmessage = (event) => { + const data = JSON.parse(event.data) + if (data.type === "ready") { + resolve(data.url) + } + } + }) await render( () => { return ( - + - + { + worker.terminate() + }} + /> @@ -104,7 +120,7 @@ export const TuiCommand = cmd({ }, }) -function App() { +function App(props: { onExit: () => void }) { const route = useRoute() const dimensions = useTerminalDimensions() const renderer = useRenderer() @@ -131,6 +147,11 @@ function App() { renderer.console.toggle() return } + if (keybind.match("app_exit", evt)) { + await Instance.disposeAll() + renderer.destroy() + props.onExit() + } }) createEffect(() => { diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts new file mode 100644 index 000000000..e6bc13679 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -0,0 +1,19 @@ +import { Installation } from "@/installation" +import { Server } from "@/server/server" +import { Log } from "@/util/log" + +await Log.init({ + print: process.argv.includes("--print-logs"), + dev: Installation.isDev(), + level: (() => { + if (Installation.isDev()) return "DEBUG" + return "INFO" + })(), +}) + +const server = Server.listen({ + port: 4096, + hostname: "127.0.0.1", +}) + +postMessage(JSON.stringify({ type: "ready", url: server.url }))