From c3da093a1d066b3da302b6783f6e849b6aac01f6 Mon Sep 17 00:00:00 2001 From: left-to-right <2201218482@qq.com> Date: Sun, 9 Aug 2026 18:09:17 +0800 Subject: [PATCH] fix(compaction): manual /shake keeps a recent tail of tool results Manual /shake used protectTokens: 0, stripping every eligible tool result including the ones the agent is still working from. Keep a small 4k-token recent window (matching the automatic shake mechanism, at a quarter of its 16k budget) so the full escape hatch stays aggressive without destroying the live tail. Two matcher-focused tests that implicitly relied on the zero window now pin protectTokens: 0 explicitly. Fixes #7776 --- packages/agent/CHANGELOG.md | 4 ++++ packages/agent/src/compaction/shake.ts | 8 +++++--- packages/agent/test/shake.test.ts | 15 +++++++++++++-- packages/agent/test/tool-protection.test.ts | 4 +++- .../test/plan-mode/plan-protection.test.ts | 1 + 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 56c6c404d..e43f4e15d 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Manual `/shake` now keeps a small recent tail of tool results instead of stripping every eligible result, so the agent does not lose the context it is currently working from ([#7776](https://github.com/can1357/oh-my-pi/issues/7776)). + ## [17.2.10] - 2026-08-06 ### Fixed diff --git a/packages/agent/src/compaction/shake.ts b/packages/agent/src/compaction/shake.ts index 3d5289b7d..1286d7b1f 100644 --- a/packages/agent/src/compaction/shake.ts +++ b/packages/agent/src/compaction/shake.ts @@ -52,11 +52,13 @@ export const DEFAULT_SHAKE_CONFIG: ShakeConfig = { }; /** - * Manual `/shake`: aggressive — drops every eligible region across history, - * artifact recovery reads included (the user's full escape hatch). + * Manual `/shake`: aggressive — no savings threshold and drops eligible + * regions across history, artifact recovery reads included (the user's full + * escape hatch). Still keeps a small recent tail so it cannot strip the tool + * results the agent is currently working from (#7776). */ export const AGGRESSIVE_SHAKE_CONFIG: ShakeConfig = { - protectTokens: 0, + protectTokens: 4_000, minSavings: 0, protectedTools: ["skill", isSkillReadToolResult], fenceMinTokens: 400, diff --git a/packages/agent/test/shake.test.ts b/packages/agent/test/shake.test.ts index 86978f24f..dee8926c1 100644 --- a/packages/agent/test/shake.test.ts +++ b/packages/agent/test/shake.test.ts @@ -204,12 +204,23 @@ describe("applyShakeRegions — multi-region ordering", () => { }); describe("shake config presets", () => { - test("aggressive preset protects skill and drops everything else", () => { - expect(AGGRESSIVE_SHAKE_CONFIG.protectTokens).toBe(0); + test("aggressive preset protects skill and keeps a small recent tail", () => { + expect(AGGRESSIVE_SHAKE_CONFIG.protectTokens).toBeGreaterThan(0); expect(AGGRESSIVE_SHAKE_CONFIG.minSavings).toBe(0); expect(AGGRESSIVE_SHAKE_CONFIG.protectedTools).toContain("skill"); }); + test("manual shake preserves the recent tool-result tail instead of stripping everything", () => { + const older = messageEntry(toolResultMessage("bash", "old-result ".repeat(300))); + const recent = messageEntry(toolResultMessage("bash", "recent-result ".repeat(3000))); + const regions = collectShakeRegions([older, recent], AGGRESSIVE_SHAKE_CONFIG); + + // The recent result sits inside the preserved tail; the older one is + // still shaken aggressively. + expect(regions).toHaveLength(1); + expect(regions[0].entry).toBe(older); + }); + test("default preset keeps a protect window", () => { expect(DEFAULT_SHAKE_CONFIG.protectTokens).toBeGreaterThan(0); expect(DEFAULT_SHAKE_CONFIG.protectedTools).toContain("skill"); diff --git a/packages/agent/test/tool-protection.test.ts b/packages/agent/test/tool-protection.test.ts index 0d5281dae..48c61e9ee 100644 --- a/packages/agent/test/tool-protection.test.ts +++ b/packages/agent/test/tool-protection.test.ts @@ -84,7 +84,9 @@ describe("conditional tool-result protection", () => { fileResult, ]; - const regions = collectShakeRegions(entries, AGGRESSIVE_SHAKE_CONFIG); + // protectTokens: 0 isolates the matcher behavior from the aggressive + // preset's recent-tail window (covered by shake.test.ts). + const regions = collectShakeRegions(entries, { ...AGGRESSIVE_SHAKE_CONFIG, protectTokens: 0 }); expect(regions).toHaveLength(1); expect(regions[0]?.kind).toBe("toolResult"); diff --git a/packages/coding-agent/test/plan-mode/plan-protection.test.ts b/packages/coding-agent/test/plan-mode/plan-protection.test.ts index ab234b102..fa8424307 100644 --- a/packages/coding-agent/test/plan-mode/plan-protection.test.ts +++ b/packages/coding-agent/test/plan-mode/plan-protection.test.ts @@ -155,6 +155,7 @@ describe("plan-read protection in compaction", () => { const regions = collectShakeRegions(entries, { ...AGGRESSIVE_SHAKE_CONFIG, + protectTokens: 0, protectedTools: [...AGGRESSIVE_SHAKE_CONFIG.protectedTools, matcher], });