From 017cfe861c223e9e8b2b38c9b0d9254d2baa6c51 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 2 Nov 2026 05:16:24 +0100 Subject: [PATCH] refactor(pi): name runtime options and assert restored tool output --- .../server/agent/providers/pi/cli-runtime.ts | 45 ++++++++++--------- .../daemon-e2e/pi-rewind.real.e2e.test.ts | 9 +++- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/packages/server/src/server/agent/providers/pi/cli-runtime.ts b/packages/server/src/server/agent/providers/pi/cli-runtime.ts index 83249f0a8a..78c1fc7cf6 100644 --- a/packages/server/src/server/agent/providers/pi/cli-runtime.ts +++ b/packages/server/src/server/agent/providers/pi/cli-runtime.ts @@ -79,24 +79,27 @@ export class PiCliRuntime implements PiRuntime { await process.close(input.signal.reason); input.signal.throwIfAborted(); } - return new PiCliRuntimeSession( + return new PiCliRuntimeSession({ process, - this.commandsRpcName, - launch.env, - input.session === undefined, - ); + commandsRpcName: this.commandsRpcName, + launchEnvironment: launch.env, + newSession: input.session === undefined, + }); } } +interface PiCliRuntimeSessionOptions { + process: JsonlRpcProcess; + commandsRpcName: string; + launchEnvironment: Record; + newSession: boolean; +} + class PiCliRuntimeSession implements PiRuntimeSession { private readonly subscribers = new Set<(event: PiRuntimeEvent) => void>(); - constructor( - private readonly process: JsonlRpcProcess, - private readonly commandsRpcName: string, - private readonly launchEnvironment: Record, - private readonly newSession: boolean, - ) { + constructor(private readonly options: PiCliRuntimeSessionOptions) { + const { process } = options; process.onMessage((message) => { this.emit(message as PiRuntimeEvent); }); @@ -106,7 +109,7 @@ class PiCliRuntimeSession implements PiRuntimeSession { } get environment(): Record { - return this.launchEnvironment; + return this.options.launchEnvironment; } onEvent(callback: (event: PiRuntimeEvent) => void): () => void { @@ -120,7 +123,7 @@ class PiCliRuntimeSession implements PiRuntimeSession { message: string, images?: Array<{ type: "image"; data: string; mimeType: string }>, ): Promise { - const { id: requestId, promise } = this.process.startRequest({ + const { id: requestId, promise } = this.options.process.startRequest({ type: "prompt", message, ...(images?.length ? { images } : {}), @@ -177,7 +180,7 @@ class PiCliRuntimeSession implements PiRuntimeSession { } catch (error) { // Pi does not create the file until the first conversation message is saved. if ( - this.newSession && + this.options.newSession && state.messageCount === 0 && error instanceof Error && "code" in error && @@ -236,21 +239,21 @@ class PiCliRuntimeSession implements PiRuntimeSession { } async getCommands(): Promise { - const data = (await this.request({ type: this.commandsRpcName })) as { + const data = (await this.request({ type: this.options.commandsRpcName })) as { commands?: PiRpcSlashCommand[]; }; return data.commands ?? []; } sendRawFrame(frame: object & { type: string }): void { - this.process.send(frame); + this.options.process.send(frame); } respondToExtensionUiRequest( id: string, response: { value?: string; confirmed?: boolean; cancelled?: boolean }, ): void { - this.process.send({ type: "extension_ui_response", id, ...response }); + this.options.process.send({ type: "extension_ui_response", id, ...response }); } cancelExtensionUiRequest(id: string): void { @@ -258,22 +261,22 @@ class PiCliRuntimeSession implements PiRuntimeSession { } async close(): Promise { - await this.process.close(new Error("Pi RPC session is closed")); + await this.options.process.close(new Error("Pi RPC session is closed")); } request(command: PiRpcCommand, timeoutMs?: number | null): Promise { - return this.process.request(command, timeoutMs); + return this.options.process.request(command, timeoutMs); } private requestStopWork(command: PiRpcCommand): Promise { - return this.process.requestStopWork(command); + return this.options.process.requestStopWork(command); } private async waitForCompletion(command: PiRpcCommand): Promise { // Pi only replies after its compaction work is durable. Its child process and // session close paths already reject pending RPCs, so no elapsed-time failure // is useful here. - await this.process.request(command, JSONL_RPC_NO_TIMEOUT); + await this.options.process.request(command, JSONL_RPC_NO_TIMEOUT); } private emit(event: PiRuntimeEvent): void { diff --git a/packages/server/src/server/daemon-e2e/pi-rewind.real.e2e.test.ts b/packages/server/src/server/daemon-e2e/pi-rewind.real.e2e.test.ts index efc1522371..06eab7dd4a 100644 --- a/packages/server/src/server/daemon-e2e/pi-rewind.real.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/pi-rewind.real.e2e.test.ts @@ -498,8 +498,13 @@ test("seeded compacted Pi history survives daemon reload, native rewind and Fork await client.refreshAgent(imported.id); const restored = await fetchTimelineItems(client, imported.id); expectTimeline(restored, expected); - expect(restored.filter((item) => item.type === "tool_call")).toHaveLength(1); - expect(JSON.stringify(restored)).toContain("saved tool output"); + const tools = restored.filter((item) => item.type === "tool_call"); + expect(tools).toHaveLength(1); + expect(tools[0].detail).toEqual({ + type: "read", + filePath: "readme.txt", + content: "saved tool output", + }); expect( restored.filter((item) => item.type === "user_message").map((item) => item.messageId), ).toEqual(["u1", "u2"]);