refactor(queue): share drain helpers across announce and reply

This commit is contained in:
Peter Steinberger 2026-02-22 17:54:19 +00:00
parent 78220db2be
commit dacb3d1aa2
3 changed files with 60 additions and 31 deletions

View File

@ -8,9 +8,10 @@ import {
import { import {
applyQueueRuntimeSettings, applyQueueRuntimeSettings,
applyQueueDropPolicy, applyQueueDropPolicy,
beginQueueDrain,
buildCollectPrompt, buildCollectPrompt,
clearQueueSummaryState, clearQueueSummaryState,
drainCollectItemIfNeeded, drainCollectQueueStep,
drainNextQueueItem, drainNextQueueItem,
hasCrossChannelItems, hasCrossChannelItems,
previewQueueSummaryPrompt, previewQueueSummaryPrompt,
@ -97,33 +98,35 @@ function getAnnounceQueue(
return created; return created;
} }
function hasAnnounceCrossChannelItems(items: AnnounceQueueItem[]): boolean {
return hasCrossChannelItems(items, (item) => {
if (!item.origin) {
return {};
}
if (!item.originKey) {
return { cross: true };
}
return { key: item.originKey };
});
}
function scheduleAnnounceDrain(key: string) { function scheduleAnnounceDrain(key: string) {
const queue = ANNOUNCE_QUEUES.get(key); const queue = beginQueueDrain(ANNOUNCE_QUEUES, key);
if (!queue || queue.draining) { if (!queue) {
return; return;
} }
queue.draining = true;
void (async () => { void (async () => {
try { try {
let forceIndividualCollect = false; const collectState = { forceIndividualCollect: false };
while (queue.items.length > 0 || queue.droppedCount > 0) { for (;;) {
if (queue.items.length === 0 && queue.droppedCount === 0) {
break;
}
await waitForQueueDebounce(queue); await waitForQueueDebounce(queue);
if (queue.mode === "collect") { if (queue.mode === "collect") {
const isCrossChannel = hasCrossChannelItems(queue.items, (item) => { const collectDrainResult = await drainCollectQueueStep({
if (!item.origin) { collectState,
return {}; isCrossChannel: hasAnnounceCrossChannelItems(queue.items),
}
if (!item.originKey) {
return { cross: true };
}
return { key: item.originKey };
});
const collectDrainResult = await drainCollectItemIfNeeded({
forceIndividualCollect,
isCrossChannel,
setForceIndividualCollect: (next) => {
forceIndividualCollect = next;
},
items: queue.items, items: queue.items,
run: async (item) => await queue.send(item), run: async (item) => await queue.send(item),
}); });

View File

@ -1,8 +1,9 @@
import { defaultRuntime } from "../../../runtime.js"; import { defaultRuntime } from "../../../runtime.js";
import { import {
buildCollectPrompt, buildCollectPrompt,
beginQueueDrain,
clearQueueSummaryState, clearQueueSummaryState,
drainCollectItemIfNeeded, drainCollectQueueStep,
drainNextQueueItem, drainNextQueueItem,
hasCrossChannelItems, hasCrossChannelItems,
previewQueueSummaryPrompt, previewQueueSummaryPrompt,
@ -16,14 +17,13 @@ export function scheduleFollowupDrain(
key: string, key: string,
runFollowup: (run: FollowupRun) => Promise<void>, runFollowup: (run: FollowupRun) => Promise<void>,
): void { ): void {
const queue = FOLLOWUP_QUEUES.get(key); const queue = beginQueueDrain(FOLLOWUP_QUEUES, key);
if (!queue || queue.draining) { if (!queue) {
return; return;
} }
queue.draining = true;
void (async () => { void (async () => {
try { try {
let forceIndividualCollect = false; const collectState = { forceIndividualCollect: false };
while (queue.items.length > 0 || queue.droppedCount > 0) { while (queue.items.length > 0 || queue.droppedCount > 0) {
await waitForQueueDebounce(queue); await waitForQueueDebounce(queue);
if (queue.mode === "collect") { if (queue.mode === "collect") {
@ -50,12 +50,9 @@ export function scheduleFollowupDrain(
}; };
}); });
const collectDrainResult = await drainCollectItemIfNeeded({ const collectDrainResult = await drainCollectQueueStep({
forceIndividualCollect, collectState,
isCrossChannel, isCrossChannel,
setForceIndividualCollect: (next) => {
forceIndividualCollect = next;
},
items: queue.items, items: queue.items,
run: runFollowup, run: runFollowup,
}); });

View File

@ -132,6 +132,18 @@ export function waitForQueueDebounce(queue: {
}); });
} }
export function beginQueueDrain<T extends { draining: boolean }>(
map: Map<string, T>,
key: string,
): T | undefined {
const queue = map.get(key);
if (!queue || queue.draining) {
return undefined;
}
queue.draining = true;
return queue;
}
export async function drainNextQueueItem<T>( export async function drainNextQueueItem<T>(
items: T[], items: T[],
run: (item: T) => Promise<void>, run: (item: T) => Promise<void>,
@ -162,6 +174,23 @@ export async function drainCollectItemIfNeeded<T>(params: {
return drained ? "drained" : "empty"; return drained ? "drained" : "empty";
} }
export async function drainCollectQueueStep<T>(params: {
collectState: { forceIndividualCollect: boolean };
isCrossChannel: boolean;
items: T[];
run: (item: T) => Promise<void>;
}): Promise<"skipped" | "drained" | "empty"> {
return await drainCollectItemIfNeeded({
forceIndividualCollect: params.collectState.forceIndividualCollect,
isCrossChannel: params.isCrossChannel,
setForceIndividualCollect: (next) => {
params.collectState.forceIndividualCollect = next;
},
items: params.items,
run: params.run,
});
}
export function buildQueueSummaryPrompt(params: { export function buildQueueSummaryPrompt(params: {
state: QueueSummaryState; state: QueueSummaryState;
noun: string; noun: string;