Skip to content

Commit 83b638e

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 ae5dba9 commit 83b638e

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
@@ -1097,4 +1097,57 @@ describe("handoff arming (/handoff)", () => {
10971097
expect(prompt).not.toContain("keep the UI audit");
10981098
expect(prompt).not.toContain("Operator compact instructions");
10991099
});
1100+
1101+
test("empty trailing then cancel restores committed extras", () => {
1102+
const governor = createCompactionGovernor(undefined);
1103+
governor.syncFromTurns(tenTurns);
1104+
expect(governor.requestHandoff("keep the UI audit")).toBe("armed");
1105+
governor.interceptIdleContinuation(
1106+
pivot("keep the UI audit"),
1107+
capabilities,
1108+
);
1109+
expect(governor.requestHandoff(" ")).toBe("armed");
1110+
expect(governor.extraInstructions).toBeUndefined();
1111+
governor.cancelManual();
1112+
expect(governor.extraInstructions).toBe("keep the UI audit");
1113+
});
1114+
1115+
test("noop then cancel after a restored idle fold has already fired does not re-arm idle", () => {
1116+
const governor = createCompactionGovernor(undefined);
1117+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1118+
expect(
1119+
governor.noteIdleTurn(inferenceDone(overThreshold), [
1120+
{ type: "reply", content: "done" },
1121+
]),
1122+
).toBe(true);
1123+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
1124+
governor.cancelManual();
1125+
expect(
1126+
governor.interceptIdleContinuation(emptyMessage(), capabilities),
1127+
).not.toBeNull();
1128+
governor.syncFromTurns(threeTurns);
1129+
expect(governor.requestHandoff("wipe this")).toBe("noop");
1130+
governor.cancelManual();
1131+
expect(
1132+
governor.interceptIdleContinuation(emptyMessage(), capabilities),
1133+
).toBeNull();
1134+
});
1135+
1136+
test("overflow then handoff does not double-fold", () => {
1137+
const governor = createCompactionGovernor(undefined);
1138+
governor.syncFromTurns(tenTurns);
1139+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
1140+
const overflow = governor.interceptOverflow(overflowError(), capabilities);
1141+
expect(overflow).not.toBeNull();
1142+
expect(overflow?.find((a) => a.type === "compact")).toMatchObject({
1143+
reason: "context-overflow",
1144+
});
1145+
expect(
1146+
governor.interceptIdleContinuation(
1147+
pivot("now do the UI audit"),
1148+
capabilities,
1149+
),
1150+
).toBeNull();
1151+
expect(governor.extraInstructions).toBe("now do the UI audit");
1152+
});
11001153
});

‎src/agent/compaction.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,9 @@ export function createCompactionGovernor(
334334
}
335335
if (overflowRecoveries >= MAX_OVERFLOW_RECOVERIES) return null;
336336
overflowRecoveries++;
337-
pending = false;
337+
// Overflow compact spends any operator arming so a queued handoff
338+
// pivot cannot fold again after this recovery. Sticky extras stay.
339+
clearManualArming();
338340
postCompactInfer = true;
339341
noteCompactIssued();
340342
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)