Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion registry/agent/pi/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,32 @@ Object.defineProperty(process, "stdin", {

type SessionManagerLike = {
inMemory(cwd?: string): unknown;
continueRecent(cwd: string, sessionDir: string): unknown;
};

/**
* Choose the Pi `SessionManager` for a new session.
*
* By default sessions are in-memory (nothing is written to disk), so a
* conversation is lost when the adapter process restarts. When the embedder
* provides a session directory via the `PI_SESSION_DIR` env var, use pi's
* `continueRecent` instead: it persists the session's `.jsonl` under that
* directory and resumes the most recent one, so conversations survive an adapter
* restart. No behavior change unless `PI_SESSION_DIR` is set.
*
* Exported for unit testing (the real `newSession` path needs the Pi SDK).
*/
export function resolveSessionManager(
SessionManager: SessionManagerLike,
cwd: string,
env: Record<string, string | undefined> = process.env,
): unknown {
const sessionDir = env.PI_SESSION_DIR?.trim();
return sessionDir
? SessionManager.continueRecent(cwd, sessionDir)
: SessionManager.inMemory(cwd);
}

type ModelLike = {
id: string;
provider: string;
Expand Down Expand Up @@ -873,7 +897,7 @@ export class PiSdkAgent implements Agent {
const { session } = await __trace.span("createAgentSession", () =>
createAgentSession({
cwd: params.cwd,
sessionManager: SessionManager.inMemory(params.cwd),
sessionManager: resolveSessionManager(SessionManager, params.cwd),
resourceLoader,
tools: this.wrapTools(
createCodingTools(params.cwd, {
Expand Down
47 changes: 47 additions & 0 deletions registry/agent/pi/tests/session-manager.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import test from "node:test";
import assert from "node:assert/strict";
import { resolve as resolvePath } from "node:path";

// Unit tests for resolveSessionManager. The real newSession path needs the Pi
// SDK, so we test the SessionManager selection directly with a fake.
const packageDir = resolvePath(import.meta.dirname, "..");
const { resolveSessionManager } = await import(
resolvePath(packageDir, "dist", "adapter.js")
);

function fakeSessionManager() {
const calls = [];
return {
calls,
inMemory(cwd) {
calls.push(["inMemory", cwd]);
return { kind: "inMemory", cwd };
},
continueRecent(cwd, dir) {
calls.push(["continueRecent", cwd, dir]);
return { kind: "continueRecent", cwd, dir };
},
};
}

test("PI_SESSION_DIR persists + resumes via continueRecent", () => {
const sm = fakeSessionManager();
const result = resolveSessionManager(sm, "/workspace", {
PI_SESSION_DIR: "/sessions/a/main",
});
assert.deepEqual(sm.calls, [["continueRecent", "/workspace", "/sessions/a/main"]]);
assert.equal(result.kind, "continueRecent");
});

test("no PI_SESSION_DIR falls back to in-memory (default, unchanged)", () => {
const sm = fakeSessionManager();
const result = resolveSessionManager(sm, "/workspace", {});
assert.deepEqual(sm.calls, [["inMemory", "/workspace"]]);
assert.equal(result.kind, "inMemory");
});

test("blank/whitespace PI_SESSION_DIR is ignored", () => {
const sm = fakeSessionManager();
resolveSessionManager(sm, "/workspace", { PI_SESSION_DIR: " " });
assert.deepEqual(sm.calls, [["inMemory", "/workspace"]]);
});
Loading