Appearance
构建渠道插件(Channel Plugins)
本指南将带你完整地构建一个渠道插件,将 OpenClaw 接入某个消息平台。读完后,你将拥有一个具备 DM 安全策略、配对流程、回复线程和出站消息的可用渠道——相当于给你的"小龙虾"安装了一个新的聊天入口。
信息: 如果你还没有构建过任何 OpenClaw 插件,请先阅读 快速入门,了解基础包结构和 manifest 设置。
渠道插件的工作原理
渠道插件无需自己实现 send/edit/react 工具——OpenClaw 的 core 层维护了一个共享的 message 工具。你的插件负责:
- 配置 — 账户解析和安装向导
- 安全 — DM 策略和白名单
- 配对 — DM 审批流程
- 出站 — 向平台发送文本、媒体和投票
- 线程 — 回复如何串联
Core 层负责共享消息工具、Prompt 接线、会话记账和调度。
开发步骤
第一步:创建包和 manifest
创建标准插件文件。package.json 中的 channel 字段是渠道插件的标志:
json
// package.json
{
"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."
}
}
}json
// openclaw.plugin.json
{
"id": "acme-chat",
"kind": "channel",
"channels": ["acme-chat"],
"name": "Acme Chat",
"description": "Acme Chat channel plugin",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"acme-chat": {
"type": "object",
"properties": {
"token": { "type": "string" },
"allowFrom": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
}
}第二步:构建 channel plugin 对象
ChannelPlugin 接口有很多可选的适配器接口。从最小配置开始——id 和 setup——根据需要逐步添加适配器。
创建 src/channel.ts:
typescript
// src/channel.ts
import {
createChatChannelPlugin,
createChannelPluginBase,
} from "openclaw/plugin-sdk/core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
import { acmeChatApi } from "./client.js"; // 你的平台 API 客户端
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 安全:谁可以给 bot 发消息
security: {
dm: {
channelKey: "acme-chat",
resolvePolicy: (account) => account.dmPolicy,
resolveAllowFrom: (account) => account.allowFrom,
defaultPolicy: "allowlist",
},
},
// 配对:新 DM 联系人的审批流程
pairing: {
text: {
idLabel: "Acme Chat 用户名",
message: "发送此代码以验证你的身份:",
notify: async ({ target, code }) => {
await acmeChatApi.sendDm(target, `配对码:${code}`);
},
},
},
// 线程:回复的投递方式
threading: { topLevelReplyToMode: "reply" },
// 出站:向平台发送消息
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);
},
},
},
});createChatChannelPlugin 为你做了什么:
你只需传入声明式选项,builder 会自动组合适配器,而不用手动实现底层接口:
| 选项 | 接线内容 |
|---|---|
security.dm | 从配置字段派生的 DM 安全解析器 |
pairing.text | 基于文本的 DM 配对流程(含验证码交换) |
threading | 回复模式解析器(固定、账户级别或自定义) |
outbound.attachedResults | 返回结果元数据(消息 ID)的发送函数 |
如需完全控制,也可以直接传入原始适配器对象。
第三步:接线入口点
创建 index.ts:
typescript
// 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 自动处理 setup/full 注册拆分。所有选项见入口点文档。
第四步:添加 setup entry
创建 setup-entry.ts,用于引导流程中的轻量加载:
typescript
// setup-entry.ts
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { acmeChatPlugin } from "./src/channel.js";
export default defineSetupPluginEntry(acmeChatPlugin);当渠道被禁用或未配置时,OpenClaw 会加载这个文件而不是完整入口,避免在安装流程中引入重型运行时代码。详见 Setup and Config。
第五步:处理入站消息
你的插件需要从平台接收消息并转发给 OpenClaw。典型做法是设置一个 Webhook,验证请求后通过渠道的入站处理器进行分发:
typescript
registerFull(api) {
api.registerHttpRoute({
path: "/acme-chat/webhook",
auth: "plugin", // 插件自管理认证(自行验证签名)
handler: async (req, res) => {
const event = parseWebhookPayload(req);
// 入站处理器将消息分发给 OpenClaw
// 具体接线方式取决于你的平台 SDK——
// 可参考 extensions/msteams 或 extensions/googlechat 中的真实示例
await handleAcmeChatInbound(api, event);
res.statusCode = 200;
res.end("ok");
return true;
},
});
}注意: 入站消息处理是渠道专属的,每个渠道插件都有自己的入站流水线。可参考内置渠道插件(如
extensions/msteams、extensions/googlechat)中的真实模式。
第六步:编写测试
在 src/channel.test.ts 中编写同置测试:
typescript
// src/channel.test.ts
import { describe, it, expect } from "vitest";
import { acmeChatPlugin } from "./channel.js";
describe("acme-chat plugin", () => {
it("从配置中解析账户", () => {
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("在不暴露密钥的情况下检查账户", () => {
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("报告缺失配置", () => {
const cfg = { channels: {} } as any;
const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined);
expect(result.configured).toBe(false);
});
});bash
pnpm test -- extensions/acme-chat/共享测试工具见 Testing。
文件结构
extensions/acme-chat/
├── package.json # openclaw.channel 元数据
├── openclaw.plugin.json # 含 config schema 的 manifest
├── index.ts # defineChannelPluginEntry
├── setup-entry.ts # defineSetupPluginEntry
├── api.ts # 公共导出(可选)
├── runtime-api.ts # 内部运行时导出(可选)
└── src/
├── channel.ts # 通过 createChatChannelPlugin 构建的 ChannelPlugin
├── channel.test.ts # 测试
├── client.ts # 平台 API 客户端
└── runtime.ts # 运行时 store(如需要)进阶主题
- 线程选项 — 固定、账户级别或自定义回复模式,见入口点:注册模式
- 消息工具集成 — describeMessageTool 与 action 发现,见架构:渠道插件与共享消息工具
- 目标解析 — inferTargetChatType、looksLikeId、resolveTarget,见架构:渠道目标解析
- 运行时辅助工具 — TTS、STT、媒体、subagent via api.runtime,见SDK 运行时辅助工具
下一步
- Provider 插件 — 如果你的插件同时提供模型
- SDK 概览 — 完整子路径导入参考
- SDK 测试 — 测试工具和契约测试
- Plugin Manifest 规范