UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

421 lines (420 loc) 16.4 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js"; import { l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js"; import { a as normalizeAnyChannelId } from "./registry-9YoS2BJP.js"; import { t as INTERNAL_MESSAGE_CHANNEL } from "./message-channel-constants-CgI32lqD.js"; import { r as listLoadedChannelPlugins, t as getLoadedChannelPluginById } from "./registry-loaded-C3DZQnLP.js"; import { i as normalizeMessageChannel } from "./message-channel-normalize-BLLf0ubu.js"; import { r as isInternalMessageChannel } from "./message-channel-BiOeMu0l.js"; import { a as isNativeCommandTurn, s as resolveCommandTurnContext } from "./command-turn-context-YXLLZVCi.js"; //#region src/auto-reply/sender-identity.ts /** Shared sender identity helpers for authorization checks. */ function isConversationLikeIdentity(value) { const normalized = normalizeOptionalLowercaseString(value); if (!normalized) return false; if (normalized.startsWith("chat_id:")) return true; return /(^|:)(channel|group|thread|topic|room|space|spaces):/.test(normalized); } function shouldUseFromAsSenderFallback(params) { const from = normalizeOptionalString(params.from) ?? ""; if (!from) return false; const chatType = normalizeLowercaseStringOrEmpty(params.chatType); if (chatType && chatType !== "direct") return false; return !isConversationLikeIdentity(from); } //#endregion //#region src/auto-reply/command-auth.ts /** Command authorization helpers for owner and allowlist checks. */ function resolveProviderFromContext(ctx, cfg) { const explicitMessageChannels = [ ctx.Surface, ctx.OriginatingChannel, ctx.Provider ].map((value) => normalizeMessageChannel(value)).filter((value) => Boolean(value)); const explicitMessageChannel = explicitMessageChannels.find((value) => value !== INTERNAL_MESSAGE_CHANNEL); if (!explicitMessageChannel && explicitMessageChannels.includes("webchat")) return { providerId: void 0, hadResolutionError: false }; const direct = normalizeAnyChannelId(explicitMessageChannel ?? void 0) ?? explicitMessageChannel ?? normalizeAnyChannelId(ctx.Provider) ?? normalizeAnyChannelId(ctx.Surface) ?? normalizeAnyChannelId(ctx.OriginatingChannel); if (direct) return { providerId: direct, hadResolutionError: false }; const candidates = [ctx.From, ctx.To].filter((value) => Boolean(value?.trim())).flatMap((value) => value.split(":").map((part) => part.trim())); for (const candidate of candidates) { const normalizedCandidateChannel = normalizeMessageChannel(candidate); if (normalizedCandidateChannel === "webchat") return { providerId: void 0, hadResolutionError: false }; const normalized = normalizeAnyChannelId(normalizedCandidateChannel ?? void 0) ?? normalizedCandidateChannel ?? normalizeAnyChannelId(candidate); if (normalized) return { providerId: normalized, hadResolutionError: false }; } const inferredProviders = probeInferredProviders(ctx, cfg); const inferred = inferredProviders.candidates; if (inferred.length === 1) return { providerId: inferred[0].providerId, hadResolutionError: inferred[0].hadResolutionError }; return { providerId: void 0, hadResolutionError: inferredProviders.droppedResolutionError || inferred.some((entry) => entry.hadResolutionError) }; } function probeInferredProviders(ctx, cfg) { let droppedResolutionError = false; return { candidates: listLoadedChannelPlugins().map((plugin) => { const resolvedAllowFrom = buildProviderAllowFromResolution({ plugin, cfg, accountId: ctx.AccountId }); if (resolvedAllowFrom.allowFromList.length === 0) { if (resolvedAllowFrom.hadResolutionError) droppedResolutionError = true; return null; } return { providerId: plugin.id, hadResolutionError: resolvedAllowFrom.hadResolutionError }; }).filter((value) => Boolean(value)), droppedResolutionError }; } function formatAllowFromList(params) { const { plugin, cfg, accountId, allowFrom } = params; if (!allowFrom || allowFrom.length === 0) return []; if (plugin?.config?.formatAllowFrom) return plugin.config.formatAllowFrom({ cfg, accountId, allowFrom }); return normalizeStringEntries(allowFrom); } function normalizeAllowFromEntry(params) { return formatAllowFromList({ plugin: params.plugin, cfg: params.cfg, accountId: params.accountId, allowFrom: [params.value] }).filter((entry) => entry.trim().length > 0); } function isWildcardAllowFromEntry(entry) { return entry.trim() === "*"; } function hasWildcardAllowFrom(list) { return list.some((entry) => isWildcardAllowFromEntry(entry)); } function stripWildcardAllowFrom(list) { return list.filter((entry) => !isWildcardAllowFromEntry(entry)); } function resolveProviderAllowFrom(params) { const { plugin, cfg, accountId } = params; const providerId = plugin?.id; if (!plugin?.config?.resolveAllowFrom) return { allowFrom: resolveFallbackAllowFrom({ cfg, providerId, accountId }), hadResolutionError: false }; try { const allowFrom = plugin.config.resolveAllowFrom({ cfg, accountId }); if (allowFrom == null) return { allowFrom: [], hadResolutionError: false }; if (!Array.isArray(allowFrom)) { console.warn(`[command-auth] resolveAllowFrom returned an invalid allowFrom for provider "${providerId}", falling back to config allowFrom: invalid_result`); return { allowFrom: resolveFallbackAllowFrom({ cfg, providerId, accountId }), hadResolutionError: true }; } return { allowFrom, hadResolutionError: false }; } catch (err) { console.warn(`[command-auth] resolveAllowFrom threw for provider "${providerId}", falling back to config allowFrom: ${describeAllowFromResolutionError(err)}`); return { allowFrom: resolveFallbackAllowFrom({ cfg, providerId, accountId }), hadResolutionError: true }; } } function buildProviderAllowFromResolution(params) { const providerId = params.providerId ?? params.plugin?.id; const resolvedAllowFrom = params.forceFallbackResolutionError ? { allowFrom: resolveFallbackAllowFrom({ cfg: params.cfg, providerId, accountId: params.accountId }), hadResolutionError: true } : resolveProviderAllowFrom({ plugin: params.plugin, cfg: params.cfg, accountId: params.accountId }); return { ...resolvedAllowFrom, allowFromList: formatAllowFromList({ plugin: params.plugin, cfg: params.cfg, accountId: params.accountId, allowFrom: resolvedAllowFrom.allowFrom }) }; } function describeAllowFromResolutionError(err) { if (err instanceof Error) return (normalizeOptionalString(err.name) ?? "") || "Error"; return "unknown_error"; } function resolveOwnerAllowFromList(params) { const raw = params.allowFrom ?? params.cfg.commands?.ownerAllowFrom; if (!Array.isArray(raw) || raw.length === 0) return []; const filtered = []; for (const entry of raw) { const trimmed = normalizeOptionalString(String(entry ?? "")) ?? ""; if (!trimmed) continue; const separatorIndex = trimmed.indexOf(":"); if (separatorIndex > 0) { const channel = normalizeAnyChannelId(trimmed.slice(0, separatorIndex)); if (channel) { if (!params.providerId || channel !== params.providerId) continue; const remainder = trimmed.slice(separatorIndex + 1).trim(); if (remainder) filtered.push(remainder); continue; } } filtered.push(trimmed); } return formatAllowFromList({ plugin: params.plugin, cfg: params.cfg, accountId: params.accountId, allowFrom: filtered }); } /** * Resolves the commands.allowFrom list for a given provider. * Returns the provider-specific list if defined, otherwise the "*" global list. * Returns null if commands.allowFrom is not configured at all (fall back to channel allowFrom). */ function resolveCommandsAllowFromList(params) { const { plugin, cfg, accountId, providerId } = params; const commandsAllowFrom = cfg.commands?.allowFrom; if (!commandsAllowFrom || typeof commandsAllowFrom !== "object") return null; const providerList = commandsAllowFrom[providerId ?? ""]; const globalList = commandsAllowFrom["*"]; const rawList = Array.isArray(providerList) ? providerList : globalList; if (!Array.isArray(rawList)) return null; return formatAllowFromList({ plugin, cfg, accountId, allowFrom: rawList }); } function resolveOwnerCandidatesForCommands(params) { if (params.allowAll) return []; const ownerCandidatesForCommands = stripWildcardAllowFrom(params.allowFromList); if (ownerCandidatesForCommands.length > 0 || !params.to) return ownerCandidatesForCommands; const normalizedTo = normalizeAllowFromEntry({ plugin: params.plugin, cfg: params.cfg, accountId: params.accountId, value: params.to }); return normalizedTo.length > 0 ? [...ownerCandidatesForCommands, ...normalizedTo] : []; } function resolveOwnerAuthorizationState(params) { const configOwnerAllowFromList = resolveOwnerAllowFromList({ plugin: params.plugin, cfg: params.cfg, accountId: params.accountId, providerId: params.providerId, allowFrom: params.configOwnerAllowFrom }); const contextOwnerAllowFromList = resolveOwnerAllowFromList({ plugin: params.plugin, cfg: params.cfg, accountId: params.accountId, providerId: params.providerId, allowFrom: params.contextOwnerAllowFrom }); const allowAll = !params.hadResolutionError && (params.allowFromList.length === 0 || hasWildcardAllowFrom(params.allowFromList)); const ownerCandidatesForCommands = resolveOwnerCandidatesForCommands({ plugin: params.plugin, cfg: params.cfg, accountId: params.accountId, to: params.to, allowAll, allowFromList: params.allowFromList }); const ownerAllowAll = hasWildcardAllowFrom(configOwnerAllowFromList); const explicitOwners = stripWildcardAllowFrom(configOwnerAllowFromList); const explicitOverrides = stripWildcardAllowFrom(contextOwnerAllowFromList); return { allowAll, ownerAllowAll, ownerCandidatesForCommands, explicitOwners, ownerList: Array.from(new Set(explicitOwners.length > 0 ? explicitOwners : ownerAllowAll ? [] : explicitOverrides.length > 0 ? explicitOverrides : ownerCandidatesForCommands)) }; } function resolveCommandSenderAuthorization(params) { if (params.enforceOwnerForCommands && !params.isOwnerForCommands) return false; if (params.commandsAllowFromList !== null || params.providerResolutionError && params.commandsAllowFromConfigured) { const commandsAllowFromList = params.commandsAllowFromList; const commandsAllowAll = !params.providerResolutionError && Boolean(commandsAllowFromList && hasWildcardAllowFrom(commandsAllowFromList)); const matchedCommandsAllowFrom = commandsAllowFromList?.length ? params.senderCandidates.find((candidate) => commandsAllowFromList.includes(candidate)) : void 0; return !params.providerResolutionError && (commandsAllowAll || Boolean(matchedCommandsAllowFrom)); } return params.commandAuthorized && (params.isOwnerForCommands || params.nativeCommandAuthorized); } function resolveSenderCandidates(params) { const { plugin, cfg, accountId } = params; const candidates = []; const pushCandidate = (value) => { const trimmed = normalizeOptionalString(value) ?? ""; if (!trimmed) return; candidates.push(trimmed); }; if (plugin?.commands?.preferSenderE164ForCommands) { pushCandidate(params.senderE164); pushCandidate(params.senderId); } else { pushCandidate(params.senderId); pushCandidate(params.senderE164); } if (candidates.length === 0 && shouldUseFromAsSenderFallback({ from: params.from, chatType: params.chatType })) pushCandidate(params.from); const normalized = []; for (const sender of candidates) { const entries = normalizeAllowFromEntry({ plugin, cfg, accountId, value: sender }); for (const entry of entries) if (!normalized.includes(entry)) normalized.push(entry); } return normalized; } function resolveFallbackAllowFrom(params) { const providerId = normalizeOptionalString(params.providerId); if (!providerId) return []; const channelCfg = params.cfg.channels?.[providerId]; const accountCfg = resolveFallbackAccountConfig(channelCfg?.accounts, params.accountId) ?? resolveFallbackDefaultAccountConfig(channelCfg); const allowFrom = accountCfg?.allowFrom ?? accountCfg?.dm?.allowFrom ?? channelCfg?.allowFrom ?? channelCfg?.dm?.allowFrom; return Array.isArray(allowFrom) ? allowFrom : []; } function resolveFallbackAccountConfig(accounts, accountId) { const normalizedAccountId = normalizeOptionalLowercaseString(accountId); if (!accounts || !normalizedAccountId) return; const direct = accounts[normalizedAccountId]; if (direct) return direct; const matchKey = Object.keys(accounts).find((key) => normalizeOptionalLowercaseString(key) === normalizedAccountId); return matchKey ? accounts[matchKey] : void 0; } function resolveFallbackDefaultAccountConfig(channelCfg) { const accounts = channelCfg?.accounts; if (!accounts) return; const preferred = resolveFallbackAccountConfig(accounts, channelCfg?.defaultAccount) ?? resolveFallbackAccountConfig(accounts, "default"); if (preferred) return preferred; const definedAccounts = Object.values(accounts).filter(Boolean); return definedAccounts.length === 1 ? definedAccounts[0] : void 0; } function resolveCommandAuthorization(params) { const { ctx, cfg, commandAuthorized } = params; const { providerId, hadResolutionError: providerResolutionError } = resolveProviderFromContext(ctx, cfg); const plugin = providerId ? getLoadedChannelPluginById(providerId) ?? void 0 : void 0; const from = normalizeOptionalString(ctx.From) ?? ""; const to = normalizeOptionalString(ctx.To) ?? ""; const commandsAllowFromConfigured = Boolean(cfg.commands?.allowFrom && typeof cfg.commands.allowFrom === "object"); const commandsAllowFromList = resolveCommandsAllowFromList({ plugin, cfg, accountId: ctx.AccountId, providerId }); const resolvedAllowFrom = buildProviderAllowFromResolution({ plugin, cfg, accountId: ctx.AccountId, providerId, forceFallbackResolutionError: providerResolutionError }); const ownerState = resolveOwnerAuthorizationState({ plugin, cfg, accountId: ctx.AccountId, providerId, to, allowFromList: resolvedAllowFrom.allowFromList, hadResolutionError: resolvedAllowFrom.hadResolutionError, configOwnerAllowFrom: cfg.commands?.ownerAllowFrom, contextOwnerAllowFrom: ctx.OwnerAllowFrom }); const senderCandidates = resolveSenderCandidates({ plugin, providerId, cfg, accountId: ctx.AccountId, senderId: ctx.SenderId, senderE164: ctx.SenderE164, from, chatType: ctx.ChatType }); const matchedSender = ownerState.ownerList.length ? senderCandidates.find((candidate) => ownerState.ownerList.includes(candidate)) : void 0; const matchedCommandOwner = ownerState.ownerCandidatesForCommands.length ? senderCandidates.find((candidate) => ownerState.ownerCandidatesForCommands.includes(candidate)) : void 0; const senderId = matchedSender ?? senderCandidates[0]; const enforceOwner = Boolean(plugin?.commands?.enforceOwnerForCommands); const senderIsOwnerByIdentity = Boolean(matchedSender); const senderIsOwnerByScope = isInternalMessageChannel(ctx.Provider) && Array.isArray(ctx.GatewayClientScopes) && ctx.GatewayClientScopes.includes("operator.admin"); const ownerAllowlistConfigured = ownerState.ownerAllowAll || ownerState.explicitOwners.length > 0; const senderIsOwner = senderIsOwnerByIdentity || senderIsOwnerByScope || ownerState.ownerAllowAll; const requireOwner = enforceOwner || ownerAllowlistConfigured; const isOwnerForCommands = !requireOwner ? true : ownerState.ownerAllowAll ? true : ownerAllowlistConfigured ? senderIsOwner : senderIsOwnerByScope || Boolean(matchedCommandOwner); const isAuthorizedSender = resolveCommandSenderAuthorization({ commandAuthorized, enforceOwnerForCommands: enforceOwner, nativeCommandAuthorized: commandAuthorized && isNativeCommandTurn(resolveCommandTurnContext(ctx)) && !requireOwner, isOwnerForCommands, senderCandidates, commandsAllowFromList, providerResolutionError, commandsAllowFromConfigured }); return { providerId, ownerList: ownerState.ownerList, senderId: senderId || void 0, senderIsOwner, isAuthorizedSender, from: from || void 0, to: to || void 0 }; } //#endregion export { shouldUseFromAsSenderFallback as n, resolveCommandAuthorization as t };