import { stripVTControlCharacters } from "node:util"; import type { Theme, ToolRenderResultOptions, } from "@earendil-works/pi-coding-agent"; import { initTheme } from "@earendil-works/pi-coding-agent"; import type { TUI } from "@earendil-works/pi-tui"; import { beforeAll, describe, expect, it, vi } from "vitest"; import { type AgentControls, initialReducerState, reduceProgress, type UltraProgressEvent, } from "../progress.ts"; import { type OverlayAgentControls, type OverlayHost, renderUltraResult, UltraOverlayComponent, } from "../ui.ts"; // Real terminal byte sequences (verified against pi-tui's matchesKey). const K = { esc: "\x1b", tab: "\t", up: "\x1b[A", down: "\x1b[B", altUp: "\x1bp", altDown: "\x1bn", pageUp: "\x1b[5~", pageDown: "\x1b[6~", enter: "\r", backspace: "\x7f", }; function makeOverlay(opts: { rows?: number; now?: () => number; throttleMs?: number; onRunAbort?: () => void; toolUi?: boolean; toolsExpanded?: () => boolean; hideToolPreviews?: boolean; isToolsExpandKey?: (data: string) => boolean; onToolsExpand?: () => void; }) { const requestRender = vi.fn(); const host: OverlayHost = { requestRender, terminal: { rows: opts.rows ?? 24, columns: 80 }, }; const overlay = new UltraOverlayComponent({ host, theme, onRunAbort: opts.onRunAbort ?? vi.fn(), now: opts.now, throttleMs: opts.throttleMs, toolUi: opts.toolUi ? (host as unknown as TUI) : undefined, toolsExpanded: opts.toolsExpanded, hideToolPreviews: opts.hideToolPreviews, isToolsExpandKey: opts.isToolsExpandKey, onToolsExpand: opts.onToolsExpand, cwd: opts.toolUi ? process.cwd() : undefined, }); return { overlay, requestRender, host }; } // `keyHint` (used by the collapsed tool view) reads the global theme; the host // initialises it in prod, so the fakes-only harness must do the same once. beforeAll(() => { initTheme(); }); // A no-op theme: `fg`/`bold` return the text verbatim so assertions can target // plain substrings. (`keyHint` uses the *global* theme, not this one.) const theme = { fg: (_c: string, t: string) => t, bold: (t: string) => t, } as unknown as Theme; const pe = ( e: Partial & Pick, ): UltraProgressEvent => e; function stateFrom(events: UltraProgressEvent[]) { return events.reduce(reduceProgress, initialReducerState()); } function renderTool( state: ReturnType, options: ToolRenderResultOptions, width = 80, ): string[] { return renderUltraResult( { content: [], details: state }, options, theme, ).render(width); } const toolText = ( state: ReturnType, options: ToolRenderResultOptions, ): string => renderTool(state, options).join("\n"); // --- Layer 7a — TOOL sink (renderResult) --------------------------------- describe("ultra tool sink — renderUltraResult", () => { it("renders one compact emphasized progress header", () => { vi.useFakeTimers(); try { vi.setSystemTime(180_000); let state = initialReducerState({ workflowName: "spec-guidance-review", }); state = reduceProgress(state, { kind: "plan", startedAt: 0, phases: [ { phase: "review", status: "resolved", agentIds: ["review#0"], }, ], } as unknown as UltraProgressEvent); state = reduceProgress( state, pe({ agentId: "review#0", phase: "review", kind: "start" }), ); const emphasizedTheme = { ...theme, bold: (text: string) => `__${text}__`, } as Theme; const lines = renderUltraResult( { content: [], details: state }, { expanded: true, isPartial: true }, emphasizedTheme, ).render(120); expect(lines[0]).toBe( "__ultra__ · spec-guidance-review · 3m 0s elapsed · phase 1/1 · 0/1 done · 1 active", ); expect(lines.filter((line) => line.includes("phase 1/1"))).toHaveLength( 1, ); expect(lines.some((line) => line.includes("⏳"))).toBe(false); } finally { vi.useRealTimers(); } }); it("header shows workflow name and description when supplied", () => { const s = initialReducerState({ workflowName: "review", workflowDescription: "Review the diff from every angle.", }); const out = toolText(s, { expanded: false, isPartial: true }); expect(out).toContain("review"); expect(out).toContain("Review the diff from every angle."); }); it("shows workflow wall time in the header and freezes it after completion", () => { vi.useFakeTimers(); try { vi.setSystemTime(0); const planned = reduceProgress(initialReducerState(), { kind: "plan", startedAt: 0, phases: [{ phase: "one", status: "resolved", agentIds: ["one#0"] }], } as unknown as UltraProgressEvent); vi.setSystemTime(65_000); expect(toolText(planned, { expanded: false, isPartial: true })).toContain( "1m 5s elapsed", ); const done = reduceProgress(planned, { kind: "phase", phase: "one", status: "done", agentIds: ["one#0"], } as unknown as UltraProgressEvent); vi.setSystemTime(125_000); const frozen = toolText(done, { expanded: true, isPartial: false }); expect(frozen).toContain("1m 5s elapsed"); expect(frozen).not.toContain("2m 5s elapsed"); } finally { vi.useRealTimers(); } }); it("uses phase-first disclosure: completed and future phases collapse while active and failed phases open", () => { const s = stateFrom([ { kind: "plan", startedAt: Date.now(), phases: [ { phase: "done", status: "resolved", agentIds: ["done#0"] }, { phase: "active", status: "resolved", agentIds: ["active#0", "active#1"], }, { phase: "queued", status: "resolved", agentIds: ["queued#0"] }, { phase: "verify", status: "agents-pending", agentIds: [] }, { phase: "publish", status: "condition-pending", agentIds: [], }, ], } as unknown as UltraProgressEvent, pe({ agentId: "done#0", phase: "done", kind: "start" }), pe({ agentId: "done#0", phase: "done", kind: "end", status: "done" }), { kind: "phase", phase: "done", status: "done", agentIds: ["done#0"], } as UltraProgressEvent, pe({ agentId: "active#0", phase: "active", kind: "prompt", summary: "Review active path", }), pe({ agentId: "active#0", phase: "active", kind: "start" }), pe({ agentId: "active#1", phase: "active", kind: "prompt", summary: "Check failed path", }), pe({ agentId: "active#1", phase: "active", kind: "end", status: "failed", }), ]); const expanded = toolText(s, { expanded: true, isPartial: true }); expect(expanded).toContain("󰗠 done · 1/1 done"); expect(expanded).not.toContain("done#0"); expect(expanded).toContain("▼ active"); expect(expanded).toMatch(/Review active path\n\s+active#0 · running/); expect(expanded).toMatch(/Check failed path\n\s+active#1 · dropped/); expect(expanded).toContain("󰅐 queued · 1 queued"); expect(expanded).not.toContain("queued#0"); expect(expanded).toContain("agents not planned yet"); expect(expanded).toContain("conditional"); const collapsed = toolText(s, { expanded: false, isPartial: true }); expect(collapsed).toContain("1 active"); expect(collapsed).toContain("1 queued"); expect(collapsed).toContain("2 later"); expect(collapsed).toContain("▶ active"); expect(collapsed).toContain("active#0 · Review active path"); expect(collapsed).toContain("active#1 · Check failed path"); expect(collapsed).not.toContain("transport: rate limited"); }); it("truncation keeps ellipsis in the surrounding style span", () => { const s = stateFrom([ pe({ agentId: "scan#0", phase: "scan", kind: "start" }), pe({ agentId: "scan#0", phase: "scan", kind: "action", toolName: "bash", text: JSON.stringify({ command: "python3 - <<'PY'\n" + "x".repeat(200), }), }), ]); const out = renderTool(s, { expanded: true, isPartial: true }, 54).join( "\n", ); expect(out).toContain("..."); expect(out).not.toContain("\x1b[0m...\x1b[0m"); }); // A run with a running agent whose latest action + transcript carry distinct // sentinels, plus a dropped agent. const scripted = stateFrom([ pe({ agentId: "a", phase: "review", kind: "start" }), pe({ agentId: "a", phase: "review", kind: "action", toolName: "read", text: "/SECRET-ACTION", }), pe({ agentId: "a", phase: "review", kind: "delta", text: "SECRET-TRANSCRIPT-BODY", }), pe({ agentId: "b", phase: "review", kind: "start" }), pe({ agentId: "b", phase: "review", kind: "end", status: "failed", item: { task: "scope" }, failure: { code: "transport", message: "rate limited", retryable: true, attempts: 2, model: "openai/gpt", thinkingLevel: "high", transcriptTail: "bounded bad response", }, }), ]); it("collapsed: shows phase and run summaries without actions or transcripts", () => { const out = toolText(scripted, { expanded: false, isPartial: true }); // aggregate counts + phase counter present expect(out).toContain("phase 1/1"); expect(out).toContain("0/2 done"); expect(out).toContain("1✗"); // the collapsed expand affordance expect(out).toContain("to expand"); expect(out).toContain("▶ review"); expect(out).toContain("󰐌 a"); expect(out).toContain("󰅙 b"); expect(out).not.toContain("/SECRET-ACTION"); expect(out).not.toContain("SECRET-TRANSCRIPT-BODY"); expect(out).not.toContain("rate limited"); }); it("collapsed: bounds visible phases and runs while keeping the active phase", () => { const phases = ["prepare", "scan", "review", "verify", "publish"].map( (phase) => ({ phase, status: "resolved" as const, agentIds: [] }), ); let state = reduceProgress(initialReducerState(), { kind: "plan", startedAt: Date.now(), phases, }); state = reduceProgress(state, { kind: "phase", phase: "verify", status: "resolved", agentIds: ["verify#0", "verify#1", "verify#2", "verify#3", "verify#4"], }); for (let index = 0; index < 5; index++) { state = reduceProgress( state, pe({ agentId: `verify#${index}`, phase: "verify", kind: "prompt", summary: `Run ${index}`, prompt: "SECRET EXACT PROMPT", }), ); state = reduceProgress( state, pe({ agentId: `verify#${index}`, phase: "verify", kind: "start" }), ); } for (const width of [24, 54, 120]) { const lines = renderTool( state, { expanded: false, isPartial: true }, width, ); const out = lines.join("\n"); expect(out).toContain("▶ verify"); expect(out).toContain("earlier phases"); expect(out).toContain("more runs"); expect(out).toContain("verify#0"); expect(out).not.toContain("SECRET EXACT PROMPT"); expect( lines.every((line) => stripVTControlCharacters(line).length <= width), ).toBe(true); } }); it("expanded: shows authored summaries but never exact prompts", () => { const state = stateFrom([ pe({ agentId: "review#0", phase: "review", kind: "prompt", summary: "Review security", prompt: "SECRET EXACT PROMPT", }), ]); const out = toolText(state, { expanded: true, isPartial: true }); expect(out).toContain("Review security"); expect(out).not.toContain("SECRET EXACT PROMPT"); }); it("expanded: emphasizes active and dropped work without transcript bodies", () => { const out = renderTool( scripted, { expanded: true, isPartial: true }, 140, ).join("\n"); // running agent + its latest action expect(out).toContain("a"); expect(out).toContain("running"); expect(out).toContain("/SECRET-ACTION"); // dropped agent and its bounded typed diagnostic are surfaced expect(out).toContain("b"); expect(out).toContain("dropped"); expect(out).toContain("openai/gpt · high"); expect(out).toContain("transport: rate limited"); expect(out).toContain("retryable · 2 attempts"); // Full transcripts and verbose failure tails stay out of the compact board. expect(out).not.toContain("bounded bad response"); expect(out).not.toContain("SECRET-TRANSCRIPT-BODY"); }); it.each(["tool", "overlay"])( "%s: separates failure diagnostics from metadata", (surface) => { const events = [ pe({ agentId: "review#0", phase: "review", kind: "start" }), pe({ agentId: "review#0", phase: "review", kind: "end", status: "failed", failure: { code: "missing-output", message: "no structured output produced", retryable: false, attempts: 1, }, }), ]; const { overlay } = makeOverlay({ rows: 24 }); for (const event of events) overlay.handleProgress(event); const lines = surface === "tool" ? renderTool(stateFrom(events), { expanded: true, isPartial: true }) : overlay.render(80); const diagnostic = lines.findIndex((line) => line.includes("missing-output:"), ); expect(diagnostic).toBeGreaterThanOrEqual(0); expect(lines[diagnostic].trim()).toBe( "missing-output: no structured output produced", ); expect(lines[diagnostic + 1].trim()).toBe( "dropped · terminal · 1 attempt", ); }, ); it.each([54, 140])( "expanded: separates live activity from metadata at width %i", (width) => { let state = stateFrom([ pe({ agentId: "validate#0", phase: "validate", kind: "prompt", summary: "Validate Niri handoff", }), pe({ agentId: "validate#0", phase: "validate", kind: "start", model: "openai/gpt", }), ]); const options = { expanded: true, isPartial: true }; let lines = renderTool(state, options, width); let agent = lines.findIndex((line) => line.includes("Validate Niri handoff"), ); expect(agent).toBeGreaterThanOrEqual(0); expect(lines[agent + 1]).toBe(" validate#0 · running · openai/gpt"); state = reduceProgress( state, pe({ agentId: "validate#0", phase: "validate", kind: "action", actionSummary: "Validating Niri integration behavior and screenshots", }), ); lines = renderTool(state, options, width); agent = lines.findIndex((line) => line.includes("Validate Niri handoff")); expect(lines[agent + 1]).toMatch(/^ {4}Validating Niri/); expect(lines[agent + 1]).not.toContain("validate#0"); expect(lines[agent + 2]).toBe(" validate#0 · running · openai/gpt"); }, ); it("expanded: separates completed agent summaries from their metadata", () => { const s = stateFrom([ pe({ agentId: "plan#0", phase: "plan", kind: "prompt", summary: "Plan implementation", }), pe({ agentId: "plan#0", phase: "plan", kind: "start", tier: "small", model: "openai/gpt-5-mini", thinkingLevel: "low", startedAt: Date.now() - 18_000, }), pe({ agentId: "plan#0", phase: "plan", kind: "action", toolName: "read", text: "docs/phase-1.html", }), pe({ agentId: "plan#0", phase: "plan", kind: "end", status: "done", usage: { input: 1800, output: 300, total: 2100, cacheRead: 0, cacheWrite: 0, cost: 0, }, }), ]); const lines = renderTool(s, { expanded: true, isPartial: false }, 140); const out = lines.join("\n"); const agent = lines.findIndex((line) => line.includes("Plan implementation"), ); expect(agent).toBeGreaterThanOrEqual(0); expect(lines[agent]).toMatch(/Plan implementation$/); expect(lines[agent + 1]).toMatch(/^ {4}plan#0 · done/); expect(lines[agent + 1]).toContain("small → openai/gpt-5-mini · low"); expect(lines[agent + 1]).toContain("18s"); expect(lines[agent + 1]).toContain("$0.0000 · 300 output"); expect(lines[agent + 1]).not.toContain("2.1k tokens"); expect(out).not.toContain("docs/phase-1.html"); }); it("expanded: separates queued agent labels from their metadata", () => { const state = stateFrom([ { kind: "plan", startedAt: Date.now(), phases: [ { phase: "review", status: "resolved", agentIds: ["review#0", "review#1"], }, ], }, pe({ agentId: "review#0", phase: "review", kind: "start" }), ]); const lines = renderTool(state, { expanded: true, isPartial: true }); const agent = lines.findIndex((line) => line.includes("review#1")); expect(agent).toBeGreaterThanOrEqual(0); expect(lines[agent]).toMatch(/review#1$/); expect(lines[agent + 1]).toMatch(/^ {4}queued/); }); it("formats token counts with three significant SI digits", () => { for (const [tokens, expected] of [ [999, "999"], [1000, "1k"], [12_345, "12.3k"], [999_000, "999k"], [4_326_000, "4.33M"], [1_250_000_000, "1.25G"], ] as const) { const state = stateFrom([ pe({ agentId: "p#0", phase: "p", kind: "start" }), pe({ agentId: "p#0", phase: "p", kind: "end", status: "done", usage: { input: 0, output: tokens, total: tokens, cacheRead: 0, cacheWrite: 0, cost: 0, }, }), ]); expect(toolText(state, { expanded: true, isPartial: false })).toContain( `${expected} output`, ); } }); it("shows aggregate cost, checkpoints, and resumed steps", () => { const s = stateFrom([ { kind: "plan", startedAt: 0, phases: [{ phase: "p", status: "resolved", agentIds: ["p#0", "p#1"] }], } as UltraProgressEvent, pe({ agentId: "p#0", phase: "p", kind: "start", text: "cached" }), pe({ agentId: "p#0", phase: "p", kind: "end", status: "done" }), pe({ agentId: "p#1", phase: "p", kind: "start" }), pe({ agentId: "p#1", phase: "p", kind: "end", status: "done", usage: { input: 800, output: 200, total: 1000, cacheRead: 0, cacheWrite: 0, cost: 0.125, }, }), ]); const out = toolText(s, { expanded: true, isPartial: true }); expect(out).toContain("checkpoint reused"); expect(out).toContain("$0.1250 · 200 output · checkpoint 2/2 · 1 resumed"); }); it.each(["tool", "overlay"])( "%s prioritizes cost/output and expands input/cache separately", (surface) => { let expanded = false; const { overlay } = makeOverlay({ rows: 40, toolsExpanded: () => expanded, }); const events = [ pe({ agentId: "work#0", phase: "work", kind: "start" }), pe({ agentId: "work#0", phase: "work", kind: "end", status: "done", usage: { input: 1000, output: 100, total: 3600, cacheRead: 2000, cacheWrite: 500, cost: 0.125, }, }), pe({ agentId: "work#1", phase: "work", kind: "start" }), pe({ agentId: "work#1", phase: "work", kind: "end", status: "failed", usage: { input: 2000, output: 200, total: 4200, cacheRead: 2000, cacheWrite: 0, cost: 0.25, }, }), ]; for (const event of events) overlay.handleProgress(event); const state = stateFrom(events); const render = () => (surface === "tool" ? renderTool(state, { expanded, isPartial: true }, 140) : overlay.render(140) ).join("\n"); let out = render(); expect(out).toContain("$0.3750 · 300 output · checkpoint 1/2"); expect(out).not.toContain("uncached input"); expect(out).not.toContain("cache read"); expect(out).not.toContain("7.8k tokens"); expanded = true; out = render(); expect(out).toContain( "$0.3750 · 300 output · checkpoint 1/2\n3k uncached input · 4k cache read · 500 cache write", ); expect(out).toContain("$0.2500 · 200 output"); expect(out).not.toContain("7.8k tokens"); const narrow = renderTool( state, { expanded: false, isPartial: true }, 40, ); expect( narrow.some((line) => line.startsWith("$0.3750 · 300 output")), ).toBe(true); }, ); it("renders the final human-readable outcome only when expanded", () => { const base = stateFrom([ pe({ agentId: "p#0", phase: "p", kind: "end", status: "done" }), ]); const state = { ...base, workflowResult: { workflow: "fix-pmd", phases: [{ id: "p", ran: 1, ok: 1, dropped: 0 }], phaseResults: {}, phaseFailures: {}, result: [{ success: true, summary: "All accepted findings fixed." }], steered: false, tokenUsage: { input: 1, output: 2, total: 3, cacheRead: 0, cacheWrite: 0, cost: 0, }, }, }; const collapsed = toolText(state, { expanded: false, isPartial: false }); expect(collapsed).not.toContain("All accepted findings fixed."); expect(collapsed).toContain("$0.0000 · 2 output"); expect(collapsed).not.toContain("uncached input"); const expanded = toolText(state, { expanded: true, isPartial: false }); expect(expanded).toContain("outcome"); expect(expanded).toContain("All accepted findings fixed."); expect(expanded).not.toContain("phaseResults"); }); it("freezes elapsed time when an agent ends", () => { vi.useFakeTimers(); try { vi.setSystemTime(100_000); const s = stateFrom([ pe({ agentId: "plan#0", phase: "plan", kind: "start", startedAt: 0, }), pe({ agentId: "plan#0", phase: "plan", kind: "end", status: "done" }), ]); vi.setSystemTime(200_000); const out = toolText(s, { expanded: true, isPartial: false }); expect(out).toContain("1m 40s"); expect(out).not.toContain("3m 20s"); } finally { vi.useRealTimers(); } }); it("never emits bodies when expanded=false (sentinels absent collapsed, present expanded)", () => { const collapsed = toolText(scripted, { expanded: false, isPartial: false }); const expanded = toolText(scripted, { expanded: true, isPartial: false }); expect(collapsed).not.toContain("/SECRET-ACTION"); expect(expanded).toContain("/SECRET-ACTION"); }); it("tracks fresh details — render state N then N+1, the lines change", () => { const sN = stateFrom([ pe({ agentId: "a", phase: "p", kind: "end", status: "done" }), ]); const sN1 = stateFrom([ pe({ agentId: "a", phase: "p", kind: "end", status: "done" }), pe({ agentId: "b", phase: "p", kind: "end", status: "done" }), ]); const opts: ToolRenderResultOptions = { expanded: false, isPartial: true }; const tN = toolText(sN, opts); const tN1 = toolText(sN1, opts); expect(tN).toContain("1/1 done"); expect(tN1).toContain("2/2 done"); expect(tN).not.toEqual(tN1); // not a frozen snapshot }); it.each([undefined, initialReducerState()])( "shows SDK errors instead of an empty or stale progress board (details: %s)", (details) => { const message = "Invalid workflow spec: /phases/0/step: missing summary\n" + "Repair guidance: ".repeat(30) + "END-OF-DIAGNOSTIC"; const result = { content: [{ type: "text" as const, text: message }], details, }; const errorTheme = { ...theme, fg: vi.fn(theme.fg) }; for (const width of [24, 80, 240]) { const lines = renderUltraResult( result, { expanded: false, isPartial: false }, errorTheme, { isError: true }, ) .render(width) .map(stripVTControlCharacters); expect(lines).toHaveLength(2); expect(lines[0]).toContain("ultra · failed:"); if (width >= 80) expect(lines[0]).toContain("/phases/0/step"); expect(lines[1]).toContain("to expand"); expect(lines.join("\n")).not.toContain("END-OF-DIAGNOSTIC"); expect(lines.join("\n")).not.toContain("0/0 done"); expect(lines.every((line) => line.length <= Math.min(width, 160))).toBe( true, ); } expect(errorTheme.fg).toHaveBeenCalledWith("error", expect.any(String)); const expanded = renderUltraResult( result, { expanded: true, isPartial: false }, theme, { isError: true }, ) .render(80) .map(stripVTControlCharacters) .join("\n"); expect(expanded).toContain("ultra · failed"); expect(expanded).toContain("/phases/0/step: missing summary"); expect(expanded).toContain("END-OF-DIAGNOSTIC"); expect(result.content[0].text).toBe(message); }, ); it("labels errors even when the SDK provides no text", () => { const out = renderUltraResult( { content: [], details: undefined }, { expanded: false, isPartial: false }, theme, { isError: true }, ) .render(80) .join("\n"); expect(out).toContain( "ultra · failed: Workflow failed without an error message.", ); expect(out).not.toContain("0/0 done"); }); it("tolerates missing details (headless / no run yet)", () => { const comp = renderUltraResult( { content: [], details: undefined as unknown as ReturnType, }, { expanded: false, isPartial: false }, theme, ); expect(() => comp.render(80)).not.toThrow(); expect(comp.render(80).join("\n")).toContain("0/0 done"); }); // Regression: a truthy `details` that is NOT a ReducerState — a resumed or // foreign tool result, a structured-output payload, a serialization double-wrap // — must fall back to an empty board, NEVER throw. An uncaught throw here is // fatal: it escapes tool-error handling and crashes the whole TUI render loop // (`state.phases.length` on an object without `phases`). it("tolerates a foreign (non-ReducerState) details without crashing the render loop", () => { const foreign = { results: [{ ok: true }] } as unknown as ReturnType< typeof initialReducerState >; for (const options of [ { expanded: false, isPartial: false }, { expanded: true, isPartial: true }, // expanded also reads `state.rows` ] as const) { const comp = renderUltraResult( { content: [], details: foreign }, options, theme, ); expect(() => comp.render(80)).not.toThrow(); expect(comp.render(80).join("\n")).toContain("0/0 done"); } }); }); // --- Layer 7b — COMMAND overlay: board + drill-in transcript -------------- describe("ultra command overlay — focus + drill-in transcript (7b)", () => { it("header shows workflow name and description when supplied", () => { const requestRender = vi.fn(); const overlay = new UltraOverlayComponent({ host: { requestRender, terminal: { rows: 24, columns: 80 } }, theme, onRunAbort: vi.fn(), workflowName: "review", workflowDescription: "Review the diff from every angle.", }); overlay.handleProgress( pe({ agentId: "review#0", phase: "review", kind: "start" }), ); overlay.handleProgress( pe({ agentId: "review#0", phase: "review", kind: "end", status: "done" }), ); const lines = overlay.render(80); expect(lines[0]).toContain("review"); expect(lines[1]).toBe("Review the diff from every angle."); expect(lines[2]).toBe("checkpoint 1/1"); }); it("shows planning placeholders but skips them during focus navigation", () => { const { overlay } = makeOverlay({ rows: 24 }); overlay.handleProgress({ kind: "plan", startedAt: Date.now(), phases: [ { phase: "a", status: "resolved", agentIds: ["a#0"] }, { phase: "pending", status: "agents-pending", agentIds: [] }, { phase: "b", status: "resolved", agentIds: ["b#0"] }, ], }); let out = overlay.render(80).join("\n"); expect(out).toContain("agents not planned yet"); expect(out).toContain("transcript: a#0"); overlay.handleInput(K.down); out = overlay.render(80).join("\n"); expect(out).toContain("phase: pending"); overlay.handleInput(K.down); overlay.handleInput(K.enter); overlay.handleInput(K.down); out = overlay.render(80).join("\n"); expect(out).toContain("transcript: b#0"); }); it("renders the exact resolved prompt as the first user transcript message", () => { const { overlay } = makeOverlay({ rows: 24 }); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "prompt", summary: "Review security", prompt: "Inspect every authentication path.", }), ); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "Working" }), ); const out = overlay.render(80).join("\n"); expect(out).toContain("Review security"); expect(out).toContain("user"); expect(out).toContain("Inspect every authentication path."); expect(out.indexOf("Inspect every authentication path.")).toBeLessThan( out.indexOf("Working"), ); }); it("renders task-first rows with live activity above metadata", () => { const { overlay } = makeOverlay({ rows: 30 }); overlay.handleProgress( pe({ agentId: "done#0", phase: "p", kind: "prompt", summary: "Inspect persistence path", }), ); overlay.handleProgress( pe({ agentId: "done#0", phase: "p", kind: "start" }), ); overlay.handleProgress( pe({ agentId: "done#0", phase: "p", kind: "end", status: "done" }), ); overlay.handleProgress( pe({ agentId: "active#0", phase: "p", kind: "prompt", summary: "Verify authentication boundary", }), ); overlay.handleProgress( pe({ agentId: "active#0", phase: "p", kind: "start" }), ); overlay.handleProgress( pe({ agentId: "active#0", phase: "p", kind: "action", actionSummary: "Reading authentication source", }), ); const lines = overlay.render(120); const active = lines.findIndex((line) => line.includes("Verify authentication boundary"), ); expect(active).toBeGreaterThanOrEqual(0); expect(lines[active + 1].trim()).toBe("Reading authentication source"); expect(lines[active + 2]).toMatch(/^\s+active#0 · running/); const done = lines.findIndex((line) => line.includes("Inspect persistence path"), ); expect(done).toBeGreaterThanOrEqual(0); expect(lines[done]).toMatch(/Inspect persistence path$/); expect(lines[done + 1]).toMatch(/^\s+done#0 · done/); }); it("toggles phase rows with Enter and cycles visible agents with Tab", () => { const { overlay } = makeOverlay({ rows: 24 }); overlay.handleProgress({ kind: "plan", startedAt: Date.now(), phases: [{ phase: "p", status: "resolved", agentIds: ["p#0", "p#1"] }], }); overlay.handleInput(K.up); overlay.handleInput(K.enter); let out = overlay.render(80).join("\n"); expect(out).toContain("phase: p"); expect(out).not.toContain("p#0 · queued"); overlay.handleInput(K.enter); overlay.handleInput(K.tab); out = overlay.render(80).join("\n"); expect(out).toContain("transcript: p#0"); overlay.handleInput(K.tab); expect(overlay.render(80).join("\n")).toContain("transcript: p#1"); }); it("focusing agent k shows its deltas and not the others'", () => { const { overlay } = makeOverlay({ rows: 24 }); for (const id of ["a", "b", "c"]) { overlay.handleProgress(pe({ agentId: id, phase: "p", kind: "start" })); } overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "AAA-DELTA" }), ); overlay.handleProgress( pe({ agentId: "b", phase: "p", kind: "delta", text: "BBB-DELTA" }), ); overlay.handleProgress( pe({ agentId: "c", phase: "p", kind: "delta", text: "CCC-DELTA" }), ); // default focus is the first agent let out = overlay.render(60).join("\n"); expect(out).toContain("transcript: a"); expect(out).toContain("AAA-DELTA"); expect(out).not.toContain("BBB-DELTA"); expect(out).not.toContain("CCC-DELTA"); // Tab advances focus → b overlay.handleInput(K.tab); out = overlay.render(60).join("\n"); expect(out).toContain("transcript: b"); expect(out).toContain("BBB-DELTA"); expect(out).not.toContain("AAA-DELTA"); expect(out).not.toContain("CCC-DELTA"); // ↓ advances focus → c overlay.handleInput(K.down); out = overlay.render(60).join("\n"); expect(out).toContain("transcript: c"); expect(out).toContain("CCC-DELTA"); // ↑ moves focus back → b overlay.handleInput(K.up); out = overlay.render(60).join("\n"); expect(out).toContain("transcript: b"); expect(out).toContain("BBB-DELTA"); }); it("auto-focuses the first agent in a new phase so verifier transcripts are visible during the run", () => { const { overlay } = makeOverlay({ rows: 12 }); for (let i = 0; i < 3; i++) { overlay.handleProgress( pe({ agentId: `review#${i}`, phase: "review", kind: "start" }), ); overlay.handleProgress( pe({ agentId: `review#${i}`, phase: "review", kind: "end", status: "done", }), ); } overlay.handleProgress( pe({ agentId: "verify#0", phase: "verify", kind: "start" }), ); overlay.handleProgress( pe({ agentId: "verify#0", phase: "verify", kind: "delta", text: "VERIFY-DELTA", }), ); const out = overlay.render(60).join("\n"); expect(out).toContain("transcript: verify#0"); expect(out).toContain("VERIFY-DELTA"); }); it("keeps rendered overlay within terminal height minus a 3-row safety margin", () => { const { overlay } = makeOverlay({ rows: 12 }); for (let i = 0; i < 8; i++) { overlay.handleProgress( pe({ agentId: `a${i}`, phase: "p", kind: "start" }), ); } for (let i = 0; i < 20; i++) { overlay.handleProgress( pe({ agentId: "a7", phase: "p", kind: "delta", text: `L${i}\n` }), ); } expect(overlay.render(60).length).toBeLessThanOrEqual(9); }); it("keeps one native Pi tool row through start, result, and expansion", () => { let expanded = false; const { overlay } = makeOverlay({ rows: 80, toolUi: true, toolsExpanded: () => expanded, isToolsExpandKey: (data) => data === "EXPAND", onToolsExpand: () => { expanded = !expanded; }, }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "tool-1", toolName: "read", args: { path: "extensions/ultra/index.ts" }, }), ); let out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain("extensions/ultra/index.ts"); expect(out).not.toContain("read ..."); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "tool-1", toolName: "read", result: { content: [{ type: "text", text: "PARTIAL-RESULT" }], }, isPartial: true, }), ); out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).not.toContain("PARTIAL-RESULT"); overlay.handleInput("EXPAND"); expect(expanded).toBe(true); out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain("PARTIAL-RESULT"); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "tool-1", toolName: "read", result: { content: [{ type: "text", text: "NATIVE-RESULT" }], }, isError: false, isPartial: false, }), ); out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain("NATIVE-RESULT"); expect(out).not.toContain("PARTIAL-RESULT"); expect(out).not.toContain("read ..."); }); it.each(["structured_output", "custom_tool"])( "renders %s arguments and JSON results as expandable fields", (toolName) => { let expanded = false; const { overlay } = makeOverlay({ rows: 80, toolUi: true, toolsExpanded: () => expanded, }); const args = { task: "Submit findings", records: [{ file: "nested-evidence.ts", accepted: false }], }; const result = { content: [{ type: "text", text: '{"status":"pending","count":2}' }], details: { findings: [{ reason: "RENDERED-EVIDENCE" }], empty: [] }, }; const original = JSON.stringify({ args, result }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "json-tool", toolName, args, }), ); let out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).not.toContain("nested-evidence.ts"); expect(out).not.toContain('"task":'); expanded = true; out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain("Task: Submit findings"); expect(out).toContain("File: nested-evidence.ts"); expect(out).not.toContain('"records":'); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "json-tool", toolName, result, isPartial: true, }), ); out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain("Status: pending"); expect(out).toContain("Reason: RENDERED-EVIDENCE"); expect(out).toContain("No items"); expect(out).not.toContain('"status":'); expanded = false; out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).not.toContain("RENDERED-EVIDENCE"); expect(out).not.toContain("nested-evidence.ts"); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "json-tool", toolName, result, isPartial: false, }), ); expanded = true; out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain("RENDERED-EVIDENCE"); expect(JSON.stringify({ args, result })).toBe(original); }, ); it.each([ ["read", { path: "source.ts" }], ["write", { path: "source.ts", content: "const x = 1;" }], ["edit", { path: "source.ts", edits: [{ oldText: "a", newText: "b" }] }], ["bash", { command: "echo source.ts" }], ["powershell", { command: "Write-Output source.ts" }], ["grep", { path: "source.ts", pattern: "needle" }], ["find", { path: ".", pattern: "source.ts" }], ["ls", { path: "source.ts" }], ] as const)( "uses Pi's native %s renderer instead of JSON argument dumps", (toolName, args) => { const { overlay } = makeOverlay({ rows: 80, toolUi: true }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "native", toolName, args, }), ); const out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain("source.ts"); expect(out).not.toContain('"path":'); expect(out).not.toContain('"command":'); expect(out).not.toContain('"edits":'); }, ); it("preserves JSON source text in native read results", () => { const { overlay } = makeOverlay({ rows: 40, toolUi: true, toolsExpanded: () => true, }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "source", toolName: "read", args: { path: "source.json" }, result: { content: [{ type: "text", text: '{"setting":1}' }] }, isPartial: false, }), ); const out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain('{"setting":1}'); expect(out).not.toContain('"path":'); }); it("hides collapsed tool previews when action summaries are enabled", () => { let expanded = false; const { overlay } = makeOverlay({ rows: 80, toolUi: true, hideToolPreviews: true, toolsExpanded: () => expanded, isToolsExpandKey: (data) => data === "EXPAND", onToolsExpand: () => { expanded = !expanded; }, }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "action", toolCallId: "tool-1", toolName: "read", args: { path: "HIDDEN-PREVIEW.ts" }, actionSummary: "Inspecting hidden preview", }), ); let out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).not.toContain("HIDDEN-PREVIEW.ts"); overlay.handleInput("EXPAND"); out = stripVTControlCharacters(overlay.render(100).join("\n")); expect(out).toContain("HIDDEN-PREVIEW.ts"); }); it("renders transcript markdown with Pi's markdown renderer", () => { const { overlay } = makeOverlay({ rows: 24 }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "**bold**" }), ); const out = overlay.render(60).join("\n"); expect(out).toContain("bold"); expect(out).not.toContain("**bold**"); }); it("renders transcript title as a compact divider header", () => { const { overlay } = makeOverlay({ rows: 16 }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); const lines = overlay.render(50); const header = lines.find((l) => l.includes("transcript: a")); expect(header).toBeDefined(); expect(header).toMatch(/─.*transcript: a.*─/); expect(lines.some((l) => l === "transcript: a")).toBe(false); }); it("Alt+Up/Alt+Down scroll transcript when PageUp/PageDown is intercepted by the terminal", () => { const { overlay } = makeOverlay({ rows: 12 }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); for (let i = 0; i < 10; i++) { overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: `L${i}\n` }), ); } expect(overlay.render(40).join("\n")).toContain("L9"); overlay.handleInput(K.altUp); let out = overlay.render(40).join("\n"); expect(out).toContain("L0"); expect(out).not.toContain("L9"); overlay.handleInput(K.altDown); out = overlay.render(40).join("\n"); expect(out).toContain("L9"); }); it("follow-mode pins to the newest line until the user scrolls, then re-pins", () => { // rows=12 with a 3-row safety margin leaves room for a transcript tail. const { overlay } = makeOverlay({ rows: 12 }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); for (let i = 0; i < 10; i++) { overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: i < 9 ? `L${i}\n` : `L${i}`, }), ); } // follow-mode shows the newest tail, not the head let out = overlay.render(40).join("\n"); expect(out).toContain("L9"); expect(out).not.toContain("L0"); // scroll up → follow disengages, view moves to the head overlay.handleInput(K.pageUp); out = overlay.render(40).join("\n"); expect(out).toContain("L0"); expect(out).not.toContain("L9"); // new deltas arrive WHILE scrolled → view stays put (does not jump to newest) overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "\nL10" }), ); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "\nL11" }), ); out = overlay.render(40).join("\n"); expect(out).toContain("L0"); expect(out).not.toContain("L11"); // scroll back to the bottom → follow re-engages, newest shown again overlay.handleInput(K.pageDown); out = overlay.render(40).join("\n"); expect(out).toContain("L11"); }); it("throttles re-render requests to the window, but state stays fresh; terminal events bypass it", () => { let clock = 0; const { overlay, requestRender } = makeOverlay({ now: () => clock, throttleMs: 80, }); clock = 0; overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "x" }), ); // leading edge clock = 10; overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "y" }), ); // throttled clock = 50; overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "z" }), ); // throttled expect(requestRender).toHaveBeenCalledTimes(1); clock = 100; overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "delta", text: "w" }), ); // window elapsed expect(requestRender).toHaveBeenCalledTimes(2); clock = 110; overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "end", status: "done" }), ); // forced expect(requestRender).toHaveBeenCalledTimes(3); // every delta was folded despite the throttle — no lost state expect(overlay.render(80).join("\n")).toContain("xyzw"); }); }); // --- Layer 7c — steer + per-agent abort (command overlay only) ------------ function type(overlay: UltraOverlayComponent, text: string) { for (const ch of text) overlay.handleInput(ch); } describe("ultra command overlay — steer + per-agent abort (7c)", () => { it("s+text on a RUNNING agent calls controls.steer and marks steered at enqueue time", () => { const steer = vi.fn().mockResolvedValue(undefined); const followUp = vi.fn().mockResolvedValue(undefined); const controls: AgentControls = { steer, abort: vi.fn() }; (controls as OverlayAgentControls).followUp = followUp; const { overlay } = makeOverlay({ rows: 24 }); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "start", controls }), ); expect(overlay.steered).toBe(false); overlay.handleInput("s"); // enter steer sub-mode // the message itself contains 's' and 'x' — in steer mode they are TEXT, // not the steer/abort commands (Esc-collision guard). type(overlay, "use bash xtools"); expect(overlay.steered).toBe(false); // not yet enqueued overlay.handleInput(K.enter); // submit expect(steer).toHaveBeenCalledTimes(1); expect(steer).toHaveBeenCalledWith("use bash xtools"); expect(followUp).not.toHaveBeenCalled(); expect(overlay.steered).toBe(true); // synchronous, before the promise settles }); it("x aborts the focused agent via controls.abort", () => { const abort = vi.fn(); const controls: AgentControls = { steer: vi.fn(), abort }; const { overlay } = makeOverlay({ rows: 24 }); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "start", controls }), ); overlay.handleProgress(pe({ agentId: "b", phase: "p", kind: "start" })); overlay.handleInput("x"); // focus is a expect(abort).toHaveBeenCalledTimes(1); }); it("steering a FINISHED agent routes to followUp, not steer, and does not taint the run", () => { const steer = vi.fn(); const followUp = vi.fn().mockResolvedValue(undefined); const controls: AgentControls = { steer, abort: vi.fn() }; (controls as OverlayAgentControls).followUp = followUp; const { overlay } = makeOverlay({ rows: 24 }); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "start", controls }), ); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "end", status: "done" }), ); overlay.handleInput("s"); type(overlay, "rerun"); overlay.handleInput(K.enter); expect(followUp).toHaveBeenCalledWith("rerun"); expect(steer).not.toHaveBeenCalled(); expect(overlay.steered).toBe(false); // follow-up is not a steer taint }); it("requires two Esc presses to abort the whole run", () => { const onRunAbort = vi.fn(); const { overlay } = makeOverlay({ rows: 24, onRunAbort }); overlay.handleProgress(pe({ agentId: "a", phase: "p", kind: "start" })); overlay.handleInput(K.esc); expect(onRunAbort).not.toHaveBeenCalled(); expect(overlay.render(80).join("\n")).toContain( "Press Esc again to abort all agents", ); overlay.handleInput(K.esc); expect(onRunAbort).toHaveBeenCalledTimes(1); }); it("expires abort confirmation instead of treating a later Esc as confirmation", () => { let clock = 0; const onRunAbort = vi.fn(); const { overlay } = makeOverlay({ rows: 24, now: () => clock, onRunAbort, }); overlay.handleInput(K.esc); clock = 1600; overlay.handleInput(K.esc); expect(onRunAbort).not.toHaveBeenCalled(); overlay.handleInput(K.esc); expect(onRunAbort).toHaveBeenCalledTimes(1); }); it("Esc inside steer-input cancels the buffer; it does NOT abort the run", () => { const onRunAbort = vi.fn(); const steer = vi.fn(); const controls: AgentControls = { steer, abort: vi.fn() }; const { overlay } = makeOverlay({ rows: 24, onRunAbort }); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "start", controls }), ); overlay.handleInput("s"); // steer mode type(overlay, "half typed"); overlay.handleInput(K.esc); // cancel the buffer, back to normal mode expect(onRunAbort).not.toHaveBeenCalled(); expect(steer).not.toHaveBeenCalled(); // Two fresh Esc presses in normal mode now confirm the whole-run abort. overlay.handleInput(K.esc); expect(onRunAbort).not.toHaveBeenCalled(); overlay.handleInput(K.esc); expect(onRunAbort).toHaveBeenCalledTimes(1); }); it("a rejecting steer is caught (no floating rejection)", async () => { const steer = vi.fn().mockRejectedValue(new Error("boom")); const controls: AgentControls = { steer, abort: vi.fn() }; const { overlay } = makeOverlay({ rows: 24 }); overlay.handleProgress( pe({ agentId: "a", phase: "p", kind: "start", controls }), ); overlay.handleInput("s"); type(overlay, "go"); expect(() => overlay.handleInput(K.enter)).not.toThrow(); expect(steer).toHaveBeenCalledWith("go"); // let the rejected promise settle — the .catch() handles it, no crash await Promise.resolve(); await Promise.resolve(); }); it("the tool sink exposes NO controls path (read-only) — render never invokes steer/abort", () => { // Re-assert the surface split: feeding controls-bearing state to the tool // renderer must not call them; it only draws. const steer = vi.fn(); const abort = vi.fn(); const controls: AgentControls = { steer, abort }; const s = stateFrom([ pe({ agentId: "a", phase: "p", kind: "start", controls }), ]); renderUltraResult( { content: [], details: s }, { expanded: true, isPartial: true }, theme, ).render(80); expect(steer).not.toHaveBeenCalled(); expect(abort).not.toHaveBeenCalled(); }); });