openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
372 lines (371 loc) • 16.2 kB
JavaScript
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { m as normalizeUniqueSingleOrTrimmedStringList, y as uniqueStrings } from "./string-normalization-DsCfAx8q.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { c as resolveAgentConfig, i as listAgentEntries, n as hasAgentRosterProperty } from "./agent-scope-config-DcbEhP0R.js";
import { A as parseRawSessionConversationRef, M as parseThreadSessionSuffix, O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js";
import { n as normalizeAccountId } from "./account-id-CETVCrTz.js";
import { r as resolveProviderToolPolicy } from "./provider-tool-policy-BKZP2YeG.js";
import { t as AUTOMATIONS_TOOL_NAME } from "./automations-tool-name-DBMZPbPL.js";
import { u as resolveToolProfilePolicy } from "./tool-policy-shared-DIyS0iQC.js";
import { n as createToolPolicyMatcher } from "./tool-policy-match-TnFxBs5z.js";
import { n as pickSandboxToolPolicy } from "./sandbox-tool-policy-G7E6RfNN.js";
import { l as mergeAlsoAllowPolicy } from "./tool-policy-Wi0C45cX.js";
import { y as resolveSessionAgentIds } from "./agent-scope-DbtJyKUL.js";
import { i as logWarn } from "./logger-DwECwNVZ.js";
import { n as normalizeMessageChannel } from "./message-channel-core-AaEWic-A.js";
import "./message-channel-BQrhwUEA.js";
import { n as getLoadedChannelPlugin } from "./registry-Cz6cv4VC.js";
import "./plugins-Cozj1Enf.js";
import { t as resolveSessionConversation } from "./session-conversation-_SCrM5we.js";
import { n as markFrozenClawToolAllowPolicy, r as resolveClawToolPolicyConsent } from "./tool-policy-runtime-ByCJNU4r.js";
import { r as resolveChannelGroupToolsPolicy } from "./group-policy-BXx44oG5.js";
import { r as resolveSandboxToolPolicyForAgent } from "./tool-policy-WMCUEiT6.js";
import { a as resolveStoredSubagentInheritedToolDenylist, i as resolveStoredSubagentInheritedToolAllowlist, r as resolveStoredSubagentCapabilities, s as resolveSubagentCapabilityStore } from "./subagent-capabilities-Cd0-4jrx.js";
//#region src/agents/agent-tools.policy.ts
/**
* Resolves sandbox tool policies for agents, providers, sub-agents, and group
* sessions. Keeps runtime tool filtering tied to canonical config, session
* provenance, and inherited sub-agent capabilities.
*/
/**
* Tools always denied for sub-agents regardless of depth.
* These are system-level or interactive tools that sub-agents should never use.
*/
const SUBAGENT_TOOL_DENY_ALWAYS = [
"gateway",
"agents_list",
"openclaw",
"session_status",
AUTOMATIONS_TOOL_NAME,
"message",
"sessions_send",
"conversations_list",
"conversations_send",
"conversations_turn"
];
/** Tools that only make sense for orchestrator sub-agents that can spawn children. */
const SUBAGENT_TOOL_DENY_LEAF = [
"subagents",
"sessions_list",
"sessions_history",
"sessions_search",
"sessions_spawn"
];
function resolveSubagentDenyListForRole(role) {
if (role === "leaf") return [...SUBAGENT_TOOL_DENY_ALWAYS, ...SUBAGENT_TOOL_DENY_LEAF];
return [...SUBAGENT_TOOL_DENY_ALWAYS];
}
function mergeConfiguredSubagentAllow(allow, alsoAllow) {
return allow && alsoAllow ? uniqueStrings([...allow, ...alsoAllow]) : allow;
}
/** Resolve sub-agent tool policy from stored session capabilities. */
function resolveSubagentToolPolicyForSession(cfg, sessionKey, opts) {
const configured = cfg?.tools?.subagents?.tools;
const store = resolveSubagentCapabilityStore(sessionKey, {
cfg,
store: opts?.store
});
const capabilities = resolveStoredSubagentCapabilities(sessionKey, {
cfg,
store
});
const allow = Array.isArray(configured?.allow) ? configured.allow : void 0;
const alsoAllow = Array.isArray(configured?.alsoAllow) ? configured.alsoAllow : void 0;
const deny = [...resolveSubagentDenyListForRole(capabilities.role), ...Array.isArray(configured?.deny) ? configured.deny : []];
return {
allow: mergeConfiguredSubagentAllow(allow, alsoAllow),
deny
};
}
/** Resolve the tool policy inherited from a parent sub-agent session. */
function resolveInheritedToolPolicyForSession(cfg, sessionKey, opts) {
const inheritedToolAllow = resolveStoredSubagentInheritedToolAllowlist(sessionKey, {
cfg,
store: opts?.store
});
const inheritedToolDeny = resolveStoredSubagentInheritedToolDenylist(sessionKey, {
cfg,
store: opts?.store
});
if (inheritedToolAllow.length === 0 && inheritedToolDeny.length === 0) return;
return {
...inheritedToolAllow.length > 0 ? { allow: inheritedToolAllow } : {},
...inheritedToolDeny.length > 0 ? { deny: inheritedToolDeny } : {}
};
}
/** Resolve the shared profile, scope, extra, and sandbox policy layers. */
function resolveConfiguredToolPolicies(params) {
const policies = [];
const profile = params.agentTools?.profile ?? params.cfg.tools?.profile;
const profileAlsoAllow = resolveExplicitProfileAlsoAllow(params.agentTools) ?? resolveExplicitProfileAlsoAllow(params.cfg.tools);
const profilePolicy = mergeAlsoAllowPolicy(resolveToolProfilePolicy(profile), profileAlsoAllow);
if (profilePolicy) policies.push(profilePolicy);
const globalPolicy = pickSandboxToolPolicy(params.cfg.tools ?? void 0);
if (globalPolicy) policies.push(globalPolicy);
const agentPolicy = pickSandboxToolPolicy(params.agentTools);
if (agentPolicy) policies.push(agentPolicy);
for (const policy of params.extraPolicies ?? []) if (policy) policies.push(policy);
if (params.sandboxMode === "all") policies.push(resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? void 0));
return policies;
}
function collectUniqueStrings(values) {
return normalizeUniqueSingleOrTrimmedStringList(values);
}
function buildScopedGroupIdCandidates(groupId) {
const raw = groupId?.trim();
if (!raw) return [];
const topicSenderMatch = raw.match(/^(.+):topic:([^:]+):sender:([^:]+)$/i);
if (topicSenderMatch) {
const [, chatId, topicId] = topicSenderMatch;
return collectUniqueStrings([
raw,
`${chatId}:topic:${topicId}`,
chatId
]);
}
const topicMatch = raw.match(/^(.+):topic:([^:]+)$/i);
if (topicMatch) {
const [, chatId, topicId] = topicMatch;
return collectUniqueStrings([`${chatId}:topic:${topicId}`, chatId]);
}
const senderMatch = raw.match(/^(.+):sender:([^:]+)$/i);
if (senderMatch) {
const [, chatId] = senderMatch;
return collectUniqueStrings([raw, chatId]);
}
return [raw];
}
function resolveGroupContextFromSessionKey(sessionKey) {
const raw = (sessionKey ?? "").trim();
if (!raw) return {};
const { baseSessionKey, threadId } = parseThreadSessionSuffix(raw);
const conversationKey = threadId ? baseSessionKey : raw;
const conversation = parseRawSessionConversationRef(conversationKey);
if (conversation) {
const resolvedConversation = resolveSessionConversation({
channel: conversation.channel,
kind: conversation.kind,
rawId: conversation.rawId
});
return {
channel: conversation.channel,
groupIds: collectUniqueStrings([
...buildScopedGroupIdCandidates(conversation.rawId),
resolvedConversation?.id,
resolvedConversation?.baseConversationId,
...resolvedConversation?.parentConversationCandidates ?? []
])
};
}
const parts = (conversationKey ?? raw).split(":").filter(Boolean);
let body = parts[0] === "agent" ? parts.slice(2) : parts;
if (body[0] === "subagent") body = body.slice(1);
if (body.length < 3) return {};
const [channel, kind, ...rest] = body;
if (kind !== "group" && kind !== "channel") return {};
const groupId = rest.join(":").trim();
if (!groupId) return {};
return {
channel: normalizeLowercaseStringOrEmpty(channel),
groupIds: buildScopedGroupIdCandidates(groupId)
};
}
function resolveTrustedGroupIdFromContexts(params) {
const callerGroupId = (params.groupId ?? "").trim();
if (!callerGroupId) return {
groupId: params.groupId,
dropped: false
};
const trustedGroupIds = collectUniqueStrings([...params.sessionContext.groupIds ?? [], ...params.spawnedContext.groupIds ?? []]);
if (trustedGroupIds.length === 0) return {
groupId: null,
dropped: true
};
if (trustedGroupIds.includes(callerGroupId)) return {
groupId: params.groupId,
dropped: false
};
return {
groupId: null,
dropped: true
};
}
/** Validate caller-supplied group ids against server-derived session context. */
function resolveTrustedGroupId(params) {
return resolveTrustedGroupIdFromContexts({
groupId: params.groupId,
sessionContext: resolveGroupContextFromSessionKey(params.sessionKey),
spawnedContext: resolveGroupContextFromSessionKey(params.spawnedBy)
});
}
/** True when a server-derived session key names a group/channel conversation. */
function sessionKeyNamesGroupConversation(sessionKey) {
return (resolveGroupContextFromSessionKey(sessionKey).groupIds?.length ?? 0) > 0;
}
function resolveExplicitProfileAlsoAllow(tools) {
return Array.isArray(tools?.alsoAllow) ? tools.alsoAllow : void 0;
}
function hasExplicitToolSection(section) {
return section !== void 0 && section !== null;
}
/** Detect removed implicit grants for migration warnings only (#47487). */
function detectImplicitProfileGrants(params) {
const entries = [];
if (hasExplicitToolSection(params.agentTools?.exec) || params.includeGlobalSections && hasExplicitToolSection(params.globalTools?.exec)) entries.push({
section: "tools.exec",
grants: ["exec", "process"]
});
if (hasExplicitToolSection(params.agentTools?.fs) || params.includeGlobalSections && hasExplicitToolSection(params.globalTools?.fs)) entries.push({
section: "tools.fs",
grants: [
"read",
"write",
"edit"
]
});
return entries;
}
/** Resolve the layered global, provider, agent, and profile tool policies. */
function resolveEffectiveToolPolicy(params) {
const explicitAgentId = typeof params.agentId === "string" && params.agentId.trim() ? normalizeAgentId(params.agentId) : void 0;
const agentId = params.config && (!hasAgentRosterProperty(params.config) || listAgentEntries(params.config).length > 0) ? resolveSessionAgentIds({
config: params.config,
agentId: explicitAgentId,
sessionKey: params.sessionKey
}).sessionAgentId : explicitAgentId ?? parseAgentSessionKey(params.sessionKey)?.agentId;
const agentConfig = params.config && agentId ? resolveAgentConfig(params.config, agentId) : void 0;
const implicitDefaultTools = params.config ? (params.config.agents?.defaults)?.tools : void 0;
const agentTools = agentConfig?.tools ?? (params.config && !hasAgentRosterProperty(params.config) ? implicitDefaultTools : void 0);
const globalTools = params.config?.tools;
const profile = agentTools?.profile ?? globalTools?.profile;
const profileSource = agentTools?.profile ? "agent" : globalTools?.profile ? "global" : void 0;
const providerPolicy = resolveProviderToolPolicy({
byProvider: globalTools?.byProvider,
modelProvider: params.modelProvider,
modelId: params.modelId
});
const agentProviderPolicy = resolveProviderToolPolicy({
byProvider: agentTools?.byProvider,
modelProvider: params.modelProvider,
modelId: params.modelId
});
const explicitProfileAlsoAllow = resolveExplicitProfileAlsoAllow(agentTools) ?? resolveExplicitProfileAlsoAllow(globalTools);
const agentPolicy = pickSandboxToolPolicy(agentTools);
if (resolveClawToolPolicyConsent({
agentTools,
agentId,
profile,
ownsProfile: profileSource === "agent",
hasAgentAllowlist: (agentPolicy?.allow?.length ?? 0) > 0
}).frozen) markFrozenClawToolAllowPolicy(agentPolicy);
const effectivePolicy = {
agentId,
globalPolicy: pickSandboxToolPolicy(globalTools),
globalProviderPolicy: pickSandboxToolPolicy(providerPolicy),
agentPolicy,
agentProviderPolicy: pickSandboxToolPolicy(agentProviderPolicy),
profile,
providerProfile: agentProviderPolicy?.profile ?? providerPolicy?.profile,
profileAlsoAllow: explicitProfileAlsoAllow ? uniqueStrings(explicitProfileAlsoAllow) : void 0,
providerProfileAlsoAllow: Array.isArray(agentProviderPolicy?.alsoAllow) ? agentProviderPolicy?.alsoAllow : Array.isArray(providerPolicy?.alsoAllow) ? providerPolicy?.alsoAllow : void 0
};
if (profile) {
const implicitGrants = detectImplicitProfileGrants({
globalTools,
agentTools,
includeGlobalSections: profileSource === "global"
});
if (implicitGrants.length > 0) {
const profilePolicy = mergeAlsoAllowPolicy(resolveToolProfilePolicy(profile), explicitProfileAlsoAllow);
const matchesProfile = createToolPolicyMatcher(profilePolicy);
const restrictionMatchers = [
effectivePolicy.globalPolicy,
effectivePolicy.globalProviderPolicy,
effectivePolicy.agentPolicy,
effectivePolicy.agentProviderPolicy,
mergeAlsoAllowPolicy(resolveToolProfilePolicy(effectivePolicy.providerProfile), effectivePolicy.providerProfileAlsoAllow)
].map((policy) => createToolPolicyMatcher(policy));
const uncoveredEntries = implicitGrants.map((entry) => ({
section: entry.section,
grants: entry.grants.filter((toolName) => !matchesProfile(toolName) && restrictionMatchers.every((matches) => matches(toolName)))
})).filter((entry) => entry.grants.length > 0);
const uncovered = uncoveredEntries.flatMap((entry) => entry.grants);
if (uncovered.length > 0) logWarn(`tools policy: profile "${profile}"${agentId ? ` (agent "${agentId}")` : ""} has configured tool sections (${uncoveredEntries.map((entry) => entry.section).join(" / ")}) that no longer implicitly widen the profile. Add alsoAllow: [${uncovered.map((toolName) => `"${toolName}"`).join(", ")}] explicitly if these tools should be available. See #47487.`);
}
}
return effectivePolicy;
}
function denyAllToolPolicy() {
return {
allow: [],
deny: ["*"]
};
}
/** Resolve group-scoped tool policy after validating session provenance. */
function resolveGroupToolPolicy(params) {
if (!params.config) return;
const sessionContext = resolveGroupContextFromSessionKey(params.sessionKey);
const spawnedContext = resolveGroupContextFromSessionKey(params.spawnedBy);
const trustedGroup = resolveTrustedGroupIdFromContexts({
groupId: params.groupId,
sessionContext,
spawnedContext
});
const groupIds = collectUniqueStrings([
...sessionContext.groupIds ?? [],
...spawnedContext.groupIds ?? [],
...buildScopedGroupIdCandidates(trustedGroup.groupId)
]);
const channelRaw = sessionContext.channel ?? spawnedContext.channel ?? params.messageProvider;
const channel = normalizeMessageChannel(channelRaw);
const accountId = normalizeAccountId(params.accountId);
if (!channel) return params.requireConfiguredAccount && accountId !== "default" ? denyAllToolPolicy() : void 0;
let plugin;
try {
plugin = getLoadedChannelPlugin(channel);
} catch {
plugin = void 0;
}
if (params.requireConfiguredAccount && accountId !== "default") {
let configured;
try {
configured = plugin?.config.listAccountIds(params.config).some((candidate) => normalizeAccountId(candidate) === accountId) === true;
} catch {
configured = false;
}
if (!configured) return denyAllToolPolicy();
}
if (groupIds.length === 0) return;
for (const groupId of groupIds) {
const toolsConfig = plugin?.groups?.resolveToolPolicy?.({
cfg: params.config,
groupId,
groupChannel: trustedGroup.dropped ? null : params.groupChannel,
groupSpace: trustedGroup.dropped ? null : params.groupSpace,
accountId,
senderPolicyMode: params.senderPolicyMode,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164
});
const policy = pickSandboxToolPolicy(toolsConfig);
if (policy) return policy;
}
const configTools = resolveChannelGroupToolsPolicy({
cfg: params.config,
channel,
messageProvider: channel,
groupId: groupIds[0],
groupIdCandidates: groupIds.slice(1),
accountId,
senderPolicyMode: params.senderPolicyMode,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164
});
return pickSandboxToolPolicy(configTools);
}
//#endregion
export { resolveSubagentToolPolicyForSession as a, resolveInheritedToolPolicyForSession as i, resolveEffectiveToolPolicy as n, resolveTrustedGroupId as o, resolveGroupToolPolicy as r, sessionKeyNamesGroupConversation as s, resolveConfiguredToolPolicies as t };