diff --git a/packages/core/src/kilocode/pty/smoke.ts b/packages/core/src/kilocode/pty/smoke.ts index 47dee8ebba4..9a2380fa52a 100644 --- a/packages/core/src/kilocode/pty/smoke.ts +++ b/packages/core/src/kilocode/pty/smoke.ts @@ -3,6 +3,16 @@ import { KiloPtyTermination } from "./termination" import { spawn } from "#pty" const TIMEOUT = 15_000 +const RENDER_TIMEOUT = 60_000 + +export function marker(output: string) { + const text = output + .replace(/\x1b\](?:[^\x07\x1b]|\x1b(?!\\))*(?:\x07|\x1b\\)/g, "") + .replace(/\x1b[P^_](?:[^\x1b]|\x1b(?!\\))*\x1b\\/g, "") + .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "") + .replace(/\x1b[@-_]/g, "") + return text.split(/\r?\n/).some((line) => line.trim() === "KILO_PTY_READY") +} async function render() { const proc = spawn(process.execPath, ["--pure"], { @@ -34,7 +44,7 @@ async function render() { state.exited = true ready.reject(new Error(`TUI exited before rendering (code ${event.exitCode}): ${JSON.stringify(state.output)}`)) }) - const timeout = AbortSignal.timeout(TIMEOUT) + const timeout = AbortSignal.timeout(RENDER_TIMEOUT) try { await Promise.race([ @@ -42,7 +52,10 @@ async function render() { new Promise((_, reject) => timeout.addEventListener( "abort", - () => reject(new Error(`TUI produced no rendered frame within ${TIMEOUT}ms: ${JSON.stringify(state.output)}`)), + () => + reject( + new Error(`TUI produced no rendered frame within ${RENDER_TIMEOUT}ms: ${JSON.stringify(state.output)}`), + ), { once: true }, ), ), @@ -67,7 +80,7 @@ export async function smoke() { const exited = Promise.withResolvers() const data = proc.onData((chunk) => { state.output += chunk - if (/(?:^|[\r\n])KILO_PTY_READY(?:\r?\n|$)/.test(state.output)) output.resolve() + if (marker(state.output)) output.resolve() }) const exit = proc.onExit((event) => { state.exited = true diff --git a/packages/core/test/kilocode/pty-durability.test.ts b/packages/core/test/kilocode/pty-durability.test.ts index bd0995da240..bc854ab245f 100644 --- a/packages/core/test/kilocode/pty-durability.test.ts +++ b/packages/core/test/kilocode/pty-durability.test.ts @@ -183,7 +183,13 @@ describe("durable PTY registry", () => { const info = yield* Effect.scoped( Effect.gen(function* () { const pty = yield* Pty.Service - return yield* pty.create({ command: "/bin/sh", args: ["-c", "exit 7"], cwd: dir.path }) + return yield* pty.create({ command: "/bin/sh", cwd: dir.path }) + }).pipe(Effect.provide(locations.get(target))), + ) + yield* Effect.scoped( + Effect.gen(function* () { + const pty = yield* Pty.Service + yield* pty.write(info.id, "exit 7\r") }).pipe(Effect.provide(locations.get(target))), ) const exited = yield* Queue.take(queue).pipe(Effect.timeout("5 seconds")) diff --git a/packages/core/test/kilocode/pty-smoke.test.ts b/packages/core/test/kilocode/pty-smoke.test.ts new file mode 100644 index 00000000000..6103787d4d9 --- /dev/null +++ b/packages/core/test/kilocode/pty-smoke.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, test } from "bun:test" +import { marker } from "../../src/kilocode/pty/smoke" + +describe("PTY smoke output", () => { + test("detects a marker after PowerShell formatting", () => { + const output = + "\x1b[93mecho KILO_PTY_READY\r\n\x1b[mKILO_PTY_READY\r\n\x1b]0;Administrator: PowerShell\x07PS> " + + expect(marker(output)).toBe(true) + }) + + test("does not accept the echoed command", () => { + expect(marker("\x1b[93mecho KILO_PTY_READY\r\n\x1b[mPS> ")).toBe(false) + }) + + test("detects a marker around OSC and DCS sequences", () => { + const output = "\x1b]133;A\x07\x1bP+q4d73\x1b\\KILO_PTY_READY\r\n" + + expect(marker(output)).toBe(true) + }) +})