

























@@ -443,6 +443,8 @@ describe("initSessionState thread forking", () => {
443443expect(result.sessionKey).toBe(threadSessionKey);
444444expect(result.sessionEntry.sessionId).not.toBe(parentSessionId);
445445expect(result.sessionEntry.displayName).toBe(threadLabel);
446+expect(result.sessionEntry.totalTokens).toBeUndefined();
447+expect(result.sessionEntry.totalTokensFresh).toBe(false);
446448447449const newSessionFile = requireString(
448450result.sessionEntry.sessionFile,
@@ -512,6 +514,8 @@ describe("initSessionState thread forking", () => {
512514[threadSessionKey]: {
513515sessionId: "preseed-thread-session",
514516updatedAt: Date.now(),
517+totalTokens: 0,
518+totalTokensFresh: true,
515519},
516520});
517521@@ -531,6 +535,8 @@ describe("initSessionState thread forking", () => {
531535532536expect(first.sessionEntry.sessionId).not.toBe("preseed-thread-session");
533537expect(first.sessionEntry.forkedFromParent).toBe(true);
538+expect(first.sessionEntry.totalTokens).toBeUndefined();
539+expect(first.sessionEntry.totalTokensFresh).toBe(false);
534540535541const second = await initSessionState({
536542ctx: {
@@ -544,6 +550,8 @@ describe("initSessionState thread forking", () => {
544550545551expect(second.sessionEntry.sessionId).toBe(first.sessionEntry.sessionId);
546552expect(second.sessionEntry.forkedFromParent).toBe(true);
553+expect(second.sessionEntry.totalTokens).toBeUndefined();
554+expect(second.sessionEntry.totalTokensFresh).toBe(false);
547555warn.mockRestore();
548556});
549557@@ -614,6 +622,8 @@ describe("initSessionState thread forking", () => {
614622expect(result.sessionEntry.sessionId).not.toBe(parentSessionId);
615623// Session file should NOT be the parent's file (it was not forked)
616624expect(result.sessionEntry.sessionFile).not.toBe(parentSessionFile);
625+expect(result.sessionEntry.totalTokens).toBe(0);
626+expect(result.sessionEntry.totalTokensFresh).toBe(true);
617627});
618628619629it("skips fork when resolved parent token estimate exceeds threshold", async () => {
@@ -848,7 +858,8 @@ describe("initSessionState RawBody", () => {
848858expect(result.resetTriggered).toBe(true);
849859expect(result.sessionId).not.toBe(existingSessionId);
850860expect(result.sessionEntry.skillsSnapshot).toBeUndefined();
851-expect(result.sessionEntry.totalTokens).toBeUndefined();
861+expect(result.sessionEntry.totalTokens).toBe(0);
862+expect(result.sessionEntry.totalTokensFresh).toBe(true);
852863expect(result.sessionEntry.contextTokens).toBeUndefined();
853864expect(result.sessionEntry.contextBudgetStatus).toBeUndefined();
854865@@ -857,12 +868,14 @@ describe("initSessionState RawBody", () => {
857868{
858869skillsSnapshot?: unknown;
859870totalTokens?: number;
871+totalTokensFresh?: boolean;
860872contextTokens?: number;
861873contextBudgetStatus?: unknown;
862874}
863875>;
864876expect(store[sessionKey]?.skillsSnapshot).toBeUndefined();
865-expect(store[sessionKey]?.totalTokens).toBeUndefined();
877+expect(store[sessionKey]?.totalTokens).toBe(0);
878+expect(store[sessionKey]?.totalTokensFresh).toBe(true);
866879expect(store[sessionKey]?.contextTokens).toBeUndefined();
867880expect(store[sessionKey]?.contextBudgetStatus).toBeUndefined();
868881});
@@ -3047,9 +3060,9 @@ describe("initSessionState preserves behavior overrides across /new and /reset",
30473060expect(result.sessionEntry.modelOverrideSource).toBeUndefined();
30483061expect(result.sessionEntry.modelOverrideFallbackOriginProvider).toBeUndefined();
30493062expect(result.sessionEntry.modelOverrideFallbackOriginModel).toBeUndefined();
3050-expect(result.sessionEntry.totalTokens).toBeUndefined();
3063+expect(result.sessionEntry.totalTokens).toBe(0);
30513064expect(result.sessionEntry.contextTokens).toBeUndefined();
3052-expect(result.sessionEntry.totalTokensFresh).toBeUndefined();
3065+expect(result.sessionEntry.totalTokensFresh).toBe(true);
30533066});
3054306730553068it("preserves spawned session ownership metadata across /new and /reset", async () => {
@@ -3635,6 +3648,59 @@ describe("persistSessionUsageUpdate", () => {
36353648expect(stored[sessionKey].outputTokens).toBe(10_000);
36363649});
363736503651+it("marks a fresh zero stale when a completed run has no context snapshot", async () => {
3652+const storePath = await createStorePath("openclaw-usage-no-snapshot-");
3653+const sessionKey = "main";
3654+await seedSessionStore({
3655+ storePath,
3656+ sessionKey,
3657+entry: {
3658+sessionId: "s1",
3659+updatedAt: Date.now(),
3660+totalTokens: 0,
3661+totalTokensFresh: true,
3662+},
3663+});
3664+3665+await persistSessionUsageUpdate({
3666+ storePath,
3667+ sessionKey,
3668+modelUsed: "claude-sonnet-4-6",
3669+contextTokensUsed: 200_000,
3670+});
3671+3672+const stored = JSON.parse(await fs.readFile(storePath, "utf-8"));
3673+expect(stored[sessionKey].totalTokens).toBe(0);
3674+expect(stored[sessionKey].totalTokensFresh).toBe(false);
3675+});
3676+3677+it("preserves fresh post-compaction totalTokens across model-only updates", async () => {
3678+const storePath = await createStorePath("openclaw-usage-no-snapshot-");
3679+const sessionKey = "main";
3680+await seedSessionStore({
3681+ storePath,
3682+ sessionKey,
3683+entry: {
3684+sessionId: "s1",
3685+updatedAt: Date.now(),
3686+totalTokens: 42_000,
3687+totalTokensFresh: true,
3688+},
3689+});
3690+3691+await persistSessionUsageUpdate({
3692+ storePath,
3693+ sessionKey,
3694+modelUsed: "claude-sonnet-4-6",
3695+contextTokensUsed: 200_000,
3696+preserveFreshTotalTokensOnStaleUsage: true,
3697+});
3698+3699+const stored = JSON.parse(await fs.readFile(storePath, "utf-8"));
3700+expect(stored[sessionKey].totalTokens).toBe(42_000);
3701+expect(stored[sessionKey].totalTokensFresh).toBe(true);
3702+});
3703+36383704it("accounts exhausted-run usage without committing its model", async () => {
36393705const storePath = await createStorePath("openclaw-usage-exhausted-");
36403706const sessionKey = "main";
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。