mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-02 22:52:58 +00:00
fix(pi): preserve history cleanup across current resume behavior
Keep the original session identity and missing-folder cleanup when releasing history-only runtimes. Use an existing directory in the regression so the newer resume fallback does not try to copy a nonexistent session fixture. Cover interrupted and failed replay, interactive residency, and automatic cleanup of a missing-folder history copy. Co-authored-by: Ryan Winograd <ryan@thewinograds.com>
This commit is contained in:
co-authored by
Ryan Winograd
parent
beda7180c0
commit
5038999e81
@@ -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);
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
type AgentRunOptions,
|
||||
type AgentRunResult,
|
||||
type AgentRuntimeInfo,
|
||||
type AgentResumeSessionOptions,
|
||||
type AgentSelectOption,
|
||||
type AgentSession,
|
||||
type AgentSessionConfig,
|
||||
|
||||
@@ -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<PiAgentMessage[]> {
|
||||
if (this.getMessagesError) throw this.getMessagesError;
|
||||
return this.messages;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user