diff --git a/packages/opencode/script/kilocode/test-profile.ts b/packages/opencode/script/kilocode/test-profile.ts index ed4a61eb524..5e3e2226366 100644 --- a/packages/opencode/script/kilocode/test-profile.ts +++ b/packages/opencode/script/kilocode/test-profile.ts @@ -21,6 +21,7 @@ export namespace TestProfile { "cli/serve/*.test.ts", "kilocode/background-process.test.ts", "kilocode/cli/install-artifact.test.ts", + "kilocode/cli/tui/thread.test.ts", "kilocode/core-watcher.test.ts", "kilocode/interactive-terminal.test.ts", "tool/shell.test.ts", diff --git a/packages/opencode/test/kilocode/cli/tui/thread.test.ts b/packages/opencode/test/kilocode/cli/tui/thread.test.ts index 20e42894153..a17fd1725a1 100644 --- a/packages/opencode/test/kilocode/cli/tui/thread.test.ts +++ b/packages/opencode/test/kilocode/cli/tui/thread.test.ts @@ -1,6 +1,9 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" import fs from "fs/promises" import path from "path" +import { fileURLToPath } from "node:url" +import { spawn, type Exit } from "@opencode-ai/core/pty/driver" +import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" import { tmpdir } from "../../../fixture/fixture" import { embeddedRemoteExitClient, @@ -41,6 +44,81 @@ describe("kilo tui thread", () => { expect(calls).toBe(1) }) + test( + "starts the TUI from a directory without OpenTUI dependencies", + async () => { + await using root = await tmpdir() + const state = { text: "", exit: undefined as Exit | undefined } + const ready = Promise.withResolvers() + const stopped = Promise.withResolvers() + const proc = spawn( + process.execPath, + [ + "--conditions=browser", + `--preload=${fileURLToPath(import.meta.resolve("@opentui/solid/preload"))}`, + path.resolve(import.meta.dir, "../../../../src/index.ts"), + ], + { + name: "xterm-256color", + cols: 120, + rows: 40, + cwd: root.path, + env: sanitizedProcessEnv({ + HOME: root.path, + XDG_CONFIG_HOME: path.join(root.path, ".config"), + XDG_DATA_HOME: path.join(root.path, ".local/share"), + XDG_STATE_HOME: path.join(root.path, ".local/state"), + XDG_CACHE_HOME: path.join(root.path, ".cache"), + KILO_TEST_HOME: root.path, + KILO_CONFIG_CONTENT: "{}", + KILO_AUTH_CONTENT: "{}", + KILO_DISABLE_PROJECT_CONFIG: "1", + KILO_DISABLE_AUTOUPDATE: "1", + KILO_DISABLE_MODELS_FETCH: "1", + KILO_DISABLE_TERMINAL_TITLE: "0", + KILO_DEV_CWD: "", + KILO_PURE: "1", + KILO_NO_DAEMON: "1", + TERM: "xterm-256color", + }), + }, + ) + const data = proc.onData((chunk) => { + state.text = (state.text + chunk).slice(-20_000) + if (state.text.includes("TUI worker error")) { + ready.reject(new Error(`TUI worker failed during startup:\n${state.text}`)) + return + } + // The title is emitted only after the worker-backed TUI reaches its rendered app. + if (state.text.includes("Kilo CLI")) ready.resolve() + }) + const exit = proc.onExit((event) => { + state.exit = event + stopped.resolve() + ready.reject( + new Error( + `TUI exited before rendering (code ${event.exitCode}, signal ${event.signal ?? "none"}):\n${state.text}`, + ), + ) + }) + const timer = setTimeout(() => { + ready.reject(new Error(`Timed out waiting for the TUI to render:\n${state.text}`)) + }, 30_000) + + try { + await ready.promise + expect(state.text).toContain("Kilo CLI") + } finally { + clearTimeout(timer) + data.dispose() + if (!state.exit) proc.kill() + await stopped.promise + exit.dispose() + } + }, + 45_000, + ) + test("ignores stale PWD after cwd is changed by a process wrapper", async () => { await using root = await tmpdir() const pkg = path.join(root.path, "packages", "opencode") diff --git a/packages/opencode/test/kilocode/test-profile.test.ts b/packages/opencode/test/kilocode/test-profile.test.ts index 7e37cc27d11..a9abc16759e 100644 --- a/packages/opencode/test/kilocode/test-profile.test.ts +++ b/packages/opencode/test/kilocode/test-profile.test.ts @@ -14,6 +14,7 @@ describe("test profiles", () => { expect(result.files.length).toBeGreaterThan(20) expect(result.files).toContain("pty/pty-shell.test.ts") expect(result.files).toContain("kilocode/cli/install-artifact.test.ts") + expect(result.files).toContain("kilocode/cli/tui/thread.test.ts") expect(result.files).toContain("kilocode/sandbox/macos-confinement.test.ts") expect(result.files).toContain("kilocode/core-watcher.test.ts") expect(result.files).toContain("kilocode/background-process.test.ts")