fix: handle session start persistence

This commit is contained in:
Federico Jaramillo Martinez
2026-07-02 18:46:44 +02:00
parent d2e10cd89a
commit 2665d1e4bc
18 changed files with 1154 additions and 196 deletions
+30 -1
View File
@@ -215,6 +215,35 @@ describe("PiSessionService", () => {
expect(fake.calls.dispose).toBe(1);
});
it("reports persistence from actual session-file existence for fresh active sessions", async () => {
const dir = await mkdtemp(join(tmpdir(), "pi-web-persisted-"));
const sessionFile = join(dir, "new-session.jsonl");
const hub = new CapturingSessionEventHub();
const fake = fakeRuntime("new-session", { sessionFile });
let service: PiSessionService | undefined;
try {
service = new PiSessionService(hub, {
createAgentRuntime: runtimeCreator(fake.runtime),
sessionManager: sessionGateway([]),
heartbeatIntervalMs: 60_000,
});
const session = await service.start("/workspace");
const createdEvent = hub.globalEvents.find((event) => event.type === "session.created");
expect(session).toMatchObject({ id: "new-session", path: sessionFile, persisted: false });
expect(createdEvent).toMatchObject({ type: "session.created", session: { id: "new-session", persisted: false } });
await expect(service.status(sessionRef("new-session"))).resolves.toMatchObject({ sessionId: "new-session", persisted: false });
await writeFile(sessionFile, '{"type":"session","id":"new-session"}\n', "utf8");
await expect(service.status(sessionRef("new-session"))).resolves.toMatchObject({ sessionId: "new-session", persisted: true });
} finally {
await service?.dispose();
await rm(dir, { recursive: true, force: true });
}
});
it("opens legacy id-only lookups from the default session store gateway", async () => {
const hub = new CapturingSessionEventHub();
const fake = fakeRuntime("legacy-session");
@@ -376,7 +405,7 @@ describe("PiSessionService", () => {
const sessions = await service.list("/workspace");
expect(sessions).toHaveLength(2);
expect(sessions[0]).toMatchObject({ id: "active" });
expect(sessions[0]).toMatchObject({ id: "active", persisted: true });
expect(sessions[0]?.archived).toBeUndefined();
expect(sessions[1]).toMatchObject({ id: "archived", archived: true, archivedAt: "2026-01-01T00:00:00.000Z" });
+13
View File
@@ -1,3 +1,4 @@
import { statSync } from "node:fs";
import { open, readFile, writeFile } from "node:fs/promises";
import type { ImageContent } from "@earendil-works/pi-ai";
import type { StreamFn } from "@earendil-works/pi-agent-core";
@@ -540,6 +541,7 @@ export class PiSessionService {
id: session.sessionId,
path: session.sessionFile ?? "",
cwd,
persisted: sessionFileExists(session.sessionFile),
created: new Date().toISOString(),
modified: new Date().toISOString(),
messageCount: session.messages.length,
@@ -1857,6 +1859,7 @@ export class PiSessionService {
const contextUsage = session.getContextUsage();
return {
sessionId: session.sessionId,
persisted: sessionFileExists(session.sessionFile),
...(model === undefined ? {} : { model }),
thinkingLevel: session.thinkingLevel,
isStreaming: session.isStreaming,
@@ -1949,6 +1952,7 @@ function clientSessionFromListEntry(session: PiSessionListEntry): ClientSession
id: session.id,
path: session.path,
cwd: session.cwd,
persisted: true,
...(session.name === undefined ? {} : { name: session.name }),
created: session.created.toISOString(),
modified: session.modified.toISOString(),
@@ -2151,6 +2155,15 @@ function sessionPathsEqual(a: string, b: string): boolean {
return cwdPathsEqual(a, b);
}
function sessionFileExists(sessionFile: string | undefined): sessionFile is string {
if (sessionFile === undefined || sessionFile === "") return false;
try {
return statSync(sessionFile).isFile();
} catch {
return false;
}
}
function sessionFileMatches(session: PiAgentSession, expectedSessionFile: string | undefined): boolean {
const sessionFile = nonEmptyString(session.sessionFile);
return sessionFile !== undefined && expectedSessionFile !== undefined && sessionPathsEqual(sessionFile, expectedSessionFile);