Skip to content

Commit 1594bd0

Browse files
committed
fix(compaction): spend operator arming on overflow recovery
Overflow compact left /handoff armed, so the pivot folded a second time. Keepers pin empty-trailing cancel, noop-after-idle cancel, and TUI uncertain noop.
1 parent 792a56d commit 1594bd0

3 files changed

Lines changed: 69 additions & 1 deletion

File tree

‎src/agent/compaction.test.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,4 +1627,57 @@ describe("handoff arming (/handoff)", () => {
16271627
expect(prompt).not.toContain("keep the UI audit");
16281628
expect(prompt).not.toContain("Operator compact instructions");
16291629
});
1630+
1631+
test("empty trailing then cancel restores committed extras", () => {
1632+
const governor = createCompactionGovernor(undefined);
1633+
governor.syncFromTurns(tenTurns);
1634+
expect(governor.requestHandoff("keep the UI audit")).toBe("armed");
1635+
governor.interceptIdleContinuation(
1636+
pivot("keep the UI audit"),
1637+
capabilities,
1638+
);
1639+
expect(governor.requestHandoff(" ")).toBe("armed");
1640+
expect(governor.extraInstructions).toBeUndefined();
1641+
governor.cancelManual();
1642+
expect(governor.extraInstructions).toBe("keep the UI audit");
1643+
});
1644+
1645+
test("noop then cancel after a restored idle fold has already fired does not re-arm idle", () => {
1646+
const governor = createCompactionGovernor(undefined);
1647+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1648+
expect(
1649+
governor.noteIdleTurn(inferenceDone(overThreshold), [
1650+
{ type: "reply", content: "done" },
1651+
]),
1652+
).toBe(true);
1653+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
1654+
governor.cancelManual();
1655+
expect(
1656+
governor.interceptIdleContinuation(emptyMessage(), capabilities),
1657+
).not.toBeNull();
1658+
governor.syncFromTurns(threeTurns);
1659+
expect(governor.requestHandoff("wipe this")).toBe("noop");
1660+
governor.cancelManual();
1661+
expect(
1662+
governor.interceptIdleContinuation(emptyMessage(), capabilities),
1663+
).toBeNull();
1664+
});
1665+
1666+
test("overflow then handoff does not double-fold", () => {
1667+
const governor = createCompactionGovernor(undefined);
1668+
governor.syncFromTurns(tenTurns);
1669+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
1670+
const overflow = governor.interceptOverflow(overflowError(), capabilities);
1671+
expect(overflow).not.toBeNull();
1672+
expect(overflow?.find((a) => a.type === "compact")).toMatchObject({
1673+
reason: "context-overflow",
1674+
});
1675+
expect(
1676+
governor.interceptIdleContinuation(
1677+
pivot("now do the UI audit"),
1678+
capabilities,
1679+
),
1680+
).toBeNull();
1681+
expect(governor.extraInstructions).toBe("now do the UI audit");
1682+
});
16301683
});

‎src/agent/compaction.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,9 @@ export function createCompactionGovernor(
480480
}
481481
if (overflowRecoveries >= MAX_OVERFLOW_RECOVERIES) return null;
482482
overflowRecoveries++;
483-
pending = false;
483+
// Overflow compact spends any operator arming so a queued handoff
484+
// pivot cannot fold again after this recovery. Sticky extras stay.
485+
clearManualArming();
484486
postCompactInfer = true;
485487
noteCompactIssued();
486488
return [

‎src/tui/runner/handoff.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,19 @@ describe("runner /handoff wiring", () => {
151151
expect(h.cancelled).toBe(0);
152152
});
153153

154+
test("an uncertain noop pivot does not cancelManualCompact", async () => {
155+
const h = setUpHandoffHarness();
156+
h.setArming("noop");
157+
h.setSendResult({
158+
status: "uncertain",
159+
detail: "network reset",
160+
});
161+
expect(h.requestHandoff("ship the dashboard")).toBeUndefined();
162+
await flushSends();
163+
expect(h.sent).toHaveLength(1);
164+
expect(h.cancelled).toBe(0);
165+
});
166+
154167
test("a rejected noop pivot send does not cancelManualCompact", async () => {
155168
const h = setUpHandoffHarness();
156169
h.setArming("noop");

0 commit comments

Comments
 (0)