Skip to main content

构建渠道插件

本指南介绍了如何构建一个连接 OpenClaw 与消息平台的渠道插件。在结束时,你将拥有一个可用的渠道,具备私信安全性、配对、回复线程和出站消息功能。
如果你之前没有构建过任何 OpenClaw 插件,请先阅读 入门指南,了解基本的包结构和清单设置。

渠道插件的工作原理

渠道插件不需要自己的发送/编辑/反应工具。OpenClaw 在核心中保留了一个共享的 message 工具。你的插件拥有:
  • 配置 — 账户解析和设置向导
  • 安全性 — 私信策略和允许列表
  • 配对 — 私信审批流程
  • 出站 — 向平台发送文本、媒体和投票
  • 线程化 — 回复如何进行线程化
核心拥有共享的消息工具、提示词连接、会话记录和分发。

演练

1

包和清单

创建标准的插件文件。package.json 中的 channel 字段 使其成为一个渠道插件:
{
  "name": "@myorg/openclaw-acme-chat",
  "version": "1.0.0",
  "type": "module",
  "openclaw": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "channel": {
      "id": "acme-chat",
      "label": "Acme Chat",
      "blurb": "Connect OpenClaw to Acme Chat."
    }
  }
}
2

构建渠道插件对象

ChannelPlugin 接口有许多可选的适配器表面。从最基础的开始 — idsetup — 然后根据需要添加适配器。创建 src/channel.ts
src/channel.ts
import {
  createChatChannelPlugin,
  createChannelPluginBase,
} from "openclaw/plugin-sdk/core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
import { acmeChatApi } from "./client.js"; // your platform API client

type ResolvedAccount = {
  accountId: string | null;
  token: string;
  allowFrom: string[];
  dmPolicy: string | undefined;
};

function resolveAccount(
  cfg: OpenClawConfig,
  accountId?: string | null,
): ResolvedAccount {
  const section = (cfg.channels as Record<string, any>)?.["acme-chat"];
  const token = section?.token;
  if (!token) throw new Error("acme-chat: token is required");
  return {
    accountId: accountId ?? null,
    token,
    allowFrom: section?.allowFrom ?? [],
    dmPolicy: section?.dmSecurity,
  };
}

export const acmeChatPlugin = createChatChannelPlugin<ResolvedAccount>({
  base: createChannelPluginBase({
    id: "acme-chat",
    setup: {
      resolveAccount,
      inspectAccount(cfg, accountId) {
        const section =
          (cfg.channels as Record<string, any>)?.["acme-chat"];
        return {
          enabled: Boolean(section?.token),
          configured: Boolean(section?.token),
          tokenStatus: section?.token ? "available" : "missing",
        };
      },
    },
  }),

  // DM security: who can message the bot
  security: {
    dm: {
      channelKey: "acme-chat",
      resolvePolicy: (account) => account.dmPolicy,
      resolveAllowFrom: (account) => account.allowFrom,
      defaultPolicy: "allowlist",
    },
  },

  // Pairing: approval flow for new DM contacts
  pairing: {
    text: {
      idLabel: "Acme Chat username",
      message: "Send this code to verify your identity:",
      notify: async ({ target, code }) => {
        await acmeChatApi.sendDm(target, `Pairing code: ${code}`);
      },
    },
  },

  // Threading: how replies are delivered
  threading: { topLevelReplyToMode: "reply" },

  // Outbound: send messages to the platform
  outbound: {
    attachedResults: {
      sendText: async (params) => {
        const result = await acmeChatApi.sendMessage(
          params.to,
          params.text,
        );
        return { messageId: result.id };
      },
    },
    base: {
      sendMedia: async (params) => {
        await acmeChatApi.sendFile(params.to, params.filePath);
      },
    },
  },
});
您无需手动实现低级适配器接口,只需传递声明式选项,构建器会将它们组合起来:
选项它连接的内容
security.dm来自配置字段的范围界定私信安全解析器
pairing.text基于代码交换的文本私信配对流程
threading回复模式解析器(固定、账户范围或自定义)
outbound.attachedResults返回结果元数据(消息 ID)的发送函数
如果需要完全控制,您也可以传递原始适配器对象来代替声明式选项。
3

连接入口点

创建 index.ts
index.ts
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { acmeChatPlugin } from "./src/channel.js";

export default defineChannelPluginEntry({
  id: "acme-chat",
  name: "Acme Chat",
  description: "Acme Chat channel plugin",
  plugin: acmeChatPlugin,
  registerFull(api) {
    api.registerCli(
      ({ program }) => {
        program
          .command("acme-chat")
          .description("Acme Chat management");
      },
      { commands: ["acme-chat"] },
    );
  },
});
defineChannelPluginEntry 会自动处理设置/完整注册的拆分。请参阅 Entry Points 了解所有 选项。
4

添加设置入口

创建 setup-entry.ts 以便在新手引导期间进行轻量级加载:
setup-entry.ts
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { acmeChatPlugin } from "./src/channel.js";

export default defineSetupPluginEntry(acmeChatPlugin);
当渠道被禁用或未配置时,OpenClaw 会加载此文件而不是完整入口。这避免了在设置流程中引入繁重的运行时代码。详情请参阅 Setup and Config
5

处理入站消息

您的插件需要接收来自平台的消息并将其转发给 OpenClaw。典型的模式是使用 webhook 来验证请求并通过您的渠道的入站处理器进行分发:
registerFull(api) {
  api.registerHttpRoute({
    path: "/acme-chat/webhook",
    auth: "plugin", // plugin-managed auth (verify signatures yourself)
    handler: async (req, res) => {
      const event = parseWebhookPayload(req);

      // Your inbound handler dispatches the message to OpenClaw.
      // The exact wiring depends on your platform SDK —
      // see a real example in extensions/msteams or extensions/googlechat.
      await handleAcmeChatInbound(api, event);

      res.statusCode = 200;
      res.end("ok");
      return true;
    },
  });
}
入站消息处理因渠道而异。每个渠道插件都拥有自己的入站管道。请查看内置渠道插件 (例如 extensions/msteamsextensions/googlechat)以了解实际模式。
6

测试

src/channel.test.ts 中编写同置测试(colocated tests):
src/channel.test.ts
import { describe, it, expect } from "vitest";
import { acmeChatPlugin } from "./channel.js";

describe("acme-chat plugin", () => {
  it("resolves account from config", () => {
    const cfg = {
      channels: {
        "acme-chat": { token: "test-token", allowFrom: ["user1"] },
      },
    } as any;
    const account = acmeChatPlugin.setup!.resolveAccount(cfg, undefined);
    expect(account.token).toBe("test-token");
  });

  it("inspects account without materializing secrets", () => {
    const cfg = {
      channels: { "acme-chat": { token: "test-token" } },
    } as any;
    const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined);
    expect(result.configured).toBe(true);
    expect(result.tokenStatus).toBe("available");
  });

  it("reports missing config", () => {
    const cfg = { channels: {} } as any;
    const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined);
    expect(result.configured).toBe(false);
  });
});
pnpm test -- extensions/acme-chat/
有关共享测试辅助工具,请参阅 Testing

文件结构

extensions/acme-chat/
├── package.json              # openclaw.channel metadata
├── openclaw.plugin.json      # Manifest with config schema
├── index.ts                  # defineChannelPluginEntry
├── setup-entry.ts            # defineSetupPluginEntry
├── api.ts                    # Public exports (optional)
├── runtime-api.ts            # Internal runtime exports (optional)
└── src/
    ├── channel.ts            # ChannelPlugin via createChatChannelPlugin
    ├── channel.test.ts       # Tests
    ├── client.ts             # Platform API client
    └── runtime.ts            # Runtime store (if needed)

高级主题

线程选项

固定、账户范围或自定义回复模式

消息工具集成

describeMessageTool 和 action discovery

目标解析

inferTargetChatType, looksLikeId, resolveTarget

运行时辅助工具

通过 api.runtime 进行 TTS、STT、媒体处理和子代理调用

后续步骤


本页面源自 openclaw/openclaw,由 BeaversLab 翻译,遵循 MIT 协议 发布。
Last modified on March 27, 2026