UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

321 lines (320 loc) 11.9 kB
import { y as uniqueStrings } from "./string-normalization-DsCfAx8q.js"; import { t as createLazyImportLoader } from "./lazy-promise-DGqyc4Y4.js"; import "./session-key-BnWWjqNc.js"; import { n as normalizeAccountId } from "./account-id-CETVCrTz.js"; import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js"; import { o as resolveSessionStorePathCore } from "./paths-CXdaYWF_.js"; import { r as getLoadedChannelPluginForRead } from "./registry-loaded-Bh7xuMJh.js"; import "./message-channel-constants-2zSoJXQC.js"; import "./message-channel-BQrhwUEA.js"; import { l as normalizeSessionDeliveryState } from "./delivery-context.shared-CXmRgetN.js"; import "./session-accessor-YsytfDtG.js"; import { u as loadSessionEntryReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js"; import { r as resolveAgentMainSessionKey } from "./main-session-Br0F9dzh.js"; import { r as stripTargetProviderPrefix } from "./channel-target-prefix-D7Ut6oKe.js"; import { t as resolveSessionDeliveryTarget } from "./targets-session-yUMvizHh.js"; import { r as isReservedTargetLiteralError } from "./target-errors-CmkAS9Ko.js"; import { t as resolveCronAgentSessionKey } from "./session-key-VAvgVMk2.js"; import { n as resolveCronStoredDeliveryContext } from "./delivery-context-zUJOeLlg.js"; import { t as resolveOutboundTargetWithPlugin } from "./targets-resolve-shared-1aPdLpJz.js"; //#region src/infra/outbound/targets-loaded.ts /** Resolves targets through an already-loaded channel plugin without bootstrap discovery. */ function tryResolveLoadedOutboundTarget(params) { return resolveOutboundTargetWithPlugin({ plugin: getLoadedChannelPluginForRead(params.channel), target: params }); } //#endregion //#region src/cron/isolated-agent/delivery-target.ts /** Resolves isolated cron delivery requests into concrete outbound targets. */ /** * Returns whether a delivery resolution names an external channel route. * Registration is intentionally irrelevant: an unavailable plugin route still * owes delivery. Only webchat/Control UI and an absent route are satisfied by * the durable session commit alone. */ function resolvedDeliveryTargetsExternalChannel(resolution) { return resolution.channel !== void 0 && resolution.channel !== "webchat"; } const targetsRuntimeLoader = createLazyImportLoader(() => import("./targets.runtime.js")); async function resolveOutboundTargetWithRuntime(params) { try { const loaded = tryResolveLoadedOutboundTarget(params); if (loaded) return loaded; const { resolveOutboundTarget } = await targetsRuntimeLoader.load(); return resolveOutboundTarget({ ...params, allowBootstrap: true }); } catch (err) { return { ok: false, error: /* @__PURE__ */ new Error(`Invalid delivery target: ${formatErrorMessage(err)}`) }; } } const channelSelectionRuntimeLoader = createLazyImportLoader(() => import("./channel-selection.runtime.js")); const deliveryTargetRuntimeLoader = createLazyImportLoader(() => import("./delivery-target.runtime.js")); function isNonEmptyThreadId(value) { return value != null && value !== ""; } function routesSharePeer(left, right) { return Boolean(left && right && left.baseSessionKey === right.baseSessionKey && left.peer.kind === right.peer.kind && left.peer.id === right.peer.id); } function shouldCarrySessionThread(params) { if (!isNonEmptyThreadId(params.resolved.threadId)) return false; if (!params.explicitTo) return params.resolved.channel === params.resolved.lastChannel && params.resolved.to === params.resolved.lastTo; return routesSharePeer(params.route, params.lastRoute); } function stripSelectedProviderPrefix(params) { const trimmed = params.to?.trim(); if (!trimmed) return; return stripTargetProviderPrefix(trimmed, params.channel).trim() || void 0; } function shouldStripResolvedTargetProviderPrefix(target) { return target.resolutionSource === "normalized"; } /** Resolves cron delivery config into a concrete channel target and optional thread/account. */ async function resolveDeliveryTarget(cfg, agentId, jobPayload, options) { const requestedChannel = typeof jobPayload.channel === "string" ? jobPayload.channel : "last"; const explicitTo = typeof jobPayload.to === "string" ? jobPayload.to : void 0; const allowMismatchedLastTo = requestedChannel === "last"; const deliveryTargetRuntime = await deliveryTargetRuntimeLoader.load(); const sessionCfg = cfg.session; const mainSessionKey = resolveAgentMainSessionKey({ cfg, agentId }); const storePath = resolveSessionStorePathCore(sessionCfg?.store, { agentId }); const rawSessionKey = jobPayload.sessionKey?.trim(); const threadSessionKey = rawSessionKey ? resolveCronAgentSessionKey({ sessionKey: rawSessionKey, agentId, mainKey: cfg.session?.mainKey, cfg }) : void 0; const storedDeliveryContext = resolveCronStoredDeliveryContext({ cfg, sessionKey: threadSessionKey }); const storedDeliveryEntry = storedDeliveryContext ? { sessionId: threadSessionKey ?? mainSessionKey, updatedAt: 0, delivery: normalizeSessionDeliveryState({ context: storedDeliveryContext }) } : void 0; const threadEntry = threadSessionKey ? loadSessionEntryReadOnly({ agentId, sessionKey: threadSessionKey, storePath }) : void 0; const mainEntry = loadSessionEntryReadOnly({ agentId, sessionKey: mainSessionKey, storePath }); const main = storedDeliveryEntry ?? threadEntry ?? mainEntry; const usedSharedMainFallback = mainEntry !== void 0 && main === mainEntry; const preliminary = resolveSessionDeliveryTarget({ entry: main, requestedChannel, explicitTo, explicitThreadId: jobPayload.threadId, allowMismatchedLastTo }); let fallbackChannel; let channelResolutionError; if (!preliminary.channel) { if (preliminary.lastChannel) fallbackChannel = preliminary.lastChannel; else try { const { resolveMessageChannelSelection } = await channelSelectionRuntimeLoader.load(); fallbackChannel = (await resolveMessageChannelSelection({ cfg })).channel; } catch (err) { const detail = formatErrorMessage(err); channelResolutionError = /* @__PURE__ */ new Error(`${detail} Set delivery.channel explicitly or use a main session with a previous channel.`); } } const resolved = fallbackChannel ? resolveSessionDeliveryTarget({ entry: main, requestedChannel, explicitTo, explicitThreadId: jobPayload.threadId, fallbackChannel, allowMismatchedLastTo, mode: preliminary.mode }) : preliminary; const channel = resolved.channel ?? fallbackChannel; const mode = resolved.mode; let toCandidate = resolved.to; let accountId = (typeof jobPayload.accountId === "string" ? jobPayload.accountId.trim() || void 0 : void 0) ?? resolved.accountId; if (!accountId && channel) accountId = deliveryTargetRuntime.resolveFirstBoundAccountId({ cfg, channelId: channel, agentId }); if (!channel) return { ok: false, channel: void 0, to: void 0, accountId, threadId: void 0, mode, error: channelResolutionError ?? /* @__PURE__ */ new Error("Channel is required when delivery.channel=last has no previous channel.") }; const explicitThreadId = isNonEmptyThreadId(jobPayload.threadId) ? jobPayload.threadId : void 0; let effectiveAllowFrom; if (mode === "implicit") { const { getLoadedChannelPluginForRead, mapAllowFromEntries } = deliveryTargetRuntime; const channelPlugin = getLoadedChannelPluginForRead(channel); const resolvedAccountId = normalizeAccountId(accountId); const configuredAllowFromRaw = channelPlugin?.config.resolveAllowFrom?.({ cfg, accountId: resolvedAccountId }); const configuredAllowFrom = configuredAllowFromRaw ? mapAllowFromEntries(configuredAllowFromRaw) : []; const allowFromOverride = uniqueStrings(configuredAllowFrom); effectiveAllowFrom = allowFromOverride; if (toCandidate && allowFromOverride.length > 0) { if (!(await resolveOutboundTargetWithRuntime({ channel, to: toCandidate, cfg, accountId, mode, allowFrom: effectiveAllowFrom })).ok) toCandidate = allowFromOverride[0]; } } if (!rawSessionKey && mode === "implicit" && !explicitTo && usedSharedMainFallback && toCandidate != null && toCandidate === resolved.lastTo) return { ok: false, channel, to: void 0, accountId, threadId: explicitThreadId, mode, error: /* @__PURE__ */ new Error("Refusing implicit isolated cron delivery: the target would be inherited from the shared agent-main session bucket's last recipient, which is ambiguous across conversations and can deliver to the wrong room (and replay there after a restart). Set delivery.channel and delivery.to explicitly, or run the cron from a session that carries its own delivery context.") }; const preResolvedRouteTargetCandidate = toCandidate; const docked = await resolveOutboundTargetWithRuntime({ channel, to: toCandidate, cfg, accountId, mode, allowFrom: effectiveAllowFrom }); if (!docked.ok) { if (!toCandidate || !isReservedTargetLiteralError(docked.error)) return { ok: false, channel, to: void 0, accountId, threadId: explicitThreadId, mode, error: docked.error }; } else toCandidate = docked.to; const targetResolution = await deliveryTargetRuntime.resolveChannelTargetForDelivery({ cfg, channel, agentId, input: toCandidate, accountId }); if (!targetResolution.ok) return { ok: false, channel, to: void 0, accountId, threadId: explicitThreadId, mode, error: targetResolution.error }; const resolvedTarget = targetResolution.target; const routeTargetCandidate = resolvedTarget.source === "directory" ? resolvedTarget.to : preResolvedRouteTargetCandidate ?? toCandidate; const selectedTarget = shouldStripResolvedTargetProviderPrefix(resolvedTarget) ? stripSelectedProviderPrefix({ channel, to: resolvedTarget.to }) : resolvedTarget.to.trim(); if (!selectedTarget) return { ok: false, channel, to: void 0, accountId, threadId: explicitThreadId, mode, error: /* @__PURE__ */ new Error("Target is required") }; toCandidate = selectedTarget; const route = await (async () => { try { return await deliveryTargetRuntime.resolveOutboundSessionRouteForDelivery({ cfg, channel, agentId, accountId, target: routeTargetCandidate, resolvedTarget, threadId: explicitThreadId, currentSessionKey: threadSessionKey ?? mainSessionKey }); } catch { return null; } })(); const routeCanCanonicalizeTarget = deliveryTargetRuntime.channelCanResolveOutboundSessionRoute({ cfg, channel, agentId }); const routeShouldCanonicalizeTarget = route && (route.threadId !== void 0 || route.to !== routeTargetCandidate); if (route && routeCanCanonicalizeTarget && routeShouldCanonicalizeTarget) { const routeTo = stripSelectedProviderPrefix({ channel, to: route.to }); if (!routeTo) return { ok: false, channel, to: void 0, accountId, threadId: explicitThreadId, mode, error: /* @__PURE__ */ new Error("Target is required") }; toCandidate = routeTo; } const lastTo = resolved.lastTo; const lastRoute = lastTo && resolved.lastChannel === channel ? await (async () => { try { return await deliveryTargetRuntime.resolveOutboundSessionRouteForDelivery({ cfg, channel, agentId, accountId: resolved.lastAccountId ?? accountId, target: lastTo, threadId: resolved.lastThreadId, currentSessionKey: threadSessionKey ?? mainSessionKey }); } catch { return null; } })() : null; const canUseSessionThread = options?.inheritSessionThread !== false && shouldCarrySessionThread({ resolved, explicitTo, route, lastRoute }); const threadId = explicitThreadId ?? route?.threadId ?? (canUseSessionThread ? resolved.threadId : void 0); return { ok: true, channel, to: toCandidate, accountId, threadId, mode }; } //#endregion export { resolvedDeliveryTargetsExternalChannel as n, resolveDeliveryTarget as t };