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
+64 -4
View File
@@ -20,10 +20,12 @@ function createAdapterFactory() {
startAdapter: (options) => {
const adapter = {
sent: [],
sentWithOptions: [],
extensionResponses: [],
stopped: false,
send(command) {
send(command, options) {
this.sent.push(command);
this.sentWithOptions.push({ command, options });
return Promise.resolve({
type: "response",
command: command.type,
@@ -53,6 +55,10 @@ test("starts only the home agent and creates other worktree agents on explicit s
});
const home = await registry.start();
assert.deepEqual(fixture.calls[0].adapter.sentWithOptions[0], {
command: { type: "get_state" },
options: { timeoutMs: 90_000 },
});
assert.equal(registry.listAgents().length, 1);
assert.equal(home.worktreePath, worktrees.home);
@@ -72,6 +78,58 @@ test("starts only the home agent and creates other worktree agents on explicit s
assert.ok(fixture.calls.every(({ adapter }) => adapter.stopped));
});
test("sends abort without waiting behind a blocked prompt", async () => {
const worktrees = await createWorktrees();
const fixture = createAdapterFactory();
const registry = createAgentRegistry({
homeWorktree: worktrees.home,
sessionRoot: worktrees.sessionRoot,
startAdapter: fixture.startAdapter,
});
const agent = await registry.start();
const adapter = fixture.calls[0].adapter;
const send = adapter.send.bind(adapter);
let releasePrompt;
adapter.send = (command) => {
if (command.type !== "prompt") return send(command);
adapter.sent.push(command);
return new Promise((resolve) => {
releasePrompt = () =>
resolve({ type: "response", command: "prompt", success: true });
});
};
const prompt = registry.route(agent.id, "prompt", { message: "Work" });
while (!releasePrompt) await new Promise((resolve) => setImmediate(resolve));
const abort = registry.route(agent.id, "abort");
await new Promise((resolve) => setImmediate(resolve));
assert.equal(adapter.sent.at(-1).type, "abort");
releasePrompt();
await Promise.all([prompt, abort]);
await registry.stop();
});
test("keeps a runtime healthy after a non-terminal adapter diagnostic", async () => {
const worktrees = await createWorktrees();
const fixture = createAdapterFactory();
const registry = createAgentRegistry({
homeWorktree: worktrees.home,
sessionRoot: worktrees.sessionRoot,
startAdapter: fixture.startAdapter,
});
await registry.start();
fixture.calls[0].options.onError(
{ code: "frame_too_large", message: "Pi RPC frame exceeds 32 bytes" },
{ terminal: false },
);
const runtime = registry.getWorkspace().directories[0].runtimes[0];
assert.equal(runtime.state, "idle");
assert.equal(runtime.attention, false);
assert.equal(runtime.error, undefined);
await registry.stop();
});
test("stops agents created during shutdown and rejects new selections", async () => {
const worktrees = await createWorktrees();
const fixture = createAdapterFactory();
@@ -270,8 +328,9 @@ test("coordinates forgetting with directory creation and active commands", async
/runtimes are open/,
);
const closing = registry.closeSessionRuntime(
registry.listAgents().find((agent) => agent.worktreePath === worktrees.feature)
.runtimeId,
registry
.listAgents()
.find((agent) => agent.worktreePath === worktrees.feature).runtimeId,
);
await new Promise((resolve) => setImmediate(resolve));
releaseStop();
@@ -483,9 +542,10 @@ test("routes commands by explicit agent ID and replays only events after the cur
type: "agent_state",
data: { state: "streaming" },
});
await registry.route(agent.id, "submit_prompt", {
const followUp = await registry.route(agent.id, "submit_prompt", {
message: "After this turn",
});
assert.equal(followUp.delivery, "follow_up");
assert.deepEqual(adapter.sent.at(-1), {
type: "follow_up",
message: "After this turn",