Files
pi-gui/test/pi-rpc-events.test.js
alex b7fea83ed6 feat(bridge): establish local pi status bridge
Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
2026-07-27 14:51:45 +02:00

97 lines
2.8 KiB
JavaScript

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();
});