diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index 57b99c45ce..0a9036e15e 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -183,21 +183,23 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { const result = yield* Effect.scoped( Effect.gen(function* () { const renderer = yield* Effect.acquireRelease( - Effect.tryPromise(() => - createCliRenderer({ - externalOutputMode: "passthrough", - targetFps: 60, - gatherStats: false, - exitOnCtrlC: false, - useKittyKeyboard: {}, - autoFocus: false, - openConsoleOnError: false, - useMouse: !Flag.OPENCODE_DISABLE_MOUSE && input.config.mouse, - consoleOptions: { - keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }], - }, - }), - ), + Effect.tryPromise({ + try: () => + createCliRenderer({ + externalOutputMode: "passthrough", + targetFps: 60, + gatherStats: false, + exitOnCtrlC: false, + useKittyKeyboard: {}, + autoFocus: false, + openConsoleOnError: false, + useMouse: !Flag.OPENCODE_DISABLE_MOUSE && input.config.mouse, + consoleOptions: { + keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }], + }, + }), + catch: (error) => (error instanceof Error ? error : new Error(String(error))), + }), (renderer) => Effect.sync(() => { destroyRenderer(renderer) diff --git a/packages/tui/test/app-lifecycle.test.tsx b/packages/tui/test/app-lifecycle.test.tsx index d3983ae8e0..559af1e29e 100644 --- a/packages/tui/test/app-lifecycle.test.tsx +++ b/packages/tui/test/app-lifecycle.test.tsx @@ -6,6 +6,37 @@ import { Global } from "@opencode-ai/core/global" import { createTuiResolvedConfig } from "./fixture/tui-runtime" import { createEventSource, createFetch, directory, json } from "./fixture/tui-sdk" +test("renderer initialization preserves the original error message", async () => { + const core = await import("@opentui/core") + const message = 'Failed to open library "opentui.dll": error code 126' + mock.module("@opentui/core", () => ({ + ...core, + createCliRenderer: async () => { + throw new Error(message) + }, + })) + + try { + const { run } = await import("../src/app") + await expect( + Effect.runPromise( + run({ + url: "http://test", + directory, + config: createTuiResolvedConfig({ plugin_enabled: {} }), + args: {}, + pluginHost: { + async start() {}, + async dispose() {}, + }, + }).pipe(Effect.provide(Global.defaultLayer)), + ), + ).rejects.toThrow(message) + } finally { + mock.restore() + } +}) + test("SIGHUP clears title and disposes scoped resources once", async () => { const setup = await createTestRenderer({ width: 80, height: 24, useThread: false }) const core = await import("@opentui/core")