openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
421 lines (420 loc) • 16.4 kB
JavaScript
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 };