From 2ff19171dcc733e31f8aa312b7743251cba7fd6b Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 3 Jul 2026 14:01:00 -0400 Subject: [PATCH] feat(tui): reload command restarts the service --- packages/cli/src/commands/handlers/default.ts | 20 +++++++++++++++- packages/cli/src/tui.ts | 8 ++++++- packages/tui/src/app.tsx | 23 ++++++++++++++++++- packages/tui/src/context/sdk.tsx | 3 +++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/handlers/default.ts b/packages/cli/src/commands/handlers/default.ts index 1f8ebe89c4..d7ba88a824 100644 --- a/packages/cli/src/commands/handlers/default.ts +++ b/packages/cli/src/commands/handlers/default.ts @@ -63,6 +63,24 @@ export default Runtime.handler(Commands, (input) => }).pipe(Effect.provide(NodeFileSystem.layer)), ) : () => Promise.resolve(transport) - yield* runTui(transport, { continue: input.continue, sessionID: Option.getOrUndefined(input.session) }, discover) + // Restart the managed service in place; start() resolves once the + // replacement is healthy and the reconnect loop reattaches on its own. + // Only meaningful in service mode: --server is not ours to restart and a + // standalone child cannot be respawned. + const reload = serviceOptions + ? () => + Effect.runPromise( + Effect.gen(function* () { + yield* Service.stop(serviceOptions) + yield* Service.start(serviceOptions) + }).pipe(Effect.provide(NodeFileSystem.layer)), + ) + : undefined + yield* runTui( + transport, + { continue: input.continue, sessionID: Option.getOrUndefined(input.session) }, + discover, + reload, + ) }), ) diff --git a/packages/cli/src/tui.ts b/packages/cli/src/tui.ts index a535309d42..96d92c50b2 100644 --- a/packages/cli/src/tui.ts +++ b/packages/cli/src/tui.ts @@ -9,7 +9,12 @@ import type { Service } from "@opencode-ai/client/effect" import { createOpencodeClient } from "@opencode-ai/sdk/v2/client" import type { Args } from "@opencode-ai/tui/context/args" -export function runTui(transport: Service.Transport, args: Args, discover?: () => Promise) { +export function runTui( + transport: Service.Transport, + args: Args, + discover?: () => Promise, + reload?: () => Promise, +) { const config = TuiConfig.resolve({}, { terminalSuspend: false }) let disposeSlots: (() => void) | undefined return Effect.gen(function* () { @@ -33,6 +38,7 @@ export function runTui(transport: Service.Transport, args: Args, discover?: () = } } : undefined, + reload, args, config, pluginHost: { diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index ccde2fa5e6..876faff9de 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -139,6 +139,7 @@ export type TuiInput = { client: OpencodeClient api: OpenCodeClient discover?: () => Promise<{ client: OpencodeClient; api: OpenCodeClient }> + reload?: () => Promise args: Args config: TuiConfig.Resolved onSnapshot?: () => Promise @@ -301,7 +302,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { > - + @@ -800,6 +801,26 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi }, category: "System", }, + ...(sdk.reload + ? [ + { + name: "server.reload", + title: "Reload server", + slashName: "reload", + run: async () => { + dialog.clear() + toast.show({ variant: "info", message: "Reloading server...", duration: 30000 }) + // reload resolves once the replacement service is healthy; the + // event stream reattaches through the reconnect loop. + await sdk + .reload!() + .then(() => toast.show({ variant: "success", message: "Server reloaded" })) + .catch(toast.error) + }, + category: "System", + }, + ] + : []), { name: "theme.switch", title: "Switch theme", diff --git a/packages/tui/src/context/sdk.tsx b/packages/tui/src/context/sdk.tsx index 7686ed6e94..0ad516ceac 100644 --- a/packages/tui/src/context/sdk.tsx +++ b/packages/tui/src/context/sdk.tsx @@ -16,6 +16,8 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ client: OpencodeClient api: OpenCodeClient discover?: () => Promise<{ client: OpencodeClient; api: OpenCodeClient }> + // Stops and starts the managed service; present only in service mode. + reload?: () => Promise }) => { const abort = new AbortController() let client = props.client @@ -138,6 +140,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ return connection.connectedOnce }, }, + reload: props.reload, } }, })