@@ -461,17 +461,20 @@ describe("maybeCompactCodexAppServerSession", () => {
461461 expect ( details . codexThreadBindingInvalidated ) . toBe ( true ) ;
462462 expect ( await readCodexAppServerBinding ( sessionFile ) ) . toBeUndefined ( ) ;
463463 expect ( compact ) . toHaveBeenCalledTimes ( 1 ) ;
464- expect ( compact ) . toHaveBeenCalledWith ( {
465- sessionId : "session-1" ,
466- sessionKey : "agent:main:session-1" ,
467- sessionFile,
468- tokenBudget : 777 ,
469- currentTokenCount : 123 ,
470- compactionTarget : "threshold" ,
471- customInstructions : undefined ,
472- force : true ,
473- runtimeContext : { workspaceDir : tempDir , provider : "codex" } ,
474- } ) ;
464+ expect ( compact ) . toHaveBeenCalledWith (
465+ expect . objectContaining ( {
466+ sessionId : "session-1" ,
467+ sessionKey : "agent:main:session-1" ,
468+ sessionFile,
469+ tokenBudget : 777 ,
470+ currentTokenCount : 123 ,
471+ compactionTarget : "threshold" ,
472+ customInstructions : undefined ,
473+ force : true ,
474+ runtimeContext : { workspaceDir : tempDir , provider : "codex" } ,
475+ abortSignal : expect . any ( AbortSignal ) ,
476+ } ) ,
477+ ) ;
475478 expect ( maintain ) . toHaveBeenCalledTimes ( 1 ) ;
476479 const [ maintainCall ] = maintain . mock . calls [ 0 ] ?? [ ] ;
477480 const maintainParams = maintainCall as
@@ -683,6 +686,100 @@ describe("maybeCompactCodexAppServerSession", () => {
683686 expect ( compactResult . reason ) . toBe ( "below threshold" ) ;
684687 expect ( maintain ) . not . toHaveBeenCalled ( ) ;
685688 } ) ;
689+
690+ describe ( "owning context-engine compaction safety timeout" , ( ) => {
691+ afterEach ( ( ) => {
692+ vi . useRealTimers ( ) ;
693+ } ) ;
694+
695+ it ( "bounds a hung owning context-engine compact() and reports a clean ok:false" , async ( ) => {
696+ const sessionFile = await writeTestBinding ( ) ;
697+ const compact = vi . fn < ContextEngine [ "compact" ] > ( ( ) => new Promise ( ( ) => { } ) ) ;
698+ const contextEngine : ContextEngine = {
699+ info : { id : "lossless-claw" , name : "Lossless Claw" , ownsCompaction : true } ,
700+ assemble : vi . fn ( ) as never ,
701+ ingest : vi . fn ( ) as never ,
702+ compact,
703+ } ;
704+
705+ vi . useFakeTimers ( ) ;
706+ const pendingResult = maybeCompactCodexAppServerSession ( {
707+ sessionId : "session-1" ,
708+ sessionKey : "agent:main:session-1" ,
709+ sessionFile,
710+ workspaceDir : tempDir ,
711+ contextEngine,
712+ // 1 s host-resolved compaction timeout.
713+ config : { agents : { defaults : { compaction : { timeoutSeconds : 1 } } } } ,
714+ } ) ;
715+
716+ await vi . advanceTimersByTimeAsync ( 1_000 ) ;
717+ const result = requireCompactResult ( await pendingResult ) ;
718+
719+ expect ( result . ok ) . toBe ( false ) ;
720+ expect ( result . compacted ) . toBe ( false ) ;
721+ expect ( result . reason ) . toContain ( "timed out" ) ;
722+ expect ( compact ) . toHaveBeenCalledTimes ( 1 ) ;
723+ expect ( vi . getTimerCount ( ) ) . toBe ( 0 ) ;
724+ } ) ;
725+
726+ it ( "threads a composed caller abort signal into the owning context-engine compact()" , async ( ) => {
727+ const sessionFile = await writeTestBinding ( ) ;
728+ const controller = new AbortController ( ) ;
729+ const compact = vi . fn < ContextEngine [ "compact" ] > ( async ( ) => ( {
730+ ok : true ,
731+ compacted : false ,
732+ reason : "below threshold" ,
733+ } ) ) ;
734+ const contextEngine : ContextEngine = {
735+ info : { id : "lossless-claw" , name : "Lossless Claw" , ownsCompaction : true } ,
736+ assemble : vi . fn ( ) as never ,
737+ ingest : vi . fn ( ) as never ,
738+ compact,
739+ } ;
740+
741+ await maybeCompactCodexAppServerSession ( {
742+ sessionId : "session-1" ,
743+ sessionKey : "agent:main:session-1" ,
744+ sessionFile,
745+ workspaceDir : tempDir ,
746+ contextEngine,
747+ abortSignal : controller . signal ,
748+ } ) ;
749+
750+ expect ( compact ) . toHaveBeenCalledTimes ( 1 ) ;
751+ expect ( compact . mock . calls [ 0 ] ?. [ 0 ] ?. abortSignal ) . toBeInstanceOf ( AbortSignal ) ;
752+ } ) ;
753+
754+ it ( "aborts a hung owning context-engine compact() when the caller signal fires" , async ( ) => {
755+ const sessionFile = await writeTestBinding ( ) ;
756+ const controller = new AbortController ( ) ;
757+ const compact = vi . fn < ContextEngine [ "compact" ] > ( ( ) => new Promise ( ( ) => { } ) ) ;
758+ const contextEngine : ContextEngine = {
759+ info : { id : "lossless-claw" , name : "Lossless Claw" , ownsCompaction : true } ,
760+ assemble : vi . fn ( ) as never ,
761+ ingest : vi . fn ( ) as never ,
762+ compact,
763+ } ;
764+
765+ const pendingResult = maybeCompactCodexAppServerSession ( {
766+ sessionId : "session-1" ,
767+ sessionKey : "agent:main:session-1" ,
768+ sessionFile,
769+ workspaceDir : tempDir ,
770+ contextEngine,
771+ abortSignal : controller . signal ,
772+ } ) ;
773+
774+ controller . abort ( new Error ( "run aborted" ) ) ;
775+ const result = requireCompactResult ( await pendingResult ) ;
776+
777+ expect ( result . ok ) . toBe ( false ) ;
778+ expect ( result . compacted ) . toBe ( false ) ;
779+ expect ( result . reason ) . toContain ( "run aborted" ) ;
780+ expect ( compact ) . toHaveBeenCalledTimes ( 1 ) ;
781+ } ) ;
782+ } ) ;
686783} ) ;
687784
688785function createFakeCodexClient ( ) : {
0 commit comments