From d58844a29c0a1bb9a005a8b3d3ef02907cb05753 Mon Sep 17 00:00:00 2001 From: H-TTTTT Date: Sat, 11 Jul 2026 14:12:25 +0800 Subject: [PATCH] fix(pinned): restore latest context usage --- src/bot/pinned/pinned-message-manager.ts | 15 +++-- .../bot/pinned/pinned-message-manager.test.ts | 59 ++++++++++++++++++- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/bot/pinned/pinned-message-manager.ts b/src/bot/pinned/pinned-message-manager.ts index d2357fa7..edab55ad 100644 --- a/src/bot/pinned/pinned-message-manager.ts +++ b/src/bot/pinned/pinned-message-manager.ts @@ -174,9 +174,10 @@ class PinnedMessageManager { return; } - // Get the maximum context size and total cost from session history + // Get the latest measured context size and total cost from session history // Context = input + cache.read (cache.read contains previously cached context) - let maxContextSize = 0; + let latestContextSize = 0; + let latestContextCreated = Number.NEGATIVE_INFINITY; let totalCost = 0; logger.debug(`[PinnedManager] Processing ${messagesData.length} messages from history`); @@ -188,6 +189,7 @@ class PinnedMessageManager { input: number; cache?: { read: number }; }; + time?: { created?: number }; cost?: number; }; @@ -206,9 +208,10 @@ class PinnedMessageManager { `[PinnedManager] Assistant message: input=${input}, cache.read=${cacheRead}, total=${contextSize}, cost=$${cost.toFixed(2)}`, ); - // Keep track of maximum context size (peak usage in session) - if (contextSize > maxContextSize) { - maxContextSize = contextSize; + const created = assistantInfo.time?.created ?? 0; + if (contextSize > 0 && created >= latestContextCreated) { + latestContextSize = contextSize; + latestContextCreated = created; } // Accumulate total session cost @@ -216,7 +219,7 @@ class PinnedMessageManager { } }); - this.state.tokensUsed = maxContextSize; + this.state.tokensUsed = latestContextSize; this.state.cost = totalCost; this.state.sessionId = sessionId; diff --git a/tests/bot/pinned/pinned-message-manager.test.ts b/tests/bot/pinned/pinned-message-manager.test.ts index 652cb9b2..8229ba84 100644 --- a/tests/bot/pinned/pinned-message-manager.test.ts +++ b/tests/bot/pinned/pinned-message-manager.test.ts @@ -2,7 +2,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const mocked = vi.hoisted(() => ({ opencodeClient: { - session: { list: vi.fn().mockResolvedValue({ data: [] }) }, + session: { + list: vi.fn().mockResolvedValue({ data: [] }), + messages: vi.fn().mockResolvedValue({ data: [] }), + }, config: { get: vi.fn().mockResolvedValue({ data: {} }) }, }, getCurrentSession: vi.fn(), @@ -83,6 +86,7 @@ describe("pinned/manager", () => { mocked.getStoredModel.mockReturnValue({ providerID: "openai", modelID: "gpt-5" }); mocked.getModelContextLimit.mockResolvedValue(204800); mocked.getPinnedMessageId.mockReturnValue(null); + mocked.opencodeClient.session.messages.mockResolvedValue({ data: [] }); mocked.getGitWorktreeContext.mockResolvedValue({ mainProjectPath: "D:/repo", activeWorktreePath: "D:/repo", @@ -92,6 +96,59 @@ describe("pinned/manager", () => { }); }); + describe("loadContextFromHistory", () => { + it("restores the latest non-summary non-zero context instead of the historical peak", async () => { + await pinnedMessageManager.onSessionChange("ses-1", "Test Session"); + mocked.opencodeClient.session.messages.mockResolvedValue({ + data: [ + { + info: { + role: "assistant", + time: { created: 200 }, + tokens: { input: 300, cache: { read: 100 } }, + cost: 0.5, + }, + parts: [], + }, + { + info: { + role: "assistant", + time: { created: 100 }, + tokens: { input: 900, cache: { read: 100 } }, + cost: 0.25, + }, + parts: [], + }, + { + info: { + role: "assistant", + time: { created: 300 }, + tokens: { input: 0, cache: { read: 0 } }, + cost: 0.1, + }, + parts: [], + }, + { + info: { + role: "assistant", + summary: true, + time: { created: 400 }, + tokens: { input: 1500, cache: { read: 0 } }, + cost: 4, + }, + parts: [], + }, + ], + }); + + await pinnedMessageManager.loadContextFromHistory("ses-1", "D:/repo"); + + const state = pinnedMessageManager.getState(); + expect(state.tokensUsed).toBe(400); + expect(state.cost).toBeCloseTo(0.85); + }); + }); + describe("updateTokensSilent", () => { it("updates tokensUsed in memory without triggering API call", () => { pinnedMessageManager.updateTokensSilent({