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.
This commit is contained in:
paseo-bot[bot]
2026-11-03 20:40:03 +00:00
parent ce8196f6c5
commit d7b941680a
3 changed files with 33 additions and 6 deletions
@@ -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();
@@ -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 {
@@ -427,6 +427,10 @@ export class FakePiSession implements PiRuntimeSession {
async close(): Promise<void> {
this.closeCalls += 1;
this.emit({
type: "process_exit",
error: "Pi RPC process exited with code 0 and signal null",
});
}
emit(event: PiRuntimeEvent): void {