如果你以前没有构建过任何 OpenClaw 插件,请先阅读
入门,了解基础包结构和 manifest 设置。
通道插件如何工作
通道插件不需要自己提供发送/编辑/反应工具。OpenClaw 在核心中保留了一个共享的message 工具。你的插件负责:
- 配置 - 账户解析和设置向导
- 安全性 - DM 策略和允许列表
- 配对 - DM 审批流程
- 会话语法 - 提供商特定的会话 id 如何映射到基础聊天、线程 id 和父级回退
- 外发 - 向平台发送文本、媒体和投票
- 线程 - 回复如何被线程化
- 心跳输入中 - 用于心跳投递目标的可选输入中/忙碌信号
:thread: 记账以及分发。
新的通道插件还应通过来自 openclaw/plugin-sdk/channel-outbound 的 defineChannelMessageAdapter 暴露一个 message 适配器。该适配器声明本机传输实际支持哪些持久化的最终发送能力,并将文本/媒体发送指向与旧版 outbound 适配器相同的传输函数。只有当契约测试证明本机副作用和返回的回执时,才应声明某项能力。完整的 API 契约、示例、能力矩阵、回执规则、实时预览最终化、接收 ack 策略、测试和迁移表,请参见 Channel outbound API。如果现有的 outbound 适配器已经拥有正确的发送方法和能力元数据,则应使用 createChannelMessageAdapterFromOutbound(...) 派生 message 适配器,而不是手工编写另一层桥接。适配器发送应返回 MessageReceipt 值。当兼容性代码仍然需要旧版 id 时,应通过 listMessageReceiptPlatformIds(...) 或 resolveMessageReceiptPrimaryId(...) 派生它们,而不是在新的生命周期代码中保留并行的 messageIds 字段。支持预览的通道还应声明 message.live.capabilities,并精确列出其拥有的实时生命周期,例如 draftPreview、previewFinalization、progressUpdates、nativeStreaming 或 quietFinalization。会将草稿预览就地定稿的通道还应声明 message.live.finalizer.capabilities,例如 finalEdit、normalFallback、discardPending、previewReceipt 和 retainOnAmbiguousFailure,并通过 defineFinalizableLivePreviewAdapter(...) 加上 deliverWithFinalizableLivePreviewAdapter(...) 将运行时逻辑路由过去。请用 verifyChannelMessageLiveCapabilityAdapterProofs(...) 和 verifyChannelMessageLiveFinalizerProofs(...) 测试来支撑这些能力,以防本机预览、进度、编辑、回退/保留、清理和回执行为悄然漂移。延迟平台确认的入站接收器应声明 message.receive.defaultAckPolicy 和 supportedAckPolicies,而不是把 ack 时序隐藏在 monitor 本地状态中。为每一种声明的策略都要通过 verifyChannelMessageReceiveAckPolicyAdapterProofs(...) 覆盖。
像 createChannelTurnReplyPipeline、dispatchInboundReplyWithBase 和 recordInboundSessionAndDispatchReply 这样的遗留回复帮助器仍可用于兼容性分发器。不要在新的通道代码中使用这些名称;新的插件应从 message 适配器、回执以及 openclaw/plugin-sdk/channel-outbound 上的接收/发送生命周期帮助器开始。
迁移入站授权的通道可以在运行时接收路径中使用实验性的
openclaw/plugin-sdk/channel-ingress-runtime 子路径。该子路径将平台查找和副作用保留在插件内,同时共享允许列表状态解析、路由/发送者/命令/事件/激活决策、脱敏诊断以及轮次准入映射。请把插件身份规范化保留在传给解析器的描述符中;不要从已解析状态或决策中序列化原始匹配值。有关 API 设计、
所有权边界和测试预期,请参见
Channel ingress API。
如果你的通道在入站回复之外也支持输入中指示器,请在通道插件上暴露
heartbeat.sendTyping(...)。核心会在心跳模型运行开始前,使用已解析的心跳投递目标调用它,并使用共享的输入中保持活动/清理生命周期。当平台需要显式停止信号时,请添加 heartbeat.clearTyping(...)。
如果你的通道添加了携带媒体源的消息工具参数,请通过 describeMessageTool(...).mediaSourceParams 暴露这些参数名。核心会将该显式列表用于沙箱路径规范化和外发媒体访问策略,因此插件无需为提供商特定的头像、附件或封面图参数在共享核心中添加特殊处理。优先返回形如
{ "set-profile": ["avatarUrl", "avatarPath"] } 的按动作键控映射,这样无关动作就不会继承其他动作的媒体参数。对于刻意在每个暴露动作间共享的参数,平面数组仍然可用。
必须为平台侧媒体抓取暴露临时公共 URL 的通道,可以使用 openclaw/plugin-sdk/outbound-media 中的 createHostedOutboundMediaStore(...) 并结合插件状态存储。平台路由解析和令牌校验应保留在通道插件中;共享帮助器只负责媒体加载、过期元数据、分块行和清理。
如果你的通道需要对 message(action="send") 进行提供商特定的形状处理,请优先使用 actions.prepareSendPayload(...)。将原生卡片、块、嵌入或其他持久化数据放到 payload.channelData.<channel> 下,并让核心通过 outbound/message 适配器执行实际发送。仅当负载无法序列化和重试时,才使用 actions.handleAction(...) 作为发送的兼容性回退。
如果你的平台将额外的作用域存储在会话 id 中,请使用 messaging.resolveSessionConversation(...) 在插件中完成这部分解析。这是将 rawId 映射到基础会话 id、可选线程 id、显式 baseConversationId 以及任何 parentConversationCandidates 的标准挂钩。当你返回 parentConversationCandidates 时,请按从最窄父级到最宽/基础会话的顺序排列。
当插件代码需要规范化类似路由的字段、比较子线程与其父路由,或从 { channel, to, accountId, threadId } 构建稳定的去重键时,请使用 openclaw/plugin-sdk/channel-route。该帮助器会像核心一样规范化数字线程 id,因此插件应优先使用它,而不是临时的 String(threadId) 比较。
具有提供商特定目标语法的插件应暴露 messaging.resolveOutboundSessionRoute(...),这样核心就能获得提供商原生的会话和线程标识,而无需使用解析器 shim。
打包后的插件如果在通道注册表启动之前就需要相同的解析逻辑,也可以暴露一个顶层的 session-key-api.ts 文件,并提供匹配的
resolveSessionConversation(...) 导出。只有在运行时插件注册表尚不可用时,核心才会使用这个适用于引导阶段的接口。
当插件只需要在通用/raw id 之上提供父级回退时,messaging.resolveParentConversationCandidates(...) 仍然可作为遗留兼容回退。如果两个钩子都存在,核心会先使用
resolveSessionConversation(...).parentConversationCandidates,只有在规范钩子省略它们时才回退到 resolveParentConversationCandidates(...)。
审批与通道能力
大多数通道插件不需要审批相关的专门代码。- Core owns same-chat
/approve, shared approval button payloads, and generic fallback delivery. - Prefer one
approvalCapabilityobject on the channel plugin when the channel needs approval-specific behavior. ChannelPlugin.approvalsis removed. Put approval delivery/native/render/auth facts onapprovalCapability.plugin.authis login/logout only; core no longer reads approval auth hooks from that object.approvalCapability.authorizeActorActionandapprovalCapability.getActionAvailabilityStateare the canonical approval-auth seam.- Use
approvalCapability.getActionAvailabilityStatefor same-chat approval auth availability. - If your channel exposes native exec approvals, use
approvalCapability.getExecInitiatingSurfaceStatefor the initiating-surface/native-client state when it differs from same-chat approval auth. Core uses that exec-specific hook to distinguishenabledvsdisabled, decide whether the initiating channel supports native exec approvals, and include the channel in native-client fallback guidance.createApproverRestrictedNativeApprovalCapability(...)fills this in for the common case. - Use
outbound.shouldSuppressLocalPayloadPromptoroutbound.beforeDeliverPayloadfor channel-specific payload lifecycle behavior such as hiding duplicate local approval prompts or sending typing indicators before delivery. - Use
approvalCapability.deliveryonly for native approval routing or fallback suppression. - Use
approvalCapability.nativeRuntimefor channel-owned native approval facts. Keep it lazy on hot channel entrypoints withcreateLazyChannelApprovalNativeRuntimeAdapter(...), which can import your runtime module on demand while still letting core assemble the approval lifecycle. - Use
approvalCapability.renderonly when a channel truly needs custom approval payloads instead of the shared renderer. - Use
approvalCapability.describeExecApprovalSetupwhen the channel wants the disabled-path reply to explain the exact config knobs needed to enable native exec approvals. The hook receives{ channel, channelLabel, accountId }; named-account channels should render account-scoped paths such aschannels.<channel>.accounts.<id>.execApprovals.*instead of top-level defaults. - If a channel can infer stable owner-like DM identities from existing config, use
createResolvedApproverActionAuthAdapterfromopenclaw/plugin-sdk/approval-runtimeto restrict same-chat/approvewithout adding approval-specific core logic. - If custom approval auth intentionally allows only same-chat fallback, return
markImplicitSameChatApprovalAuthorization({ authorized: true })fromopenclaw/plugin-sdk/approval-auth-runtime; otherwise core treats the result as explicit approver authorization. - If a channel-owned native callback resolves approvals directly, use
isImplicitSameChatApprovalAuthorization(...)before resolving so implicit fallback still goes through the channel’s normal actor authorization. - If a channel needs native approval delivery, keep channel code focused on target normalization plus transport/presentation facts. Use
createChannelExecApprovalProfile,createChannelNativeOriginTargetResolver,createChannelApproverDmTargetResolver, andcreateApproverRestrictedNativeApprovalCapabilityfromopenclaw/plugin-sdk/approval-runtime. Put the channel-specific facts behindapprovalCapability.nativeRuntime, ideally viacreateChannelApprovalNativeRuntimeAdapter(...)orcreateLazyChannelApprovalNativeRuntimeAdapter(...), so core can assemble the handler and own request filtering, routing, dedupe, expiry, gateway subscription, and routed-elsewhere notices.nativeRuntimeis split into a few smaller seams: - Use
createNativeApprovalChannelRouteGatesfromopenclaw/plugin-sdk/approval-native-runtimewhen a channel supports both session-origin native delivery and explicit approval forwarding targets. The helper centralizes approval config selection,modehandling, agent/session filters, account binding, session-target matching, and target-list matching while callers still own the channel id, default forwarding mode, account lookup, transport-enabled check, target normalization, and turn-source target resolution. Do not use it to create core-owned channel policy defaults; pass the channel’s documented default mode explicitly. createChannelNativeOriginTargetResolveruses the shared channel-route matcher by default for{ to, accountId, threadId }targets. PasstargetsMatchonly when a channel has provider-specific equivalence rules, such as Slack timestamp prefix matching.- Pass
normalizeTargetForMatchtocreateChannelNativeOriginTargetResolverwhen the channel needs to canonicalize provider ids before the default route matcher or a customtargetsMatchcallback runs, while preserving the original target for delivery. UsenormalizeTargetonly when the resolved delivery target itself should be canonicalized. availability- whether the account is configured and whether a request should be handledpresentation- map the shared approval view model into pending/resolved/expired native payloads or final actionstransport- prepare targets plus send/update/delete native approval messagesinteractions- optional bind/unbind/clear-action hooks for native buttons or reactions, plus an optionalcancelDeliveredhook. ImplementcancelDeliveredwhendeliverPendingregisters in-process or persistent state (such as a reaction target store) so that state can be released if a handler stop cancels the delivery beforebindPendingruns or whenbindPendingreturns no handleobserve- optional delivery diagnostics hooks- If the channel needs runtime-owned objects such as a client, token, Bolt app, or webhook receiver, register them through
openclaw/plugin-sdk/channel-runtime-context. The generic runtime-context registry lets core bootstrap capability-driven handlers from channel startup state without adding approval-specific wrapper glue. - Reach for the lower-level
createChannelApprovalHandlerorcreateChannelNativeApprovalRuntimeonly when the capability-driven seam is not expressive enough yet. - Native approval channels must route both
accountIdandapprovalKindthrough those helpers.accountIdkeeps multi-account approval policy scoped to the right bot account, andapprovalKindkeeps exec vs plugin approval behavior available to the channel without hardcoded branches in core. - Core now owns approval reroute notices too. Channel plugins should not send their own “approval went to DMs / another channel” follow-up messages from
createChannelNativeApprovalRuntime; instead, expose accurate origin + approver-DM routing through the shared approval capability helpers and let core aggregate actual deliveries before posting any notice back to the initiating chat. - Preserve the delivered approval id kind end-to-end. Native clients should not guess or rewrite exec vs plugin approval routing from channel-local state.
- Different approval kinds can intentionally expose different native surfaces.
Current bundled examples:
- Slack keeps native approval routing available for both exec and plugin ids.
- Matrix keeps the same native DM/channel routing and reaction UX for exec and plugin approvals, while still letting auth differ by approval kind.
createApproverRestrictedNativeApprovalAdapterstill exists as a compatibility wrapper, but new code should prefer the capability builder and exposeapprovalCapabilityon the plugin.
openclaw/plugin-sdk/approval-auth-runtimeopenclaw/plugin-sdk/approval-client-runtimeopenclaw/plugin-sdk/approval-delivery-runtimeopenclaw/plugin-sdk/approval-gateway-runtimeopenclaw/plugin-sdk/approval-handler-adapter-runtimeopenclaw/plugin-sdk/approval-handler-runtimeopenclaw/plugin-sdk/approval-native-runtimeopenclaw/plugin-sdk/approval-reply-runtimeopenclaw/plugin-sdk/channel-runtime-context
openclaw/plugin-sdk/setup-runtime、
openclaw/plugin-sdk/setup-runtime、
openclaw/plugin-sdk/reply-runtime、
openclaw/plugin-sdk/reply-dispatch-runtime、
openclaw/plugin-sdk/reply-reference 和
openclaw/plugin-sdk/reply-chunking。
特别是针对设置:
openclaw/plugin-sdk/setup-runtime覆盖运行时安全的设置帮助器:createSetupTranslator、导入安全的设置补丁适配器(createPatchedAccountSetupAdapter、createEnvPatchedAccountSetupAdapter、createSetupInputPresenceValidator)、lookup-note 输出、promptResolvedAllowFrom、splitSetupEntries以及委派式 setup-proxy 构建器openclaw/plugin-sdk/setup-runtime包含用于createEnvPatchedAccountSetupAdapter的环境感知适配器接缝openclaw/plugin-sdk/channel-setup覆盖可选安装的设置 构建器,以及一些设置安全的原语:createOptionalChannelSetupSurface、createOptionalChannelSetupAdapter,
channelEnvVars 声明它们。仅将通道运行时的 envVars 或本地常量用于面向操作员的文案。
如果你的通道可能在插件运行时启动前出现在 status、channels list、channels status 或 SecretRef 扫描中,请在 package.json 中添加 openclaw.setupEntry。该入口点应当能够在只读命令路径中安全导入,并返回这些摘要所需的通道元数据、设置安全配置适配器、状态适配器以及通道密钥目标元数据。不要从设置入口启动客户端、监听器或传输运行时。
主通道入口导入路径也要保持精简。发现流程可以在不激活通道的情况下评估入口和通道插件模块,以注册能力。像 channel-plugin-api.ts 这样的文件应导出通道插件对象,而不要导入设置向导、传输客户端、socket 监听器、子进程启动器或服务启动模块。把这些运行时部分放在从 registerFull(...)、运行时 setter 或惰性能力适配器加载的模块中。
createOptionalChannelSetupWizard、DEFAULT_ACCOUNT_ID、
createTopLevelChannelDmPolicy、setSetupChannelEnabled 和
splitSetupEntries
- 只有在你还需要更重的共享设置/配置帮助器,例如
moveSingleAccountChannelSectionToDefaultAccount(...)时,才使用更宽泛的openclaw/plugin-sdk/setup接入面
createOptionalChannelSetupSurface(...)。生成的适配器/向导会在配置写入和最终化时关闭失败,并且它们会在校验、最终化和文档链接文案中复用相同的“需要安装”消息。
对于其他高频通道路径,在不需要更宽泛的遗留接入面时,优先使用更窄的帮助器:
openclaw/plugin-sdk/account-core,openclaw/plugin-sdk/account-id,openclaw/plugin-sdk/account-resolution, andopenclaw/plugin-sdk/account-helpersfor multi-account config and default-account fallbackopenclaw/plugin-sdk/inbound-envelopeandopenclaw/plugin-sdk/channel-inboundfor inbound route/envelope and record-and-dispatch wiringopenclaw/plugin-sdk/channel-targetsfor target parsing helpersopenclaw/plugin-sdk/outbound-mediafor media loading andopenclaw/plugin-sdk/channel-outboundfor outbound identity/send delegates and payload planningbuildThreadAwareOutboundSessionRoute(...)fromopenclaw/plugin-sdk/channel-corewhen an outbound route should preserve an explicitreplyToId/threadIdor recover the current:thread:session after the base session key still matches. Provider plugins can override precedence, suffix behavior, and thread id normalization when their platform has native thread delivery semantics.openclaw/plugin-sdk/thread-bindings-runtimefor thread-binding lifecycle and adapter registrationopenclaw/plugin-sdk/agent-media-payloadonly when a legacy agent/media payload field layout is still requiredopenclaw/plugin-sdk/telegram-command-configfor Telegram custom-command normalization, duplicate/conflict validation, and a fallback-stable command config contract
传入提及策略
将传入提及处理分成两层:- 插件拥有的证据收集
- 共享策略评估
openclaw/plugin-sdk/channel-mention-gating。
仅当你需要更广泛的传入辅助入口时,才使用 openclaw/plugin-sdk/channel-inbound。
适合放在插件本地逻辑中的内容:
- 回复到机器人检测
- 引用机器人检测
- 线程参与检查
- 服务/系统消息排除
- 用于证明机器人参与所需的平台原生缓存
requireMention- 显式提及结果
- 隐式提及允许列表
- 命令旁路
- 最终跳过决策
- 计算本地提及事实。
- 将这些事实传入
resolveInboundMentionDecision({ facts, policy })。 - 在你的传入门控中使用
decision.effectiveWasMentioned、decision.shouldBypassMention和decision.shouldSkip。
api.runtime.channel.mentions 为已经依赖运行时注入的打包频道插件暴露相同的共享提及辅助:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
implicitMentionKindWhen 和 resolveInboundMentionDecision,请从 openclaw/plugin-sdk/channel-mention-gating 导入,以避免加载无关的传入运行时辅助。
使用 resolveInboundMentionDecision({ facts, policy }) 进行提及门控。
操作指南
包与清单
创建标准插件文件。
package.json 中的 channel 字段表明这是一个频道插件。有关完整的包元数据规范,请参见
插件设置与配置:configSchema 验证 plugins.entries.acme-chat.config。将其用于不属于频道账户配置的插件自有设置。channelConfigs 验证 channels.acme-chat,并且是在插件运行时加载之前,由配置 schema、设置和 UI 界面使用的冷路径来源。构建频道插件对象
ChannelPlugin 接口有许多可选的适配器表面。先从最小配置开始——id 和 setup——然后按需添加适配器。创建 src/channel.ts:src/channel.ts
plugin-sdk/channel-config-helpers 中的辅助函数:resolveChannelDmAccess、resolveChannelDmPolicy、resolveChannelDmAllowFrom 和 normalizeChannelDmPolicy 可优先使用账户本地值,而不是继承的根值。通过 normalizeLegacyDmAliases 将同一个解析器用于 doctor 修复,以便运行时和迁移读取同一份契约。createChatChannelPlugin 会为你做什么
createChatChannelPlugin 会为你做什么
你无需手动实现底层适配器接口,只需传入声明式选项,构建器会将它们组合起来:
如果你需要完全控制,也可以传入原始适配器对象,而不是声明式选项。原始发出适配器可以定义
| 选项 | 作用 |
|---|---|
security.dm | 从配置字段中作用域化解析 DM 安全策略 |
pairing.text | 基于文本的 DM 配对流程,带代码交换 |
threading | 回复模式解析器(固定、按账户作用域或自定义) |
outbound.attachedResults | 返回结果元数据(消息 ID)的发送函数 |
chunker(text, limit, ctx) 函数。
可选的 ctx.formatting 携带投递时的格式化决策,例如 maxLinesPerMessage;发送前应先应用它,以便共享发出投递只需解析一次回复线程和分块边界。
当解析出了原生回复目标时,发送上下文还包括 replyToIdSource(implicit 或 explicit),这样负载辅助函数可以保留显式回复标签,而不会消耗隐式的一次性回复槽位。连接入口点
创建 将频道拥有的 CLI 描述符放在
index.ts:index.ts
registerCliMetadata(...) 中,这样 OpenClaw 就能在根帮助中显示它们,而无需激活完整的频道运行时;同时正常的完整加载仍会获得相同的描述符用于真实的命令注册。将 registerFull(...) 保留给仅运行时工作。
如果 registerFull(...) 注册 gateway RPC 方法,请使用插件特定前缀。核心管理命名空间(config.*、exec.approvals.*、wizard.*、update.*)仍然保留,并且始终解析为 operator.admin。
defineChannelPluginEntry 会自动处理注册模式分离。请参见
入口点 了解所有选项。添加设置入口
创建 当频道被禁用或未配置时,OpenClaw 会加载此入口而不是完整入口。
这样可避免在设置流程中拉取沉重的运行时代码。
详见 设置与配置。将设置安全导出拆分到 sidecar 模块的打包工作区频道,如果还需要一个显式的设置期运行时 setter,可以使用
setup-entry.ts,用于在引导期间进行轻量加载:setup-entry.ts
openclaw/plugin-sdk/channel-entry-contract 中的 defineBundledChannelSetupEntry(...)。处理传入消息
你的插件需要接收来自平台的消息并将它们转发给 OpenClaw。典型模式是使用 webhook 验证请求,然后通过频道的传入处理器分发它:
传入消息处理是频道特定的。每个频道插件都拥有自己的传入流水线。查看打包的频道插件(例如 Microsoft Teams 或 Google Chat 插件包)以了解真实模式。
测试
文件结构
高级主题
线程选项
固定、按账户作用域或自定义回复模式
消息工具集成
describeMessageTool 和动作发现
目标解析
inferTargetChatType、looksLikeId、resolveTarget
运行时辅助
通过 api.runtime 提供 TTS、STT、媒体、subagent
Channel inbound API
Shared inbound event lifecycle: ingest, resolve, record, dispatch, finalize
某些打包辅助接缝仍然存在,用于打包插件维护和兼容性。对于新的频道插件,它们不是推荐模式;除非你正在直接维护该打包插件家族,否则应优先使用通用 SDK 表面的 channel/setup/reply/runtime 子路径。