首页 教程 分类 Skills下载 关于
ZH EN JA KO
高级用法

OpenClaw插件开发完整指南:从零到发布

· 24 分钟

前言

OpenClaw 的架构设计秉持"一切皆插件"的理念。Telegram、Discord、Slack 等聊天平台的支持,本质上都是通过 Channel 插件实现的。当你需要接入一个 OpenClaw 尚未支持的聊天平台,或者需要创建一个完全自定义的消息通道时,就需要开发自己的 Channel 插件。

本文将从零开始,带你走完一个 Channel 插件从开发到发布的完整流程。

插件架构概览

Channel 插件的职责

一个 Channel 插件负责以下工作:

外部平台 ←→ Channel 插件 ←→ OpenClaw Core ←→ AI Agent
                │
                ├── 接收外部消息,转换为 OpenClaw 内部格式
                ├── 将 AI 回复转换为外部平台的消息格式
                ├── 管理与外部平台的连接(WebSocket / HTTP / 轮询)
                └── 处理平台特有的功能(按钮、卡片、媒体等)

插件目录结构

my-channel-plugin/
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts          # 插件入口
│   ├── channel.ts        # Channel 实现
│   ├── message.ts        # 消息转换
│   ├── connection.ts     # 连接管理
│   └── types.ts          # 类型定义
├── test/
│   ├── channel.test.ts
│   └── message.test.ts
└── README.md

开发环境搭建

初始化项目

mkdir openclaw-channel-myplatform
cd openclaw-channel-myplatform

npm init -y
npm install @openclaw/plugin-sdk typescript
npm install -D @types/node vitest

配置 TypeScript

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

package.json 插件元数据

{
  "name": "openclaw-channel-myplatform",
  "version": "1.0.0",
  "description": "OpenClaw Channel plugin for MyPlatform",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "openclaw": {
    "type": "channel",
    "name": "myplatform",
    "displayName": "MyPlatform",
    "version": "1.0.0",
    "minGatewayVersion": "0.8.0"
  }
}

实现 Channel 接口

核心接口定义

OpenClaw Plugin SDK 定义了 Channel 插件必须实现的接口:

// src/types.ts
import {
  ChannelPlugin,
  ChannelConfig,
  IncomingMessage,
  OutgoingMessage,
  ChannelConnection,
  ChannelCapabilities
} from "@openclaw/plugin-sdk";

export interface MyPlatformConfig extends ChannelConfig {
  apiToken: string;
  apiEndpoint?: string;
  webhookPort?: number;
}

实现插件入口

// src/index.ts
import { PluginDefinition } from "@openclaw/plugin-sdk";
import { MyPlatformChannel } from "./channel";

const plugin: PluginDefinition = {
  name: "myplatform",
  displayName: "MyPlatform",
  type: "channel",

  // 配置项声明(用于Dashboard UI生成)
  configSchema: {
    apiToken: {
      type: "string",
      required: true,
      secret: true,
      label: "API Token",
      description: "MyPlatform 的 Bot API Token"
    },
    apiEndpoint: {
      type: "string",
      required: false,
      default: "https://api.myplatform.com",
      label: "API 端点"
    }
  },

  // 创建Channel实例
  createChannel(config: MyPlatformConfig) {
    return new MyPlatformChannel(config);
  }
};

export default plugin;

实现 Channel 类

// src/channel.ts
import {
  ChannelPlugin,
  IncomingMessage,
  OutgoingMessage,
  ChannelCapabilities,
  MessageHandler
} from "@openclaw/plugin-sdk";
import { MyPlatformConfig } from "./types";
import { MyPlatformConnection } from "./connection";
import { convertIncoming, convertOutgoing } from "./message";

export class MyPlatformChannel implements ChannelPlugin {
  private config: MyPlatformConfig;
  private connection: MyPlatformConnection;
  private messageHandler: MessageHandler | null = null;

  constructor(config: MyPlatformConfig) {
    this.config = config;
    this.connection = new MyPlatformConnection(config);
  }

  // 声明插件能力
  getCapabilities(): ChannelCapabilities {
    return {
      supportsDM: true,
      supportsGroup: true,
      supportsMedia: true,
      supportsButtons: true,
      supportsEditing: true,
      supportsReactions: false,
      supportsThreads: false,
      supportsVoice: false,
      maxMessageLength: 4096,
      supportedMediaTypes: ["image", "file", "video"]
    };
  }

  // 启动连接
  async start(handler: MessageHandler): Promise<void> {
    this.messageHandler = handler;

    // 连接到外部平台
    await this.connection.connect();

    // 监听收到的消息
    this.connection.on("message", async (rawMessage) => {
      const message = convertIncoming(rawMessage);
      if (this.messageHandler) {
        await this.messageHandler(message);
      }
    });

    console.log("[MyPlatform] Channel started successfully");
  }

  // 发送消息
  async sendMessage(message: OutgoingMessage): Promise<string> {
    const platformMessage = convertOutgoing(message);
    const result = await this.connection.send(platformMessage);
    return result.messageId;  // 返回平台消息ID
  }

  // 编辑已发送的消息
  async editMessage(messageId: string, message: OutgoingMessage): Promise<void> {
    const platformMessage = convertOutgoing(message);
    await this.connection.edit(messageId, platformMessage);
  }

  // 停止连接
  async stop(): Promise<void> {
    await this.connection.disconnect();
    console.log("[MyPlatform] Channel stopped");
  }

  // 健康检查
  async healthCheck(): Promise<boolean> {
    return this.connection.isConnected();
  }
}

实现消息转换

// src/message.ts
import { IncomingMessage, OutgoingMessage } from "@openclaw/plugin-sdk";

// 将平台消息转换为OpenClaw内部格式
export function convertIncoming(raw: any): IncomingMessage {
  return {
    id: raw.id,
    userId: raw.sender.id,
    userName: raw.sender.name,
    channelId: raw.chat.id,
    channelType: raw.chat.type === "private" ? "dm" : "group",
    content: raw.text || "",
    // 媒体附件
    attachments: (raw.attachments || []).map((a: any) => ({
      type: a.type,
      url: a.url,
      filename: a.name,
      mimeType: a.mimeType,
      size: a.size
    })),
    // 原始数据(供高级用途)
    rawData: raw,
    timestamp: new Date(raw.timestamp)
  };
}

// 将OpenClaw回复转换为平台消息格式
export function convertOutgoing(message: OutgoingMessage): any {
  const result: any = {
    chat_id: message.channelId,
    text: message.content
  };

  // 处理富文本格式
  if (message.format === "markdown") {
    result.parse_mode = "Markdown";
  }

  // 处理媒体
  if (message.attachments?.length) {
    result.attachments = message.attachments.map(a => ({
      type: a.type,
      url: a.url,
      caption: a.caption
    }));
  }

  // 处理按钮
  if (message.buttons?.length) {
    result.keyboard = message.buttons.map(b => ({
      text: b.label,
      callback_data: b.action
    }));
  }

  return result;
}

连接管理

// src/connection.ts
import { EventEmitter } from "events";
import { MyPlatformConfig } from "./types";

export class MyPlatformConnection extends EventEmitter {
  private config: MyPlatformConfig;
  private ws: WebSocket | null = null;
  private connected = false;

  constructor(config: MyPlatformConfig) {
    super();
    this.config = config;
  }

  async connect(): Promise<void> {
    const endpoint = this.config.apiEndpoint || "https://api.myplatform.com";

    // WebSocket 连接示例
    this.ws = new WebSocket(`${endpoint}/ws?token=${this.config.apiToken}`);

    this.ws.onopen = () => {
      this.connected = true;
      console.log("[MyPlatform] WebSocket connected");
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === "message") {
        this.emit("message", data.payload);
      }
    };

    this.ws.onclose = () => {
      this.connected = false;
      // 自动重连
      setTimeout(() => this.connect(), 5000);
    };
  }

  async send(message: any): Promise<{ messageId: string }> {
    const response = await fetch(
      `${this.config.apiEndpoint}/api/messages`,
      {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${this.config.apiToken}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify(message)
      }
    );
    return response.json();
  }

  async edit(messageId: string, message: any): Promise<void> {
    await fetch(
      `${this.config.apiEndpoint}/api/messages/${messageId}`,
      {
        method: "PUT",
        headers: {
          "Authorization": `Bearer ${this.config.apiToken}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify(message)
      }
    );
  }

  async disconnect(): Promise<void> {
    this.ws?.close();
    this.connected = false;
  }

  isConnected(): boolean {
    return this.connected;
  }
}

测试

单元测试

// test/message.test.ts
import { describe, it, expect } from "vitest";
import { convertIncoming, convertOutgoing } from "../src/message";

describe("Message Conversion", () => {
  it("should convert incoming DM message", () => {
    const raw = {
      id: "msg_001",
      sender: { id: "user_1", name: "张三" },
      chat: { id: "chat_1", type: "private" },
      text: "你好",
      timestamp: "2026-03-14T10:00:00Z"
    };

    const result = convertIncoming(raw);

    expect(result.userId).toBe("user_1");
    expect(result.channelType).toBe("dm");
    expect(result.content).toBe("你好");
  });

  it("should convert outgoing message with buttons", () => {
    const message = {
      channelId: "chat_1",
      content: "请选择:",
      buttons: [
        { label: "选项A", action: "option_a" },
        { label: "选项B", action: "option_b" }
      ]
    };

    const result = convertOutgoing(message);

    expect(result.keyboard).toHaveLength(2);
    expect(result.keyboard[0].text).toBe("选项A");
  });
});

集成测试

# 使用OpenClaw的插件测试框架
openclaw plugin test ./openclaw-channel-myplatform \
  --mock-platform \
  --scenario send-receive

本地调试

在 OpenClaw 中加载本地插件

{
  plugins: {
    local: [
      // 从本地路径加载插件
      "./plugins/openclaw-channel-myplatform"
    ]
  },
  channels: {
    myplatform: {
      enabled: true,
      apiToken: "your-test-token"
    }
  }
}
# 以开发模式启动,支持热重载
openclaw start --dev --watch-plugins

发布插件

发布到 npm

# 构建
npm run build

# 发布
npm publish --access public

提交到 OpenClaw 插件市场

  1. 在 GitHub 上创建仓库,确保包含完整的 README 和 LICENSE
  2. 在 OpenClaw 插件市场提交 PR,包含插件元数据
  3. 通过审核后,用户可以通过以下方式安装:
openclaw plugin install openclaw-channel-myplatform

安装第三方插件

# 从npm安装
openclaw plugin install openclaw-channel-myplatform

# 从GitHub安装
openclaw plugin install github:username/openclaw-channel-myplatform

# 查看已安装插件
openclaw plugin list

生命周期钩子

Channel 插件还可以实现以下可选的生命周期钩子:

export class MyPlatformChannel implements ChannelPlugin {
  // 插件加载时调用(初始化资源)
  async onLoad(): Promise<void> { }

  // 插件卸载时调用(清理资源)
  async onUnload(): Promise<void> { }

  // 配置更新时调用(热更新配置)
  async onConfigUpdate(newConfig: MyPlatformConfig): Promise<void> { }

  // Agent 上线时调用
  async onAgentOnline(agentId: string): Promise<void> { }

  // Agent 下线时调用
  async onAgentOffline(agentId: string): Promise<void> { }
}

小结

OpenClaw 的插件架构让你可以将任何通信平台接入 AI Agent 生态系统。通过实现 ChannelPlugin 接口的几个核心方法——startsendMessagestop——你就完成了一个可用的 Channel 插件。消息转换层负责在平台格式和 OpenClaw 内部格式之间进行翻译,连接管理层负责维护与外部平台的通信链路。配合完善的测试框架、本地调试支持和插件市场发布流程,OpenClaw 让插件开发变得高效而有序。

OpenClaw 是开源免费的个人AI助手,支持 WhatsApp、Telegram、Discord 等多平台接入