{"version":3,"file":"sdk-startup-resume.test.d.ts","sourceRoot":"","sources":["../../src/core/sdk-startup-resume.test.ts"],"names":[],"mappings":"","sourcesContent":["import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Model } from \"@apholdings/jensen-ai\";\nimport { describe, expect, it } from \"vitest\";\nimport { AuthStorage } from \"./auth-storage.js\";\nimport { SESSION_MEMORY_CUSTOM_TYPE, SESSION_TASKS_CUSTOM_TYPE, SESSION_TODOS_CUSTOM_TYPE } from \"./memory.js\";\nimport { ModelRegistry } from \"./model-registry.js\";\nimport { DefaultResourceLoader } from \"./resource-loader.js\";\nimport { createAgentSession } from \"./sdk.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { SettingsManager } from \"./settings-manager.js\";\n\nfunction createTestModel(): Model<\"openai-chat\"> {\n\treturn {\n\t\tid: \"startup-resume-model\",\n\t\tname: \"Startup Resume Model\",\n\t\tprovider: \"startup-test-provider\",\n\t\tapi: \"openai-chat\",\n\t\tbaseUrl: \"https://example.test\",\n\t\treasoning: true,\n\t\tinput: [\"text\"],\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n\t\tcontextWindow: 200_000,\n\t\tmaxTokens: 8_000,\n\t};\n}\n\ndescribe(\"createAgentSession startup resume boundary\", () => {\n\tit(\"restores persisted session state when startup opens an existing session\", async () => {\n\t\tconst rootDir = mkdtempSync(join(tmpdir(), \"jensen-sdk-startup-resume-\"));\n\t\tconst cwd = join(rootDir, \"repo\");\n\t\tconst agentDir = join(rootDir, \"agent\");\n\t\tconst sessionDir = join(rootDir, \"sessions\");\n\n\t\tmkdirSync(cwd, { recursive: true });\n\t\tmkdirSync(agentDir, { recursive: true });\n\t\tmkdirSync(sessionDir, { recursive: true });\n\n\t\ttry {\n\t\t\tconst settingsManager = SettingsManager.inMemory();\n\t\t\tsettingsManager.setDefaultThinkingLevel(\"high\");\n\n\t\t\tconst authStorage = AuthStorage.create(join(agentDir, \"auth.json\"));\n\t\t\tconst modelRegistry = new ModelRegistry(authStorage);\n\t\t\tconst restoredModel = createTestModel();\n\t\t\tmodelRegistry.registerProvider(restoredModel.provider, {\n\t\t\t\tapi: restoredModel.api,\n\t\t\t\tapiKey: \"test-api-key\",\n\t\t\t\tbaseUrl: restoredModel.baseUrl,\n\t\t\t\tmodels: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: restoredModel.id,\n\t\t\t\t\t\tname: restoredModel.name,\n\t\t\t\t\t\tapi: restoredModel.api,\n\t\t\t\t\t\treasoning: restoredModel.reasoning,\n\t\t\t\t\t\tinput: restoredModel.input,\n\t\t\t\t\t\tcost: restoredModel.cost,\n\t\t\t\t\t\tcontextWindow: restoredModel.contextWindow,\n\t\t\t\t\t\tmaxTokens: restoredModel.maxTokens,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tconst persistedSession = SessionManager.create(cwd, sessionDir);\n\t\t\tpersistedSession.appendModelChange(restoredModel.provider, restoredModel.id);\n\t\t\tpersistedSession.appendThinkingLevelChange(\"low\");\n\t\t\tpersistedSession.appendCustomEntry(SESSION_MEMORY_CUSTOM_TYPE, [\n\t\t\t\t{ key: \"project.goal\", value: \"restore startup state\", timestamp: \"2026-04-02T12:00:00.000Z\" },\n\t\t\t]);\n\t\t\tpersistedSession.appendCustomEntry(SESSION_TODOS_CUSTOM_TYPE, [\n\t\t\t\t{ content: \"Resume startup test\", activeForm: \"Restoring startup test\", status: \"in_progress\" },\n\t\t\t]);\n\t\t\tpersistedSession.appendCustomEntry(SESSION_TASKS_CUSTOM_TYPE, [\n\t\t\t\t{\n\t\t\t\t\tid: \"task_resume_1\",\n\t\t\t\t\tsubject: \"Resume test task\",\n\t\t\t\t\tdescription: \"verify startup resume\",\n\t\t\t\t\tstatus: \"in_progress\",\n\t\t\t\t},\n\t\t\t]);\n\t\t\tpersistedSession.appendMessage({\n\t\t\t\trole: \"assistant\",\n\t\t\t\tapi: restoredModel.api,\n\t\t\t\tprovider: restoredModel.provider,\n\t\t\t\tmodel: restoredModel.id,\n\t\t\t\tstopReason: \"stop\",\n\t\t\t\tcontent: [{ type: \"text\", text: \"persisted assistant reply\" }],\n\t\t\t\tusage: {\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t},\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t});\n\n\t\t\tconst sessionFile = persistedSession.getSessionFile();\n\t\t\tif (!sessionFile) {\n\t\t\t\tthrow new Error(\"Expected persisted session file\");\n\t\t\t}\n\n\t\t\tconst resourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\t\tawait resourceLoader.reload();\n\n\t\t\tconst openedSession = SessionManager.open(sessionFile, sessionDir);\n\t\t\tconst { session } = await createAgentSession({\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tsettingsManager,\n\t\t\t\tauthStorage,\n\t\t\t\tmodelRegistry,\n\t\t\t\tresourceLoader,\n\t\t\t\tsessionManager: openedSession,\n\t\t\t});\n\n\t\t\texpect(session.sessionFile).toBe(sessionFile);\n\t\t\texpect(session.sessionId).toBe(openedSession.getSessionId());\n\t\t\texpect(session.model?.provider).toBe(restoredModel.provider);\n\t\t\texpect(session.model?.id).toBe(restoredModel.id);\n\t\t\texpect(session.thinkingLevel).toBe(\"low\");\n\t\t\texpect(session.getMemoryItems()).toEqual([\n\t\t\t\t{ key: \"project.goal\", value: \"restore startup state\", timestamp: \"2026-04-02T12:00:00.000Z\" },\n\t\t\t]);\n\t\t\texpect(session.getTodos()).toMatchObject([\n\t\t\t\t{ content: \"Resume startup test\", activeForm: \"Restoring startup test\", status: \"in_progress\" },\n\t\t\t]);\n\t\t\texpect(session.getTasks()).toEqual([\n\t\t\t\t{\n\t\t\t\t\tid: \"task_resume_1\",\n\t\t\t\t\tsubject: \"Resume test task\",\n\t\t\t\t\tdescription: \"verify startup resume\",\n\t\t\t\t\tstatus: \"in_progress\",\n\t\t\t\t},\n\t\t\t]);\n\t\t\texpect(session.messages).toEqual(\n\t\t\t\texpect.arrayContaining([\n\t\t\t\t\texpect.objectContaining({\n\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\tprovider: restoredModel.provider,\n\t\t\t\t\t\tmodel: restoredModel.id,\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"persisted assistant reply\" }],\n\t\t\t\t\t}),\n\t\t\t\t]),\n\t\t\t);\n\t\t} finally {\n\t\t\trmSync(rootDir, { recursive: true, force: true });\n\t\t}\n\t});\n\n\tit(\"starts fresh when startup opens a corrupt session file with partial persisted state\", async () => {\n\t\tconst rootDir = mkdtempSync(join(tmpdir(), \"jensen-sdk-startup-resume-\"));\n\t\tconst cwd = join(rootDir, \"repo\");\n\t\tconst agentDir = join(rootDir, \"agent\");\n\t\tconst sessionDir = join(rootDir, \"sessions\");\n\n\t\tmkdirSync(cwd, { recursive: true });\n\t\tmkdirSync(agentDir, { recursive: true });\n\t\tmkdirSync(sessionDir, { recursive: true });\n\n\t\ttry {\n\t\t\tconst settingsManager = SettingsManager.inMemory();\n\t\t\tsettingsManager.setDefaultThinkingLevel(\"high\");\n\t\t\tsettingsManager.setDefaultModelAndProvider(\"startup-test-provider\", \"startup-resume-model\");\n\n\t\t\tconst authStorage = AuthStorage.create(join(agentDir, \"auth.json\"));\n\t\t\tconst modelRegistry = new ModelRegistry(authStorage);\n\t\t\tconst fallbackModel = createTestModel();\n\t\t\tmodelRegistry.registerProvider(fallbackModel.provider, {\n\t\t\t\tapi: fallbackModel.api,\n\t\t\t\tapiKey: \"test-api-key\",\n\t\t\t\tbaseUrl: fallbackModel.baseUrl,\n\t\t\t\tmodels: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: fallbackModel.id,\n\t\t\t\t\t\tname: fallbackModel.name,\n\t\t\t\t\t\tapi: fallbackModel.api,\n\t\t\t\t\t\treasoning: fallbackModel.reasoning,\n\t\t\t\t\t\tinput: fallbackModel.input,\n\t\t\t\t\t\tcost: fallbackModel.cost,\n\t\t\t\t\t\tcontextWindow: fallbackModel.contextWindow,\n\t\t\t\t\t\tmaxTokens: fallbackModel.maxTokens,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tconst corruptSessionFile = join(sessionDir, \"corrupt-startup-session.jsonl\");\n\t\t\twriteFileSync(\n\t\t\t\tcorruptSessionFile,\n\t\t\t\t[\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"not_session\",\n\t\t\t\t\t\tid: \"claimed-corrupt-session-id\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:00.000Z\",\n\t\t\t\t\t\tcwd,\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"model_change\",\n\t\t\t\t\t\tid: \"model-entry\",\n\t\t\t\t\t\tparentId: null,\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:01.000Z\",\n\t\t\t\t\t\tprovider: \"corrupt-provider\",\n\t\t\t\t\t\tmodelId: \"corrupt-model\",\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"thinking_level_change\",\n\t\t\t\t\t\tid: \"thinking-entry\",\n\t\t\t\t\t\tparentId: \"model-entry\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:02.000Z\",\n\t\t\t\t\t\tthinkingLevel: \"low\",\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\tid: \"memory-entry\",\n\t\t\t\t\t\tparentId: \"thinking-entry\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:03.000Z\",\n\t\t\t\t\t\tcustomType: SESSION_MEMORY_CUSTOM_TYPE,\n\t\t\t\t\t\tdata: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tkey: \"project.goal\",\n\t\t\t\t\t\t\t\tvalue: \"corrupt restore leak\",\n\t\t\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:03.000Z\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\tid: \"todo-entry\",\n\t\t\t\t\t\tparentId: \"memory-entry\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:04.000Z\",\n\t\t\t\t\t\tcustomType: SESSION_TODOS_CUSTOM_TYPE,\n\t\t\t\t\t\tdata: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontent: \"Corrupt todo\",\n\t\t\t\t\t\t\t\tactiveForm: \"Corrupt todo\",\n\t\t\t\t\t\t\t\tstatus: \"in_progress\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"message\",\n\t\t\t\t\t\tid: \"assistant-entry\",\n\t\t\t\t\t\tparentId: \"todo-entry\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:05.000Z\",\n\t\t\t\t\t\tmessage: {\n\t\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\t\tapi: fallbackModel.api,\n\t\t\t\t\t\t\tprovider: \"corrupt-provider\",\n\t\t\t\t\t\t\tmodel: \"corrupt-model\",\n\t\t\t\t\t\t\tstopReason: \"stop\",\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"corrupt persisted assistant reply\" }],\n\t\t\t\t\t\t\tusage: {\n\t\t\t\t\t\t\t\tinput: 0,\n\t\t\t\t\t\t\t\toutput: 0,\n\t\t\t\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t].join(\"\\n\"),\n\t\t\t);\n\n\t\t\tconst resourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\t\tawait resourceLoader.reload();\n\n\t\t\tconst openedSession = SessionManager.open(corruptSessionFile, sessionDir);\n\t\t\tconst { session } = await createAgentSession({\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tsettingsManager,\n\t\t\t\tauthStorage,\n\t\t\t\tmodelRegistry,\n\t\t\t\tresourceLoader,\n\t\t\t\tsessionManager: openedSession,\n\t\t\t});\n\n\t\t\texpect(session.sessionFile).toBe(corruptSessionFile);\n\t\t\texpect(session.sessionId).toBe(openedSession.getSessionId());\n\t\t\texpect(session.sessionId).not.toBe(\"claimed-corrupt-session-id\");\n\t\t\texpect(session.model?.provider).toBe(fallbackModel.provider);\n\t\t\texpect(session.model?.id).toBe(fallbackModel.id);\n\t\t\texpect(session.thinkingLevel).toBe(\"high\");\n\t\t\texpect(session.getMemoryItems()).toEqual([]);\n\t\t\texpect(session.getTodos()).toMatchObject([]);\n\t\t\texpect(session.messages).toHaveLength(0);\n\t\t\texpect(session.messages).not.toEqual(\n\t\t\t\texpect.arrayContaining([\n\t\t\t\t\texpect.objectContaining({\n\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\tprovider: \"corrupt-provider\",\n\t\t\t\t\t\tmodel: \"corrupt-model\",\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"corrupt persisted assistant reply\" }],\n\t\t\t\t\t}),\n\t\t\t\t]),\n\t\t\t);\n\t\t} finally {\n\t\t\trmSync(rootDir, { recursive: true, force: true });\n\t\t}\n\t});\n\n\tit(\"starts fresh when startup opens a missing session file path\", async () => {\n\t\tconst rootDir = mkdtempSync(join(tmpdir(), \"jensen-sdk-startup-resume-\"));\n\t\tconst cwd = join(rootDir, \"repo\");\n\t\tconst agentDir = join(rootDir, \"agent\");\n\t\tconst sessionDir = join(rootDir, \"sessions\");\n\n\t\tmkdirSync(cwd, { recursive: true });\n\t\tmkdirSync(agentDir, { recursive: true });\n\t\tmkdirSync(sessionDir, { recursive: true });\n\n\t\ttry {\n\t\t\tconst settingsManager = SettingsManager.inMemory();\n\t\t\tsettingsManager.setDefaultThinkingLevel(\"high\");\n\t\t\tsettingsManager.setDefaultModelAndProvider(\"startup-test-provider\", \"startup-resume-model\");\n\n\t\t\tconst authStorage = AuthStorage.create(join(agentDir, \"auth.json\"));\n\t\t\tconst modelRegistry = new ModelRegistry(authStorage);\n\t\t\tconst fallbackModel = createTestModel();\n\t\t\tmodelRegistry.registerProvider(fallbackModel.provider, {\n\t\t\t\tapi: fallbackModel.api,\n\t\t\t\tapiKey: \"test-api-key\",\n\t\t\t\tbaseUrl: fallbackModel.baseUrl,\n\t\t\t\tmodels: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: fallbackModel.id,\n\t\t\t\t\t\tname: fallbackModel.name,\n\t\t\t\t\t\tapi: fallbackModel.api,\n\t\t\t\t\t\treasoning: fallbackModel.reasoning,\n\t\t\t\t\t\tinput: fallbackModel.input,\n\t\t\t\t\t\tcost: fallbackModel.cost,\n\t\t\t\t\t\tcontextWindow: fallbackModel.contextWindow,\n\t\t\t\t\t\tmaxTokens: fallbackModel.maxTokens,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tconst missingSessionFile = join(sessionDir, \"missing-startup-session.jsonl\");\n\t\t\texpect(existsSync(missingSessionFile)).toBe(false);\n\n\t\t\tconst resourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\t\tawait resourceLoader.reload();\n\n\t\t\tconst openedSession = SessionManager.open(missingSessionFile, sessionDir);\n\t\t\tconst { session } = await createAgentSession({\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tsettingsManager,\n\t\t\t\tauthStorage,\n\t\t\t\tmodelRegistry,\n\t\t\t\tresourceLoader,\n\t\t\t\tsessionManager: openedSession,\n\t\t\t});\n\n\t\t\texpect(session.sessionFile).toBe(missingSessionFile);\n\t\t\texpect(session.sessionId).toBe(openedSession.getSessionId());\n\t\t\texpect(session.model?.provider).toBe(fallbackModel.provider);\n\t\t\texpect(session.model?.id).toBe(fallbackModel.id);\n\t\t\texpect(session.thinkingLevel).toBe(\"high\");\n\t\t\texpect(session.getMemoryItems()).toEqual([]);\n\t\t\texpect(session.getTodos()).toMatchObject([]);\n\t\t\texpect(session.messages).toHaveLength(0);\n\t\t\texpect(existsSync(missingSessionFile)).toBe(false);\n\t\t} finally {\n\t\t\trmSync(rootDir, { recursive: true, force: true });\n\t\t}\n\t});\n\n\tit(\"starts fresh when startup opens a valid-header session file with a malformed later entry\", async () => {\n\t\tconst rootDir = mkdtempSync(join(tmpdir(), \"jensen-sdk-startup-resume-\"));\n\t\tconst cwd = join(rootDir, \"repo\");\n\t\tconst agentDir = join(rootDir, \"agent\");\n\t\tconst sessionDir = join(rootDir, \"sessions\");\n\n\t\tmkdirSync(cwd, { recursive: true });\n\t\tmkdirSync(agentDir, { recursive: true });\n\t\tmkdirSync(sessionDir, { recursive: true });\n\n\t\ttry {\n\t\t\tconst settingsManager = SettingsManager.inMemory();\n\t\t\tsettingsManager.setDefaultThinkingLevel(\"high\");\n\t\t\tsettingsManager.setDefaultModelAndProvider(\"startup-test-provider\", \"startup-resume-model\");\n\n\t\t\tconst authStorage = AuthStorage.create(join(agentDir, \"auth.json\"));\n\t\t\tconst modelRegistry = new ModelRegistry(authStorage);\n\t\t\tconst fallbackModel = createTestModel();\n\t\t\tmodelRegistry.registerProvider(fallbackModel.provider, {\n\t\t\t\tapi: fallbackModel.api,\n\t\t\t\tapiKey: \"test-api-key\",\n\t\t\t\tbaseUrl: fallbackModel.baseUrl,\n\t\t\t\tmodels: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: fallbackModel.id,\n\t\t\t\t\t\tname: fallbackModel.name,\n\t\t\t\t\t\tapi: fallbackModel.api,\n\t\t\t\t\t\treasoning: fallbackModel.reasoning,\n\t\t\t\t\t\tinput: fallbackModel.input,\n\t\t\t\t\t\tcost: fallbackModel.cost,\n\t\t\t\t\t\tcontextWindow: fallbackModel.contextWindow,\n\t\t\t\t\t\tmaxTokens: fallbackModel.maxTokens,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tconst corruptSessionFile = join(sessionDir, \"valid-header-malformed-entry-session.jsonl\");\n\t\t\twriteFileSync(\n\t\t\t\tcorruptSessionFile,\n\t\t\t\t[\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"session\",\n\t\t\t\t\t\tversion: 3,\n\t\t\t\t\t\tid: \"claimed-valid-header-session-id\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:00.000Z\",\n\t\t\t\t\t\tcwd,\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"model_change\",\n\t\t\t\t\t\tid: \"model-entry\",\n\t\t\t\t\t\tparentId: null,\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:01.000Z\",\n\t\t\t\t\t\tprovider: \"corrupt-provider\",\n\t\t\t\t\t\tmodelId: \"corrupt-model\",\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"thinking_level_change\",\n\t\t\t\t\t\tid: \"thinking-entry\",\n\t\t\t\t\t\tparentId: \"model-entry\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:02.000Z\",\n\t\t\t\t\t\tthinkingLevel: \"low\",\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\tid: \"memory-entry\",\n\t\t\t\t\t\tparentId: \"thinking-entry\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:03.000Z\",\n\t\t\t\t\t\tcustomType: SESSION_MEMORY_CUSTOM_TYPE,\n\t\t\t\t\t\tdata: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tkey: \"project.goal\",\n\t\t\t\t\t\t\t\tvalue: \"malformed later entry should not leak\",\n\t\t\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:03.000Z\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\tid: \"todo-entry\",\n\t\t\t\t\t\tparentId: \"memory-entry\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:04.000Z\",\n\t\t\t\t\t\tcustomType: SESSION_TODOS_CUSTOM_TYPE,\n\t\t\t\t\t\tdata: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontent: \"Leaky todo\",\n\t\t\t\t\t\t\t\tactiveForm: \"Leaky todo\",\n\t\t\t\t\t\t\t\tstatus: \"in_progress\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"message\",\n\t\t\t\t\t\tid: \"assistant-entry\",\n\t\t\t\t\t\tparentId: \"todo-entry\",\n\t\t\t\t\t\ttimestamp: \"2026-04-02T12:00:05.000Z\",\n\t\t\t\t\t\tmessage: {\n\t\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\t\tapi: fallbackModel.api,\n\t\t\t\t\t\t\tprovider: \"corrupt-provider\",\n\t\t\t\t\t\t\tmodel: \"corrupt-model\",\n\t\t\t\t\t\t\tstopReason: \"stop\",\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"partial assistant state should not restore\" }],\n\t\t\t\t\t\t\tusage: {\n\t\t\t\t\t\t\t\tinput: 0,\n\t\t\t\t\t\t\t\toutput: 0,\n\t\t\t\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\t'{\"type\":\"message\",\"id\":\"broken-entry\",\"parentId\":\"assistant-entry\",\"timestamp\":\"2026-04-02T12:00:06.000Z\"',\n\t\t\t\t].join(\"\\n\"),\n\t\t\t);\n\n\t\t\tconst resourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\t\tawait resourceLoader.reload();\n\n\t\t\tconst openedSession = SessionManager.open(corruptSessionFile, sessionDir);\n\t\t\tconst { session } = await createAgentSession({\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tsettingsManager,\n\t\t\t\tauthStorage,\n\t\t\t\tmodelRegistry,\n\t\t\t\tresourceLoader,\n\t\t\t\tsessionManager: openedSession,\n\t\t\t});\n\n\t\t\texpect(session.sessionFile).toBe(corruptSessionFile);\n\t\t\texpect(session.sessionId).toBe(openedSession.getSessionId());\n\t\t\texpect(session.sessionId).not.toBe(\"claimed-valid-header-session-id\");\n\t\t\texpect(session.model?.provider).toBe(fallbackModel.provider);\n\t\t\texpect(session.model?.id).toBe(fallbackModel.id);\n\t\t\texpect(session.thinkingLevel).toBe(\"high\");\n\t\t\texpect(session.getMemoryItems()).toEqual([]);\n\t\t\texpect(session.getTodos()).toMatchObject([]);\n\t\t\texpect(session.messages).toHaveLength(0);\n\t\t\texpect(session.messages).not.toEqual(\n\t\t\t\texpect.arrayContaining([\n\t\t\t\t\texpect.objectContaining({\n\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\tprovider: \"corrupt-provider\",\n\t\t\t\t\t\tmodel: \"corrupt-model\",\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"partial assistant state should not restore\" }],\n\t\t\t\t\t}),\n\t\t\t\t]),\n\t\t\t);\n\t\t} finally {\n\t\t\trmSync(rootDir, { recursive: true, force: true });\n\t\t}\n\t});\n});\n"]}