From 2a83609287f41642a52389c44d14364019fdd71d Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sat, 14 Feb 2026 18:14:11 -0800 Subject: [PATCH] Subagents: retain announce queue items on send failure --- src/agents/subagent-announce-queue.ts | 41 ++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/agents/subagent-announce-queue.ts b/src/agents/subagent-announce-queue.ts index 93416ff3342..468cd02aa2d 100644 --- a/src/agents/subagent-announce-queue.ts +++ b/src/agents/subagent-announce-queue.ts @@ -44,6 +44,22 @@ type AnnounceQueueState = { const ANNOUNCE_QUEUES = new Map(); +function previewQueueSummaryPrompt(queue: AnnounceQueueState): string | undefined { + return buildQueueSummaryPrompt({ + state: { + dropPolicy: queue.dropPolicy, + droppedCount: queue.droppedCount, + summaryLines: [...queue.summaryLines], + }, + noun: "announce", + }); +} + +function clearQueueSummaryState(queue: AnnounceQueueState) { + queue.droppedCount = 0; + queue.summaryLines = []; +} + export function resetAnnounceQueuesForTests() { // Test isolation: other suites may leave a draining queue behind in the worker. // Clearing the map alone isn't enough because drain loops capture `queue` by reference. @@ -105,11 +121,12 @@ function scheduleAnnounceDrain(key: string) { await waitForQueueDebounce(queue); if (queue.mode === "collect") { if (forceIndividualCollect) { - const next = queue.items.shift(); + const next = queue.items[0]; if (!next) { break; } await queue.send(next); + queue.items.shift(); continue; } const isCrossChannel = hasCrossChannelItems(queue.items, (item) => { @@ -123,15 +140,16 @@ function scheduleAnnounceDrain(key: string) { }); if (isCrossChannel) { forceIndividualCollect = true; - const next = queue.items.shift(); + const next = queue.items[0]; if (!next) { break; } await queue.send(next); + queue.items.shift(); continue; } - const items = queue.items.splice(0, queue.items.length); - const summary = buildQueueSummaryPrompt({ state: queue, noun: "announce" }); + const items = queue.items.slice(); + const summary = previewQueueSummaryPrompt(queue); const prompt = buildCollectPrompt({ title: "[Queued announce messages while agent was busy]", items, @@ -143,26 +161,35 @@ function scheduleAnnounceDrain(key: string) { break; } await queue.send({ ...last, prompt }); + queue.items.splice(0, items.length); + if (summary) { + clearQueueSummaryState(queue); + } continue; } - const summaryPrompt = buildQueueSummaryPrompt({ state: queue, noun: "announce" }); + const summaryPrompt = previewQueueSummaryPrompt(queue); if (summaryPrompt) { - const next = queue.items.shift(); + const next = queue.items[0]; if (!next) { break; } await queue.send({ ...next, prompt: summaryPrompt }); + queue.items.shift(); + clearQueueSummaryState(queue); continue; } - const next = queue.items.shift(); + const next = queue.items[0]; if (!next) { break; } await queue.send(next); + queue.items.shift(); } } catch (err) { + // Keep items in queue and retry after debounce; avoid hot-loop retries. + queue.lastEnqueuedAt = Date.now(); defaultRuntime.error?.(`announce queue drain failed for ${key}: ${String(err)}`); } finally { queue.draining = false;