import { fileURLToPath, pathToFileURL } from "node:url"; import { join } from "node:path"; import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from "node:fs"; import { tmpdir } from "node:os"; import { resolvePrimeRoot } from "./prime-root.mjs"; const primeRoot = resolvePrimeRoot(); const prime = await import(pathToFileURL(join(primeRoot, "dist/index.js"))); const { deleteSessionFile } = await import(pathToFileURL(join( primeRoot, "dist/core/session-file-actions.js", ))); const root = mkdtempSync(join(tmpdir(), "prime-snapcompact-lifecycle-")); const cwd = process.cwd(); const agentDir = join(root, "agent"); mkdirSync(join(agentDir, "extensions"), { recursive: true }); symlinkSync( fileURLToPath(new URL("..", import.meta.url)), join(agentDir, "extensions", "snapcompact"), "dir", ); const sessionManager = prime.SessionManager.create(cwd, join(root, "sessions")); const usage = (tokens) => ({ input: tokens - 1000, output: 1000, cacheRead: 0, cacheWrite: 0, totalTokens: tokens, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }); const model = { id: "gpt-5.2", name: "SnapCompact lifecycle model", api: "openai-responses", provider: "openai", baseUrl: "https://example.invalid", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 272000, maxTokens: 16384, }; const auth = prime.AuthStorage.inMemory(); auth.setRuntimeApiKey(model.provider, "snapcompact-test"); const modelRegistry = prime.ModelRegistry.inMemory(auth); for (let turn = 0; turn < 10; turn++) { sessionManager.appendMessage({ role: "user", content: `analyze repository turn ${turn}`, timestamp: Date.now() }); sessionManager.appendMessage({ role: "assistant", content: [{ type: "text", text: (`turn ${turn} repository analysis src/file.ts [] {}\n`).repeat(1200) }], api: model.api, provider: model.provider, model: model.id, usage: usage((turn + 1) * 15000), stopReason: "stop", timestamp: Date.now(), }); } sessionManager.flushNow(); const { session, extensionsResult } = await prime.createAgentSession({ cwd, agentDir, sessionManager, model, modelRegistry, thinkingLevel: "medium", noTools: "all", includeGoals: false, includeCompactSkill: false, prewarmIpythonKernel: false, telemetryDisabled: true, }); if (extensionsResult.errors.length) throw new Error(JSON.stringify(extensionsResult.errors)); await session.bindExtensions({}); const sessionFile = session.sessionFile; const sessionArtifactDirectory = join(root, "session-artifacts", session.sessionId); let report; try { const result = await session.compact(); const branch = sessionManager.getBranch(); const entry = prime.getLatestCompactionEntry(branch); if (!entry?.fromHook || !entry.details?.snapcompact?.artifact) { throw new Error("real AgentSession compaction did not persist SnapCompact details"); } const detailsBytes = Buffer.byteLength(JSON.stringify(entry.details)); if (detailsBytes >= 1024 * 1024) throw new Error("real compaction details exceed 1 MiB"); if (JSON.stringify(entry.details).includes("iVBORw0KGgo")) throw new Error("real compaction inlined PNG data"); const nativeSummary = session.messages.find((message) => message.role === "compactionSummary"); if (!nativeSummary) throw new Error("real AgentSession reload lost the compaction summary"); report = { extensions: extensionsResult.extensions.map((extension) => extension.path), detailsBytes, artifactBytes: entry.details.snapcompact.artifact.bytes, nativeSummaryPresent: true, summaryChars: result.summary.length, sessionFile: session.sessionFile, }; } finally { await session.disposeAsync(); } const deleted = await deleteSessionFile(sessionFile); if (!deleted.ok) throw new Error(`Prime session deletion failed: ${deleted.error}`); if (existsSync(sessionArtifactDirectory)) { throw new Error("Prime session deletion left its SnapCompact artifact directory behind"); } console.log(JSON.stringify({ ...report, nativeDeleteRemovedArtifacts: true }, null, 2)); rmSync(root, { recursive: true, force: true });