import { describe, expect, test } from "vitest"; import { initTheme } from "@earendil-works/pi-coding-agent"; import { buildLatestRenderBlock, createSubagentInspectorResultComponent, SubagentSteeringEditorComponent, } from "../runtime-tui"; import type { SubagentDashboardRunState } from "../types"; initTheme(); function createRunState(): SubagentDashboardRunState { const usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0, }, }; return { runId: "run-1", createdAt: 1, updatedAt: 1, active: false, tasks: [ { taskId: "task-a", prompt: "Inspect A", cwd: "/tmp/a", status: "completed", launchContext: "fresh", latestActivity: "finished", activityCount: 2, transcript: [ { kind: "assistantMessage", timestamp: 16, message: { role: "assistant", api: "openai-responses", provider: "openai", model: "gpt-5", usage, stopReason: "stop", timestamp: 16, content: [{ type: "text", text: "task-a latest summary" }], }, }, ], output: "done", references: [], stderr: "", startedAt: 1, finishedAt: 2, steeringNotes: [], }, { taskId: "task-b", prompt: "Inspect B", cwd: "/tmp/b", status: "failed", launchContext: "fresh", latestActivity: "failed", activityCount: 2, transcript: [ { kind: "assistantMessage", timestamp: 1, message: { role: "assistant", api: "openai-responses", provider: "openai", model: "gpt-5", usage, stopReason: "toolUse", timestamp: 1, content: [{ type: "toolCall", id: "tool-call-1", name: "read", arguments: { path: "README.md" } }], }, }, { kind: "toolResultMessage", timestamp: 2, message: { role: "toolResult", toolCallId: "tool-call-1", toolName: "read", content: [{ type: "text", text: "README contents" }], details: { path: "README.md" }, isError: true, timestamp: 2, }, }, ], output: "", references: [], stderr: "boom", startedAt: 1, finishedAt: 2, steeringNotes: [], }, { taskId: "task-c", prompt: "Inspect C", cwd: "/tmp/c", status: "running", launchContext: "fresh", latestActivity: "drafting summary", activityCount: 3, transcript: [ { kind: "assistantMessage", timestamp: 3, message: { role: "assistant", api: "openai-responses", provider: "openai", model: "gpt-5", usage, stopReason: "stop", timestamp: 3, content: [{ type: "text", text: "partial summary from the running task" }], }, }, ], output: "", references: [], stderr: "", startedAt: 1, finishedAt: null, steeringNotes: [], }, ], }; } describe("runtime-tui helpers", () => { test("buildLatestRenderBlock prefers the latest tool execution block", () => { const run = createRunState(); const block = buildLatestRenderBlock(run.tasks[1]!); expect(block).toMatchObject({ kind: "toolExecution", toolName: "read", }); }); test("renders expanded-style details for the current subagent", () => { const run = createRunState(); let selectedTaskId = "task-a"; const component = createSubagentInspectorResultComponent({ runId: "run-1", getRunState: () => run, getSelectedTaskId: () => selectedTaskId, accentColor: (text) => `${text}`, mutedColor: (text) => `${text}`, dimColor: (text) => `${text}`, }); const taskARender = component.render(100).join("\n"); expect(taskARender).toContain("Subagent: task-a"); expect(taskARender).not.toContain("[ ✓ task-a ]"); expect(taskARender).toContain("Prompt: Inspect A"); expect(taskARender).toContain("CWD:"); expect(taskARender).toContain("Duration:"); expect(taskARender).toContain("Activity:"); expect(taskARender).toContain("Output:"); expect(taskARender).toContain("done"); selectedTaskId = "task-b"; const taskBRender = component.render(100).join("\n"); expect(taskBRender).toContain("Subagent: task-b"); expect(taskBRender).toContain("Latest tool call:"); expect(taskBRender).toContain("read"); expect(taskBRender).toContain("README.md"); selectedTaskId = "task-c"; const taskCRender = component.render(100).join("\n"); expect(taskCRender).toContain("Latest update:"); expect(taskCRender).toContain("partial summary from the running task"); expect(taskCRender).not.toContain("(no output)"); }); }); describe("SubagentSteeringEditorComponent", () => { function createHarness(options?: { submitDraft?: (taskId: string, draft: string) => Promise }) { const run = createRunState(); let selectedTaskId = "task-a"; const drafts = new Map(); let renderRequests = 0; let closed = 0; let listener: (() => void) | undefined; let statusMessage = ""; const component = new SubagentSteeringEditorComponent( { requestRender: () => { renderRequests += 1; }, terminal: { rows: 24 } } as any, {}, { getRunState: () => run, getSelectedTaskId: () => selectedTaskId, setSelectedTaskId: (taskId) => { selectedTaskId = taskId; }, getDraft: (taskId) => drafts.get(taskId) ?? "", setDraft: (taskId, draft) => { drafts.set(taskId, draft); }, submitDraft: options?.submitDraft ?? (async () => {}), close: () => { closed += 1; }, subscribe: (nextListener) => { listener = nextListener; return () => { if (listener === nextListener) { listener = undefined; } }; }, getStatusMessage: () => statusMessage || undefined, }, ); return { component, render: () => component.render(100).join("\n"), setStatusMessage: (value: string) => { statusMessage = value; listener?.(); }, getSelectedTaskId: () => selectedTaskId, getDraft: (taskId: string) => drafts.get(taskId), getClosed: () => closed, getRenderRequests: () => renderRequests, }; } test("switches tabs with tab and shift+tab", () => { const harness = createHarness(); for (const char of "first draft") { harness.component.handleInput(char); } harness.component.handleInput("\t"); expect(harness.getSelectedTaskId()).toBe("task-b"); harness.component.handleInput("\u001b[Z"); expect(harness.getSelectedTaskId()).toBe("task-a"); expect(harness.render()).toContain("first draft"); }); test("submits the selected task draft with enter", async () => { const calls: Array<{ taskId: string; draft: string }> = []; const harness = createHarness({ submitDraft: async (taskId, draft) => { calls.push({ taskId, draft }); }, }); for (const char of "Focus on config") { harness.component.handleInput(char); } harness.component.handleInput("\r"); await new Promise((resolve) => setTimeout(resolve, 0)); expect(calls).toEqual([{ taskId: "task-a", draft: "Focus on config" }]); }); test("closes on escape", () => { const harness = createHarness(); harness.component.handleInput("\u001b"); expect(harness.getClosed()).toBe(1); }); test("renders tabs above the editor and shows status", () => { const harness = createHarness(); harness.setStatusMessage("Steered task-a."); const rendered = harness.render(); const tabsIndex = rendered.indexOf("task-a"); const editorBorderIndex = rendered.indexOf("─"); expect(tabsIndex).toBeGreaterThanOrEqual(0); expect(editorBorderIndex).toBeGreaterThanOrEqual(0); expect(tabsIndex).toBeLessThan(editorBorderIndex); expect(rendered).toContain("Steered task-a."); expect(rendered).toContain("Enter submit"); expect(rendered).not.toContain("Ctrl+S"); }); });