Files
oh-my-pi/packages/coding-agent/test/session-manager-cwd-adoption.test.ts
T
can1357 7b3ea248d4 feat(coding-agent): enabled cross-project resume with folder/all scope fallback
- Enabled resume picker to preload sessions and toggle folder/all scope with Tab.
- Enabled resume flow to fall back to all-project sessions and switch cwd on resume.
- Added centralized applyCwdChange to refresh caches, commands, and UI after cwd updates.
- Updated session restoration to adopt restored session cwd and sessionDir when present.
2026-06-02 05:53:57 +02:00

90 lines
3.4 KiB
TypeScript

import { afterEach, describe, expect, it } from "bun:test";
import * as path from "node:path";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import { TempDir } from "@oh-my-pi/pi-utils";
const tempDirs: TempDir[] = [];
function makeTempDir(prefix: string): string {
const dir = TempDir.createSync(prefix);
tempDirs.push(dir);
return dir.path();
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map(dir => dir.remove()));
});
/**
* Persist a single-message session under `cwd`/`sessionDir` and return its file path.
* The on-disk header records `cwd`, which is what resume adoption keys off of.
*/
async function writeSession(cwd: string, sessionDir: string): Promise<string> {
const manager = SessionManager.create(cwd, sessionDir);
manager.appendMessage({ role: "user", content: "hello", timestamp: Date.now() });
await manager.rewriteEntries();
const file = manager.getSessionFile();
if (!file) throw new Error("expected a persisted session file");
return file;
}
describe("SessionManager cwd adoption on resume", () => {
it("adopts the resumed session's own cwd and session directory", async () => {
const projectA = makeTempDir("@pi-cwd-a-");
const projectB = makeTempDir("@pi-cwd-b-");
const sessionsB = path.join(projectB, "sessions");
const fileB = await writeSession(projectB, sessionsB);
// A manager started in project A loads a session that lives in project B.
const manager = SessionManager.create(projectA, path.join(projectA, "sessions"));
expect(manager.getCwd()).toBe(path.resolve(projectA));
await manager.setSessionFile(fileB);
expect(manager.getCwd()).toBe(path.resolve(projectB));
expect(manager.getSessionDir()).toBe(path.resolve(sessionsB));
// New session/fork targets must follow the adopted directory, not the launch one.
expect(manager.getHeader()?.cwd).toBe(path.resolve(projectB));
});
it("leaves cwd untouched when the resumed session has no recorded cwd", async () => {
const projectA = makeTempDir("@pi-cwd-a-");
const projectB = makeTempDir("@pi-cwd-b-");
const sessionsB = path.join(projectB, "sessions");
const fileB = await writeSession(projectB, sessionsB);
// Simulate a legacy session whose header predates the cwd field.
const raw = await Bun.file(fileB).text();
const lines = raw.split("\n").filter(Boolean);
const header = JSON.parse(lines[0]) as Record<string, unknown>;
header.cwd = "";
lines[0] = JSON.stringify(header);
await Bun.write(fileB, `${lines.join("\n")}\n`);
const launchDir = path.join(projectA, "sessions");
const manager = SessionManager.create(projectA, launchDir);
await manager.setSessionFile(fileB);
expect(manager.getCwd()).toBe(path.resolve(projectA));
expect(manager.getSessionDir()).toBe(path.resolve(launchDir));
});
it("restores cwd and session directory when a switch is rolled back", async () => {
const projectA = makeTempDir("@pi-cwd-a-");
const projectB = makeTempDir("@pi-cwd-b-");
const sessionsA = path.join(projectA, "sessions");
const sessionsB = path.join(projectB, "sessions");
const fileB = await writeSession(projectB, sessionsB);
const manager = SessionManager.create(projectA, sessionsA);
const snapshot = manager.captureState();
await manager.setSessionFile(fileB);
expect(manager.getCwd()).toBe(path.resolve(projectB));
manager.restoreState(snapshot);
expect(manager.getCwd()).toBe(path.resolve(projectA));
expect(manager.getSessionDir()).toBe(path.resolve(sessionsA));
});
});