From 115f79a82ee6dc50fae66257f78b6664fa391ecc Mon Sep 17 00:00:00 2001 From: PierrunoYT Date: Mon, 28 Sep 2026 20:16:15 +0200 Subject: [PATCH] fix(condense): summarize effective history, excluding truncated messages summarizeConversation selected its input with getMessagesSinceLastSummary, which ignores truncationParent. Messages already hidden by sliding-window truncation were sent to the condensing request, which could exceed the model context window even though normal requests still fit. Use getEffectiveApiHistory, the same selection normal requests use. The stored history is unchanged; all messages are still tagged with condenseParent. Fixes #1823 Co-Authored-By: Claude Sonnet 5.5 --- src/core/condense/__tests__/condense.spec.ts | 41 ++++++++++++++++++++ src/core/condense/index.ts | 6 ++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/core/condense/__tests__/condense.spec.ts b/src/core/condense/__tests__/condense.spec.ts index c209fa9724..45a725d29f 100644 --- a/src/core/condense/__tests__/condense.spec.ts +++ b/src/core/condense/__tests__/condense.spec.ts @@ -186,6 +186,47 @@ Line 2 } }) + it("should not send messages hidden by sliding-window truncation to the summarizer", async () => { + const truncationId = "trunc-1" + const messages: ApiMessage[] = [ + { role: "user", content: "Hidden first", truncationParent: truncationId }, + { role: "assistant", content: "Hidden second", truncationParent: truncationId }, + { role: "assistant", content: "Truncation marker", isTruncationMarker: true, truncationId }, + { role: "user", content: "Visible third" }, + { role: "assistant", content: "Visible fourth" }, + { role: "user", content: "Visible fifth" }, + ] + const original = structuredClone(messages) + const sentMessages: unknown[] = [] + const createMessageSpy = vi + .spyOn(mockApiHandler, "createMessage") + .mockImplementation((...args: unknown[]) => { + sentMessages.push(args[1]) + return (async function* () { + yield { type: "text", text: "Mock summary" } + })() + }) + + const result = await summarizeConversation({ + messages, + apiHandler: mockApiHandler, + systemPrompt: "System prompt", + taskId, + isAutomaticTrigger: false, + }) + + const sentText = JSON.stringify(sentMessages[0]) + expect(sentText).not.toContain("Hidden first") + expect(sentText).not.toContain("Hidden second") + expect(sentText).toContain("Visible third") + + // Complete stored history is preserved (non-destructive) and the input is not mutated + expect(result.messages.filter((msg) => !msg.isSummary)).toHaveLength(messages.length) + expect(messages).toEqual(original) + + createMessageSpy.mockRestore() + }) + it("should preserve blocks in the summary", async () => { const messages: ApiMessage[] = [ { diff --git a/src/core/condense/index.ts b/src/core/condense/index.ts index 0438bf6bcb..920f047d44 100644 --- a/src/core/condense/index.ts +++ b/src/core/condense/index.ts @@ -275,8 +275,10 @@ export async function summarizeConversation(options: SummarizeConversationOption const response: SummarizeResponse = { messages, cost: 0, summary: "" } - // Get messages to summarize (all messages since the last summary, if any) - const messagesToSummarize = getMessagesSinceLastSummary(messages) + // Summarize the effective history (what normal requests send): excludes messages already hidden + // by sliding-window truncation or a previous condense, and starts at the last summary if any. + // The stored history in `messages` is left intact and tagged below. + const messagesToSummarize = getEffectiveApiHistory(messages) if (messagesToSummarize.length <= 1) { const error =