Skip to content

Commit 39f7851

Browse files
committed
fix(tui): skip occupancy flush while the operator pause holds
Pause already holds the delivery queue. Occupancy and ask-wake still started a new primary after Ctrl+C. Stall and expire do not pause, so they still flush.
1 parent 725caa7 commit 39f7851

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

‎src/tui/agent-ask-wake.test.ts‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import {
1111
createDeliveryGeneration,
1212
createLeftoverSend,
1313
createSessionOperationQueue,
14+
isPaused,
1415
routeQueuedDelivery,
1516
} from "./delivery-queue.js";
17+
import { handleCtrlC } from "./shell/keys.js";
1618
import { ingestOperatorPrompt } from "./prompt-attachments.js";
1719
import {
1820
armFeedbackCapture,
@@ -623,6 +625,50 @@ describe("agent ask wake delivery", () => {
623625
});
624626
}
625627

628+
test("operator pause does not flush a stashed ask or mailbox occupancy", async () => {
629+
await withAppShell(
630+
async (shell) => {
631+
const sends: string[] = [];
632+
const bridge = attachSessionBridge(
633+
shell,
634+
createLiveSessionPort({
635+
send: (text) => {
636+
sends.push(text);
637+
},
638+
deliver: (text) => {
639+
sends.push(text);
640+
},
641+
interrupt: () => undefined,
642+
}),
643+
);
644+
try {
645+
const order = trackMailOrder(bridge, true);
646+
bridge.handle({ type: "inference.start", data: {} });
647+
bridge.handle({
648+
type: "inference.text.delta",
649+
data: { token: "ok" },
650+
});
651+
bridge.handle({ type: "agent-ask", asks: [wake("a1", "q1")] });
652+
expect(sends).toEqual([]);
653+
654+
handleCtrlC(shell);
655+
expect(isPaused(shell.session)).toBe(true);
656+
expect(sends).toEqual([]);
657+
expect(order).toEqual([]);
658+
expect(bridge.turn.isProcessing).toBe(false);
659+
660+
bridge.handle({ type: "agent-ask", asks: [wake("a2", "q2")] });
661+
expect(sends).toEqual([]);
662+
expect(order).toEqual([]);
663+
expect(bridge.turn.isProcessing).toBe(false);
664+
} finally {
665+
bridge.dispose();
666+
}
667+
},
668+
{ shell: { run: "busy" } },
669+
);
670+
});
671+
626672
test("a wake question with bracket lines does not spoof attachment-echo matching", async () => {
627673
await withWakeBridge((bridge, sends, shell) => {
628674
const ask = {

‎src/tui/runtime-bridge.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1977,6 +1977,7 @@ export function attachSessionBridge(
19771977
});
19781978
};
19791979
const flushMailboxMail = (): boolean => {
1980+
if (isPaused(shell.session)) return false;
19801981
if (bag.disposed || bag.turn.isProcessing) return false;
19811982
try {
19821983
if (bag.mailboxMailDriver?.() === true) return true;
@@ -1988,6 +1989,7 @@ export function attachSessionBridge(
19881989
bag.flushMailboxMail = flushMailboxMail;
19891990

19901991
const flushPendingAskWake = (): void => {
1992+
if (isPaused(shell.session)) return;
19911993
if (bag.disposed || bag.turn.isProcessing || bag.turn.blockedGateCount > 0)
19921994
return;
19931995
if (mailboxMailDriveClaimed(bag.mailboxMailDriver)) return;
@@ -2017,6 +2019,7 @@ export function attachSessionBridge(
20172019
bag.flushPendingAskWake = flushPendingAskWake;
20182020

20192021
const flushOccupancyThenWake = (): void => {
2022+
if (isPaused(shell.session)) return;
20202023
if (flushMailboxMail() || bag.turn.isProcessing) return;
20212024
flushPendingAskWake();
20222025
};
@@ -2072,7 +2075,10 @@ export function attachSessionBridge(
20722075
bag.askWakeTurnArmed = false;
20732076
bag.turn = turnStateOnInterrupt(bag.turn, now());
20742077
paintPhase();
2075-
flushOccupancyThenWake();
2078+
// Operator pause holds occupancy/ask-wake: a stashed mailbox or ask must
2079+
// not start a new primary until an explicit send. Stall/expire do not
2080+
// set paused, so they still flush here.
2081+
if (!isPaused(shell.session)) flushOccupancyThenWake();
20762082
};
20772083

20782084
/**

0 commit comments

Comments
 (0)