openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
199 lines (198 loc) • 8.04 kB
JavaScript
import { h as normalizeUniqueStringEntries } from "./string-normalization-DsCfAx8q.js";
import { c as resolveAgentConfig } from "./agent-scope-config-DcbEhP0R.js";
import { t as getChannelPlugin } from "./registry-Cz6cv4VC.js";
import "./plugins-Cozj1Enf.js";
import { t as MessageActionDeniedError } from "./message-action-denial-DOmE5Ll7.js";
import { a as normalizeTargetForProvider } from "./target-normalization-RYHhn0bW.js";
import { n as lookupDirectoryDisplay, t as formatTargetDisplay } from "./target-resolver-q_MBPTOw.js";
//#region src/infra/outbound/outbound-policy.ts
const CONTEXT_GUARDED_ACTIONS = /* @__PURE__ */ new Set([
"send",
"poll",
"poll-vote",
"reply",
"sendWithEffect",
"sendAttachment",
"upload-file",
"edit",
"delete",
"pin",
"unpin",
"thread-create",
"thread-reply",
"sticker"
]);
const CONTEXT_MARKER_ACTIONS = /* @__PURE__ */ new Set([
"send",
"poll",
"reply",
"sendWithEffect",
"sendAttachment",
"upload-file",
"thread-reply",
"sticker"
]);
function resolveContextGuardTarget(action, params) {
if (!CONTEXT_GUARDED_ACTIONS.has(action)) return;
if (action === "thread-reply" || action === "thread-create") {
if (typeof params.channelId === "string") return params.channelId;
if (typeof params.to === "string") return params.to;
return;
}
if (typeof params.to === "string") return params.to;
if (typeof params.channelId === "string") return params.channelId;
}
function normalizeTarget(channel, raw) {
return normalizeTargetForProvider(channel, raw) ?? raw.trim();
}
function isCrossContextTarget(params) {
if (params.toolContext && getChannelPlugin(params.channel)?.threading?.matchesToolContextTarget?.({
target: params.target,
toolContext: params.toolContext
})) return false;
const currentTargets = [params.toolContext?.currentMessagingTarget?.trim(), params.toolContext?.currentChannelId?.trim()].filter((target) => Boolean(target));
if (currentTargets.length === 0) return false;
const normalizedTarget = normalizeTarget(params.channel, params.target);
if (!normalizedTarget) return false;
return !currentTargets.some((currentTarget) => normalizeTarget(params.channel, currentTarget) === normalizedTarget);
}
function resolveAgentMessageToolsConfig(cfg, agentId) {
const trimmedAgentId = agentId?.trim();
const globalConfig = cfg.tools?.message;
if (!trimmedAgentId) return globalConfig;
const agentConfig = resolveAgentConfig(cfg, trimmedAgentId)?.tools?.message;
if (!agentConfig) return globalConfig;
return {
...globalConfig,
...agentConfig,
crossContext: globalConfig?.crossContext || agentConfig.crossContext ? {
...globalConfig?.crossContext,
...agentConfig.crossContext,
marker: globalConfig?.crossContext?.marker || agentConfig.crossContext?.marker ? {
...globalConfig?.crossContext?.marker,
...agentConfig.crossContext?.marker
} : void 0
} : void 0,
broadcast: globalConfig?.broadcast || agentConfig.broadcast ? {
...globalConfig?.broadcast,
...agentConfig.broadcast
} : void 0,
actions: globalConfig?.actions || agentConfig.actions ? {
...globalConfig?.actions,
...agentConfig.actions
} : void 0
};
}
/**
* Resolves the message-tool policy after applying any agent-specific overrides.
*/
function resolveEffectiveMessageToolsConfig(params) {
return resolveAgentMessageToolsConfig(params.cfg, params.agentId);
}
/**
* Returns the normalized allowed message actions for an agent or the global policy.
*/
function resolveAllowedMessageActions(params) {
const allow = resolveEffectiveMessageToolsConfig(params)?.actions?.allow;
if (!allow) return;
const normalized = normalizeUniqueStringEntries(allow);
return normalized.length > 0 ? normalized : void 0;
}
/**
* Rejects disabled message actions before channel-specific send handling runs.
*/
function enforceMessageActionAllowlist(params) {
const allowed = resolveAllowedMessageActions(params);
if (!allowed || allowed.includes(params.action)) return;
throw new MessageActionDeniedError(`Message action "${params.action}" is disabled for this agent.`, "message_action_disabled", "message-actions:allowlist");
}
/**
* Enforces cross-context message-send policy for a bound channel/thread context.
*/
function enforceCrossContextPolicy(params) {
const currentTarget = params.toolContext?.currentChannelId?.trim() ?? params.toolContext?.currentMessagingTarget?.trim();
if (!currentTarget) return;
if (!CONTEXT_GUARDED_ACTIONS.has(params.action)) return;
const messageConfig = resolveEffectiveMessageToolsConfig({
cfg: params.cfg,
agentId: params.agentId
});
const currentProvider = params.toolContext?.currentChannelProvider;
const allowWithinProvider = messageConfig?.crossContext?.allowWithinProvider !== false;
const allowAcrossProviders = messageConfig?.crossContext?.allowAcrossProviders === true;
if (currentProvider && currentProvider !== params.channel) {
if (!allowAcrossProviders) throw new MessageActionDeniedError(`Cross-context messaging denied: action=${params.action} target provider "${params.channel}" while bound to "${currentProvider}".`, "message_cross_context_denied", "message-cross-context:provider");
return;
}
if (allowWithinProvider) return;
const target = resolveContextGuardTarget(params.action, params.args);
if (!target) return;
if (!isCrossContextTarget({
channel: params.channel,
target,
toolContext: params.toolContext
})) return;
throw new MessageActionDeniedError(`Cross-context messaging denied: action=${params.action} target="${target}" while bound to "${currentTarget}" (channel=${params.channel}).`, "message_cross_context_denied", "message-cross-context:target");
}
/**
* Builds cross-context marker text or a channel-native presentation for forwarded sends.
*/
async function buildCrossContextDecoration(params) {
const currentTarget = params.toolContext?.currentChannelId ?? params.toolContext?.currentMessagingTarget;
if (!currentTarget) return null;
if (params.toolContext?.skipCrossContextDecoration) return null;
if (!isCrossContextTarget(params)) return null;
const markerConfig = resolveEffectiveMessageToolsConfig({
cfg: params.cfg,
agentId: params.agentId
})?.crossContext?.marker;
if (markerConfig?.enabled === false) return null;
const currentName = await lookupDirectoryDisplay({
cfg: params.cfg,
channel: params.channel,
targetId: currentTarget,
accountId: params.accountId ?? void 0
}) ?? currentTarget;
const originLabel = formatTargetDisplay({
channel: params.channel,
target: currentTarget,
display: currentName
});
const prefixTemplate = markerConfig?.prefix ?? "[from {channel}] ";
const suffixTemplate = markerConfig?.suffix ?? "";
const prefix = prefixTemplate.replaceAll("{channel}", originLabel);
const suffix = suffixTemplate.replaceAll("{channel}", originLabel);
const buildPresentation = getChannelPlugin(params.channel)?.messaging?.buildCrossContextPresentation;
return {
prefix,
suffix,
presentationBuilder: buildPresentation ? (message) => buildPresentation({
originLabel,
message,
cfg: params.cfg,
accountId: params.accountId ?? void 0
}) : void 0
};
}
/**
* Reports whether an action can carry a cross-context marker in outbound payloads.
*/
function shouldApplyCrossContextMarker(action) {
return CONTEXT_MARKER_ACTIONS.has(action);
}
/**
* Applies text markers or a preferred rich presentation to a cross-context message.
*/
function applyCrossContextDecoration(params) {
if (params.preferPresentation && params.decoration.presentationBuilder) return {
message: params.message,
presentation: params.decoration.presentationBuilder?.(params.message),
usedPresentation: true
};
return {
message: `${params.decoration.prefix}${params.message}${params.decoration.suffix}`,
usedPresentation: false
};
}
//#endregion
export { resolveAllowedMessageActions as a, enforceMessageActionAllowlist as i, buildCrossContextDecoration as n, resolveEffectiveMessageToolsConfig as o, enforceCrossContextPolicy as r, shouldApplyCrossContextMarker as s, applyCrossContextDecoration as t };