UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

278 lines (277 loc) 11.5 kB
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { t as createDeferredCore } from "./deferred-D0La5CRk.js"; import { t as normalizeChatType } from "./chat-type-CG0X_HJM.js"; import { n as channelRouteDedupeKey } from "./channel-route--2in3Ckg.js"; import { n as getLoadedChannelPlugin } from "./registry-Cz6cv4VC.js"; import "./plugins-Cozj1Enf.js"; import { n as extractTextFromChatContent } from "./chat-content-DNdfeXZh.js"; import { a as logMessageQueuedWithBacklogPolicy } from "./diagnostic-runtime-Cq2Cubt3.js"; import { A as shouldSkipQueueItem, a as getExistingFollowupQueue, b as resetRecentQueuedMessageIdDedupe, f as completeFollowupRunLifecycle, h as markFollowupRunEnqueued, l as trimSummaryElisionsToCap, o as getFollowupQueue, p as isFollowupRunAborted, r as FOLLOWUP_QUEUES, t as resolveQueueSettingsCore, v as peekRecentQueueMessageId, w as countPendingQueueItems, x as applyQueueDropPolicy, y as recordRecentQueueMessageId } from "./settings-jmDsm5RI.js"; import { a as rememberFollowupDrainCallback, n as createOverflowSummaryRetrySource, o as resolveFollowupDeliveryContextKey, r as kickFollowupDrainIfIdle, s as resolveFollowupReplyAnchor, t as clearFollowupDrainCallback } from "./drain-Bt23d2Dt.js"; import "./cleanup-DKkilV3a.js"; //#region src/auto-reply/reply/queue/enqueue.ts function followupRouteIdentityKey(run) { return JSON.stringify([ channelRouteDedupeKey({ channel: run.originatingChannel, to: run.originatingTo, accountId: run.originatingAccountId, threadId: run.originatingThreadId }), resolveFollowupReplyAnchor(run) ?? "", run.originatingReplyToMode ?? "", normalizeChatType(run.originatingChatType) ?? "" ]); } 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, allowPromptFallback = false) { const messageId = normalizeOptionalString(run.messageId); if (messageId) { const messageRouteKey = followupMessageRouteIdentityKey(run); return items.some((item) => normalizeOptionalString(item.messageId) === messageId && followupMessageRouteIdentityKey(item) === messageRouteKey); } if (!allowPromptFallback) return false; const routeKey = followupRouteIdentityKey(run); return items.some((item) => item.prompt === run.prompt && followupRouteIdentityKey(item) === routeKey); } 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); if (params.runFollowup) rememberFollowupDrainCallback(params.key, params.runFollowup); 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; if (options.steerCandidate) run.steerAnchor = 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 : (item, items) => isRunAlreadyQueued(item, items, dedupeMode === "prompt"); if (shouldSkipQueueItem({ item: run, items: queue.items, dedupe })) return false; if (options.steerCandidate) { if (!markFollowupRunEnqueued(run)) return false; const { promise: acceptance, resolve: settle } = createDeferredCore(); run.steerPending = { predecessor: queue.steerAcceptanceTail, settle }; queue.steerAcceptanceTail = acceptance; appendQueueItem({ key, queue, run, recentMessageIdKey, runFollowup, restartIfIdle, front: options.position === "front" }); return true; } if (queue.items.some((item) => item.steerPending)) { if (!markFollowupRunEnqueued(run)) return false; appendQueueItem({ key, queue, run, recentMessageIdKey, runFollowup, restartIfIdle, front: false }); return true; } const pendingCount = countPendingQueueItems(queue.items, queue.inFlight); if (!options.steerCandidate && queue.dropPolicy === "new" && queue.cap > 0 && pendingCount >= queue.cap) { run.onQueueDisposition?.("queue-cap-new"); completeFollowupRunLifecycle(run); return false; } if (!markFollowupRunEnqueued(run)) return false; 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 || item.steerAnchor === 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); if (lastElision?.contextKey === contextKey) { const compactSource = createOverflowSummaryRetrySource(item); lastElision.count += 1; lastElision.sources.push(compactSource); lastElision.summaryLines.push(summaryLine); lastElision.sourceRefs.set(item, compactSource); if (queue.activeSummarySources.has(item)) queue.activeSummarySources.add(compactSource); } else { const compactSource = createOverflowSummaryRetrySource(item); 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-cap"); completeFollowupRunLifecycle(run); return false; } appendQueueItem({ key, queue, run, recentMessageIdKey, runFollowup, restartIfIdle, front: options.position === "front" }); 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)) return; const lastAnchor = queue.items.findLastIndex((item) => item.steerAnchor === true); const suffix = queue.items.splice(lastAnchor + 1); if (suffix.length === 0) return; const originalCap = queue.cap; const settings = { mode: queue.mode, debounceMs: queue.debounceMs, cap: originalCap + lastAnchor + 1, dropPolicy: queue.dropPolicy }; for (const item of suffix) if (!enqueueFollowupRun(key, item, settings, "none", void 0, false)) completeFollowupRunLifecycle(item); queue.cap = originalCap; } /** 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; delete run.steerAnchor; 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 predecessorAccepted = await run.steerPending?.predecessor ?? true; if (isFollowupRunAborted(run) || !isParkedFollowupRunOwned(key, run)) return "cancelled"; return predecessorAccepted ? "steer" : "fallback"; }, 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 };