Skip to main content

创建 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 (配置正确的 nameversionmain)
  • openclaw.plugin.json (清单文件)
  • dist/ (构建产物)
  • README.md (使用文档)
  • LICENSE (许可证)

2. 发布到 npm

# 登录 npm
npm login

# 发布
npm publish --access public

3. 添加到社区列表

提交 PR 到 OpenClaw 文档,将你的插件添加到 社区插件 列表。

最佳实践

1

使用 TypeScript

获得类型安全和更好的 IDE 支持
2

验证配置

使用 Zod 或 JSON Schema 验证用户配置
3

记录日志

使用 api.logger 记录重要事件和错误
4

处理错误

优雅处理异常,避免崩溃整个网关
5

清理资源

在服务 stop 中关闭连接、清理定时器
6

编写测试

至少为核心功能编写单元测试
7

提供文档

在 README 中说明安装、配置和使用方法

示例插件

参考官方插件源码:

下一步

Plugin SDK API

探索完整的 SDK API 参考

代理工具

深入了解工具注册和配置

插件清单

理解清单文件的结构和验证