import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import type { SessionHeader } from "@oh-my-pi/pi-coding-agent/session/session-entries"; import { loadEntriesFromFile } from "@oh-my-pi/pi-coding-agent/session/session-loader"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { getConfigRootDir, removeSyncWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; import { makeAssistantMessage } from "./helpers"; function getHeader(entries: unknown[]): SessionHeader | undefined { return entries.find( (entry): entry is SessionHeader => typeof entry === "object" && entry !== null && "type" in entry && entry.type === "session", ); } describe("session title source persistence", () => { let testAgentDir: string; let cwd: string; const originalAgentDir = process.env.PI_CODING_AGENT_DIR; const fallbackAgentDir = path.join(getConfigRootDir(), "agent"); beforeEach(() => { testAgentDir = fs.mkdtempSync(path.join(os.tmpdir(), "omp-title-source-")); cwd = path.join(testAgentDir, "cwd"); fs.mkdirSync(cwd, { recursive: true }); setAgentDir(testAgentDir); }); afterEach(() => { if (originalAgentDir) { setAgentDir(originalAgentDir); } else { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } removeSyncWithRetries(testAgentDir); }); it("persists auto title source across reopen", async () => { const session = SessionManager.create(cwd); session.appendMessage({ role: "user", content: "hello", timestamp: 1 }); await session.setSessionName("Auto title", "auto"); session.appendMessage(makeAssistantMessage()); await session.flush(); const sessionFile = session.getSessionFile(); expect(sessionFile).toBeDefined(); const entries = await loadEntriesFromFile(sessionFile!); expect(getHeader(entries)?.titleSource).toBe("auto"); const reopened = await SessionManager.open(sessionFile!); expect(reopened.getSessionName()).toBe("Auto title"); expect(reopened.titleSource).toBe("auto"); }); it("persists user title source across reopen", async () => { const session = SessionManager.create(cwd); session.appendMessage({ role: "user", content: "hello", timestamp: 1 }); await session.setSessionName("Manual title", "user"); session.appendMessage(makeAssistantMessage()); await session.flush(); const sessionFile = session.getSessionFile(); expect(sessionFile).toBeDefined(); const entries = await loadEntriesFromFile(sessionFile!); expect(getHeader(entries)?.titleSource).toBe("user"); const reopened = await SessionManager.open(sessionFile!); expect(reopened.getSessionName()).toBe("Manual title"); expect(reopened.titleSource).toBe("user"); }); it("notifies name-change subscribers only after successful applied names", async () => { const session = SessionManager.inMemory(cwd); const names: Array = []; const unsubscribe = session.onSessionNameChanged(() => { names.push(session.getSessionName()); }); try { await expect(session.setSessionName(" ", "user")).resolves.toBe(false); expect(names).toEqual([]); await expect(session.setSessionName("Manual title", "user")).resolves.toBe(true); expect(names).toEqual(["Manual title"]); await expect(session.setSessionName("Ignored auto title", "auto")).resolves.toBe(false); expect(names).toEqual(["Manual title"]); } finally { unsubscribe(); } await expect(session.setSessionName("Second title", "user")).resolves.toBe(true); expect(names).toEqual(["Manual title"]); }); });