OpenClaw 插件与自定义 Tool 开发:给 AI 装上“手脚”
大家好,我是阿木木。
很多同学在部署完 OpenClaw 后,最常问我的一个问题是:“阿木木,AI 除了能跟我聊天,能不能帮我干点实事?”
我一般会把“实事”拆成三类,你一对照就知道自己需要的是哪种能力:
- 查本地数据
:SQLite/MySQL 最新订单、今天的错误日志、Nginx 访问量、某个目录下最近改动的文件 - 调外部系统
:Home Assistant 开关灯、飞书/Slack 发消息、Jira 拉任务、GitHub 查 PR 状态 - 执行动作
:跑脚本生成报表、批量重命名/清理文件、拉代码并跑测试
如果你只是把 OpenClaw 当作一个网页版 ChatGPT 的替代品,那就真的大材小用了。OpenClaw 真正的价值是:把 LLM 从“会说话”升级成“能做事”——靠的就是 插件系统(Plugins/Extensions) 和 自定义 Tool(工具函数)。
这篇我不讲虚的,直接给你一套能落地的套路:从 0 到 1 写一个可安装插件,注册一个稳定可控的 Tool,并把调试与安全边界一次讲透。
提醒一句:不同版本的 OpenClaw 插件 API 命名可能略有差异,但下面的“工程结构 + Tool 四要素 + 受控执行模板”是通用的,你按同样思路替换到你当前版本即可。
1. 先把概念讲透:插件 vs Tool(别一上来就写 shell)
很多人一听“插件”,脑子里想到的是“装个包”。在 OpenClaw 里,你可以把它理解成两层:
- 插件(Plugin/Extension)
:一个可安装、可配置、可分发的能力包。它负责声明元信息、依赖、配置项,并把能力注册到网关/Agent。 - Tool(工具)
:插件暴露给 Agent 的“可调用函数”。模型会以结构化参数调用 Tool,你在 handler 里真正去查库/调接口/执行动作,然后把结果返回。
所以“给 AI 装手脚”的正确姿势是:用插件承载工程化(安装/配置/版本/日志),用 Tool 承载行动能力(输入/输出/执行)。
2. 从 0 到 1:一个最小可用插件骨架(可复制)
先把目录搭起来(最小骨架就够用):
my-first-extension/
openclaw.plugin.json
package.json
src/
index.ts
2.1 openclaw.plugin.json:插件身份证(别写得“随便”)
你至少要交代清楚:插件是谁、入口在哪、有哪些配置、约束是什么。给你一个可直接改的模板(字段名以你当前版本为准,重点是表达的信息):
{
"name":"my-first-extension",
"version":"0.1.0",
"displayName":"My First Extension",
"description":"给 Agent 提供本地查询与自动化能力",
"main":"dist/index.js",
"configSchema":{
"type":"object",
"properties":{
"allowCommands":{
"type":"array",
"items":{"type":"string"},
"description":"允许执行的命令白名单(前缀匹配)"
},
"defaultTimeoutMs":{
"type":"number",
"description":"默认超时时间(毫秒)"
}
},
"required":["allowCommands"]
}
}
这里最关键的是 configSchema:它决定你的插件是不是“可维护”。没有 schema 的插件,后面基本就是靠口头约定,越用越乱。
2.2 插件放哪儿:本地优先级带来的“开发爽点”
OpenClaw 一般会按类似这样的优先级加载扩展:
- 项目私有扩展
: .openclaw/extensions/(强烈建议:项目相关的 Tool 放这里) - 全局扩展
: ~/.openclaw/extensions/(通用工具放这里) - 内置扩展
:随核心包提供
这意味着你在调试时,把插件放在项目目录下改完重载就能生效,效率非常高。
3. 核心干货:注册一个“稳定好用”的 Tool(结构化参数 + 受控执行)
你原稿里给了一个 local_exec(command: string) 的例子,能跑,但很快会踩三类坑:
- 不稳定
:模型会把自然语言塞进 command,命令报错、输出不可解析 - 不安全
:一句“清理一下临时目录”就可能变成灾难 - 不可维护
:所有需求都往一个 command里塞,最后变成垃圾桶
更靠谱的方式是:把“自由文本”变成“结构化参数”,把“任意执行”变成“受控动作”。
一个 Tool,我建议你按四要素来写(写得越具体,模型越稳定):
- Name
:短、稳定、可读(例如 safe_exec、orders_latest) - Description
:明确“什么时候用 / 什么时候别用” - Schema
:把参数约束死(让模型更容易填对) - Handler
:执行逻辑(加超时、截断、返回结构)
3.1 可直接抄的模板:白名单 + 超时 + 结构化返回
下面给你一个“可长期迭代”的 Tool 模板。它的目标不是“万能执行”,而是把风险和不确定性压到最低:
import { execFile } from"node:child_process";
import { promisify } from"node:util";
const execFileAsync = promisify(execFile);
exportdefaultfunctionregister(api: any) {
api.registerTool({
name: "safe_exec",
description:
"执行受控的本地只读命令(必须匹配 allowCommands 白名单)。适用于:查看状态、读取日志、运行诊断命令。禁止用于删除/写入/下载等高危操作。",
schema: {
type: "object",
properties: {
program: { type: "string", description: "可执行程序名,如 git/node/ls" },
args: {
type: "array",
items: { type: "string" },
description: "参数数组(不要拼接成一整段字符串)"
},
timeoutMs: { type: "number", description: "超时毫秒数(可选)" }
},
required: ["program", "args"]
},
handler: async ({ program, args, timeoutMs }: any) => {
const cfg = api.getConfig?.() ?? {};
constallow: string[] = cfg.allowCommands ?? [];
const timeout = timeoutMs ?? cfg.defaultTimeoutMs ?? 10_000;
const commandLine = [program, ...args].join(" ");
const allowed = allow.some((prefix) => commandLine.startsWith(prefix));
if (!allowed) return { ok: false, error: `command not allowed: ${commandLine}` };
try {
const { stdout, stderr } = awaitexecFileAsync(program, args, {
timeout,
windowsHide: true,
maxBuffer: 1024 * 1024
});
return {
ok: true,
stdout: (stdout ?? "").slice(0, 20_000),
stderr: (stderr ?? "").slice(0, 20_000)
};
} catch (e: any) {
return { ok: false, error: e?.message ?? String(e) };
}
}
});
}
这段模板背后的“硬干货”是这四条:
- 不要用一整段
command字符串
,拆成 program + args[],模型更容易填对,也更安全。 - 一定要做白名单
(前缀匹配足够实用),别把“权限控制”交给提示词。 - 输出必须结构化
( ok/stdout/stderr/error),Agent 才能基于结果继续推理与行动。 - 一定要有超时 + 输出截断
,避免一个卡住的命令拖垮整条链路。
3.2 Schema 设计小抄:让模型“更愿意用、且用得对”
你想让 Tool 被稳定触发,Schema 和 Description 的写法决定了 80%:
- 把自由输入改成枚举/选项
:比如 range: "1h" | "24h" | "7d",模型更稳定 - 尽量扁平化参数
:少嵌套(模型更少填错层级) - 给字段写格式示例
:比如 date要求YYYY-MM-DD - 返回“可继续行动的信息”
:比如 count、rows、filePath,而不是一句“成功了”
4. 不止是 Tool:把插件做成“能长期用”的工程件
除了 Agent Tool,插件通常还能承载这些能力(你不一定全用,但要知道它的边界):
- 快速命令(不走 LLM)
:类似 /status这种确定性逻辑,建议直接走命令路由,速度快、成本低、结果稳定 - 网关侧常驻能力
:定时任务、缓存、队列、消息推送、指标上报 - CLI 子命令
:把运维式动作做成命令,Agent 负责触发,CLI 负责执行与打印(可控性更强)
如果你打算让它在团队里长期用,我建议你至少补齐三件事:
- 配置可视化
:用 configSchema声明类型、必填项、默认值,减少“填错参数”的无效排查 - 可观测性
:记录 Tool 入参摘要、耗时、错误原因、截断后的输出(不要在日志里泄露敏感信息) - 失败可恢复
:把常见错误变成可解释的返回(例如 ok=false + error),而不是一堆堆栈
5. 实战建议:安装/状态/排障,一张 checklist 够了
如果你已经写好了插件,或者想安装别人分享的插件,可以用 OpenClaw 的 CLI(命令可能随版本略有差异,但思路一致):
- 安装插件
: openclaw plugins install <pkg-name> - 查看状态
: openclaw plugins list - 排查故障
: openclaw plugins doctor
我自己排障基本按这张清单走(非常省时间):
- 插件没生效
-
放置目录是否在加载路径里(项目私有 vs 全局) main
指向的入口文件是否存在( dist/index.js是否真的生成了)-
构建是否更新(很多人改了 src,但忘了编译到dist) - Tool 不被调用
name/description
是否足够具体(写清触发条件与禁用场景) -
schema 是否太宽泛(只有一个 string时,模型往往更倾向“继续聊天”) -
handler 返回是否结构化(否则 Agent 很难做下一步) - 调用了但结果错
-
打印入参摘要与耗时(你会很快发现是参数填错还是执行逻辑慢) -
外部系统(数据库/API)要做超时与重试(别把随机失败放大给用户)
6. 安全边界:别让“装手脚”变成“给自己挖坑”
这一段我说得直白一点:插件通常和网关跑在同一个权限域里。你给 Agent 开的能力,就是给本机开口子。
我的建议很简单,四条就够:
- 默认最小权限
:先只读(查数据/查状态),再逐步开放写入动作 - 白名单 + 结构化参数
:把“能做什么”锁死在 schema 与配置里,不要靠提示词约束 - 不要让 Tool 直接接触密钥原文
:能统一凭证管理就别把 token 暴露给模型上下文 - 高危动作强制二次确认
:删除/写入/发外部消息等动作,建议必须带 confirm: true或走人工确认
总结
插件系统是 OpenClaw 真正“从聊天走向自动化”的核心。关键不是“注册一个函数”,而是把它做成 可安装、可配置、可调试、可控且安全 的能力包。
你可以从一个很小的 Tool 开始(例如:只读地查日志/查订单/查运行状态),按本文模板把 schema、白名单、超时、结构化输出都做好,再慢慢扩展到更复杂的自动化。
在下一篇文章中,我们聊聊 OpenClaw 的 多模态感知与媒体处理管道:图片/音频/视频是怎么进来、怎么处理、怎么变成可用信息的。
关注我,每天解锁一个 AI 技能。
夜雨聆风
