2026-07-28 13:55:59 +08:00
import { createUserMessage } from '@deepseek-ai/dsh-llm'
2026-06-16 22:28:01 +08:00
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'
2026-06-21 07:17:25 +08:00
import { SessionId } from '@deepseek-ai/dsh-session'
2026-06-16 22:28:01 +08:00
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'
2026-06-21 07:17:25 +08:00
const SESSION_ID = SessionId ( 'resume-e2e-session' )
2026-06-16 22:28:01 +08:00
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.
2026-07-05 23:23:46 +08:00
ctx = await codingHarness ( process . cwd ( ) , { persona : SYSTEM_PROMPT , persistenceRoot : root } )
2026-07-11 22:55:26 +08:00
const first = ( await ctx . agents . create ( {
2026-06-16 22:28:01 +08:00
sessionId : SESSION_ID ,
2026-07-29 16:36:07 +08:00
agentOptions : { provider : 'deepseek-official' , model : 'deepseek-v4-flash' } ,
2026-07-14 02:32:35 +08:00
} ) ) . agent
2026-07-28 13:55:59 +08:00
first . followup ( createUserMessage ( { content : [ { type : 'text' , text : ` Remember this code for later: ${ SECRET } . Just acknowledge it. ` } ] , source : { kind : 'user' } } ) )
2026-06-16 22:28:01 +08:00
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.
2026-07-05 23:23:46 +08:00
ctx = await codingHarness ( process . cwd ( ) , { persona : SYSTEM_PROMPT , persistenceRoot : root } )
2026-06-20 06:44:58 +08:00
const resumed = ( await ctx . agents . resume ( {
2026-06-16 22:28:01 +08:00
resumeSessionId : SESSION_ID ,
2026-07-29 16:36:07 +08:00
agentOptions : { provider : 'deepseek-official' , model : 'deepseek-v4-flash' } ,
2026-07-14 02:32:35 +08:00
} ) ) . agent
2026-06-16 22:28:01 +08:00
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 )
2026-07-28 13:55:59 +08:00
resumed . followup ( createUserMessage ( { content : [ { type : 'text' , text : 'What was the code I asked you to remember? Reply with just the code.' } ] , source : { kind : 'user' } } ) )
2026-06-16 22:28:01 +08:00
await waitForIdle ( ctx , resumed )
// The model recalls it — only possible from the resumed history.
expect ( finalText ( [ . . . resumed . session . events ] ) ) . toContain ( SECRET )
} , 180 _000 )
} )