@@ -636,6 +636,140 @@ describe("updateSessionStoreAfterAgentRun", () => {
636636 } ) ;
637637 } ) ;
638638
639+ it ( "persists estimated context budget status without marking stale usage fresh" , async ( ) => {
640+ await withTempSessionStore ( async ( { storePath } ) => {
641+ const cfg = { } as OpenClawConfig ;
642+ const sessionKey = "agent:main:explicit:test-context-budget-status" ;
643+ const sessionId = "test-context-budget-status-session" ;
644+ const sessionStore : Record < string , SessionEntry > = {
645+ [ sessionKey ] : {
646+ sessionId,
647+ updatedAt : 1 ,
648+ totalTokens : 21225 ,
649+ totalTokensFresh : true ,
650+ } ,
651+ } ;
652+ await fs . writeFile ( storePath , JSON . stringify ( sessionStore , null , 2 ) ) ;
653+
654+ const result : EmbeddedPiRunResult = {
655+ meta : {
656+ durationMs : 500 ,
657+ agentMeta : {
658+ sessionId,
659+ provider : "minimax" ,
660+ model : "MiniMax-M2.7" ,
661+ contextBudgetStatus : {
662+ schemaVersion : 1 ,
663+ source : "pre-prompt-estimate" ,
664+ updatedAt : 123 ,
665+ provider : "minimax" ,
666+ model : "MiniMax-M2.7" ,
667+ route : "fits" ,
668+ shouldCompact : false ,
669+ estimatedPromptTokens : 18_000 ,
670+ contextTokenBudget : 32_000 ,
671+ promptBudgetBeforeReserve : 28_000 ,
672+ reserveTokens : 4_000 ,
673+ effectiveReserveTokens : 4_000 ,
674+ remainingPromptBudgetTokens : 10_000 ,
675+ overflowTokens : 0 ,
676+ toolResultReducibleChars : 0 ,
677+ messageCount : 4 ,
678+ unwindowedMessageCount : 4 ,
679+ } ,
680+ } ,
681+ } ,
682+ } ;
683+
684+ await updateSessionStoreAfterAgentRun ( {
685+ cfg,
686+ sessionId,
687+ sessionKey,
688+ storePath,
689+ sessionStore,
690+ defaultProvider : "minimax" ,
691+ defaultModel : "MiniMax-M2.7" ,
692+ result,
693+ } ) ;
694+
695+ expect ( sessionStore [ sessionKey ] ?. totalTokens ) . toBe ( 21225 ) ;
696+ expect ( sessionStore [ sessionKey ] ?. totalTokensFresh ) . toBe ( false ) ;
697+ expect ( sessionStore [ sessionKey ] ?. contextBudgetStatus ) . toMatchObject ( {
698+ source : "pre-prompt-estimate" ,
699+ estimatedPromptTokens : 18_000 ,
700+ contextTokenBudget : 32_000 ,
701+ } ) ;
702+
703+ const persisted = loadSessionStore ( storePath ) ;
704+ expect ( persisted [ sessionKey ] ?. contextBudgetStatus ?. estimatedPromptTokens ) . toBe ( 18_000 ) ;
705+ } ) ;
706+ } ) ;
707+
708+ it ( "clears stale estimated context budget status when a runtime refresh has no current estimate" , async ( ) => {
709+ await withTempSessionStore ( async ( { storePath } ) => {
710+ const cfg = { } as OpenClawConfig ;
711+ const sessionKey = "agent:main:explicit:test-clear-context-budget-status" ;
712+ const sessionId = "test-clear-context-budget-status-session" ;
713+ const sessionStore : Record < string , SessionEntry > = {
714+ [ sessionKey ] : {
715+ sessionId,
716+ updatedAt : 1 ,
717+ totalTokens : 21225 ,
718+ totalTokensFresh : false ,
719+ contextBudgetStatus : {
720+ schemaVersion : 1 ,
721+ source : "pre-prompt-estimate" ,
722+ updatedAt : 123 ,
723+ provider : "anthropic" ,
724+ model : "claude-sonnet-4.6" ,
725+ route : "fits" ,
726+ shouldCompact : false ,
727+ estimatedPromptTokens : 18_000 ,
728+ contextTokenBudget : 32_000 ,
729+ promptBudgetBeforeReserve : 28_000 ,
730+ reserveTokens : 4_000 ,
731+ effectiveReserveTokens : 4_000 ,
732+ remainingPromptBudgetTokens : 10_000 ,
733+ overflowTokens : 0 ,
734+ toolResultReducibleChars : 0 ,
735+ messageCount : 4 ,
736+ unwindowedMessageCount : 4 ,
737+ } ,
738+ } ,
739+ } ;
740+ await fs . writeFile ( storePath , JSON . stringify ( sessionStore , null , 2 ) ) ;
741+
742+ const result : EmbeddedPiRunResult = {
743+ meta : {
744+ durationMs : 500 ,
745+ agentMeta : {
746+ sessionId,
747+ provider : "minimax" ,
748+ model : "MiniMax-M2.7" ,
749+ } ,
750+ } ,
751+ } ;
752+
753+ await updateSessionStoreAfterAgentRun ( {
754+ cfg,
755+ sessionId,
756+ sessionKey,
757+ storePath,
758+ sessionStore,
759+ defaultProvider : "minimax" ,
760+ defaultModel : "MiniMax-M2.7" ,
761+ result,
762+ } ) ;
763+
764+ expect ( sessionStore [ sessionKey ] ?. modelProvider ) . toBe ( "minimax" ) ;
765+ expect ( sessionStore [ sessionKey ] ?. model ) . toBe ( "MiniMax-M2.7" ) ;
766+ expect ( sessionStore [ sessionKey ] ?. contextBudgetStatus ) . toBeUndefined ( ) ;
767+
768+ const persisted = loadSessionStore ( storePath ) ;
769+ expect ( persisted [ sessionKey ] ?. contextBudgetStatus ) . toBeUndefined ( ) ;
770+ } ) ;
771+ } ) ;
772+
639773 it ( "does not treat CLI cumulative usage as a fresh context snapshot" , async ( ) => {
640774 await withTempSessionStore ( async ( { storePath } ) => {
641775 const cfg = {
@@ -1067,6 +1201,25 @@ describe("updateSessionStoreAfterAgentRun", () => {
10671201 modelProvider : "anthropic" ,
10681202 model : "claude-opus-4-6" ,
10691203 contextTokens : 1_000_000 ,
1204+ contextBudgetStatus : {
1205+ schemaVersion : 1 ,
1206+ source : "pre-prompt-estimate" ,
1207+ updatedAt : 100 ,
1208+ provider : "anthropic" ,
1209+ model : "claude-opus-4-6" ,
1210+ route : "fits" ,
1211+ shouldCompact : false ,
1212+ estimatedPromptTokens : 640_000 ,
1213+ contextTokenBudget : 1_000_000 ,
1214+ promptBudgetBeforeReserve : 900_000 ,
1215+ reserveTokens : 100_000 ,
1216+ effectiveReserveTokens : 100_000 ,
1217+ remainingPromptBudgetTokens : 260_000 ,
1218+ overflowTokens : 0 ,
1219+ toolResultReducibleChars : 0 ,
1220+ messageCount : 12 ,
1221+ unwindowedMessageCount : 12 ,
1222+ } ,
10701223 } ,
10711224 } ;
10721225 await fs . writeFile ( storePath , JSON . stringify ( sessionStore , null , 2 ) ) ;
@@ -1080,6 +1233,25 @@ describe("updateSessionStoreAfterAgentRun", () => {
10801233 provider : "ollama" ,
10811234 model : "llama3.2:1b" ,
10821235 contextTokens : 128_000 ,
1236+ contextBudgetStatus : {
1237+ schemaVersion : 1 ,
1238+ source : "pre-prompt-estimate" ,
1239+ updatedAt : 200 ,
1240+ provider : "ollama" ,
1241+ model : "llama3.2:1b" ,
1242+ route : "fits" ,
1243+ shouldCompact : false ,
1244+ estimatedPromptTokens : 40_000 ,
1245+ contextTokenBudget : 128_000 ,
1246+ promptBudgetBeforeReserve : 112_000 ,
1247+ reserveTokens : 16_000 ,
1248+ effectiveReserveTokens : 16_000 ,
1249+ remainingPromptBudgetTokens : 72_000 ,
1250+ overflowTokens : 0 ,
1251+ toolResultReducibleChars : 0 ,
1252+ messageCount : 3 ,
1253+ unwindowedMessageCount : 3 ,
1254+ } ,
10831255 } ,
10841256 } ,
10851257 } ;
@@ -1100,11 +1272,15 @@ describe("updateSessionStoreAfterAgentRun", () => {
11001272 expect ( sessionStore [ sessionKey ] ?. model ) . toBe ( "claude-opus-4-6" ) ;
11011273 expect ( sessionStore [ sessionKey ] ?. modelProvider ) . toBe ( "anthropic" ) ;
11021274 expect ( sessionStore [ sessionKey ] ?. contextTokens ) . toBe ( 1_000_000 ) ;
1275+ expect ( sessionStore [ sessionKey ] ?. contextBudgetStatus ?. provider ) . toBe ( "anthropic" ) ;
1276+ expect ( sessionStore [ sessionKey ] ?. contextBudgetStatus ?. estimatedPromptTokens ) . toBe ( 640_000 ) ;
11031277
11041278 const persisted = loadSessionStore ( storePath ) ;
11051279 expect ( persisted [ sessionKey ] ?. model ) . toBe ( "claude-opus-4-6" ) ;
11061280 expect ( persisted [ sessionKey ] ?. modelProvider ) . toBe ( "anthropic" ) ;
11071281 expect ( persisted [ sessionKey ] ?. contextTokens ) . toBe ( 1_000_000 ) ;
1282+ expect ( persisted [ sessionKey ] ?. contextBudgetStatus ?. provider ) . toBe ( "anthropic" ) ;
1283+ expect ( persisted [ sessionKey ] ?. contextBudgetStatus ?. estimatedPromptTokens ) . toBe ( 640_000 ) ;
11081284 } ) ;
11091285 } ) ;
11101286
@@ -1313,6 +1489,25 @@ describe("recordCliCompactionInStore", () => {
13131489 outputTokens : 100 ,
13141490 cacheRead : 2_900 ,
13151491 cacheWrite : 0 ,
1492+ contextBudgetStatus : {
1493+ schemaVersion : 1 ,
1494+ source : "pre-prompt-estimate" ,
1495+ updatedAt : 123 ,
1496+ provider : "codex" ,
1497+ model : "gpt-5.5" ,
1498+ route : "fits" ,
1499+ shouldCompact : false ,
1500+ estimatedPromptTokens : 18_000 ,
1501+ contextTokenBudget : 32_000 ,
1502+ promptBudgetBeforeReserve : 28_000 ,
1503+ reserveTokens : 4_000 ,
1504+ effectiveReserveTokens : 4_000 ,
1505+ remainingPromptBudgetTokens : 10_000 ,
1506+ overflowTokens : 0 ,
1507+ toolResultReducibleChars : 0 ,
1508+ messageCount : 4 ,
1509+ unwindowedMessageCount : 4 ,
1510+ } ,
13161511 cliSessionBindings : {
13171512 codex : {
13181513 sessionId : "stale-cli-session" ,
@@ -1341,10 +1536,12 @@ describe("recordCliCompactionInStore", () => {
13411536 expect ( sessionStore [ sessionKey ] ?. outputTokens ) . toBeUndefined ( ) ;
13421537 expect ( sessionStore [ sessionKey ] ?. cacheRead ) . toBeUndefined ( ) ;
13431538 expect ( sessionStore [ sessionKey ] ?. cacheWrite ) . toBeUndefined ( ) ;
1539+ expect ( sessionStore [ sessionKey ] ?. contextBudgetStatus ) . toBeUndefined ( ) ;
13441540 expect ( sessionStore [ sessionKey ] ?. cliSessionBindings ?. codex ) . toBeUndefined ( ) ;
13451541 expect ( sessionStore [ sessionKey ] ?. cliSessionIds ?. codex ) . toBeUndefined ( ) ;
13461542 expect ( persisted [ sessionKey ] ?. totalTokens ) . toBe ( 0 ) ;
13471543 expect ( persisted [ sessionKey ] ?. totalTokensFresh ) . toBe ( true ) ;
1544+ expect ( persisted [ sessionKey ] ?. contextBudgetStatus ) . toBeUndefined ( ) ;
13481545 } ) ;
13491546 } ) ;
13501547
@@ -1362,6 +1559,25 @@ describe("recordCliCompactionInStore", () => {
13621559 outputTokens : 100 ,
13631560 cacheRead : 6_900 ,
13641561 cacheWrite : 0 ,
1562+ contextBudgetStatus : {
1563+ schemaVersion : 1 ,
1564+ source : "pre-prompt-estimate" ,
1565+ updatedAt : 123 ,
1566+ provider : "codex" ,
1567+ model : "gpt-5.5" ,
1568+ route : "compact_only" ,
1569+ shouldCompact : true ,
1570+ estimatedPromptTokens : 48_000 ,
1571+ contextTokenBudget : 32_000 ,
1572+ promptBudgetBeforeReserve : 28_000 ,
1573+ reserveTokens : 4_000 ,
1574+ effectiveReserveTokens : 4_000 ,
1575+ remainingPromptBudgetTokens : 0 ,
1576+ overflowTokens : 20_000 ,
1577+ toolResultReducibleChars : 0 ,
1578+ messageCount : 40 ,
1579+ unwindowedMessageCount : 40 ,
1580+ } ,
13651581 } ,
13661582 } ;
13671583 await fs . writeFile ( storePath , JSON . stringify ( sessionStore , null , 2 ) ) ;
@@ -1381,8 +1597,10 @@ describe("recordCliCompactionInStore", () => {
13811597 expect ( sessionStore [ sessionKey ] ?. outputTokens ) . toBeUndefined ( ) ;
13821598 expect ( sessionStore [ sessionKey ] ?. cacheRead ) . toBeUndefined ( ) ;
13831599 expect ( sessionStore [ sessionKey ] ?. cacheWrite ) . toBeUndefined ( ) ;
1600+ expect ( sessionStore [ sessionKey ] ?. contextBudgetStatus ) . toBeUndefined ( ) ;
13841601 expect ( persisted [ sessionKey ] ?. totalTokens ) . toBe ( 37_000 ) ;
13851602 expect ( persisted [ sessionKey ] ?. totalTokensFresh ) . toBe ( false ) ;
1603+ expect ( persisted [ sessionKey ] ?. contextBudgetStatus ) . toBeUndefined ( ) ;
13861604 } ) ;
13871605 } ) ;
13881606
0 commit comments