From d7b941680ac69b8daf3c4e4c67081b2dc80355b5 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 20:40:03 +0000 Subject: [PATCH] fix(pi): preserve restart recovery during graceful shutdown Intentional runtime release emitted a failed turn and consumed the saved recovery intent. Keep process-exit cleanup while reserving turn failure for exits of a live session. --- .../server/agent/providers/pi/agent.test.ts | 19 +++++++++++++++++++ .../src/server/agent/providers/pi/agent.ts | 16 ++++++++++------ .../agent/providers/pi/test-utils/fake-pi.ts | 4 ++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/server/src/server/agent/providers/pi/agent.test.ts b/packages/server/src/server/agent/providers/pi/agent.test.ts index 039d3eaf22..c04683bec2 100644 --- a/packages/server/src/server/agent/providers/pi/agent.test.ts +++ b/packages/server/src/server/agent/providers/pi/agent.test.ts @@ -2183,6 +2183,25 @@ describe("PiRpcAgentSession", () => { ]); }); + test.each([false, true])( + "closing Pi releases an active turn without publishing a terminal outcome (autonomous=%s)", + async (autonomous) => { + const { pi, session, events } = await createSession(); + if (autonomous) { + pi.latestSession().emit({ type: "agent_start" }); + pi.latestSession().emit({ type: "turn_start" }); + } else { + await session.startTurn("keep working"); + } + const before = events.turnLifecycleEvents(); + + await session.close(); + + expect(pi.latestSession().closeCalls).toBe(1); + expect(events.turnLifecycleEvents()).toEqual(before); + }, + ); + test("fails the active turn when the Pi process exits mid-turn", async () => { const { pi, session, events } = await createSession(); diff --git a/packages/server/src/server/agent/providers/pi/agent.ts b/packages/server/src/server/agent/providers/pi/agent.ts index 198d596c9b..89276b199f 100644 --- a/packages/server/src/server/agent/providers/pi/agent.ts +++ b/packages/server/src/server/agent/providers/pi/agent.ts @@ -2204,12 +2204,16 @@ export class PiRpcAgentSession implements AgentSession { this.pendingSettledMessages = null; this.pendingSteerSubmissions.length = 0; this.clearNoTurnBuffers(); - this.emit({ - type: "turn_failed", - provider: this.provider, - turnId, - error, - }); + // Releasing the runtime is not a failed turn: restart recovery still owns + // unfinished work after the session has been intentionally closed. + if (!this.closed) { + this.emit({ + type: "turn_failed", + provider: this.provider, + turnId, + error, + }); + } } private handleSessionEvent(event: PiAgentSessionEvent): void { diff --git a/packages/server/src/server/agent/providers/pi/test-utils/fake-pi.ts b/packages/server/src/server/agent/providers/pi/test-utils/fake-pi.ts index 770f24625c..b375b291a1 100644 --- a/packages/server/src/server/agent/providers/pi/test-utils/fake-pi.ts +++ b/packages/server/src/server/agent/providers/pi/test-utils/fake-pi.ts @@ -427,6 +427,10 @@ export class FakePiSession implements PiRuntimeSession { async close(): Promise { this.closeCalls += 1; + this.emit({ + type: "process_exit", + error: "Pi RPC process exited with code 0 and signal null", + }); } emit(event: PiRuntimeEvent): void {