@@ -3,12 +3,7 @@ import { mkdtemp } from "node:fs/promises";
33import { tmpdir } from "node:os" ;
44import { join } from "node:path" ;
55import type { Agent } from "@intx/agent" ;
6- import type {
7- AuditStore ,
8- Compactor ,
9- ContextStore ,
10- ToolDefinition ,
11- } from "@intx/types/runtime" ;
6+ import type { Compactor , ContextStore , ToolDefinition } from "@intx/types/runtime" ;
127
138import { withMockedModuleDuring } from "../../tests/helpers/mock-module.js" ;
149import {
@@ -18,16 +13,10 @@ import {
1813} from "./assemble-runtime.js" ;
1914
2015function def ( name : string ) : ToolDefinition {
21- return {
22- name,
23- description : `${ name } tool` ,
24- inputSchema : { type : "object" , properties : { } } ,
25- } ;
16+ return { name, description : `${ name } tool` , inputSchema : { type : "object" , properties : { } } } ;
2617}
2718
28- function wiring (
29- overrides : Partial < Parameters < typeof createAdvertisedToolset > [ 0 ] > = { } ,
30- ) {
19+ function wiring ( overrides : Partial < Parameters < typeof createAdvertisedToolset > [ 0 ] > = { } ) {
3120 return {
3221 sessionMode : "orchestrator" as const ,
3322 toolAvailability : { languageServerAvailable : false } ,
@@ -39,34 +28,24 @@ function wiring(
3928describe ( "createAdvertisedToolset" , ( ) => {
4029 test ( "drops names outside the built-in prefix" , ( ) => {
4130 const { computeAdvertised } = createAdvertisedToolset ( wiring ( ) ) ;
42- const names = computeAdvertised ( [
43- def ( "write_file" ) ,
44- def ( "mystery_tool" ) ,
45- ] ) . map ( ( d ) => d . name ) ;
31+ const names = computeAdvertised ( [ def ( "write_file" ) , def ( "mystery_tool" ) ] ) . map ( ( d ) => d . name ) ;
4632 expect ( names ) . toContain ( "write_file" ) ;
4733 expect ( names ) . not . toContain ( "mystery_tool" ) ;
4834 } ) ;
4935
5036 test ( "appends activated tools after the prefix, in activation order" , ( ) => {
5137 const { activated, computeAdvertised } = createAdvertisedToolset ( wiring ( ) ) ;
5238 expect ( activated . activate ( [ "mystery_tool" ] ) ) . toBe ( true ) ;
53- const names = computeAdvertised ( [
54- def ( "read_file" ) ,
55- def ( "mystery_tool" ) ,
56- ] ) . map ( ( d ) => d . name ) ;
39+ const names = computeAdvertised ( [ def ( "read_file" ) , def ( "mystery_tool" ) ] ) . map ( ( d ) => d . name ) ;
5740 expect ( names [ names . length - 1 ] ) . toBe ( "mystery_tool" ) ;
5841 expect ( names . slice ( 0 , - 1 ) ) . not . toContain ( "mystery_tool" ) ;
5942 } ) ;
6043
6144 test ( "honors an explicit built-in prefix" , ( ) => {
62- const { computeAdvertised } = createAdvertisedToolset (
63- wiring ( { builtInPrefix : [ "read_file" ] } ) ,
64- ) ;
65- expect (
66- computeAdvertised ( [ def ( "read_file" ) , def ( "write_file" ) ] ) . map (
67- ( d ) => d . name ,
68- ) ,
69- ) . toEqual ( [ "read_file" ] ) ;
45+ const { computeAdvertised } = createAdvertisedToolset ( wiring ( { builtInPrefix : [ "read_file" ] } ) ) ;
46+ expect ( computeAdvertised ( [ def ( "read_file" ) , def ( "write_file" ) ] ) . map ( ( d ) => d . name ) ) . toEqual ( [
47+ "read_file" ,
48+ ] ) ;
7049 } ) ;
7150
7251 test ( "advertises nothing from an empty registry" , ( ) => {
@@ -129,26 +108,60 @@ function stubInferenceDeps(): ChatAgentWiring["inferenceDeps"] {
129108 } ;
130109}
131110
111+ function stubAuthorize ( ) : ChatAgentWiring [ "authorize" ] {
112+ return async ( ) => ( { effect : "allow" , matchingGrants : [ ] , resolvedBy : null } ) ;
113+ }
114+
115+ function stubChatAgentWiring ( overrides : Partial < ChatAgentWiring > = { } ) : ChatAgentWiring {
116+ return {
117+ toolsId : "test/tools" ,
118+ agentId : "test/agent" ,
119+ systemPrompt : "prompt" ,
120+ authorize : stubAuthorize ( ) ,
121+ getDynamicRunner : ( ) => {
122+ throw new Error ( "getDynamicRunner should not run at assemble or mocked build" ) ;
123+ } ,
124+ computeAdvertised : ( ) => [ ] ,
125+ activateTools : ( ) => false ,
126+ inactivityTimeoutMs : 1_000 ,
127+ onTasksChange : ( ) => { } ,
128+ requestContinuation : ( ) => { } ,
129+ getProvider : ( ) => ( { providerName : "test" , model : "m" } ) ,
130+ getWorkdir : ( ) => "/build-dir" ,
131+ inferenceDeps : stubInferenceDeps ( ) ,
132+ getSources : ( ) => [
133+ {
134+ id : "s" ,
135+ provider : "test" ,
136+ baseURL : "http://localhost" ,
137+ apiKey : "k" ,
138+ model : "m" ,
139+ } ,
140+ ] ,
141+ getDefaultSource : ( ) => "s" ,
142+ getCompactor : ( ) => stubCompactor ( "build" ) ,
143+ onBuilt : ( ) => { } ,
144+ ...overrides ,
145+ } ;
146+ }
147+
132148describe ( "assembleChatAgent" , ( ) => {
133- test ( "getWorkdir, getSessionId, and getCompactor run at buildAgent time" , async ( ) => {
149+ test ( "getWorkdir and getCompactor run at buildAgent time, not assemble time" , async ( ) => {
134150 const storeDirs : string [ ] = [ ] ;
135151 const agentWorkdirs : string [ ] = [ ] ;
136- const agentSessionIds : string [ ] = [ ] ;
137- const agentAudits : AuditStore [ ] = [ ] ;
138- const agentStorages : ContextStore [ ] = [ ] ;
139152 const agentCompactors : Compactor [ ] = [ ] ;
140153 const fakeStorage = {
141154 readBlob : async ( ) => new Uint8Array ( ) ,
142- } as unknown as ContextStore & AuditStore ;
143- const fakeAgent = { close : async ( ) => undefined } as unknown as Agent ;
155+ } as unknown as ContextStore ;
156+ const fakeAgent = { close : async ( ) => { } } as unknown as Agent ;
144157
145158 await withMockedModuleDuring (
146159 import . meta. resolve ( "./optimized-context-store.js" ) ,
147160 ( real : typeof import ( "./optimized-context-store.js" ) ) => ( {
148161 ...real ,
149- createSessionStores : async ( dir : string ) => {
162+ createOptimizedContextStore : async ( dir : string ) => {
150163 storeDirs . push ( dir ) ;
151- return { storage : fakeStorage , audit : fakeStorage } ;
164+ return fakeStorage ;
152165 } ,
153166 } ) ,
154167 async ( ) => {
@@ -158,104 +171,106 @@ describe("assembleChatAgent", () => {
158171 ...real ,
159172 createAgentWithLiveToolDispatch : async (
160173 _def : unknown ,
161- env : {
162- workdir : string ;
163- sessionId ?: string ;
164- storage : ContextStore ;
165- audit : AuditStore ;
166- compactors : { "pruning-compactor" : Compactor } ;
167- } ,
174+ env : { workdir : string ; compactors : { "pruning-compactor" : Compactor } } ,
168175 ) => {
169176 agentWorkdirs . push ( env . workdir ) ;
170- if ( env . sessionId !== undefined )
171- agentSessionIds . push ( env . sessionId ) ;
172- agentStorages . push ( env . storage ) ;
173- agentAudits . push ( env . audit ) ;
174177 agentCompactors . push ( env . compactors [ "pruning-compactor" ] ) ;
175178 return fakeAgent ;
176179 } ,
177180 } ) ,
178181 async ( ) => {
179182 const { assembleChatAgent } = await import ( "./assemble-runtime.js" ) ;
180183 const workdirCalls : string [ ] = [ ] ;
181- const sessionIdCalls : string [ ] = [ ] ;
182184 const compactorCalls : string [ ] = [ ] ;
183185 let liveDir = "/assemble-dir" ;
184- let liveSessionId = "assemble-session" ;
185186 let liveCompactor = stubCompactor ( "assemble" ) ;
186187
187- const { buildAgent } = assembleChatAgent ( {
188- toolsId : "test/tools" ,
189- agentId : "test/agent" ,
190- systemPrompt : "prompt" ,
191- authorize : async ( ) => ( {
192- effect : "allow" ,
193- matchingGrants : [ ] ,
194- resolvedBy : null ,
195- } ) ,
196- getDynamicRunner : ( ) => {
197- throw new Error (
198- "getDynamicRunner should not run at assemble or mocked build" ,
199- ) ;
200- } ,
201- computeAdvertised : ( ) => [ ] ,
202- activateTools : ( ) => false ,
203- inactivityTimeoutMs : 1_000 ,
204- onTasksChange : ( ) => undefined ,
205- requestContinuation : ( ) => undefined ,
206- getProvider : ( ) => ( { providerName : "test" , model : "m" } ) ,
207- getWorkdir : ( ) => {
208- workdirCalls . push ( liveDir ) ;
209- return liveDir ;
210- } ,
211- getSessionId : ( ) => {
212- sessionIdCalls . push ( liveSessionId ) ;
213- return liveSessionId ;
214- } ,
215- inferenceDeps : stubInferenceDeps ( ) ,
216- getSources : ( ) => [
217- {
218- id : "s" ,
219- provider : "test" ,
220- baseURL : "http://localhost" ,
221- apiKey : "k" ,
222- model : "m" ,
188+ const { buildAgent } = assembleChatAgent (
189+ stubChatAgentWiring ( {
190+ getWorkdir : ( ) => {
191+ workdirCalls . push ( liveDir ) ;
192+ return liveDir ;
193+ } ,
194+ getCompactor : ( ) => {
195+ compactorCalls . push ( liveCompactor . name ) ;
196+ return liveCompactor ;
223197 } ,
224- ] ,
225- getDefaultSource : ( ) => "s" ,
226- getCompactor : ( ) => {
227- compactorCalls . push ( liveCompactor . name ) ;
228- return liveCompactor ;
229- } ,
230- onBuilt : ( ) => undefined ,
231- } ) ;
198+ } ) ,
199+ ) ;
232200
233201 expect ( workdirCalls ) . toEqual ( [ ] ) ;
234- expect ( sessionIdCalls ) . toEqual ( [ ] ) ;
235202 expect ( compactorCalls ) . toEqual ( [ ] ) ;
236203 expect ( storeDirs ) . toEqual ( [ ] ) ;
237204 expect ( agentWorkdirs ) . toEqual ( [ ] ) ;
238205
239206 liveDir = "/build-dir" ;
240- liveSessionId = "build-session" ;
241207 liveCompactor = stubCompactor ( "build" ) ;
242208 const builtCompactor = liveCompactor ;
243209
244210 await buildAgent ( ) ;
245211
246212 expect ( workdirCalls ) . toEqual ( [ "/build-dir" ] ) ;
247- expect ( sessionIdCalls ) . toEqual ( [ "build-session" ] ) ;
248213 expect ( compactorCalls ) . toEqual ( [ "build" ] ) ;
249214 expect ( storeDirs ) . toEqual ( [ "/build-dir" ] ) ;
250215 expect ( agentWorkdirs ) . toEqual ( [ "/build-dir" ] ) ;
251- expect ( agentSessionIds ) . toEqual ( [ "build-session" ] ) ;
252- expect ( agentStorages ) . toEqual ( [ fakeStorage ] ) ;
253- expect ( agentAudits ) . toEqual ( [ fakeStorage ] ) ;
254- expect ( Object . is ( agentAudits [ 0 ] , agentStorages [ 0 ] ) ) . toBe ( true ) ;
255216 expect ( agentCompactors ) . toEqual ( [ builtCompactor ] ) ;
256217 } ,
257218 ) ;
258219 } ,
259220 ) ;
260221 } ) ;
222+
223+ test ( "omits evidence archive when no holder is provided" , async ( ) => {
224+ const fakeStorage = {
225+ readBlob : async ( ) => new Uint8Array ( ) ,
226+ } as unknown as ContextStore ;
227+ const fakeAgent = { close : async ( ) => { } } as unknown as Agent ;
228+ const authorize = stubAuthorize ( ) ;
229+ let capturedStorage : ContextStore | undefined ;
230+ let capturedAuthorize : unknown ;
231+ let builtAgent : Agent | undefined ;
232+ let builtStorage : ContextStore | undefined ;
233+
234+ await withMockedModuleDuring (
235+ import . meta. resolve ( "./optimized-context-store.js" ) ,
236+ ( real : typeof import ( "./optimized-context-store.js" ) ) => ( {
237+ ...real ,
238+ createOptimizedContextStore : async ( ) => fakeStorage ,
239+ } ) ,
240+ async ( ) => {
241+ await withMockedModuleDuring (
242+ import . meta. resolve ( "../agent/live-tool-dispatch.js" ) ,
243+ ( real : typeof import ( "../agent/live-tool-dispatch.js" ) ) => ( {
244+ ...real ,
245+ createAgentWithLiveToolDispatch : async (
246+ _def : unknown ,
247+ env : { storage : ContextStore ; authorize : unknown } ,
248+ ) => {
249+ capturedStorage = env . storage ;
250+ capturedAuthorize = env . authorize ;
251+ return fakeAgent ;
252+ } ,
253+ } ) ,
254+ async ( ) => {
255+ const { assembleChatAgent } = await import ( "./assemble-runtime.js" ) ;
256+ const { buildAgent } = assembleChatAgent (
257+ stubChatAgentWiring ( {
258+ authorize,
259+ onBuilt : ( agent , storage ) => {
260+ builtAgent = agent ;
261+ builtStorage = storage ;
262+ } ,
263+ } ) ,
264+ ) ;
265+ await buildAgent ( ) ;
266+ } ,
267+ ) ;
268+ } ,
269+ ) ;
270+
271+ expect ( capturedStorage ) . toBe ( fakeStorage ) ;
272+ expect ( capturedAuthorize ) . toBe ( authorize ) ;
273+ expect ( builtAgent ) . toBe ( fakeAgent ) ;
274+ expect ( builtStorage ) . toBe ( fakeStorage ) ;
275+ } ) ;
261276} ) ;
0 commit comments