diff --git a/src/cli/App.tsx b/src/cli/App.tsx index f5f9aaa..eb8e56b 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -1750,7 +1750,14 @@ export default function App({ "queue-lifecycle", `dequeued batch_id=${batch.batchId} merged_count=${batch.mergedCount} queue_len_after=${batch.queueLenAfter}`, ); - setQueueDisplay((prev) => prev.slice(batch.mergedCount)); + // queueDisplay only tracks displayable items. If non-display barrier + // kinds are ever consumed, avoid over-trimming by counting only + // message/task_notification entries in the batch. + const displayConsumedCount = batch.items.filter( + (item) => + item.kind === "message" || item.kind === "task_notification", + ).length; + setQueueDisplay((prev) => prev.slice(displayConsumedCount)); }, onBlocked: (reason, queueLen) => debugLog( @@ -6502,6 +6509,7 @@ export default function App({ async (message?: string): Promise<{ submitted: boolean }> => { const msg = message?.trim() ?? ""; const overrideContentParts = overrideContentPartsRef.current; + const hasOverrideContent = overrideContentParts !== null; if (overrideContentParts) { overrideContentPartsRef.current = null; } @@ -6512,7 +6520,7 @@ export default function App({ taskNotifications.length > 0 && userTextForInput.length === 0; // Handle profile load confirmation (Enter to continue) - if (profileConfirmPending && !msg) { + if (profileConfirmPending && !msg && !hasOverrideContent) { // User pressed Enter with empty input - proceed with loading const { name, agentId: targetAgentId, cmdId } = profileConfirmPending; const cmd = commandRunner.getHandle(cmdId, `/profile load ${name}`); @@ -6534,7 +6542,7 @@ export default function App({ // Continue processing the new message } - if (!msg) return { submitted: false }; + if (!msg && !hasOverrideContent) return { submitted: false }; // If the user just cycled reasoning tiers, flush the final choice before // sending the next message so the upcoming run uses the selected tier. diff --git a/src/tests/cli/queue-ordering-wiring.test.ts b/src/tests/cli/queue-ordering-wiring.test.ts index d71765f..57d5559 100644 --- a/src/tests/cli/queue-ordering-wiring.test.ts +++ b/src/tests/cli/queue-ordering-wiring.test.ts @@ -37,6 +37,40 @@ describe("queue ordering wiring", () => { expect(segment).toContain("queuedOverlayAction,"); }); + test("queue display trim uses displayable-item count, not mergedCount", () => { + const source = readAppSource(); + const start = source.indexOf("onDequeued: (batch) => {"); + const end = source.indexOf("onBlocked: (reason, queueLen) =>"); + + expect(start).toBeGreaterThan(-1); + expect(end).toBeGreaterThan(start); + + const segment = source.slice(start, end); + expect(segment).toContain("const displayConsumedCount ="); + expect(segment).toContain('item.kind === "message"'); + expect(segment).toContain('item.kind === "task_notification"'); + expect(segment).toContain("prev.slice(displayConsumedCount)"); + }); + + test("onSubmit allows override-only queued submissions", () => { + const source = readAppSource(); + const start = source.indexOf("const onSubmit = useCallback("); + const end = source.indexOf( + "// Process queued overlay actions when streaming ends", + ); + + expect(start).toBeGreaterThan(-1); + expect(end).toBeGreaterThan(start); + + const segment = source.slice(start, end); + expect(segment).toContain( + "if (!msg && !hasOverrideContent) return { submitted: false };", + ); + expect(segment).toContain( + "if (profileConfirmPending && !msg && !hasOverrideContent)", + ); + }); + test("queued overlay effect only runs when idle and clears action before processing", () => { const source = readAppSource(); const start = source.indexOf(