
1. 从一次误删事故说起为什么需要自定义 Agent 工具与审批钩子先说个真实场景。我在一个自动化发布流程里挂了个 Agent本意是让它整理草稿、生成摘要、推送到测试频道。结果某次对话里模型理解偏了直接调用了删除类工具把一批还没备份的素材清掉了。事后复盘发现两个问题一是这个删除能力对所有 Agent 默认可见二是执行前没有任何确认环节。OpenClaw 在 2026.3.28 版本引入的 requireApproval 审批钩子正好补上了第二块短板而插件机制则解决了第一块——把有副作用的工具做成可选、按需启用。这篇内容面向的是已经在用 OpenClaw、想给它写扩展的开发者。核心检索词就三个OpenClaw 插件开发、自定义 Agent 工具、requireApproval 审批钩子。读完你能拿到一套可复制的插件目录结构、manifest 清单、工具注册代码以及审批钩子的配置片段最后还能在本地把插件加载起来、触发一次审批、看到拦截日志。整个过程不需要改 OpenClaw 源码写几个 JS 文件放对位置就行。我试过把这套流程跑通一遍踩的坑主要集中在清单文件的 configSchema 校验和工具名冲突上后面会逐个说清楚。你如果只是想先跑通链路可以照着第三节的配置直接抄想理解每一步为什么这么写就顺着往下看。2. 前置准备TaoToken 接入与 OpenClaw 环境就绪在写插件之前得先让 OpenClaw 能正常跑起来并且有一个可用的模型后端。这里我用 TaoToken 作为模型接入层它的 API 地址是 https://taotoken.net/api兼容常见的对话补全协议配置起来比较直接。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 需要先注册拿到 Key。拿到 Key 之后OpenClaw 侧的 provider 配置可以这样写。注意 Base URL 用 https://taotoken.net/api不要带多余路径{ providers: { taotoken: { type: openai-compatible, baseUrl: https://taotoken.net/api, apiKey: sk-你的TaoToken密钥, model: claude-sonnet-4 } } }模型 ID 按你实际开通的填Claude 系列、GPT 系列都可以。配好之后先别急着写插件用一条最小请求验证链路通不通curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的TaoToken密钥 \ -H Content-Type: application/json \ -d { model: claude-sonnet-4, messages: [{role: user, content: 回复 ok}] }返回里能看到 choices 数组、content 里有内容就说明 Key 和网络都没问题。这一步很关键因为后面插件调试时如果报错你得能区分是插件本身的问题还是模型后端的问题。我见过不少人把 401 当成插件加载失败绕了半天。环境方面确认 OpenClaw 版本在 2026.3.28 及以上因为 requireApproval 是这个版本才加的。用openclaw --version看一眼。插件目录默认在~/.openclaw/plugins/如果这个目录不存在手动建一下。另外建议把 Gateway 的日志级别调到 debug方便观察插件加载过程openclaw gateway start --log-level debug日志里会打印每个插件的发现、校验、加载结果出问题时这是第一手线索。前置准备就这些接下来进入正题。3. 可复制配置插件目录、manifest 与工具注册代码这一节是全文的核心所有代码都可以直接抄。先看目录结构一个最小可用的 OpenClaw 插件长这样my-tool/ ├── openclaw.plugin.json # 必须有插件清单 ├── index.js # 入口文件 └── package.json # 可选声明依赖清单文件openclaw.plugin.json是硬性要求缺了它 OpenClaw 直接报错。里面configSchema字段必须有哪怕没有任何配置项也要写一个空对象 schema{ id: my-tool, name: 我的自定义工具, description: 演示自定义 Agent 工具与审批钩子, configSchema: { type: object, additionalProperties: false, properties: { enabled: { type: boolean, default: true } } } }id要全局唯一后面在openclaw.json里启用插件时用的就是它。configSchema会在配置读写阶段就校验不是等到运行时才报错这点比很多插件框架都友好。接着写入口文件index.js注册一个自定义 Agent 工具。Agent 工具本质就是 LLM 可以调用的函数用api.registerTool注册import { Type } from sinclair/typebox; export default function (api) { api.registerTool({ name: check_weather, description: 查询指定城市的天气, parameters: Type.Object({ city: Type.String({ description: 城市名称 }), }), async execute(_id, params) { const weather await fetchWeather(params.city); return { content: [{ type: text, text: ${params.city}${weather} }], }; }, }); }注册完之后LLM 在对话里就能调用check_weather了。但这里有个关键区分默认注册的工具是「必选」的所有 Agent 都能用。像查天气这种无副作用的没问题但发通知、删文件这类有副作用的工具应该设成可选让用户显式启用export default function (api) { api.registerTool( { name: send_notification, description: 发送通知到指定渠道, parameters: { type: object, properties: { channel: { type: string }, message: { type: string }, }, required: [channel, message], }, async execute(_id, params) { await sendToChannel(params.channel, params.message); return { content: [{ type: text, text: 通知已发送 }] }; }, }, { optional: true } ); }可选工具需要在openclaw.json的 agent 配置里手动 allow{ agents: { list: [ { id: main, tools: { allow: [send_notification] } } ] } }也可以直接用插件 ID 一次性启用该插件的所有工具写allow: [my-tool]就行。然后是重头戏requireApproval 审批钩子。它挂在before_tool_call钩子上工具执行前暂停等用户确认export default function (api) { api.hook(before_tool_call, async (context) { const dangerousTools [delete_file, publish_article, send_email]; if (dangerousTools.includes(context.toolName)) { await context.requireApproval({ reason: 即将执行 ${context.toolName}参数${JSON.stringify(context.params)}, timeout: 60000, }); } }); }timeout是毫秒60 秒内用户没确认工具调用会被取消。不同渠道的确认交互不一样Telegram 弹按钮、Discord 用交互式按钮、Slack 等渠道走/approve命令。最后把插件目录放到~/.openclaw/plugins/下在openclaw.json里启用{ plugins: { allow: [my-tool], entries: { my-tool: { enabled: true } } } }重启 Gateway 生效openclaw gateway restart。到这里配置部分就齐了下一节验证。4. 验证请求与成功结果加载插件、触发审批、查看拦截日志配置写完不代表跑通得一步步验证。第一步确认插件被正确加载。启动 Gateway 时带上 debug 日志openclaw gateway start --log-level debug日志里应该能看到类似plugin loaded: my-tool的输出。如果没看到先跑openclaw doctor它会检查清单文件的 schema 是否合法、ID 有没有冲突。这一步能挡掉大部分低级错误。第二步验证自定义工具能被调用。在对话里直接让模型查天气比如输入「帮我查一下杭州的天气」。如果模型正确调用了check_weather你会看到工具返回的文本内容。如果模型没调用检查工具的description是否清晰——模型是靠描述判断该不该调用的描述太模糊它就不动。第三步触发审批钩子。这里要构造一个会命中dangerousTools列表的调用。比如让模型执行删除操作或者直接手动触发send_email。命中之后对话界面会出现审批请求Telegram 上是按钮Slack 上是/approve命令。此时工具处于暂停状态不会真正执行。第四步观察拦截日志。在 Gateway 的 debug 日志里审批触发时会打印类似requireApproval triggered for tool: delete_file的记录。如果你选择拒绝或者等超时日志里会显示tool call cancelled。这两条日志是验证审批链路是否生效的直接证据。一个完整的成功链路是这样的插件加载成功 → 模型调用工具 → 命中审批规则 → 用户确认 → 工具执行 → 返回结果。任何一环断了日志里都有对应线索。验证通过后你可以把timeout调短一点做压力测试确认超时取消逻辑也正常。5. 常见报错排查401、local proxy failed、reading choices 与 OAuth调试插件时遇到的报错大致分两类一类是模型接入层的一类是插件本身的。先看接入层这几个报错很典型。401 UnauthorizedKey 不对或者没带上。检查openclaw.json里 provider 的apiKey字段以及请求头里的Authorization: Bearer。TaoToken 的 Key 以sk-开头别复制漏了。如果 Key 确认没问题还是 401看看是不是 Base URL 写错了正确写法是https://taotoken.net/api多一个斜杠或者少一段路径都可能出问题。local proxy failed这个通常出现在本地代理配置环节。OpenClaw 某些渠道会走本地转发如果端口被占用或者转发进程没起来就会报这个。检查 Gateway 日志里 proxy 相关行确认端口没冲突。注意这里说的是本地服务端口不是网络代理别混淆。reading choices相关报错一般是模型返回体结构不符合预期比如返回了错误对象而不是标准的 choices 数组。先用第 2 节的 curl 命令单独测一下模型接口确认返回正常。如果 curl 正常但 OpenClaw 报错检查 provider 的type字段是否写对openai-compatible是常见值。OAuth相关报错出现在用 CLI Backend 插件Claude CLI、Codex CLI、Gemini CLI时。2026.3.28 版本这些 CLI 可以作为插件自动加载不需要手动写plugins.allow只要在openclaw.json里配置 provider 引用就够了{ providers: { claude-cli: { model: claude-opus-4 } } }如果 OAuth 报错检查对应 CLI 工具本身是否已经登录、token 是否过期。这类问题跟插件代码无关是 CLI 自身的认证状态问题。插件本身的报错最常见的是工具名冲突。工具名不能和 OpenClaw 内置工具重名比如read、write、exec冲突的会被直接跳过。openclaw doctor会提示这类冲突。另外插件引用了未知的 channel ID 或 plugin ID 也会直接报错检查openclaw.json里的引用是否和清单文件里的id一致。还有一点configSchema校验失败会在配置加载阶段就报错不会拖到运行时所以看到 schema 相关报错直接对着清单文件改就行。6. 把审批钩子用对从工具粒度到长期编码场景审批钩子用起来简单但用对有几个细节。第一别把所有工具都塞进dangerousTools那样每次调用都要确认体验很差。只把不可逆、有副作用的操作放进去比如删除、发布、发送。第二reason字段要写清楚把工具名和参数都带上用户确认时才知道自己在批什么。第三timeout别设太长60 秒是个合理值太长会卡住整个对话流。如果你在做长期的编码或 Agent 任务可以把这套插件机制和 Coding Plan 结合起来用。自定义工具负责具体能力审批钩子负责安全边界模型后端走 TaoToken 的 API。需要长期跑编码任务的话Coding Plan 页面在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后说个实用技巧插件开发阶段把configSchema的additionalProperties设成false这样配置里多写一个字段就会报错能帮你早点发现拼写问题。等插件稳定了再考虑放开。另外openclaw doctor建议每次改完清单文件都跑一遍它比看日志快。