feat(rpc): add validated desktop Pi RPC command palette

This commit is contained in:
2026-08-24 20:16:51 +02:00
parent 475f95ba70
commit 99084cc5a5
25 changed files with 2170 additions and 344 deletions
+110
View File
@@ -96,6 +96,32 @@ test("starts Pi in RPC mode and correlates a command response", async () => {
await adapter.stop();
});
test("allows a per-command timeout override without serializing it to Pi", async () => {
const fixture = createFixture();
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: fixture.spawn,
commandTimeoutMs: 10,
});
const initial = adapter.send({ type: "get_state" }, { timeoutMs: 50 });
const [initialCommand] = fixture.sent();
await new Promise((resolve) => setTimeout(resolve, 20));
fixture.child.stdout.write(
`${JSON.stringify({ type: "response", id: initialCommand.id, command: "get_state", success: true })}\n`,
);
await initial;
assert.deepEqual(fixture.sent()[0], {
type: "get_state",
id: initialCommand.id,
});
await assert.rejects(
adapter.send({ type: "get_session_stats" }),
/timed out/,
);
await adapter.stop();
});
test("requests Pi session statistics for context and token status", async () => {
const fixture = createFixture();
const adapter = startPiRpcAdapter({
@@ -182,6 +208,90 @@ test("forwards extension responses without replacing Pi's request ID", async ()
await adapter.stop();
});
test("discards a fragmented oversized frame before processing the next frame", async () => {
const fixture = createFixture();
const errors = [];
const events = [];
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: fixture.spawn,
maxFrameBytes: 32,
onError: (error) => errors.push(error),
onEvent: (event) => events.push(event),
});
const oversized = JSON.stringify({
type: "message_update",
delta: "x".repeat(80),
});
fixture.child.stdout.write(oversized.slice(0, 40));
fixture.child.stdout.write(
`${oversized.slice(40)}\n${JSON.stringify({ type: "agent_settled" })}\n`,
);
assert.deepEqual(
errors.map((error) => error.code),
["frame_too_large"],
);
assert.deepEqual(events, [
{
seq: 1,
type: "agent_state",
data: { state: "idle", event: { type: "agent_settled" } },
},
]);
await adapter.stop();
});
test("discards an oversized complete frame before processing the next frame", async () => {
const fixture = createFixture();
const errors = [];
const events = [];
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: fixture.spawn,
maxFrameBytes: 32,
onError: (error) => errors.push(error),
onEvent: (event) => events.push(event),
});
const oversized = JSON.stringify({
type: "message_update",
delta: "x".repeat(80),
});
fixture.child.stdout.write(
`${oversized}\n${JSON.stringify({ type: "agent_settled" })}\n`,
);
assert.deepEqual(
errors.map((error) => error.code),
["frame_too_large"],
);
assert.equal(events.length, 1);
assert.equal(events[0].data.state, "idle");
await adapter.stop();
});
test("does not report an unterminated frame after discarding an oversized frame", async () => {
const fixture = createFixture();
const errors = [];
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: fixture.spawn,
maxFrameBytes: 32,
onError: (error) => errors.push(error),
});
fixture.child.stdout.write("x".repeat(40));
fixture.child.stdout.end();
assert.deepEqual(
errors.map((error) => error.code),
["frame_too_large"],
);
await adapter.stop();
});
test("reports invalid child output and rejects pending commands on child exit", async () => {
const fixture = createFixture();
const errors = [];