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 35e9bf7d92..60aab1bad9 100644 --- a/packages/server/src/server/agent/providers/pi/agent.test.ts +++ b/packages/server/src/server/agent/providers/pi/agent.test.ts @@ -1615,13 +1615,14 @@ describe("PiRpcAgentSession", () => { provider: "pi", sessionId: "pi-session-1", nativeHandle: "/tmp/native-pi-session", - metadata: { cwd: "/workspace/project" }, + metadata: { cwd: tmpdir() }, }, {}, undefined, { purpose: "history" }, ); + onTestFinished(() => session.close()); const history: AgentStreamEvent[] = []; for await (const event of session.streamHistory()) history.push(event); @@ -1640,6 +1641,57 @@ describe("PiRpcAgentSession", () => { expect(pi.latestSession().closeCalls).toBe(1); }); + test.each(["stopped", "failed"] as const)( + "closes a history-only Pi runtime when replay is %s", + async (outcome) => { + const pi = new FakePi(); + pi.queueSessionSetup((runtimeSession) => { + runtimeSession.messages = [ + { role: "user", content: "Old question" }, + { role: "assistant", content: [{ type: "text", text: "Old answer" }] }, + ]; + if (outcome === "failed") + runtimeSession.getMessagesError = new Error("History unavailable"); + }); + const session = await createClient(pi).resumeSession( + { + provider: "pi", + sessionId: "pi-session-1", + nativeHandle: "/tmp/native-pi-session", + metadata: { cwd: tmpdir() }, + }, + {}, + undefined, + { purpose: "history" }, + ); + onTestFinished(() => session.close()); + const history = session.streamHistory(); + if (outcome === "failed") { + await expect(history.next()).rejects.toThrow("History unavailable"); + } else { + expect((await history.next()).done).toBe(false); + await history.return(); + } + expect(pi.latestSession().closeCalls).toBe(1); + }, + ); + + test("keeps an interactive Pi runtime open after replaying history", async () => { + const pi = new FakePi(); + const session = await createClient(pi).resumeSession({ + provider: "pi", + sessionId: "pi-session-1", + nativeHandle: "/tmp/native-pi-session", + metadata: { cwd: tmpdir() }, + }); + onTestFinished(() => session.close()); + for await (const _event of session.streamHistory()) { + // Hydrate the session before its next interactive prompt. + } + expect(pi.latestSession().closeCalls).toBe(0); + await expect(session.getRuntimeInfo()).resolves.toMatchObject({ provider: "pi" }); + }); + test("resumes a session whose model was removed on the model Pi falls back to", async () => { const pi = new FakePi(); pi.removeModel("9router/deepseek-v4-flash"); @@ -3780,14 +3832,17 @@ readline.createInterface({ input: process.stdin }).on("line", (line) => { }, ); - test("a history resume of an agent whose folder is gone removes its session copy on close and leaves the original unchanged", async () => { + test("a history resume of an agent whose folder is gone removes its session copy after replay and leaves the original unchanged", async () => { const archived = writeArchivedSession(createDeletedFolder()); const pi = new FakePi(); const session = await resumeArchived(createClient(pi), archived, "history"); const sessionCopy = pi.recordedLaunches[0]!.session!; expect(existsSync(sessionCopy)).toBe(true); - await session.close(); + for await (const _event of session.streamHistory()) { + // Completion releases both the runtime and its temporary session copy. + } + expect(pi.latestSession().closeCalls).toBe(1); expect(existsSync(path.dirname(sessionCopy))).toBe(false); expect(readFileSync(archived.sessionFile)).toEqual(archived.originalBytes); diff --git a/packages/server/src/server/agent/providers/pi/agent.ts b/packages/server/src/server/agent/providers/pi/agent.ts index c60a2b44b7..368c7405a7 100644 --- a/packages/server/src/server/agent/providers/pi/agent.ts +++ b/packages/server/src/server/agent/providers/pi/agent.ts @@ -26,7 +26,6 @@ import { type AgentRunOptions, type AgentRunResult, type AgentRuntimeInfo, - type AgentResumeSessionOptions, type AgentSelectOption, type AgentSession, type AgentSessionConfig, 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 f80f5930e7..69aab7b3b8 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 @@ -162,6 +162,7 @@ export class FakePiSession implements PiRuntimeSession { setModelResult: PiModel | null = null; models: PiModel[] = []; messages: PiAgentMessage[] = []; + getMessagesError: Error | null = null; stats: PiSessionStats = { tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, cost: 0, @@ -304,6 +305,7 @@ export class FakePiSession implements PiRuntimeSession { } async getMessages(): Promise { + if (this.getMessagesError) throw this.getMessagesError; return this.messages; }