fix: make /compress handle tool-heavy conversations correctly#2659
fix: make /compress handle tool-heavy conversations correctly#2659tanzhenxin merged 2 commits intomainfrom
Conversation
…ions When conversation history is near the context window limit and dominated by tool call/response cycles, findCompressSplitPoint would return a near-zero split point because it only considered non-functionResponse user messages as valid split points. This caused /compress to send almost no history to the compression API (e.g. 29 tokens), producing a useless summary that inflated token count instead of reducing it. Changes: - Track tool completion boundaries (positions after functionResponse) as fallback split points in findCompressSplitPoint - Add user-with-functionResponse to the compress-everything safety check - Use Math.max of primary and fallback split points for better coverage - Add minimum content guard (5% threshold) to prevent futile API calls - Add 4 new test cases covering tool-heavy conversation scenarios Fixes #2647
📋 Review SummaryThis PR fixes a critical bug in the 🔍 General Feedback
🎯 Specific Feedback🟢 Medium
🔵 Low
✅ Highlights
|
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
…heavy conversations - Strip trailing orphaned funcCall (force=true) before split point calculation, so normal compression logic runs cleanly on the remaining history instead of requiring ad-hoc special-casing - Remove redundant lastToolCompletionSplitPoint machinery: after fixing the i+2 index bug, lastSplitPoint already subsumes it, making Math.max redundant - Add MIN_COMPRESSION_FRACTION constant (0.05) to guard against futile API calls when historyToCompress is too small relative to total history - Add tests for orphaned funcCall handling (force=true compresses, force=false NOOP) - Add test for MIN_COMPRESSION_FRACTION guard Fixes #2647
TL;DR
Fix
/compressfor tool-heavy conversations by handling trailing orphanedfuncCallmessages during manual compression, preventing empty or near-empty compression requests, and simplifying the split-point logic.Root cause
In tool-heavy conversations, the history can end with a trailing
modelfuncCallthat is no longer actionable by the time a user manually runs/compress.At that point:
historyToKeepstill needs to start with a regular user messagefindCompressSplitPoint()can legitimately return0funcCallshould not block compression, because manual/compressruns after the agent is idleWhat changed
Strip orphaned trailing
funcCallfor forced compression onlyforce=trueand the curated history ends with a trailingmodelfuncCall, drop that final message before computing the split point.Keep split-point logic strict and remove redundant fallback tracking
findCompressSplitPoint()now relies on the primary valid user-starting split logic only.lastToolCompletionSplitPointpath.Guard against useless compression requests
NOOPwhenhistoryToCompressis empty.MIN_COMPRESSION_FRACTION = 0.05so we skip compressing trivial slices.Expand test coverage
funcCallhandling in forced compression.Reviewer test plan
npx vitest run packages/core/src/services/chatCompressionService.test.ts/compressafter the agent becomes idle.Testing matrix
Linked issues / bugs
Fixes #2647