@@ -259,6 +259,90 @@ function stubChatAgentWiring(
259259 } ;
260260}
261261
262+ type ManifestRecord = {
263+ strategy : string ;
264+ version : string ;
265+ parameters : Record < string , unknown > ;
266+ reason : string ;
267+ decisions : Record < string , unknown > ;
268+ } ;
269+
270+ function pruningExtrasRecord ( extraInstructions : string ) : ManifestRecord {
271+ return {
272+ strategy : "pruning-compactor" ,
273+ version : "1" ,
274+ parameters : { extraInstructions } ,
275+ reason : "compacted" ,
276+ decisions : { } ,
277+ } ;
278+ }
279+
280+ function laterManifestRecord ( ) : ManifestRecord {
281+ return {
282+ strategy : "other" ,
283+ version : "1" ,
284+ parameters : { } ,
285+ reason : "later" ,
286+ decisions : { } ,
287+ } ;
288+ }
289+
290+ async function withAssembledDirector (
291+ input : {
292+ getRecords : ( ) => readonly ManifestRecord [ ] ;
293+ directorHolder : { instance ?: ChatDirector } ;
294+ wiring ?: Partial < ChatAgentWiring > ;
295+ } ,
296+ run : ( buildAgent : ( ) => Promise < unknown > ) => Promise < void > ,
297+ ) : Promise < void > {
298+ const fakeAgent = { close : async ( ) => undefined } as unknown as Agent ;
299+ const fakeStorage = {
300+ readBlob : async ( ) => new Uint8Array ( ) ,
301+ readManifestHistory : async ( limit : number ) =>
302+ input . getRecords ( ) . slice ( 0 , limit ) ,
303+ } as unknown as ContextStore ;
304+
305+ await withMockedModuleDuring (
306+ import . meta. resolve ( "./optimized-context-store.js" ) ,
307+ ( real : typeof import ( "./optimized-context-store.js" ) ) => ( {
308+ ...real ,
309+ createSessionStores : async ( ) => ( {
310+ storage : fakeStorage ,
311+ audit : stubAuditStore ( ) ,
312+ } ) ,
313+ } ) ,
314+ async ( ) => {
315+ await withMockedModuleDuring (
316+ import . meta. resolve ( "../agent/live-tool-dispatch.js" ) ,
317+ ( real : typeof import ( "../agent/live-tool-dispatch.js" ) ) => ( {
318+ ...real ,
319+ createAgentWithLiveToolDispatch : async (
320+ _def : unknown ,
321+ env : { directors : DirectorRegistry } ,
322+ ) => {
323+ env . directors . defaultFactory ( ) ( { } , { } as never , {
324+ systemPrompt : "prompt" ,
325+ toolDefinitions : [ ] ,
326+ compactorNames : [ "pruning-compactor" ] ,
327+ } ) ;
328+ return fakeAgent ;
329+ } ,
330+ } ) ,
331+ async ( ) => {
332+ const { assembleChatAgent } = await import ( "./assemble-runtime.js" ) ;
333+ const { buildAgent } = assembleChatAgent (
334+ stubChatAgentWiring ( {
335+ directorHolder : input . directorHolder ,
336+ ...input . wiring ,
337+ } ) ,
338+ ) ;
339+ await run ( buildAgent ) ;
340+ } ,
341+ ) ;
342+ } ,
343+ ) ;
344+ }
345+
262346describe ( "assembleChatAgent" , ( ) => {
263347 test ( "getWorkdir and getCompactor run at buildAgent time, not assemble time" , async ( ) => {
264348 const storeDirs : string [ ] = [ ] ;
@@ -395,69 +479,74 @@ describe("assembleChatAgent", () => {
395479 } ) ;
396480
397481 test ( "rebuild restores extraInstructions from the latest compact record" , async ( ) => {
398- let records : {
399- strategy : string ;
400- version : string ;
401- parameters : Record < string , unknown > ;
402- reason : string ;
403- decisions : Record < string , unknown > ;
404- } [ ] = [
482+ let records : ManifestRecord [ ] = [
483+ pruningExtrasRecord ( "keep the auth discussion" ) ,
484+ ] ;
485+ const directorHolder : { instance ?: ChatDirector } = { } ;
486+
487+ await withAssembledDirector (
488+ { getRecords : ( ) => records , directorHolder } ,
489+ async ( buildAgent ) => {
490+ await buildAgent ( ) ;
491+ expect ( directorHolder . instance ?. getCompactInstructions ( ) ) . toBe (
492+ "keep the auth discussion" ,
493+ ) ;
494+ // /model store-miss on the same session still inherits fromPrev.
495+ records = [ ] ;
496+ await buildAgent ( ) ;
497+ expect ( directorHolder . instance ?. getCompactInstructions ( ) ) . toBe (
498+ "keep the auth discussion" ,
499+ ) ;
500+ } ,
501+ ) ;
502+ } ) ;
503+
504+ test ( "/clear-like rebuild against an empty store does not inherit extraInstructions" , async ( ) => {
505+ let records : ManifestRecord [ ] = [
506+ pruningExtrasRecord ( "keep the auth discussion" ) ,
507+ ] ;
508+ let workdir = "/session-a" ;
509+ let sessionId = "session-a" ;
510+ const directorHolder : { instance ?: ChatDirector } = { } ;
511+
512+ await withAssembledDirector (
405513 {
406- strategy : "pruning-compactor" ,
407- version : "1" ,
408- parameters : { extraInstructions : "keep the auth discussion" } ,
409- reason : "compacted" ,
410- decisions : { } ,
514+ getRecords : ( ) => records ,
515+ directorHolder,
516+ wiring : {
517+ getWorkdir : ( ) => workdir ,
518+ getSessionId : ( ) => sessionId ,
519+ } ,
520+ } ,
521+ async ( buildAgent ) => {
522+ await buildAgent ( ) ;
523+ expect ( directorHolder . instance ?. getCompactInstructions ( ) ) . toBe (
524+ "keep the auth discussion" ,
525+ ) ;
526+ records = [ ] ;
527+ workdir = "/session-b" ;
528+ sessionId = "session-b" ;
529+ await buildAgent ( ) ;
530+ expect (
531+ directorHolder . instance ?. getCompactInstructions ( ) ,
532+ ) . toBeUndefined ( ) ;
411533 } ,
534+ ) ;
535+ } ) ;
536+
537+ test ( "cold resume restores extraInstructions beyond 32 later cycles" , async ( ) => {
538+ const records : ManifestRecord [ ] = [
539+ ...Array . from ( { length : 32 } , ( ) => laterManifestRecord ( ) ) ,
540+ pruningExtrasRecord ( "keep the auth discussion" ) ,
412541 ] ;
413- const fakeStorage = {
414- readBlob : async ( ) => new Uint8Array ( ) ,
415- readManifestHistory : async ( ) => records ,
416- } as unknown as ContextStore ;
417- const fakeAgent = { close : async ( ) => undefined } as unknown as Agent ;
418542 const directorHolder : { instance ?: ChatDirector } = { } ;
419543
420- await withMockedModuleDuring (
421- import . meta. resolve ( "./optimized-context-store.js" ) ,
422- ( real : typeof import ( "./optimized-context-store.js" ) ) => ( {
423- ...real ,
424- createSessionStores : async ( ) => ( {
425- storage : fakeStorage ,
426- audit : stubAuditStore ( ) ,
427- } ) ,
428- } ) ,
429- async ( ) => {
430- await withMockedModuleDuring (
431- import . meta. resolve ( "../agent/live-tool-dispatch.js" ) ,
432- ( real : typeof import ( "../agent/live-tool-dispatch.js" ) ) => ( {
433- ...real ,
434- createAgentWithLiveToolDispatch : async (
435- _def : unknown ,
436- env : { directors : DirectorRegistry } ,
437- ) => {
438- env . directors . defaultFactory ( ) ( { } , { } as never , {
439- systemPrompt : "prompt" ,
440- toolDefinitions : [ ] ,
441- compactorNames : [ "pruning-compactor" ] ,
442- } ) ;
443- return fakeAgent ;
444- } ,
445- } ) ,
446- async ( ) => {
447- const { assembleChatAgent } = await import ( "./assemble-runtime.js" ) ;
448- const { buildAgent } = assembleChatAgent (
449- stubChatAgentWiring ( { directorHolder } ) ,
450- ) ;
451- await buildAgent ( ) ;
452- expect ( directorHolder . instance ?. getCompactInstructions ( ) ) . toBe (
453- "keep the auth discussion" ,
454- ) ;
455- records = [ ] ;
456- await buildAgent ( ) ;
457- expect ( directorHolder . instance ?. getCompactInstructions ( ) ) . toBe (
458- "keep the auth discussion" ,
459- ) ;
460- } ,
544+ await withAssembledDirector (
545+ { getRecords : ( ) => records , directorHolder } ,
546+ async ( buildAgent ) => {
547+ await buildAgent ( ) ;
548+ expect ( directorHolder . instance ?. getCompactInstructions ( ) ) . toBe (
549+ "keep the auth discussion" ,
461550 ) ;
462551 } ,
463552 ) ;
0 commit comments