openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
301 lines (300 loc) • 12.6 kB
JavaScript
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString } from "./string-coerce-6kR-86XC.mjs";
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { r as defaultRuntime } from "./runtime-BC29JSZp.mjs";
import { r as racePromiseWithAbortSignal } from "./abort-signal-Z3A36sLL.mjs";
import { n as resolveGlobalDedupeCache } from "./dedupe-B0_bknse.mjs";
import { t as normalizeChatType } from "./chat-type-Dw-QySg9.mjs";
import { n as channelRouteDedupeKey } from "./channel-route-BIXHeyAA.mjs";
import { n as getLoadedChannelPlugin } from "./registry-B56V4gqI.mjs";
import "./plugins-CBMHLsl7.mjs";
import { R as extractTextFromChatContent } from "./user-facing-text-D-rB13dP.mjs";
import { a as logMessageQueuedWithBacklogPolicy } from "./diagnostic-runtime-Dd05ieBp.mjs";
import { T as shouldSkipQueueItem, f as completeFollowupRunLifecycle, g as applyQueueDropPolicy, n as FOLLOWUP_QUEUES, o as getExistingFollowupQueue, p as markFollowupRunEnqueued, s as getFollowupQueue, u as trimSummaryElisionsToCap, y as countPendingQueueItems } from "./state-BoXiWatG.mjs";
import { a as rememberFollowupDrainCallback, c as isFollowupRunAborted, d as createOverflowSummaryRetrySource, f as resolveFollowupDeliveryContextKey, l as resolveFollowupAbortSignal, n as dropAbortedFollowups, r as kickFollowupDrainIfIdle, t as clearFollowupDrainCallback } from "./drain-BqDIdzNv.mjs";
import "./cleanup-B8xvdWFA.mjs";
import { t as resolveQueueSettingsCore } from "./settings-DsJFYo7Y.mjs";
//#region src/auto-reply/reply/queue/recent-message-ids.ts
/**
* Keep queued message-id dedupe shared across bundled chunks so redeliveries
* are rejected no matter which chunk receives the enqueue call.
*/
const RECENT_QUEUE_MESSAGE_IDS = resolveGlobalDedupeCache(Symbol.for("openclaw.recentQueueMessageIdOwners"), {
ttlMs: 3e5,
maxSize: 1e4
});
function peekRecentQueueMessageId(key, now = Date.now()) {
return RECENT_QUEUE_MESSAGE_IDS.peek(key, now);
}
function recordRecentQueueMessageId(run, key, now = Date.now()) {
const ownerToken = {};
RECENT_QUEUE_MESSAGE_IDS.delete(key);
RECENT_QUEUE_MESSAGE_IDS.check(key, now, ownerToken);
const lifecycle = run.turnAdoptionLifecycle;
if (lifecycle) {
const onAbandoned = lifecycle.onAbandoned;
lifecycle.onAbandoned = () => {
RECENT_QUEUE_MESSAGE_IDS.delete(key, ownerToken);
onAbandoned?.();
};
}
}
function resetRecentQueuedMessageIdDedupe() {
RECENT_QUEUE_MESSAGE_IDS.clear();
}
//#endregion
//#region src/auto-reply/reply/queue/enqueue.ts
function followupMessageRouteIdentityKey(run) {
return JSON.stringify([channelRouteDedupeKey({
channel: run.originatingChannel,
to: run.originatingTo,
accountId: run.originatingAccountId,
threadId: run.originatingThreadId
}), normalizeChatType(run.originatingChatType) ?? ""]);
}
function buildRecentMessageIdKey(run, queueKey) {
const messageId = normalizeOptionalString(run.messageId);
if (!messageId) return;
return JSON.stringify([
"queue",
queueKey,
followupMessageRouteIdentityKey(run),
messageId
]);
}
function isRunAlreadyQueued(run, items) {
const messageId = normalizeOptionalString(run.messageId);
if (messageId) {
const messageRouteKey = followupMessageRouteIdentityKey(run);
return items.some((item) => normalizeOptionalString(item.messageId) === messageId && followupMessageRouteIdentityKey(item) === messageRouteKey);
}
return false;
}
function appendQueueItem(params) {
params.queue.lastEnqueuedAt = Date.now();
params.queue.lastRun = params.run.run;
params.run.queueAbortSignal = params.queue.abortController.signal;
params.queue.items[params.front ? "unshift" : "push"](params.run);
if (params.recentMessageIdKey) recordRecentQueueMessageId(params.run, params.recentMessageIdKey);
const runFollowup = params.runFollowup;
if (runFollowup) rememberFollowupDrainCallback(params.key, runFollowup);
const signal = resolveFollowupAbortSignal({
abortSignal: params.run.abortSignal,
operatorAuthority: params.run.operatorAuthority
});
const lifecycle = params.run.turnAdoptionLifecycle;
if (signal && lifecycle && runFollowup) {
const onAbort = () => {
const queue = getExistingFollowupQueue(params.key);
if (queue) dropAbortedFollowups(queue, runFollowup).catch((error) => {
defaultRuntime.error?.(`followup queue cancellation failed: ${String(error)}`);
});
};
const onSettled = lifecycle.onSettled;
lifecycle.onSettled = () => {
signal.removeEventListener("abort", onAbort);
onSettled?.();
};
signal.addEventListener("abort", onAbort, { once: true });
if (signal.aborted) onAbort();
}
if (params.restartIfIdle && !params.queue.draining) kickFollowupDrainIfIdle(params.key);
}
function enqueueFollowupRun(key, run, settings, dedupeMode = "message-id", runFollowup, restartIfIdle = true, options = {}) {
if (isFollowupRunAborted(run)) return false;
if (options.position === "front") run.protectFromQueueOverflow = true;
const recentMessageIdKey = dedupeMode !== "none" ? buildRecentMessageIdKey(run, key) : void 0;
if (recentMessageIdKey && peekRecentQueueMessageId(recentMessageIdKey)) return false;
const queue = getFollowupQueue(key, settings);
const dedupe = dedupeMode === "none" ? void 0 : isRunAlreadyQueued;
if (shouldSkipQueueItem({
item: run,
items: queue.items,
dedupe
})) return false;
if (options.steerCandidate || queue.items.some((item) => item.steerPending)) {
if (!markFollowupRunEnqueued(run)) return false;
if (options.steerCandidate) {
const { promise: acceptance, resolve: settle } = createDeferredCore();
run.steerPending = {
phase: "waiting",
predecessor: queue.steerAcceptanceTail,
settle
};
queue.steerAcceptanceTail = queue.steerAcceptanceTail.then(() => acceptance);
}
appendQueueItem({
key,
queue,
run,
recentMessageIdKey,
runFollowup,
restartIfIdle,
front: options.steerCandidate === true && options.position === "front"
});
return true;
}
const pendingCount = countPendingQueueItems(queue.items, queue.inFlight);
if (queue.dropPolicy === "new" && queue.cap > 0 && pendingCount >= queue.cap) {
run.onQueueDisposition?.("queue-cap-new");
completeFollowupRunLifecycle(run);
return false;
}
if (!markFollowupRunEnqueued(run)) return false;
if (!applyFollowupQueueOverflow(queue, run)) return false;
appendQueueItem({
key,
queue,
run,
recentMessageIdKey,
runFollowup,
restartIfIdle,
front: options.position === "front"
});
return true;
}
function applyFollowupQueueOverflow(queue, run) {
const elidedSummaryLines = [];
const shouldEnqueue = applyQueueDropPolicy({
queue,
inFlight: queue.inFlight,
summarize: (item) => {
const approved = item.userTurnTranscriptRecorder?.getPendingInputMessage?.();
return approved ? extractTextFromChatContent(approved.content, {
normalizeText: (text) => text,
joinWith: "\n"
}) ?? "" : normalizeOptionalString(item.summaryLine) || item.prompt.trim();
},
onSummaryElide: (lines) => elidedSummaryLines.push(...lines),
onDrop: (dropped) => {
if (queue.dropPolicy === "summarize") {
queue.summarySources.push(...dropped);
return;
}
for (const item of dropped) {
item.onQueueDisposition?.("queue-cap-old");
completeFollowupRunLifecycle(item);
}
},
isProtected: (item) => item.protectFromQueueOverflow === true
});
if (queue.dropPolicy === "summarize") {
const overflow = queue.summarySources.length - queue.summaryLines.length;
if (overflow > 0) {
const removed = queue.summarySources.splice(0, overflow);
for (const [index, item] of removed.entries()) {
const summaryLine = elidedSummaryLines[index];
if (summaryLine === void 0) throw new Error("followup queue summary source lost its elided line");
const contextKey = resolveFollowupDeliveryContextKey(item);
const lastElision = queue.summaryElisions.at(-1);
const compactSource = createOverflowSummaryRetrySource(item);
if (lastElision?.contextKey === contextKey) {
lastElision.count += 1;
lastElision.sources.push(compactSource);
lastElision.summaryLines.push(summaryLine);
lastElision.sourceRefs.set(item, compactSource);
} else queue.summaryElisions.push({
contextKey,
count: 1,
sources: [compactSource],
summaryLines: [summaryLine],
sourceRefs: new WeakMap([[item, compactSource]])
});
if (queue.activeSummarySources.has(item)) queue.activeSummarySources.add(compactSource);
trimSummaryElisionsToCap(queue);
}
}
}
if (!shouldEnqueue) {
run.onQueueDisposition?.(queue.dropPolicy === "new" ? "queue-cap-new" : "queue-cap");
completeFollowupRunLifecycle(run);
return false;
}
return true;
}
function getFollowupQueueDepth(key) {
const queue = getExistingFollowupQueue(key);
if (!queue) return 0;
return countPendingQueueItems(queue.items, queue.inFlight);
}
function settleParkedSteerAcceptance(key, run, accepted) {
const queue = getExistingFollowupQueue(key);
const pending = run.steerPending;
if (!queue?.items.includes(run) || !pending) return false;
pending.settle(accepted);
if (!accepted) {
delete run.steerPending;
reapplyDeferredOverflow(key);
kickFollowupDrainIfIdle(key);
}
return true;
}
function isParkedFollowupRunOwned(key, run) {
return getExistingFollowupQueue(key)?.items.includes(run) === true;
}
function reapplyDeferredOverflow(key) {
const queue = getExistingFollowupQueue(key);
if (!queue || queue.items.some((item) => item.steerPending) || countPendingQueueItems(queue.items, queue.inFlight) <= queue.cap) return;
const items = queue.items.splice(0);
for (const item of items) if (queue.inFlight.has(item) || applyFollowupQueueOverflow(queue, item)) queue.items.push(item);
}
/** Remove an exactly committed steer while preserving every sibling's FIFO position. */
function consumeParkedFollowupRun(key, run, disposition) {
const queue = getExistingFollowupQueue(key);
const index = queue?.items.indexOf(run) ?? -1;
if (!queue || index < 0) return false;
queue.items.splice(index, 1);
run.steerPending?.settle(true);
delete run.steerPending;
delete run.protectFromQueueOverflow;
reapplyDeferredOverflow(key);
completeFollowupRunLifecycle(run, disposition);
if (!queue.draining && queue.items.length === 0 && queue.inFlight.size === 0 && queue.droppedCount === 0 && FOLLOWUP_QUEUES.get(key) === queue) {
FOLLOWUP_QUEUES.delete(key);
clearFollowupDrainCallback(key);
} else kickFollowupDrainIfIdle(key);
return true;
}
function parkSteerCandidate(key, run, settings, runFollowup) {
if (!enqueueFollowupRun(key, run, settings, "message-id", runFollowup, false, { steerCandidate: true })) return;
logMessageQueuedWithBacklogPolicy({
sessionId: run.run.sessionId,
sessionKey: key,
channel: run.originatingChannel ?? run.run.messageProvider,
source: "followup-queue-steer"
}, false);
return {
async admit() {
const pending = run.steerPending;
await racePromiseWithAbortSignal(pending?.predecessor ?? Promise.resolve(true), resolveFollowupAbortSignal(run)).catch((error) => {
if (isFollowupRunAborted(run)) return false;
throw error;
});
if (isFollowupRunAborted(run) || !isParkedFollowupRunOwned(key, run)) return "cancelled";
if (!pending || run.steerPending !== pending) return "fallback";
pending.phase = "injecting";
return "steer";
},
accepted: (accepted) => settleParkedSteerAcceptance(key, run, accepted),
fallback: () => settleParkedSteerAcceptance(key, run, false),
consume: (disposition) => consumeParkedFollowupRun(key, run, disposition)
};
}
if (process.env.VITEST === "true" || false) globalThis[Symbol.for("openclaw.queueEnqueueTestApi")] = { resetRecentQueuedMessageIdDedupe };
//#endregion
//#region src/auto-reply/reply/queue/settings-runtime.ts
/** Resolves plugin-provided debounce defaults for a channel queue. */
function resolvePluginDebounce(channelKey) {
if (!channelKey) return;
const value = getLoadedChannelPlugin(channelKey)?.defaults?.queue?.debounceMs;
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : void 0;
}
/** Resolves queue settings with channel plugin defaults layered into core config. */
function resolveQueueSettings(params) {
const channelKey = normalizeOptionalLowercaseString(params.channel);
return resolveQueueSettingsCore({
...params,
pluginDebounceMs: params.pluginDebounceMs ?? resolvePluginDebounce(channelKey)
});
}
//#endregion
export { parkSteerCandidate as i, enqueueFollowupRun as n, getFollowupQueueDepth as r, resolveQueueSettings as t };