2026-06-25 16:04:42 +08:00
import { mkdtemp , mkdir , rm , writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach , describe , expect , it } from 'vitest'
import { Context } from 'cordis'
import LlmService from '@deepseek-ai/dsh-llm'
import SessionStore , { SessionId } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import AgentRegistry , { AgentId } from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import * as ProjectInstructions from '@deepseek-ai/dsh-project-instructions'
2026-07-03 11:56:58 +08:00
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
2026-07-05 18:18:32 +08:00
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
2026-06-25 16:04:42 +08:00
import type { SessionEvent } from '@deepseek-ai/dsh-session'
2026-07-03 11:56:58 +08:00
const PROBE = 'banana-271828'
2026-07-05 18:18:32 +08:00
const NESTED_PROBE = 'papaya-314159'
2026-06-25 16:04:42 +08:00
let ctx : Context | undefined
let workdir : string | undefined
afterEach ( async ( ) = > {
await ctx ? . fiber . dispose ( )
ctx = undefined
if ( workdir !== undefined ) await rm ( workdir , { recursive : true , force : true } )
workdir = undefined
} )
async function harness ( ) : Promise < { ctx : Context ; agent : Agent } > {
workdir = await mkdtemp ( join ( tmpdir ( ) , 'dsh-project-instructions-e2e-' ) )
await mkdir ( join ( workdir , '.git' ) , { recursive : true } )
2026-07-03 11:56:58 +08:00
await writeFile ( join ( workdir , 'AGENTS.md' ) , ` If the user asks for the project instruction handshake, reply with exactly this string and nothing else: ${ PROBE } . \ n ` )
2026-06-25 16:04:42 +08:00
ctx = new Context ( )
await ctx . plugin ( LlmService )
await ctx . plugin ( SessionStore )
2026-07-06 10:14:47 +08:00
await ctx . plugin ( SystemPrompt , { persona : 'Answer the user exactly and concisely.' } )
2026-06-25 16:04:42 +08:00
await ctx . plugin ( ToolRegistry )
await ctx . plugin ( AgentRegistry )
2026-07-03 11:56:58 +08:00
await ctx . plugin ( LocalFileSystem , { cwd : '/' } )
2026-07-05 18:18:32 +08:00
await ctx . plugin ( ToolFs )
2026-06-25 16:04:42 +08:00
await ctx . plugin ( ProjectInstructions )
await ctx . plugin ( AgentLoop , { agents : [ ] } )
await ctx . plugin ( LlmDeepSeek , { models : [ 'deepseek-v4-flash' ] } )
const handle = ctx . agents . create ( {
agentId : AgentId ( 'project-instructions-e2e' ) ,
sessionId : SessionId ( 'project-instructions-e2e-session' ) ,
meta : { cwd : workdir } ,
2026-07-06 10:14:47 +08:00
agentOptions : { model : 'deepseek-v4-flash' } ,
2026-06-25 16:04:42 +08:00
} )
return { ctx , agent : handle.agent }
}
function waitForIdle ( ctx : Context , agent : Agent ) : Promise < void > {
return new Promise ( ( resolve ) = > {
const dispose = ctx . on ( 'agent/status' , ( subject , status ) = > {
if ( subject === agent && status === 'idle' ) {
dispose ( )
resolve ( )
}
} )
} )
}
function finalText ( events : SessionEvent [ ] ) : string {
const message = events . findLast ( event = > event . type === 'assistant/message' )
if ( message ? . type !== 'assistant/message' ) return ''
return message . data . content
. filter ( block = > block . type === 'text' )
. map ( block = > block . text )
. join ( '' )
}
describe . skipIf ( ! process . env . DEEPSEEK_API_KEY ) ( 'project instructions e2e: real model sees AGENTS.md baseline' , ( ) = > {
it ( 'obeys a probe instruction loaded from the workspace' , async ( ) = > {
const live = await harness ( )
2026-07-03 11:56:58 +08:00
live . agent . send ( [ { type : 'text' , text : 'Project instruction handshake?' } ] )
2026-06-25 16:04:42 +08:00
await waitForIdle ( live . ctx , live . agent )
expect ( finalText ( [ . . . live . agent . session . events ] ) ) . toContain ( PROBE )
} , 120 _000 )
2026-07-05 18:18:32 +08:00
it ( 'loads a nested AGENTS.md after the real read tool touches a descendant file' , async ( ) = > {
const live = await harness ( )
await mkdir ( join ( workdir ! , 'pkg/deep' ) , { recursive : true } )
await writeFile ( join ( workdir ! , 'pkg/AGENTS.md' ) , ` If the user asks for the nested instruction handshake, reply with exactly this string and nothing else: ${ NESTED_PROBE } . \ n ` )
await writeFile ( join ( workdir ! , 'pkg/deep/file.txt' ) , 'This file exists only to trigger nested project instructions.\n' )
live . agent . send ( [ { type : 'text' , text : 'Use the read tool to inspect pkg/deep/file.txt. After reading it, answer: nested instruction handshake?' } ] )
await waitForIdle ( live . ctx , live . agent )
expect ( finalText ( [ . . . live . agent . session . events ] ) ) . toContain ( NESTED_PROBE )
} , 120 _000 )
2026-06-25 16:04:42 +08:00
} )