Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/ultra/__tests__/ui.test.ts

Raw
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<UltraProgressEvent> &
		Pick<UltraProgressEvent, "agentId" | "phase" | "kind">,
): UltraProgressEvent => e;

function stateFrom(events: UltraProgressEvent[]) {
	return events.reduce(reduceProgress, initialReducerState());
}

function renderTool(
	state: ReturnType<typeof initialReducerState>,
	options: ToolRenderResultOptions,
	width = 80,
): string[] {
	return renderUltraResult(
		{ content: [], details: state },
		options,
		theme,
	).render(width);
}

const toolText = (
	state: ReturnType<typeof initialReducerState>,
	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<typeof initialReducerState>,
			},
			{ 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();
	});
});