import assert from "node:assert/strict"; import { EventEmitter } from "node:events"; import { PassThrough } from "node:stream"; import test from "node:test"; import { startPiRpcAdapter } from "../src/bridge/pi-rpc-adapter.js"; function createChild() { const child = new EventEmitter(); child.stdin = new PassThrough(); child.stdout = new PassThrough(); child.stderr = new PassThrough(); child.kill = (signal) => { child.emit("exit", null, signal); return true; }; return child; } test("normalizes streaming, tool, and queue events in child output order", async () => { const child = createChild(); const events = []; const adapter = startPiRpcAdapter({ cwd: "/workspace/home", sessionDir: "/workspace/sessions", spawnProcess: () => child, onEvent: (event) => events.push(event), }); const piEvents = [ { type: "message_update", message: { role: "assistant" }, assistantMessageEvent: { type: "text_delta", delta: "Hello" }, }, { type: "tool_execution_start", toolCallId: "tool-1", toolName: "bash", args: { command: "pwd" }, }, { type: "tool_execution_end", toolCallId: "todo-1", toolName: "todo", result: { details: { tasks: [{ id: 1, subject: "Ship pane", status: "in_progress" }], }, }, isError: false, }, { type: "queue_update", steering: ["Refocus"], followUp: [] }, ]; child.stdout.write(JSON.stringify(piEvents[0]).slice(0, 30)); child.stdout.write( `${JSON.stringify(piEvents[0]).slice(30)}\n${JSON.stringify(piEvents[1])}\n${JSON.stringify(piEvents[2])}\n${JSON.stringify(piEvents[3])}\n`, ); assert.deepEqual(events, [ { seq: 1, type: "stream", data: { event: piEvents[0] } }, { seq: 2, type: "tool", data: { event: piEvents[1] } }, { seq: 3, type: "tool", data: { event: piEvents[2] } }, { seq: 4, type: "queue", data: { event: piEvents[3] } }, ]); await adapter.stop(); }); test("marks Pi idle only after it settles and preserves extension errors as diagnostics", async () => { const child = createChild(); const events = []; const adapter = startPiRpcAdapter({ cwd: "/workspace/home", sessionDir: "/workspace/sessions", spawnProcess: () => child, onEvent: (event) => events.push(event), }); const piEvents = [ { type: "agent_start" }, { type: "agent_end", willRetry: false }, { type: "agent_settled" }, { type: "extension_error", error: "non-fatal extension failure" }, ]; child.stdout.write( `${piEvents.map((event) => JSON.stringify(event)).join("\n")}\n`, ); assert.deepEqual( events.map((event) => ({ type: event.type, state: event.data.state })), [ { type: "agent_state", state: "streaming" }, { type: "transcript", state: undefined }, { type: "agent_state", state: "idle" }, { type: "transcript", state: undefined }, ], ); await adapter.stop(); });