{"version":3,"file":"agent-session-compaction.test.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-compaction.test.ts"],"names":[],"mappings":"","sourcesContent":["import { mkdtempSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { Agent } from \"@apholdings/jensen-agent-core\";\nimport type { Model } from \"@apholdings/jensen-ai\";\nimport { afterAll, beforeAll, describe, expect, it, vi } from \"vitest\";\nimport { AgentSession } from \"./agent-session.js\";\nimport { AuthStorage } from \"./auth-storage.js\";\nimport { SESSION_MEMORY_CUSTOM_TYPE, SESSION_TODOS_CUSTOM_TYPE } from \"./memory.js\";\nimport { ModelRegistry } from \"./model-registry.js\";\nimport { DefaultResourceLoader } from \"./resource-loader.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { SettingsManager } from \"./settings-manager.js\";\n\n// Fixed time so all Date.now() and new Date() calls return identical values,\n// eliminating 1-2ms timestamp drift in deep equality assertions.\nconst FIXED_NOW = new Date(\"2026-04-03T00:00:00.000Z\").getTime();\n\nbeforeAll(() => {\n\tvi.useFakeTimers();\n\tvi.setSystemTime(FIXED_NOW);\n});\n\nafterAll(() => {\n\tvi.useRealTimers();\n});\n\nfunction createTempDir(): string {\n\treturn mkdtempSync(join(tmpdir(), \"jensen-compact-\"));\n}\n\ndescribe(\"AgentSession compact hardening\", () => {\n\tit(\"persists the augmented compaction summary and rebuilds post-compact state from session context\", async () => {\n\t\tconst rootDir = createTempDir();\n\n\t\ttry {\n\t\t\tconst agentDir = join(rootDir, \"agent\");\n\t\t\tconst cwd = join(rootDir, \"repo\");\n\t\t\tconst authStorage = AuthStorage.create(join(agentDir, \"auth.json\"));\n\t\t\tconst modelRegistry = new ModelRegistry(authStorage, undefined);\n\t\t\tconst model: Model<\"openai-chat\"> = {\n\t\t\t\tid: \"test-model\",\n\t\t\t\tname: \"Test Model\",\n\t\t\t\tprovider: \"test-provider\",\n\t\t\t\tapi: \"openai-chat\",\n\t\t\t\tbaseUrl: \"https://example.test\",\n\t\t\t\treasoning: false,\n\t\t\t\tinput: [\"text\"],\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n\t\t\t\tcontextWindow: 8000,\n\t\t\t\tmaxTokens: 4000,\n\t\t\t};\n\n\t\t\tmodelRegistry.registerProvider(\"test-provider\", {\n\t\t\t\tbaseUrl: \"https://example.test\",\n\t\t\t\tapiKey: \"test-key\",\n\t\t\t\tapi: \"openai-chat\",\n\t\t\t\tmodels: [model],\n\t\t\t});\n\n\t\t\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\t\t\tvi.spyOn(settingsManager, \"getCompactionSettings\").mockReturnValue({\n\t\t\t\tenabled: true,\n\t\t\t\treserveTokens: 256,\n\t\t\t\tkeepRecentTokens: 1,\n\t\t\t});\n\n\t\t\tconst sessionManager = SessionManager.inMemory(cwd);\n\t\t\tsessionManager.appendMessage({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: \"Older context \".repeat(200) }],\n\t\t\t\ttimestamp: 1,\n\t\t\t});\n\t\t\tsessionManager.appendMessage({\n\t\t\t\trole: \"assistant\",\n\t\t\t\tprovider: model.provider,\n\t\t\t\tmodel: model.id,\n\t\t\t\tcontent: [{ type: \"text\", text: \"Older assistant reply\" }],\n\t\t\t\tstopReason: \"endTurn\",\n\t\t\t\tusage: {\n\t\t\t\t\tinput: 100,\n\t\t\t\t\toutput: 20,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: 120,\n\t\t\t\t\tcost: { total: 0 },\n\t\t\t\t},\n\t\t\t\ttimestamp: 2,\n\t\t\t} as any);\n\t\t\tsessionManager.appendMessage({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: \"Keep this follow-up\" }],\n\t\t\t\ttimestamp: 3,\n\t\t\t});\n\t\t\tsessionManager.appendCustomEntry(SESSION_MEMORY_CUSTOM_TYPE, [\n\t\t\t\t{\n\t\t\t\t\tkey: \"project.constraint\",\n\t\t\t\t\tvalue: \"run npm run check\",\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t},\n\t\t\t]);\n\t\t\tsessionManager.appendCustomEntry(SESSION_TODOS_CUSTOM_TYPE, [\n\t\t\t\t{\n\t\t\t\t\tcontent: \"Verify compact cleanup\",\n\t\t\t\t\tactiveForm: \"Verifying compact cleanup\",\n\t\t\t\t\tstatus: \"in_progress\",\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tconst resourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\t\tconst agent = new Agent({\n\t\t\t\tinitialState: {\n\t\t\t\t\tsystemPrompt: \"\",\n\t\t\t\t\tmodel,\n\t\t\t\t\tthinkingLevel: \"off\",\n\t\t\t\t\ttools: [],\n\t\t\t\t},\n\t\t\t});\n\t\t\tagent.replaceMessages(sessionManager.buildSessionContext().messages);\n\n\t\t\tconst session = new AgentSession({\n\t\t\t\tagent,\n\t\t\t\tsessionManager,\n\t\t\t\tsettingsManager,\n\t\t\t\tcwd,\n\t\t\t\tresourceLoader,\n\t\t\t\tmodelRegistry,\n\t\t\t});\n\n\t\t\tconst extensionEvents: Array<{ type: string; summary?: string }> = [];\n\t\t\t(session as any)._extensionRunner = {\n\t\t\t\thasHandlers: (eventType: string) => eventType === \"session_before_compact\",\n\t\t\t\temit: vi.fn(\n\t\t\t\t\tasync (event: { type: string; preparation: { firstKeptEntryId: string; tokensBefore: number } }) => {\n\t\t\t\t\t\tif (event.type === \"session_before_compact\") {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcompaction: {\n\t\t\t\t\t\t\t\t\tsummary: \"Base compact summary\",\n\t\t\t\t\t\t\t\t\tfirstKeptEntryId: event.preparation.firstKeptEntryId,\n\t\t\t\t\t\t\t\t\ttokensBefore: event.preparation.tokensBefore,\n\t\t\t\t\t\t\t\t\tdetails: { source: \"test\" },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\textensionEvents.push({ type: event.type, summary: (event as any).compactionEntry?.summary });\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t};\n\n\t\t\tconst result = await session.compact();\n\t\t\tconst compactedContext = sessionManager.buildSessionContext();\n\t\t\tconst compactionEntry = sessionManager\n\t\t\t\t.getEntries()\n\t\t\t\t.filter((entry) => entry.type === \"compaction\")\n\t\t\t\t.at(-1);\n\n\t\t\texpect(result.summary).toContain(\"## Active Session Memory\");\n\t\t\texpect(result.summary).toContain(\"- project.constraint: run npm run check\");\n\t\t\texpect(result.summary).toContain(\"## Active Todo State\");\n\t\t\texpect(result.summary).toContain(\"- [in_progress] Verifying compact cleanup\");\n\t\t\texpect(compactionEntry?.summary).toBe(result.summary);\n\t\t\texpect(extensionEvents).toContainEqual({ type: \"session_compact\", summary: result.summary });\n\t\t\texpect(agent.state.messages).toEqual(compactedContext.messages);\n\t\t\texpect(agent.state.messages.some((message) => message.role === \"compactionSummary\")).toBe(true);\n\t\t\texpect(\n\t\t\t\tagent.state.messages.some(\n\t\t\t\t\t(message) =>\n\t\t\t\t\t\tmessage.role === \"user\" &&\n\t\t\t\t\t\tArray.isArray(message.content) &&\n\t\t\t\t\t\tmessage.content.some((block) => block.type === \"text\" && block.text.includes(\"Older context\")),\n\t\t\t\t),\n\t\t\t).toBe(false);\n\t\t\texpect(compactedContext.memoryItems.map((item) => item.key)).toEqual([\"project.constraint\"]);\n\t\t\texpect(compactedContext.todos.map((todo) => todo.activeForm)).toEqual([\"Verifying compact cleanup\"]);\n\t\t} finally {\n\t\t\trmSync(rootDir, { recursive: true, force: true });\n\t\t}\n\t});\n});\n"]}