2026-07-08 11:47:15 +08:00
import { afterEach , describe , expect , it , vi } from 'vitest'
import type { Context } from 'cordis'
2026-07-28 13:55:59 +08:00
import { createUserMessage , CallId } from '@deepseek-ai/dsh-llm'
2026-07-08 11:47:15 +08:00
import { cordisHarness , waitForIdle } from './harness.ts'
2026-07-14 01:59:21 +08:00
import { SessionId } from '@deepseek-ai/dsh-session'
2026-07-08 11:47:15 +08:00
2026-07-19 23:38:54 +08:00
const testToolSignal = new AbortController ( ) . signal
2026-07-08 11:47:15 +08:00
/**
* With-key smoke for the self-referential cordis tools: a REAL model drives
2026-07-27 21:20:06 +08:00
* cordis_mount/cordis_unmount against the live context the test observes.
2026-07-08 11:47:15 +08:00
* World-verified, not self-reported: the mounted listener must actually WRITE
* its tagged console line, the self-made tool must actually EXIST in the
* registry and appear as a real `tool/call`, the cross-mount service must
* actually LAND in the reflect store. Key-gated (see vitest.e2e.config.ts).
*/
let ctx : Context | undefined
afterEach ( async ( ) = > {
vi . restoreAllMocks ( )
// Always dispose the harness, even on failure/retry/timeout: agent-loop
// teardown stops the loop, and disposing the tree unwinds every dynamic
// mount the model left behind.
await ctx ? . fiber . dispose ( )
ctx = undefined
} )
/** The tagged write-through lines (`[cordis:dyn-n] …`) captured by a console spy. */
function taggedCalls ( log : { mock : { calls : unknown [ ] [ ] } } ) : unknown [ ] [ ] {
return log . mock . calls . filter ( call = > typeof call [ 0 ] === 'string' && /^\[cordis:dyn-\d+\]$/ . test ( call [ 0 ] ) )
}
/** Model-facing text of one tool result, concatenated. */
function resultText ( result : { content : { type : string ; text? : string } [ ] } ) : string {
return result . content . filter ( block = > block . type === 'text' ) . map ( block = > block . text ) . join ( '' )
}
describe . skipIf ( ! process . env . DEEPSEEK_API_KEY ) ( 'cordis tools: a real model modifies its own runtime' , ( ) = > {
2026-07-27 21:20:06 +08:00
it ( 'mounts a temporary status listener whose tagged output actually fires, then unmounts it' , async ( ) = > {
2026-07-08 11:47:15 +08:00
ctx = await cordisHarness ( )
const log = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) = > { } )
2026-07-18 12:21:15 +08:00
const agent = ctx . agentLoop . create ( SessionId ( 'cordis-e2e-listener' ) , { provider : 'deepseek' , model : 'deepseek-v4-flash' } )
2026-07-08 11:47:15 +08:00
2026-07-28 13:55:59 +08:00
agent . followup ( createUserMessage ( {
content : [ {
type : 'text' ,
text : 'Use cordis_mount to create a temporary Plugin that listens to the \'agent/status\' '
2026-07-27 16:57:26 +08:00
+ 'Cordis event and logs every change with console.log. Reply "running" once done.' ,
2026-07-28 13:55:59 +08:00
} ] , source : { kind : 'user' } } ) )
2026-07-08 11:47:15 +08:00
await waitForIdle ( ctx , agent )
// The WORLD check: the turn's own running→idle transition must have driven
// the mounted listener through the tagged sandbox console.
expect ( taggedCalls ( log ) . length ) . toBeGreaterThan ( 0 )
const mid = await ctx . tools . execute ( {
2026-07-19 23:38:54 +08:00
signal : testToolSignal ,
2026-07-27 16:57:26 +08:00
callId : CallId ( 'verify-mounted' ) , name : 'cordis_inspect' , arguments : { what : 'temporary' } ,
2026-07-08 11:47:15 +08:00
} )
expect ( resultText ( mid ) ) . toContain ( 'dyn-' )
2026-07-28 13:55:59 +08:00
agent . followup ( createUserMessage ( { content : [ { type : 'text' , text : 'Now unmount the temporary Plugin you just mounted.' } ] , source : { kind : 'user' } } ) )
2026-07-08 11:47:15 +08:00
await waitForIdle ( ctx , agent )
const after = await ctx . tools . execute ( {
2026-07-19 23:38:54 +08:00
signal : testToolSignal ,
2026-07-27 16:57:26 +08:00
callId : CallId ( 'verify-unmounted' ) , name : 'cordis_inspect' , arguments : { what : 'temporary' } ,
2026-07-08 11:47:15 +08:00
} )
2026-07-27 16:57:26 +08:00
expect ( resultText ( after ) ) . toContain ( 'No temporary Plugins are running.' )
2026-07-08 11:47:15 +08:00
} , 120 _000 )
it ( 'builds itself a reverse_text tool and actually calls it' , async ( ) = > {
ctx = await cordisHarness ( )
2026-07-18 12:21:15 +08:00
const agent = ctx . agentLoop . create ( SessionId ( 'cordis-e2e-selftool' ) , { provider : 'deepseek' , model : 'deepseek-v4-flash' } )
2026-07-08 11:47:15 +08:00
2026-07-28 13:55:59 +08:00
agent . followup ( createUserMessage ( {
content : [ {
type : 'text' ,
text : 'Give yourself a new tool: use cordis_mount to create a temporary Plugin with '
2026-07-08 11:47:15 +08:00
+ 'inject ["tools"] that calls harness.registerTool(ctx, harness.defineTool({...})) '
+ 'to register a tool named reverse_text with one required string parameter '
+ '"text", returning the text reversed. Then CALL reverse_text with the '
+ 'exact text "harness" and report its exact output.' ,
2026-07-28 13:55:59 +08:00
} ] , source : { kind : 'user' } } ) )
2026-07-08 11:47:15 +08:00
await waitForIdle ( ctx , agent )
2026-07-12 03:36:43 +08:00
// World checks: the tool exists in the registry, was invoked as a real tool call, and its
2026-07-13 23:27:00 +08:00
// RESULT (the self-made execute actually running) is the reversed string. Model prose is only
// self-report and is deliberately not asserted.
2026-07-08 11:47:15 +08:00
expect ( ctx . tools . get ( 'reverse_text' ) ) . toBeDefined ( )
const events = [ . . . agent . session . events ]
const calls = events . filter ( event = > event . type === 'tool/call' )
2026-07-27 21:20:06 +08:00
expect ( calls . some ( event = > event . data . name === 'cordis_mount' ) ) . toBe ( true )
2026-07-08 11:47:15 +08:00
const reverseCalls = calls . filter ( event = > event . data . name === 'reverse_text' )
expect ( reverseCalls . length ) . toBeGreaterThan ( 0 )
const reverseResults = events
. filter ( event = > event . type === 'tool/result' )
2026-07-28 13:55:59 +08:00
. filter ( event = > reverseCalls . some ( call = > call . data . callId === event . data . message . source . callId ) )
. flatMap ( event = > event . data . message . content [ 0 ] . content . filter ( block = > block . type === 'text' ) . map ( block = > block . text ) )
2026-07-08 11:47:15 +08:00
// On failure, surface what the model actually mounted and what the tool
// returned — an e2e failing at a distance is undebuggable without it.
const mountCode = calls
2026-07-27 21:20:06 +08:00
. filter ( event = > event . data . name === 'cordis_mount' )
2026-07-08 11:47:15 +08:00
. map ( event = > event . data . arguments )
. join ( '\n---\n' )
const trace = events . map ( ( event ) = > {
switch ( event . type ) {
case 'tool/call' : return ` tool/call: ${ event . data . name } `
2026-07-28 13:55:59 +08:00
case 'tool/result' : return ` tool/result: ${ event . data . message . content [ 0 ] . isError ? 'ERR:' + JSON . stringify ( event . data . message . content [ 0 ] . content ) . slice ( 0 , 200 ) : 'ok' } `
2026-07-08 11:47:15 +08:00
case 'turn/end' : return ` turn/end: ${ JSON . stringify ( event . data . reason ) } `
default : return event . type
}
} ) . join ( '\n' )
expect (
reverseResults . some ( text = > text . includes ( 'ssenrah' ) ) ,
` no reversed output in reverse_text results. \ nresults: ${ JSON . stringify ( reverseResults ) } \ nmount code: ${ mountCode } \ ntrace: \ n ${ trace } ` ,
) . toBe ( true )
} , 120 _000 )
2026-07-27 21:20:06 +08:00
it ( 'composes two temporary Plugins through provide/inject, and unmounting the provider parks the consumer' , async ( ) = > {
2026-07-08 11:47:15 +08:00
ctx = await cordisHarness ( )
2026-07-18 12:21:15 +08:00
const agent = ctx . agentLoop . create ( SessionId ( 'cordis-e2e-compose' ) , { provider : 'deepseek' , model : 'deepseek-v4-flash' } )
2026-07-08 11:47:15 +08:00
2026-07-28 13:55:59 +08:00
agent . followup ( createUserMessage ( {
content : [ {
type : 'text' ,
text : 'Mount TWO separate temporary Plugins with cordis_mount. First a provider: apply calls '
2026-07-08 11:47:15 +08:00
+ 'ctx.provide(\'shouter\', { shout: (s) => s.toUpperCase() }). Second a consumer with '
+ 'inject ["shouter", "tools"] that registers (via harness.registerTool + harness.defineTool) '
+ 'a tool named shout_text with one required string parameter "text" whose execute returns '
+ 'ctx.shouter.shout(args.text) as a text content block. Then CALL shout_text with "quiet" '
+ 'and report the exact output.' ,
2026-07-28 13:55:59 +08:00
} ] , source : { kind : 'user' } } ) )
2026-07-08 11:47:15 +08:00
await waitForIdle ( ctx , agent )
// World checks: the service is really in the store, the tool really ran.
expect ( ctx . get ( 'shouter' ) ) . toBeDefined ( )
expect ( ctx . tools . get ( 'shout_text' ) ) . toBeDefined ( )
const events = [ . . . agent . session . events ]
const shoutCalls = events
. filter ( event = > event . type === 'tool/call' )
. filter ( event = > event . data . name === 'shout_text' )
expect ( shoutCalls . length ) . toBeGreaterThan ( 0 )
const shoutResults = events
. filter ( event = > event . type === 'tool/result' )
2026-07-28 13:55:59 +08:00
. filter ( event = > shoutCalls . some ( call = > call . data . callId === event . data . message . source . callId ) )
. flatMap ( event = > event . data . message . content [ 0 ] . content . filter ( block = > block . type === 'text' ) . map ( block = > block . text ) )
2026-07-08 11:47:15 +08:00
expect ( shoutResults . some ( text = > text . includes ( 'QUIET' ) ) ) . toBe ( true )
2026-07-28 13:55:59 +08:00
agent . followup ( createUserMessage ( { content : [ { type : 'text' , text : 'Now unmount ONLY the provider temporary Plugin (the one that provided shouter).' } ] , source : { kind : 'user' } } ) )
2026-07-08 11:47:15 +08:00
await waitForIdle ( ctx , agent )
// The consumer must have been parked by cordis itself: service gone,
2026-07-27 16:57:26 +08:00
// dependent tool unregistered, temporary section naming the missing service.
2026-07-08 11:47:15 +08:00
expect ( ctx . get ( 'shouter' ) ) . toBeUndefined ( )
expect ( ctx . tools . get ( 'shout_text' ) ) . toBeUndefined ( )
const after = await ctx . tools . execute ( {
2026-07-19 23:38:54 +08:00
signal : testToolSignal ,
2026-07-27 16:57:26 +08:00
callId : CallId ( 'verify-parked' ) , name : 'cordis_inspect' , arguments : { what : 'temporary' } ,
2026-07-08 11:47:15 +08:00
} )
expect ( resultText ( after ) ) . toContain ( 'waiting for: shouter' )
} , 120 _000 )
} )