import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import type { Context } from 'cordis' import type { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' import { AgentId } from '@deepseek-ai/dsh-agent' import { SessionId } from '@deepseek-ai/dsh-session' import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness.ts' /** * Proves durable conversation continuity end-to-end: run 1 tells the REAL model * a fact and persists the turn to JSONL; run 2 is a fresh harness (new Context, * same `.sessions` root) that RESUMES the persisted session id and asks the * model to recall the fact. The recall can only come from the rehydrated event * log — a fresh session would have no idea. Key-gated like the other e2es. */ const SECRET = 'plum-galaxy-1791' const SESSION_ID = SessionId('resume-e2e-session') let ctx: Context | undefined let root: string | undefined afterEach(async () => { // Dispose even on failure/retry: agent-loop teardown stops the loop and the // JSONL backend flushes; then drop the on-disk session log. await ctx?.fiber.dispose() ctx = undefined if (root !== undefined) await rm(root, { recursive: true, force: true }) root = undefined }) describe.skipIf(!process.env.DEEPSEEK_API_KEY)('resume: continue a persisted session across processes', () => { it('recalls a fact stored in a prior, separately-disposed session', async () => { root = await mkdtemp(join(tmpdir(), 'dsh-resume-e2e-')) // Run 1: a fresh agent on a KNOWN session id learns a secret, then we // dispose the whole context (simulating process exit) so only the JSONL // log on disk survives. ctx = await codingHarness(process.cwd(), { persona: SYSTEM_PROMPT, persistenceRoot: root }) const first = (await ctx.agents.create({ agentId: AgentId('resume-1'), sessionId: SESSION_ID, agentOptions: { model: 'deepseek-v4-flash' }, })).agent as ReactLoopAgent first.send([{ type: 'text', text: `Remember this code for later: ${SECRET}. Just acknowledge it.` }]) await waitForIdle(ctx, first) await ctx.fiber.dispose() ctx = undefined // Run 2: a brand-new context over the SAME root resumes the persisted // session. The loaded event log seeds the live session, so the model sees // run 1's exchange as conversation history. ctx = await codingHarness(process.cwd(), { persona: SYSTEM_PROMPT, persistenceRoot: root }) const resumed = (await ctx.agents.resume({ agentId: AgentId('resume-2'), resumeSessionId: SESSION_ID, agentOptions: { model: 'deepseek-v4-flash' }, })).agent as ReactLoopAgent expect(resumed.session.id).toBe(SESSION_ID) // The prior user turn is in the rehydrated log before the model is asked. expect(JSON.stringify(resumed.session.deriveMessages())).toContain(SECRET) resumed.send([{ type: 'text', text: 'What was the code I asked you to remember? Reply with just the code.' }]) await waitForIdle(ctx, resumed) // The model recalls it — only possible from the resumed history. expect(finalText([...resumed.session.events])).toContain(SECRET) }, 180_000) })