一、Channel 扩展定位
Channel 扩展是 OpenClaw 的消息渠道接入层——通过插件机制接入 30+ 消息平台,对上提供统一的 ChannelPlugin 接口。
┌─────────────────────────────────────────────────────────────┐
│ OpenClaw Core │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Gateway │ │ Agent RT │ │ Memory │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ ChannelPlugin Interface │ │
│ │ id · meta · capabilities · config · setup │ │
│ │ outbound · gateway · lifecycle │ │
│ └──────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────┼─────────────────────────────────┐
│ extensions/channels/ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │telegram/│ │discord/ │ │whatsapp/│ │ slack/ │ ... │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────┘
二、模块结构
| 文件 | 职责 |
|---|
src/channels/plugins/types.plugin.ts | ChannelPlugin 接口定义 |
src/channels/plugins/types.core.ts | 核心类型(Capabilities、Meta) |
src/channels/plugins/types.adapters.ts | 各 Adapter 接口 |
src/channels/plugins/config-schema.ts | 配置 Schema 构建器 |
src/channels/plugins/setup-wizard-types.ts | Setup Wizard 类型 |
src/plugin-sdk/core.ts | createChatChannelPlugin() 辅助函数 |
extensions/channels/<name>/ | 各 Channel 插件实现 |
三、ChannelPlugin 接口
文件:src/channels/plugins/types.plugin.ts
3.1 核心字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| export type ChannelPlugin<
ResolvedAccount = any,
Probe = unknown,
Audit = unknown,
> = {
// 必填字段
id: ChannelId; // 唯一标识,如 "telegram"
meta: ChannelMeta; // 用户可见的标签、文档路径
capabilities: ChannelCapabilities; // 静态能力标志
config: ChannelConfigAdapter<ResolvedAccount>; // 账号配置读写
// 生命周期
setup?: ChannelSetupAdapter; // 账号配置应用
gateway?: ChannelGatewayAdapter<ResolvedAccount>; // 运行时生命周期
lifecycle?: ChannelLifecycleAdapter; // 配置变更回调
// 安全与群组
security?: ChannelSecurityAdapter<ResolvedAccount>;
groups?: ChannelGroupAdapter;
mentions?: ChannelMentionAdapter;
allowlist?: ChannelAllowlistAdapter;
// 消息收发
outbound?: ChannelOutboundAdapter;
status?: ChannelStatusAdapter<ResolvedAccount, Probe, Audit>;
messaging?: ChannelMessagingAdapter;
streaming?: ChannelStreamingAdapter;
threading?: ChannelThreadingAdapter;
// 配对与认证
pairing?: ChannelPairingAdapter;
auth?: ChannelAuthAdapter;
approvalCapability?: ChannelApprovalCapability;
elevated?: ChannelElevatedAdapter;
// 命令与工具
commands?: ChannelCommandAdapter;
agentTools?: ChannelAgentToolFactory | ChannelAgentTool[];
// Wizard
setupWizard?: ChannelSetupWizard | ChannelSetupWizardAdapter;
// 其他
secrets?: ChannelSecretsAdapter;
doctor?: ChannelDoctorAdapter;
agentPrompt?: ChannelAgentPromptAdapter;
directory?: ChannelDirectoryAdapter;
resolver?: ChannelResolverAdapter;
actions?: ChannelMessageActionAdapter;
bindings?: ChannelConfiguredBindingProvider;
conversationBindings?: ChannelConversationBindingSupport;
};
|
四、ChannelCapabilities
文件:src/channels/plugins/types.core.ts
4.1 能力标志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| export type ChannelCapabilities = {
chatTypes: Array<ChatType | "thread">; // "direct" | "group" | "channel" | "thread"
polls?: boolean; // 支持发送/创建投票
reactions?: boolean; // 支持发送反应
edit?: boolean; // 支持编辑消息
unsend?: boolean; // 支持删除消息
reply?: boolean; // 支持回复消息
effects?: boolean; // 支持消息效果
groupManagement?: boolean; // 支持管理群组设置
threads?: boolean; // 支持线程对话
media?: boolean; // 支持发送图片/视频/音频
tts?: { // 文本转语音支持
voice?: ChannelTtsVoiceDeliveryCapabilities;
};
nativeCommands?: boolean; // 支持原生渠道命令
blockStreaming?: boolean; // 应阻止流式响应
};
|
4.2 Telegram 示例
1
2
3
4
5
6
7
8
9
10
| capabilities: {
chatTypes: ["direct", "group", "channel", "thread"],
reactions: true,
threads: true,
media: true,
tts: { voice: { synthesisTarget: "voice-note" } },
polls: true,
nativeCommands: true,
blockStreaming: true,
},
|
五、文件结构
5.1 标准 Channel 扩展结构
extensions/<channel-id>/
├── openclaw.plugin.json # Plugin manifest
├── package.json # 包定义
├── index.ts # 主入口(defineChannelPluginEntry)
├── setup-entry.ts # Setup 入口(defineSetupPluginEntry)
├── channel-plugin-api.ts # 插件 API 重导出
├── runtime-api.ts # 运行时 API 定义
└── src/
├── channel.ts # 主插件定义
├── shared.ts # 共享基类和配置适配器
├── setup-surface.ts # Setup Wizard 定义
├── setup-core.ts # Setup Adapter 实现
├── config-schema.ts # Zod 配置 Schema
├── config-ui-hints.ts # 配置 UI 提示
├── accounts.ts # 账号解析和列表
├── account-inspect.ts # 账号检查工具
├── outbound-adapter.ts # 出站消息适配器
├── channel-actions.ts # 消息操作(发送、反应等)
├── monitor.ts # 运行时监控/启动逻辑
├── probe.ts # 健康检查实现
├── doctor.ts # 配置修复
├── security.ts # 安全适配器
├── runtime.ts # 运行时访问器
└── send.ts # 底层发送函数
5.2 核心创建函数
1
2
3
4
5
6
7
| // src/plugin-sdk/core.ts
import {
createChatChannelPlugin,
createChannelPluginBase,
defineChannelPluginEntry,
defineSetupPluginEntry,
} from "openclaw/plugin-sdk/core";
|
六、Config Schema
6.1 buildChannelConfigSchema
文件:src/channels/plugins/config-schema.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import { buildChannelConfigSchema } from "openclaw/plugin-sdk/config-schema";
import { z } from "zod";
// 定义 Channel 特定的配置 Schema
export const <Channel>ConfigSchema = z.object({
enabled: z.boolean().optional(),
botToken: z.string().optional(),
// ... 更多字段
});
// 构建完整的 Channel 配置 Schema
export const <Channel>ChannelConfigSchema = buildChannelConfigSchema(
<Channel>ConfigSchema,
{
uiHints: <channel>ConfigUiHints,
}
);
|
6.2 配置适配器
1
2
3
4
5
6
7
8
9
| // shared.ts
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-core";
export const <channel>ConfigAdapter = createScopedChannelConfigAdapter({
scope: CHANNEL_ID,
schema: <Channel>ChannelConfigSchema,
read: (cfg, accountId) => {/* 读取配置 */},
write: (cfg, accountId, updates) => {/* 写入配置 */},
});
|
七、Setup Wizard
7.1 ChannelSetupWizard 结构
文件:src/channels/plugins/setup-wizard-types.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| export type ChannelSetupWizard = {
channel: string;
status: ChannelSetupWizardStatus;
introNote?: ChannelSetupWizardNote;
envShortcut?: ChannelSetupWizardEnvShortcut;
credentials: ChannelSetupWizardCredential[];
textInputs?: ChannelSetupWizardTextInput[];
finalize?: ChannelSetupWizardFinalize;
completionNote?: ChannelSetupWizardNote;
dmPolicy?: ChannelSetupDmPolicy;
allowFrom?: ChannelSetupWizardAllowFrom;
groupAccess?: ChannelSetupWizardGroupAccess;
disable?: (cfg: OpenClawConfig) => OpenClawConfig;
};
|
7.2 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // setup-surface.ts
export const telegramSetupWizard: ChannelSetupWizard = {
channel: "telegram",
status: createStandardChannelSetupStatus({...}),
credentials: [{
inputKey: "token",
providerHint: "telegram",
credentialLabel: "Telegram bot token",
preferredEnvVar: "TELEGRAM_BOT_TOKEN",
helpTitle: "Telegram bot token",
helpLines: TELEGRAM_TOKEN_HELP_LINES,
}],
allowFrom: createAllowFromSection({...}),
dmPolicy: telegramSetupDmPolicy,
};
|
八、Outbound 适配器
8.1 ChannelOutboundAdapter
文件:src/channels/plugins/outbound.types.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| export type ChannelOutboundAdapter = {
deliveryMode: "direct" | "gateway" | "hybrid";
// 消息分块
chunker?: (text: string, limit: number, ctx?: ChannelOutboundChunkContext) => string[];
textChunkLimit?: number;
// 发送方法
sendPayload?: (ctx: ChannelOutboundPayloadContext) => Promise<OutboundDeliveryResult>;
sendText?: (ctx: ChannelOutboundContext) => Promise<OutboundDeliveryResult>;
sendMedia?: (ctx: ChannelOutboundContext) => Promise<OutboundDeliveryResult>;
sendPoll?: (ctx: ChannelPollContext) => Promise<ChannelPollResult>;
//钩子
beforeDeliverPayload?: (ctx: ChannelOutboundPayloadContext) => Promise<void>;
afterDeliverPayload?: (ctx: ChannelOutboundPayloadContext) => Promise<void>;
renderPresentation?: (payload: ReplyPayload, ctx: ChannelOutboundContext) => Promise<ReplyPayload>;
sanitizeText?: (text: string) => string;
};
|
8.2 Telegram 示例
1
2
3
4
5
6
7
8
9
| // outbound-adapter.ts
export const telegramOutbound: ChannelOutboundAdapter = {
deliveryMode: "direct",
chunker: markdownToTelegramHtmlChunks,
textChunkLimit: 4000,
sendPayload: async ({ cfg, to, payload, ... }) => {
// 渲染呈现,发送媒体序列,返回结果
},
};
|
九、完整实现示例
9.1 index.ts(插件入口)
1
2
3
4
5
6
7
8
9
10
| // index.ts
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { <channel>Plugin } from "./src/channel.js";
export default defineChannelPluginEntry({
id: CHANNEL_ID,
name: "<channel>",
description: "OpenClaw <channel> channel plugin",
plugin: <channel>Plugin,
});
|
9.2 setup-entry.ts(Setup 入口)
1
2
3
4
5
| // setup-entry.ts
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { <channel>Plugin } from "./src/channel.js";
export default defineSetupPluginEntry(<channel>Plugin);
|
9.3 shared.ts(共享基类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // src/shared.ts
import { createChatChannelPlugin, createChannelPluginBase } from "openclaw/plugin-sdk/channel-core";
export const <channel>ConfigAdapter = createScopedChannelConfigAdapter({...});
export function create<Channel>PluginBase(params) {
return createChannelPluginBase({
id: CHANNEL_ID,
meta: { ...getChatChannelMeta(CHANNEL_ID) },
setupWizard: params.setupWizard,
capabilities: {
chatTypes: ["direct", "group"],
media: true,
reactions: true,
},
config: { ...<channel>ConfigAdapter, ... },
setup: params.setup,
});
}
|
9.4 channel.ts(主插件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // src/channel.ts
export const <channel>Plugin = createChatChannelPlugin({
base: {
...create<Channel>PluginBase({ setupWizard, setup }),
},
outbound: {
base: {
deliveryMode: "direct",
sendText: async (ctx) => {/* 发送文本 */},
sendMedia: async (ctx) => {/* 发送媒体 */},
},
},
gateway: {
startAccount: async (ctx) => {/* 启动监控 */},
stopAccount: async (ctx) => {/* 停止监控 */},
},
security: <channel>SecurityAdapter,
});
|
9.5 package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| {
"name": "@openclaw/<channel>",
"version": "2026.4.25",
"type": "module",
"dependencies": {
// channel-specific deps
},
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"
},
"openclaw": {
"extensions": ["./index.ts"],
"setupEntry": "./setup-entry.ts",
"channel": {
"id": "<channel-id>",
"label": "<Channel Name>",
"selectionLabel": "<Channel Name> (Bot API)"
}
}
}
|
十、Gateway 适配器
10.1 ChannelGatewayAdapter
1
2
3
4
5
| export type ChannelGatewayAdapter<ResolvedAccount> = {
startAccount(ctx: ChannelGatewayStartAccountContext): Promise<void>;
stopAccount(ctx: ChannelGatewayStopAccountContext): Promise<void>;
restartAccount?(ctx: ChannelGatewayRestartAccountContext): Promise<void>;
};
|
10.2 启动监控模式
Channel 实现通常在 startAccount 中启动:
- 长轮询:定期拉取新消息
- Webhook:注册回调 URL 接收消息
1
2
3
4
5
6
7
8
9
10
| gateway: {
startAccount: async ({ config, accountId, runtime }) => {
const channelRuntime = await load<Channel>ChannelRuntime();
await channelRuntime.startPolling({ config, accountId });
},
stopAccount: async ({ config, accountId }) => {
const channelRuntime = await load<Channel>ChannelRuntime();
await channelRuntime.stopPolling({ accountId });
},
},
|
十一、WhatsApp 特殊示例(QR 登录)
WhatsApp 使用 QR 码登录而非 Token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| export const whatsappPlugin = createChatChannelPlugin<ResolvedWhatsAppAccount>({
base: {
...createWhatsAppPluginBase({
setupWizard: whatsappSetupWizardProxy,
setup: whatsappSetupAdapter,
isConfigured: async (account) => {
const channelRuntime = await loadWhatsAppChannelRuntime();
return (await channelRuntime.readWebAuthState(account.authDir)) === "linked";
},
}),
agentTools: () => [createWhatsAppLoginTool()],
},
auth: {
login: async ({ cfg, accountId, runtime, verbose }) => {
// WhatsApp 使用 QR 码登录流程
},
},
pairing: { idLabel: "whatsappSenderId" },
});
|
十二、设计权衡
12.1 Adapter 模式
ChannelPlugin 使用大量 Adapter 接口,允许插件选择性地实现功能:
1
2
3
4
5
| // 每个 Adapter 都是可选的
setup?: ChannelSetupAdapter;
pairing?: ChannelPairingAdapter;
security?: ChannelSecurityAdapter;
// ...
|
简单渠道(如 IRC)只需实现核心接口,复杂渠道(如 Discord)可以实现全套。
12.2 createChatChannelPlugin 组合模式
1
2
3
4
5
6
| createChatChannelPlugin({
base: { /* 通用基础 */ },
outbound: { /* 出站消息 */ },
gateway: { /* 运行时生命周期 */ },
security: { /* 安全策略 */ },
});
|
组合模式允许复用基础实现并选择性覆盖。
12.3 Config Schema 分离
configSchema:声明式配置定义(用于 UI 生成)config:运行时配置访问
这种分离允许 Setup UI 在不加载运行时的情况下渲染配置表单。
下一步
篇目 10 完成,继续:
OpenClaw 源码剖析系列 · 2026 · skyseraph