@@ -502,6 +502,83 @@ describe("runCliTurnCompactionLifecycle", () => {
502502 expect ( compactCalls ) . toHaveLength ( 0 ) ;
503503 } ) ;
504504
505+ it ( "does not throw when the native harness owns automatic compaction (ok:true no-op)" , async ( ) => {
506+ // Regression: Codex app-server returns a successful no-op (ok:true,
507+ // compacted:false) for budget-triggered compaction because it owns
508+ // automatic compaction. The lifecycle must treat that as benign and let the
509+ // turn proceed, not throw "CLI native harness compaction failed" — throwing
510+ // fails the agent run and jams the session lane (endless Discord "typing").
511+ const sessionKey = "agent:main:codex-owned-skip" ;
512+ const sessionId = "session-codex-owned-skip" ;
513+ const sessionFile = path . join ( tmpDir , "session-codex-owned-skip.jsonl" ) ;
514+ const storePath = path . join ( tmpDir , "sessions-codex-owned-skip.json" ) ;
515+ await writeSessionFile ( { sessionFile, sessionId } ) ;
516+
517+ const sessionEntry : SessionEntry = {
518+ sessionId,
519+ updatedAt : Date . now ( ) ,
520+ sessionFile,
521+ contextTokens : 1_000 ,
522+ totalTokens : 950 ,
523+ totalTokensFresh : true ,
524+ agentHarnessId : "codex" ,
525+ } ;
526+ const sessionStore : Record < string , SessionEntry > = { [ sessionKey ] : sessionEntry } ;
527+ await fs . writeFile ( storePath , JSON . stringify ( sessionStore , null , 2 ) , "utf-8" ) ;
528+
529+ const compactCalls : Array < Parameters < ContextEngine [ "compact" ] > [ 0 ] > = [ ] ;
530+ const compactAgentHarnessSession = vi . fn ( async ( ) => ( {
531+ ok : true ,
532+ compacted : false ,
533+ reason : "codex app-server owns automatic compaction" ,
534+ } ) ) ;
535+ const recordCliCompactionInStore = vi . fn ( async ( ) => ( {
536+ ...sessionEntry ,
537+ compactionCount : 1 ,
538+ } ) ) ;
539+ setCliCompactionTestDeps ( {
540+ resolveContextEngine : async ( ) => buildContextEngine ( { compactCalls } ) ,
541+ ensureSelectedAgentHarnessPlugin : vi . fn ( async ( ) => undefined ) ,
542+ maybeCompactAgentHarnessSession : compactAgentHarnessSession as never ,
543+ createPreparedEmbeddedAgentSettingsManager : async ( ) => ( {
544+ getCompactionReserveTokens : ( ) => 200 ,
545+ getCompactionKeepRecentTokens : ( ) => 0 ,
546+ applyOverrides : ( ) => { } ,
547+ } ) ,
548+ shouldPreemptivelyCompactBeforePrompt : ( ) => ( {
549+ route : "fits" ,
550+ shouldCompact : false ,
551+ estimatedPromptTokens : 600 ,
552+ promptBudgetBeforeReserve : 800 ,
553+ overflowTokens : 0 ,
554+ toolResultReducibleChars : 0 ,
555+ effectiveReserveTokens : 200 ,
556+ } ) ,
557+ resolveLiveToolResultMaxChars : ( ) => 20_000 ,
558+ recordCliCompactionInStore,
559+ } ) ;
560+
561+ const updatedEntry = await runCliTurnCompactionLifecycle ( {
562+ cfg : { } as OpenClawConfig ,
563+ sessionId,
564+ sessionKey,
565+ sessionEntry,
566+ sessionStore,
567+ storePath,
568+ sessionAgentId : "main" ,
569+ workspaceDir : tmpDir ,
570+ agentDir : tmpDir ,
571+ provider : "codex" ,
572+ model : "gpt-5.5" ,
573+ } ) ;
574+
575+ expect ( compactAgentHarnessSession ) . toHaveBeenCalledTimes ( 1 ) ;
576+ // No context-engine fallback, no store rotation: the harness self-compacts.
577+ expect ( compactCalls ) . toHaveLength ( 0 ) ;
578+ expect ( recordCliCompactionInStore ) . not . toHaveBeenCalled ( ) ;
579+ expect ( updatedEntry ) . toBe ( sessionEntry ) ;
580+ } ) ;
581+
505582 it ( "passes owning context engines into native harness CLI compaction" , async ( ) => {
506583 const sessionKey = "agent:main:codex-owned-engine" ;
507584 const sessionId = "session-codex-owned-engine" ;
0 commit comments