创建 OpenClaw 插件
本指南将引导你从零开始创建一个功能完整的 OpenClaw 插件。前置要求
- Node.js 22+
- TypeScript 基础知识
- 已安装 OpenClaw
快速开始
1. 创建插件目录
mkdir my-plugin
cd my-plugin
npm init -y
2. 安装依赖
npm install openclaw
npm install -D typescript @types/node
3. 配置 TypeScript
创建tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
4. 创建插件清单
创建openclaw.plugin.json:
{
"id": "my-plugin",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"message": {
"type": "string"
}
}
},
"uiHints": {
"enabled": {
"label": "启用插件"
},
"message": {
"label": "欢迎消息",
"placeholder": "输入欢迎消息"
}
}
}
清单文件必须位于插件根目录,用于配置验证和插件发现。查看 插件清单 了解完整规范。
5. 创建插件入口
创建src/index.ts:
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { Type } from "@sinclair/typebox";
export default function (api: OpenClawPluginApi) {
api.logger.info(`插件 ${api.name} 已加载`);
// 解析配置
const config = api.pluginConfig as {
enabled?: boolean;
message?: string;
};
if (config.enabled === false) {
api.logger.warn("插件已禁用");
return;
}
// 注册工具
api.registerTool({
name: "my_tool",
description: "我的示例工具",
parameters: Type.Object({
text: Type.String({ description: "要处理的文本" }),
}),
async execute(_toolCallId, params) {
const message = config.message || "你好";
return {
content: [
{
type: "text",
text: `${message}: ${params.text}`,
},
],
};
},
});
api.logger.info("插件初始化完成");
}
6. 配置 package.json
更新package.json:
{
"name": "my-plugin",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist", "openclaw.plugin.json"],
"scripts": {
"build": "tsc",
"watch": "tsc --watch"
},
"dependencies": {
"openclaw": "latest"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/node": "^20.0.0"
}
}
7. 构建插件
npm run build
8. 安装到 OpenClaw
openclaw plugins install ./
9. 配置插件
在~/.openclaw/openclaw.json5 中添加:
{
plugins: {
entries: {
"my-plugin": {
enabled: true,
config: {
enabled: true,
message: "欢迎使用我的插件",
},
},
},
},
}
10. 重启网关并测试
openclaw gateway restart
my_tool 工具了!
进阶功能
注册生命周期钩子
监听 AI 代理运行时事件:export default function (api: OpenClawPluginApi) {
// 代理启动前
api.on("before_agent_start", async (event, ctx) => {
api.logger.info(`Agent ${ctx.agentId} 启动中`);
return {
prependContext: "这是额外的上下文信息",
};
});
// LLM 输入
api.on("llm_input", async (event, ctx) => {
api.logger.debug(
`发送到 ${event.provider}:${event.model} - ${event.prompt.slice(0, 50)}...`
);
});
// LLM 输出
api.on("llm_output", async (event, ctx) => {
api.logger.debug(
`收到回复: ${event.assistantTexts.join("")}`.slice(0, 100)
);
});
// 工具调用前
api.on("before_tool_call", async (event, ctx) => {
api.logger.info(`调用工具: ${event.toolName}`);
if (event.toolName === "dangerous_tool") {
return { block: true, blockReason: "工具被插件阻止" };
}
});
// 工具调用后
api.on("after_tool_call", async (event, ctx) => {
api.logger.info(
`工具 ${event.toolName} 执行完成,耗时 ${event.durationMs}ms`
);
});
}
before_model_resolve
before_prompt_build
before_agent_start
llm_input
llm_output
agent_end
注册后台服务
创建长期运行的后台任务:export default function (api: OpenClawPluginApi) {
let intervalId: NodeJS.Timeout | null = null;
api.registerService({
id: "my-background-service",
async start(ctx) {
api.logger.info("后台服务启动");
api.logger.info(`状态目录: ${ctx.stateDir}`);
// 启动定时任务
intervalId = setInterval(() => {
api.logger.debug("定时任务执行");
}, 60000); // 每分钟
},
async stop(ctx) {
api.logger.info("后台服务停止");
// 清理资源
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
},
});
}
注册 HTTP 路由
处理 webhook 或提供 HTTP API:import type { IncomingMessage, ServerResponse } from "node:http";
export default function (api: OpenClawPluginApi) {
api.registerHttpRoute({
path: "/my-plugin/webhook",
auth: "plugin",
match: "exact",
async handler(req: IncomingMessage, res: ServerResponse) {
// 读取请求体
const body = await new Promise<string>((resolve) => {
let data = "";
req.on("data", (chunk) => (data += chunk));
req.on("end", () => resolve(data));
});
// 处理请求
const payload = JSON.parse(body);
api.logger.info(`收到 webhook: ${payload.type}`);
// 发送响应
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
return true; // 表示已处理
},
});
}
HTTP 路由运行在网关的 HTTP 服务器上。确保路径唯一,避免与其他插件冲突。
注册网关 RPC 方法
提供可通过网关协议调用的方法:export default function (api: OpenClawPluginApi) {
api.registerGatewayMethod(
"myplugin.status",
async ({ params, respond }) => {
try {
const status = {
version: api.version,
enabled: true,
timestamp: Date.now(),
};
respond(true, status);
} catch (err) {
respond(false, {
error: err instanceof Error ? err.message : String(err),
});
}
}
);
api.registerGatewayMethod(
"myplugin.execute",
async ({ params, respond }) => {
const { action } = params;
if (action === "ping") {
respond(true, { reply: "pong" });
} else {
respond(false, { error: "未知操作" });
}
}
);
}
注册 CLI 命令
扩展openclaw CLI:
export default function (api: OpenClawPluginApi) {
api.registerCli(
({ program, config, logger }) => {
program
.command("myplugin")
.description("我的插件管理")
.option("-s, --status", "显示状态")
.option("-e, --execute <action>", "执行操作")
.action(async (opts) => {
if (opts.status) {
logger.info("插件状态: 运行中");
return;
}
if (opts.execute) {
logger.info(`执行操作: ${opts.execute}`);
return;
}
program.help();
});
},
{ commands: ["myplugin"] }
);
}
openclaw myplugin --status
openclaw myplugin --execute test
注册自定义命令
处理聊天命令(绕过 LLM):export default function (api: OpenClawPluginApi) {
api.registerCommand({
name: "hello",
description: "发送问候",
acceptsArgs: true,
requireAuth: true,
async handler(ctx) {
const name = ctx.args?.trim() || "朋友";
return {
text: `你好, ${name}! 来自 ${ctx.channel}`,
};
},
});
}
/hello Alice
// 回复: 你好, Alice! 来自 telegram
配置验证
使用 Zod
推荐使用 Zod 进行运行时配置验证:import { z } from "zod";
const ConfigSchema = z.object({
enabled: z.boolean().default(true),
apiKey: z.string().min(10, "API Key 至少 10 个字符"),
timeout: z.number().min(1000).max(60000).default(5000),
retries: z.number().int().min(0).max(10).default(3),
endpoints: z.array(z.string().url()).optional(),
});
type Config = z.infer<typeof ConfigSchema>;
export default function (api: OpenClawPluginApi) {
let config: Config;
try {
config = ConfigSchema.parse(api.pluginConfig);
} catch (err) {
if (err instanceof z.ZodError) {
api.logger.error("配置验证失败:");
for (const issue of err.issues) {
api.logger.error(` - ${issue.path.join(".")}: ${issue.message}`);
}
}
throw err;
}
api.logger.info(`配置已加载: 超时=${config.timeout}ms, 重试=${config.retries}`);
}
使用 TypeBox
TypeBox 适合工具参数定义:import { Type } from "@sinclair/typebox";
const ToolParamsSchema = Type.Object({
action: Type.Union([
Type.Literal("create"),
Type.Literal("update"),
Type.Literal("delete"),
]),
id: Type.String({ minLength: 1 }),
data: Type.Optional(
Type.Object({
name: Type.String(),
age: Type.Number({ minimum: 0 }),
})
),
});
api.registerTool({
name: "manage_resource",
description: "管理资源",
parameters: ToolParamsSchema,
async execute(_id, params) {
// params 已通过 schema 验证
switch (params.action) {
case "create":
return { content: [{ type: "text", text: "已创建" }] };
case "update":
return { content: [{ type: "text", text: "已更新" }] };
case "delete":
return { content: [{ type: "text", text: "已删除" }] };
}
},
});
测试插件
单元测试
创建src/index.test.ts:
import { describe, it, expect, vi } from "vitest";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import plugin from "./index.js";
describe("My Plugin", () => {
it("应该注册工具", () => {
const mockApi = {
id: "my-plugin",
name: "My Plugin",
pluginConfig: { enabled: true, message: "测试" },
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
registerTool: vi.fn(),
} as unknown as OpenClawPluginApi;
plugin(mockApi);
expect(mockApi.registerTool).toHaveBeenCalledWith(
expect.objectContaining({
name: "my_tool",
})
);
});
});
package.json:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
},
"devDependencies": {
"vitest": "^1.0.0"
}
}
集成测试
在真实 OpenClaw 环境中测试:# 安装到本地
openclaw plugins install ./
# 重启网关
openclaw gateway restart
# 测试工具
openclaw agent run --message "使用 my_tool 处理文本"
发布插件
1. 准备发布
确保以下文件存在:package.json(配置正确的name、version、main)openclaw.plugin.json(清单文件)dist/(构建产物)README.md(使用文档)LICENSE(许可证)
2. 发布到 npm
# 登录 npm
npm login
# 发布
npm publish --access public
3. 添加到社区列表
提交 PR 到 OpenClaw 文档,将你的插件添加到 社区插件 列表。最佳实践
示例插件
参考官方插件源码:- voice-call - 语音通话
- diffs - 代码差异工具
- memory-lancedb - 向量记忆
- device-pair - 设备配对
下一步
Plugin SDK API
探索完整的 SDK API 参考
代理工具
深入了解工具注册和配置
插件清单
理解清单文件的结构和验证