Skip to content

Commit b4d06de

Browse files
committed
fix(compaction): restore idle arming and prior fold guidance on cancel
1 parent 2046070 commit b4d06de

2 files changed

Lines changed: 76 additions & 3 deletions

File tree

‎src/agent/compaction.test.ts‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
LEGACY_COMPACT_SPACER_TEXT,
2424
compactorNoOpFloor,
2525
} from "../session/compactor.js";
26+
import { buildSummaryPrompt } from "../session/summarizer.js";
2627

2728
const capabilities = {
2829
infer: (options?: unknown) => ({
@@ -1433,6 +1434,20 @@ describe("handoff arming (/handoff)", () => {
14331434
).toBeNull();
14341435
});
14351436

1437+
test("cancelManual restores extraInstructions from a prior successful fold", () => {
1438+
const governor = createCompactionGovernor(undefined);
1439+
governor.syncFromTurns(tenTurns);
1440+
expect(governor.requestHandoff("keep the UI audit")).toBe("armed");
1441+
governor.interceptIdleContinuation(
1442+
pivot("keep the UI audit"),
1443+
capabilities,
1444+
);
1445+
expect(governor.extraInstructions).toBe("keep the UI audit");
1446+
expect(governor.requestHandoff("failed pivot: drop this")).toBe("armed");
1447+
governor.cancelManual();
1448+
expect(governor.extraInstructions).toBe("keep the UI audit");
1449+
});
1450+
14361451
test("cancelManual clears sticky extraInstructions from a failed pivot", () => {
14371452
const governor = createCompactionGovernor(undefined);
14381453
governor.syncFromTurns(tenTurns);
@@ -1441,6 +1456,55 @@ describe("handoff arming (/handoff)", () => {
14411456
expect(governor.extraInstructions).toBeUndefined();
14421457
});
14431458

1459+
test("failed-pivot instructions are not in a later threshold summary prompt", () => {
1460+
const governor = createCompactionGovernor(undefined);
1461+
governor.syncFromTurns(tenTurns);
1462+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
1463+
governor.cancelManual();
1464+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1465+
expect(
1466+
governor.interceptActions(toolDone(), inferAction, capabilities),
1467+
).not.toBeNull();
1468+
const prompt = buildSummaryPrompt(
1469+
tenTurns,
1470+
governor.extraInstructions !== undefined
1471+
? { extraInstructions: governor.extraInstructions }
1472+
: undefined,
1473+
);
1474+
expect(prompt).not.toContain("now do the UI audit");
1475+
expect(prompt).not.toContain("Operator compact instructions");
1476+
});
1477+
1478+
test("cancelManual does not invent pending", () => {
1479+
const governor = createCompactionGovernor(undefined);
1480+
governor.syncFromTurns(tenTurns);
1481+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
1482+
governor.cancelManual();
1483+
expect(
1484+
governor.interceptActions(toolDone(), inferAction, capabilities),
1485+
).toBeNull();
1486+
});
1487+
1488+
test("cancelManual restores idlePending so an idle threshold fold still fires", () => {
1489+
const governor = createCompactionGovernor(undefined);
1490+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1491+
expect(
1492+
governor.noteIdleTurn(inferenceDone(overThreshold), [
1493+
{ type: "reply", content: "done" },
1494+
]),
1495+
).toBe(true);
1496+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
1497+
governor.cancelManual();
1498+
const actions = governor.interceptIdleContinuation(
1499+
emptyMessage(),
1500+
capabilities,
1501+
);
1502+
expect(actions).not.toBeNull();
1503+
expect(actions?.find((a) => a.type === "compact")).toMatchObject({
1504+
reason: "context-threshold",
1505+
});
1506+
});
1507+
14441508
test("threshold pending survives cancelManual so a tool pause still folds", () => {
14451509
const governor = createCompactionGovernor(undefined);
14461510
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);

‎src/agent/compaction.ts‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ export function createCompactionGovernor(
124124
// non-empty argument is kept for later auto-folds and written into the
125125
// compact record.
126126
let extraInstructions: string | undefined;
127+
// Snapshots taken at requestHandoff so cancelManual can restore the
128+
// pre-pivot idle arming and a prior successful fold's guidance.
129+
let idlePendingAtHandoff = false;
130+
let extraInstructionsAtHandoff: string | undefined;
127131
let postCompactInfer = false;
128132
// Idle empty compact needs a post-compact decide cycle to adopt the shrunk
129133
// turns for the meter, but must not start a new inference (there is no
@@ -565,6 +569,8 @@ export function createCompactionGovernor(
565569
// the single operator fold, because firing clears the arming.
566570
function requestHandoff(instructions: string): HandoffArming {
567571
if (turnCount <= MIN_TURNS_TO_COMPACT) return "noop";
572+
idlePendingAtHandoff = idlePending;
573+
extraInstructionsAtHandoff = extraInstructions;
568574
const trimmed = instructions.trim();
569575
if (trimmed.length > 0) extraInstructions = trimmed;
570576
manualPending = true;
@@ -577,13 +583,16 @@ export function createCompactionGovernor(
577583

578584
// Disarm after a pivot send that never delivered: without this the next
579585
// operator message would fold unexpectedly. Threshold `pending` is independent
580-
// of the failed pivot and must still fire at the next tool pause. Sticky
581-
// extraInstructions belong to a successful fold, not a cancelled one.
586+
// of the failed pivot and must still fire at the next tool pause. Restore
587+
// idlePending and extraInstructions from the requestHandoff snapshots so a
588+
// cancelled pivot neither invents an idle fold nor wipes a prior successful
589+
// fold's guidance.
582590
function cancelManual(): void {
583591
const thresholdPending = pending;
584592
clearManualArming();
585593
pending = thresholdPending;
586-
extraInstructions = undefined;
594+
idlePending = idlePendingAtHandoff;
595+
extraInstructions = extraInstructionsAtHandoff;
587596
}
588597

589598
return {

0 commit comments

Comments
 (0)