feat(rpc): add validated desktop Pi RPC command palette
This commit is contained in:
@@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user