












@@ -636,6 +636,140 @@ describe("updateSessionStoreAfterAgentRun", () => {
636636});
637637});
638638639+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+639773it("does not treat CLI cumulative usage as a fresh context snapshot", async () => {
640774await withTempSessionStore(async ({ storePath }) => {
641775const cfg = {
@@ -1067,6 +1201,25 @@ describe("updateSessionStoreAfterAgentRun", () => {
10671201modelProvider: "anthropic",
10681202model: "claude-opus-4-6",
10691203contextTokens: 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};
10721225await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2));
@@ -1080,6 +1233,25 @@ describe("updateSessionStoreAfterAgentRun", () => {
10801233provider: "ollama",
10811234model: "llama3.2:1b",
10821235contextTokens: 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", () => {
11001272expect(sessionStore[sessionKey]?.model).toBe("claude-opus-4-6");
11011273expect(sessionStore[sessionKey]?.modelProvider).toBe("anthropic");
11021274expect(sessionStore[sessionKey]?.contextTokens).toBe(1_000_000);
1275+expect(sessionStore[sessionKey]?.contextBudgetStatus?.provider).toBe("anthropic");
1276+expect(sessionStore[sessionKey]?.contextBudgetStatus?.estimatedPromptTokens).toBe(640_000);
1103127711041278const persisted = loadSessionStore(storePath);
11051279expect(persisted[sessionKey]?.model).toBe("claude-opus-4-6");
11061280expect(persisted[sessionKey]?.modelProvider).toBe("anthropic");
11071281expect(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", () => {
13131489outputTokens: 100,
13141490cacheRead: 2_900,
13151491cacheWrite: 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+},
13161511cliSessionBindings: {
13171512codex: {
13181513sessionId: "stale-cli-session",
@@ -1341,10 +1536,12 @@ describe("recordCliCompactionInStore", () => {
13411536expect(sessionStore[sessionKey]?.outputTokens).toBeUndefined();
13421537expect(sessionStore[sessionKey]?.cacheRead).toBeUndefined();
13431538expect(sessionStore[sessionKey]?.cacheWrite).toBeUndefined();
1539+expect(sessionStore[sessionKey]?.contextBudgetStatus).toBeUndefined();
13441540expect(sessionStore[sessionKey]?.cliSessionBindings?.codex).toBeUndefined();
13451541expect(sessionStore[sessionKey]?.cliSessionIds?.codex).toBeUndefined();
13461542expect(persisted[sessionKey]?.totalTokens).toBe(0);
13471543expect(persisted[sessionKey]?.totalTokensFresh).toBe(true);
1544+expect(persisted[sessionKey]?.contextBudgetStatus).toBeUndefined();
13481545});
13491546});
13501547@@ -1362,6 +1559,25 @@ describe("recordCliCompactionInStore", () => {
13621559outputTokens: 100,
13631560cacheRead: 6_900,
13641561cacheWrite: 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};
13671583await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2));
@@ -1381,8 +1597,10 @@ describe("recordCliCompactionInStore", () => {
13811597expect(sessionStore[sessionKey]?.outputTokens).toBeUndefined();
13821598expect(sessionStore[sessionKey]?.cacheRead).toBeUndefined();
13831599expect(sessionStore[sessionKey]?.cacheWrite).toBeUndefined();
1600+expect(sessionStore[sessionKey]?.contextBudgetStatus).toBeUndefined();
13841601expect(persisted[sessionKey]?.totalTokens).toBe(37_000);
13851602expect(persisted[sessionKey]?.totalTokensFresh).toBe(false);
1603+expect(persisted[sessionKey]?.contextBudgetStatus).toBeUndefined();
13861604});
13871605});
13881606此內容由慣性聚合(RSS閱讀器)自動聚合整理,僅供閱讀參考。 原文來自 — 版權歸原作者所有。