mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 07:31:36 +00:00
After a drain loop empties the queue it deletes the key from FOLLOWUP_QUEUES. If a new message arrives at that moment enqueueFollowupRun creates a fresh queue object with draining:false but never starts a drain, leaving the message stranded until the next run completes and calls finalizeWithFollowup. Fix: persist the most recent runFollowup callback per queue key in FOLLOWUP_RUN_CALLBACKS (drain.ts). enqueueFollowupRun now calls kickFollowupDrainIfIdle after a successful push; if a cached callback exists and no drain is running it calls scheduleFollowupDrain to restart immediately. clearSessionQueues cleans up the callback cache alongside the queue state.
73 lines
2.2 KiB
TypeScript
73 lines
2.2 KiB
TypeScript
import { applyQueueDropPolicy, shouldSkipQueueItem } from "../../../utils/queue-helpers.js";
|
|
import { kickFollowupDrainIfIdle } from "./drain.js";
|
|
import { getExistingFollowupQueue, getFollowupQueue } from "./state.js";
|
|
import type { FollowupRun, QueueDedupeMode, QueueSettings } from "./types.js";
|
|
|
|
function isRunAlreadyQueued(
|
|
run: FollowupRun,
|
|
items: FollowupRun[],
|
|
allowPromptFallback = false,
|
|
): boolean {
|
|
const hasSameRouting = (item: FollowupRun) =>
|
|
item.originatingChannel === run.originatingChannel &&
|
|
item.originatingTo === run.originatingTo &&
|
|
item.originatingAccountId === run.originatingAccountId &&
|
|
item.originatingThreadId === run.originatingThreadId;
|
|
|
|
const messageId = run.messageId?.trim();
|
|
if (messageId) {
|
|
return items.some((item) => item.messageId?.trim() === messageId && hasSameRouting(item));
|
|
}
|
|
if (!allowPromptFallback) {
|
|
return false;
|
|
}
|
|
return items.some((item) => item.prompt === run.prompt && hasSameRouting(item));
|
|
}
|
|
|
|
export function enqueueFollowupRun(
|
|
key: string,
|
|
run: FollowupRun,
|
|
settings: QueueSettings,
|
|
dedupeMode: QueueDedupeMode = "message-id",
|
|
): boolean {
|
|
const queue = getFollowupQueue(key, settings);
|
|
const dedupe =
|
|
dedupeMode === "none"
|
|
? undefined
|
|
: (item: FollowupRun, items: FollowupRun[]) =>
|
|
isRunAlreadyQueued(item, items, dedupeMode === "prompt");
|
|
|
|
// Deduplicate: skip if the same message is already queued.
|
|
if (shouldSkipQueueItem({ item: run, items: queue.items, dedupe })) {
|
|
return false;
|
|
}
|
|
|
|
queue.lastEnqueuedAt = Date.now();
|
|
queue.lastRun = run.run;
|
|
|
|
const shouldEnqueue = applyQueueDropPolicy({
|
|
queue,
|
|
summarize: (item) => item.summaryLine?.trim() || item.prompt.trim(),
|
|
});
|
|
if (!shouldEnqueue) {
|
|
return false;
|
|
}
|
|
|
|
queue.items.push(run);
|
|
// If drain finished and deleted the queue before this item arrived, a new queue
|
|
// object was created (draining: false) but nobody scheduled a drain for it.
|
|
// Use the cached callback to restart the drain now.
|
|
if (!queue.draining) {
|
|
kickFollowupDrainIfIdle(key);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function getFollowupQueueDepth(key: string): number {
|
|
const queue = getExistingFollowupQueue(key);
|
|
if (!queue) {
|
|
return 0;
|
|
}
|
|
return queue.items.length;
|
|
}
|