Merge PR #8067: fix(compaction): manual /shake keeps a recent tail of tool results (@zhang17-24)

This commit is contained in:
can1357
2026-08-13 02:00:49 +02:00
6 changed files with 40 additions and 6 deletions
+3
View File
@@ -7,6 +7,9 @@
### Fixed
- Fixed Cursor sessions re-executing settled tools when an owned dialect projector rebuilds toolCall blocks: `snapshotAssistantContentBlock` now copies `kCursorExecResolved` explicitly so agent-loop still skips already-settled calls.
### 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
+9 -3
View File
@@ -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,
@@ -65,6 +67,10 @@ export const AGGRESSIVE_SHAKE_CONFIG: ShakeConfig = {
/** Compaction dead-end rescue: aggressive reach, but artifact recovery reads stay protected. */
export const RESCUE_SHAKE_CONFIG: ShakeConfig = {
...AGGRESSIVE_SHAKE_CONFIG,
// Rescue must be able to elide the newest oversized result even inside the
// manual preset's recent-tail window (#7776) — a dead-end recovery that
// cannot drop its blocker is not a recovery.
protectTokens: 0,
protectedTools: [...AGGRESSIVE_SHAKE_CONFIG.protectedTools, isArtifactRecoveryToolResult],
};
+21 -2
View File
@@ -8,6 +8,7 @@ import {
collectShakeRegions,
DEFAULT_SHAKE_CONFIG,
estimateTokens,
RESCUE_SHAKE_CONFIG,
} from "@oh-my-pi/pi-agent-core/compaction";
import type { AssistantMessage, TextContent, ToolCall, ToolResultMessage } from "@oh-my-pi/pi-ai";
@@ -204,17 +205,35 @@ 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");
});
test("rescue preset overrides the manual tail so it can elide the newest result", () => {
const recent = messageEntry(toolResultMessage("bash", "oversized-result ".repeat(2000)));
const regions = collectShakeRegions([recent], RESCUE_SHAKE_CONFIG);
expect(regions).toHaveLength(1);
expect(regions[0].entry).toBe(recent);
});
test("empty branch yields no regions", () => {
expect(collectShakeRegions([] as SessionEntry[], AGGRESSIVE_SHAKE_CONFIG)).toHaveLength(0);
});
+3 -1
View File
@@ -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");
+3
View File
@@ -129,6 +129,9 @@
- Removed the `resolveAgentModelSource` model-resolver export, whose only use was being fed to `resolveExplicitModelRole`. Replaced by `resolveAgentModelSelection`, which returns the expanded `patterns` and the pre-expansion `role` together so a spawn path cannot derive one without the other ([#7910](https://github.com/can1357/oh-my-pi/pull/7910) by [@enieuwy](https://github.com/enieuwy)).
- A run is now attributed to the model that actually produced its output, not whichever model the session was last pointed at. A retry fallback that errored on its first request — an exhausted quota, a hard provider error — was credited with the whole run in the Agent Hub row and the settled task result, even when the previous model did every turn. Sessions expose the serving model directly, holding the last model that produced output while a candidate is armed but unproven, and transcript-derived history stops at the newest turn that produced output.
### 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.12] - 2026-08-08
@@ -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],
});