工具策略
工具端钩子:参数重写、阻止、审批、exec 环境贡献以及转录持久化。属于 插件钩子 指南的一部分。
工具调用策略¶
before_tool_call 接收:
event.toolNameevent.params- 可选的
event.toolKind和event.toolInputKind,是宿主权威的判别器,用于有意共享名称的工具;例如,外层 code-modeexec调用使用toolKind: "code_mode_exec",并对接受的 Code Mode 输入包含toolInputKind: "javascript" - 可选的
event.derivedPaths,是尽力而为的宿主推导目标路径提示,用于apply_patch等已知工具信封;这些路径可能不完整,或过度近似工具实际会触及的内容(例如,输入格式错误或输入不完整时) - 可选的
event.runId - 可选的
event.toolCallId - 上下文字段,例如
ctx.agentId、ctx.sessionKey、ctx.sessionId、ctx.runId、ctx.toolKind、ctx.toolInputKind以及诊断用的ctx.trace - 可选的
ctx.abortSignal,当所属工具调用被取消时中止;处理器应将其传递给可取消的 I/O,并移除它们注册的任何监听器 - 可选的
ctx.requester,是发起当前消息运行的宿主推导请求者。它可以包含channel、accountId、senderId、senderIsOwner以及提供商原生的roleIds。缺失字段表示未证明,而不是虚假保证;当策略要求这些字段时,应失败关闭。
它可以返回:
type BeforeToolCallResult = {
params?: Record<string, unknown>;
block?: boolean;
blockReason?: string;
requireApproval?: {
title: string;
description: string;
scope?: ApprovalScope;
severity?: "info" | "warning" | "critical";
timeoutMs?: number;
/** @deprecated Unresolved approvals always deny. */
timeoutBehavior?: "allow" | "deny";
allowedDecisions?: Array<"allow-once" | "allow-always" | "deny">;
pluginId?: string;
onResolution?: (
decision: "allow-once" | "allow-always" | "deny" | "timeout" | "cancelled",
) => Promise<void> | void;
};
};
类型化生命周期钩子的守卫行为:
block: true是终止性的,并跳过较低优先级的处理器。block: false被视为无决定。- 返回
params以重写宿主拥有的工具参数。每个处理器看到的是原始事件的隔离副本,而不是先前返回的重写。在请求审批之前,最后返回的params生效。 - 第一个
requireApproval生效,其插件 id 由宿主盖章。它会冻结所选参数快照:后续处理器可以阻止,但不能更改已批准的参数。 - 原生工具中继可能有更窄的契约。Codex 原生工具支持阻止和观察,但参数重写会被拒绝;参见 Codex 钩子边界。
requireApproval会暂停代理运行,并通过插件审批询问用户。/approve可以同时批准 exec 和插件审批。在 Codex app-server report-mode 原生PreToolUse中继中,这会委托给匹配的 app-server 审批请求;参见 Codex harness 运行时。- 较低优先级的
block: true仍可在较高优先级钩子请求审批后阻止。 onResolution接收已解决的决策:allow-once、allow-always、deny、timeout或cancelled。
例如,在 register(api) 内添加以下内容,以在宿主拥有的 exec 调用前询问。before_tool_call 不需要会话访问选择加入:
api.on(
"before_tool_call",
() => ({
requireApproval: {
title: "Run command",
description: "Allow this exec tool call?",
severity: "info",
timeoutMs: 60_000,
},
}),
{ matcher: ["exec"], priority: 50 },
);
单文件中的发送者感知策略¶
独立的插件文件可以将部署特定策略保留在代码中,而不是添加另一个配置模式。此示例为所有者提供所有工具,让已配置的维护者使用保守的工具和消息操作集,并向已由通道配置授权的发送者公开 /fix:
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
const AGENT_ID = "maintenance-agent";
const MAINTAINER_SCOPES = [
{
channel: "discord",
accountId: "operations",
senderIds: new Set(["maintainer-user-id"]),
roleIds: new Set(["maintainer-role-id"]),
},
];
const MAINTAINER_TOOLS = new Set(["read", "web_fetch", "web_search", "session_status", "message"]);
const MAINTAINER_MESSAGE_ACTIONS = new Set(["react", "reply", "thread-create", "thread-reply"]);
export default definePluginEntry({
id: "maintenance-access",
name: "Maintenance access",
description: "Apply sender-aware tool policy to the maintenance agent.",
register(api) {
api.on("before_tool_call", (event, ctx) => {
if (ctx.agentId !== AGENT_ID) {
return;
}
const requester = ctx.requester;
if (requester?.senderIsOwner === true) {
return;
}
const maintainerScope = requester
? MAINTAINER_SCOPES.find(
(scope) =>
scope.channel === requester.channel && scope.accountId === requester.accountId,
)
: undefined;
const isMaintainer =
maintainerScope !== undefined &&
((requester?.senderId !== undefined && maintainerScope.senderIds.has(requester.senderId)) ||
requester?.roleIds?.some((roleId) => maintainerScope.roleIds.has(roleId)) === true);
if (!isMaintainer) {
return { block: true, blockReason: "Maintainer access required." };
}
if (event.toolName === "message") {
const action = typeof event.params.action === "string" ? event.params.action : "";
if (MAINTAINER_MESSAGE_ACTIONS.has(action)) {
return;
}
return { block: true, blockReason: `Owner required for message.${action || "unknown"}.` };
}
if (MAINTAINER_TOOLS.has(event.toolName)) {
return;
}
return { block: true, blockReason: `Owner required for ${event.toolName}.` };
});
api.registerCommand({
name: "fix",
description: "Ask the maintenance agent to investigate and fix an issue.",
acceptsArgs: true,
requireAuth: true,
handler: async (ctx) =>
ctx.agentId === AGENT_ID
? { continueAgent: true }
: { text: "This command is only available in the maintenance conversation." },
});
},
});
将文件添加到 plugins.load.paths;默认的混合重载模式会应用该更改:
{
agents: {
entries: {
"maintenance-agent": {
default: true,
workspace: "~/.openclaw/workspace-maintenance",
},
},
},
bindings: [
{
agentId: "maintenance-agent",
match: {
channel: "discord",
accountId: "operations",
peer: { kind: "channel", id: "maintenance-channel-id" },
},
},
],
plugins: {
load: { paths: ["~/.openclaw/policies/maintenance-access.ts"] },
},
}
AGENT_ID 必须指定绑定到维护会话的代理。该绑定会为普通消息和 /fix 选择该代理;独立文件仍然是所有者与维护者工具策略的唯一持有者。编辑文件本身后,运行 openclaw plugins reload maintenance-access。
requireAuth: true 会复用各频道现有的发送者准入机制。对于 Discord,服务器或频道的 users/roles 白名单可以授权维护受众。其他频道可以使用稳定的发送者 ID。随后,钩子会在运行中的每次工具调用上应用更细粒度的按工具决策,包括 Codex 原生的 PreToolUse 调用。它可以否决模型可见的工具,但无法添加宿主未提供的工具。现有的沙箱、exec 审批、仅限所有者的核心工具和频道策略仍然适用;钩子不能超越它们授权。
如示例所示,将发送者和角色 ID 限定到精确的频道/账户对;两者都是提供者本地命名空间。请保持白名单保守。仅在部署的沙箱和审批策略确保安全时,才添加写入或执行工具。对于自动化或系统运行,请明确决定当 ctx.requester 缺失时是否应让其通过;示例中对限定的代理予以拒绝。
有关审批路由、决策行为以及何时使用 requireApproval 而非可选工具或 exec 审批,请参阅 插件权限请求。
需要宿主级策略的插件可以通过 api.registerTrustedToolPolicy(...) 注册受信任的工具策略。这些策略在普通的 before_tool_call 钩子之前以及正常的钩子决策之前运行。内置的受信任策略首先运行;已安装插件的受信任策略随后按插件加载顺序运行;普通的 before_tool_call 钩子在其之后运行。内置插件保留现有的受信任策略路径。已安装插件必须被显式启用,并在 contracts.trustedToolPolicies 中声明每个策略 ID;未声明的 ID 会在注册前被拒绝。策略 ID 限定在注册插件的作用域内,因此不同插件可以复用相同的本地 ID。仅将这一层用于宿主可信的门控,例如工作区策略、预算强制执行或预留工作流安全。
受信任策略可以将 matcher 设置为 before_tool_call 接受的同一规范化工具 ID 列表。省略 matcher 以保留全匹配行为。
Exec 环境钩子¶
resolve_exec_env 允许插件在命令运行前向 OpenClaw exec 工具调用贡献环境变量。它不是针对所有宿主原生 shell 的钩子。它接收:
event.sessionKeyevent.toolName,始终为"exec"event.host,取值为"gateway"、"sandbox"或"node"- 上下文字段,如
ctx.agentId、ctx.sessionKey、ctx.sessionId、ctx.messageProvider和ctx.channelId
返回一个 Record<string, string> 以合并到 exec 环境中。处理程序按优先级顺序运行;对于相同的键,后面的结果会覆盖前面的结果。
钩子输出在合并前会经过宿主 exec 环境键策略的过滤。PATH 始终被丢弃(命令解析和安全 bin 检查依赖于它)。无效键和危险的宿主覆盖键(如 LD_*、DYLD_*、NODE_OPTIONS、代理变量(HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY)以及 TLS 覆盖变量(NODE_TLS_REJECT_UNAUTHORIZED、SSL_CERT_FILE 等))会被丢弃。过滤后的插件环境变量会包含在 Gateway 审批/审计元数据中,并转发到节点宿主执行请求。
工具结果持久化¶
tool_result_persist 和 before_message_write 是同步钩子。不要将它们的处理程序设为 async:返回的 promise 会被忽略并发出警告。每个处理程序都会接收前一个处理程序返回的消息。tool_result_persist 返回 { message } 以替换工具结果;before_message_write 可以返回 { message } 或 { block: true } 来阻止该记录写入。阻止持久化不是对工具执行的否决。
这些钩子作用于 OpenClaw 拥有的记录写入。它们不会重写 Codex 原生的工具记录;请参阅 Codex 记录边界。
工具结果可以包含结构化的 details,用于 UI 渲染、诊断、媒体路由或插件拥有的元数据。请将 details 视为运行时元数据,而非提示词内容:
- OpenClaw 会在提供者重放和压缩输入之前剥离
toolResult.details,从而使元数据不会成为模型上下文。 - 持久化的会话条目仅保留有界
details。过大的 details 会被替换为紧凑摘要,并标记persistedDetailsTruncated: true。 tool_result_persist和before_message_write在最终持久化上限之前运行。保持返回的details较小,避免仅将提示词相关文本放在details中;将模型可见的工具输出放在content中。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw