构建渠道插件
本指南将介绍如何构建一个连接 OpenClaw 和消息平台的渠道插件。在完成本指南后,您将拥有一个具备私信安全、配对、回复串接和出站消息功能的可运行渠道。
渠道插件的工作原理
Section titled “渠道插件的工作原理”通道插件不需要自己的发送/编辑/反应工具。OpenClaw 在核心中保留
一个共享的 message 工具。您的插件拥有:
- 配置 - 账户解析和设置向导
- 安全性 - 私信策略和允许列表
- 配对 - 私信批准流程
- 会话语法 - 提供商特定的对话 ID 如何映射到基础聊天、线程 ID 和父级回退
- 出站 - 向平台发送文本、媒体和投票
- 线程化 - 回复如何进行线程化
- 心跳输入 - 用于心跳传递目标的可选输入/忙碌信号
核心拥有共享消息工具、提示连线、外部会话键形状、
通用 :thread: 记账和分派。
新的渠道插件还应暴露一个来自 openclaw/plugin-sdk/channel-outbound 的 message 适配器,其中包含 defineChannelMessageAdapter。该适配器声明原生传输实际支持的持久最终发送能力,并将文本/媒体发送指向与旧版 outboundAPIAPI 适配器相同的传输函数。仅当合同测试证明原生副作用和返回回执时,才声明某项能力。
有关完整的 API 合同、示例、能力矩阵、回执规则、实时预览最终化、接收确认策略、测试和迁移表,请参阅渠道出站 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,而不是将确认时机隐藏在监视器本地状态中。使用 verifyChannelMessageReceiveAckPolicyAdapterProofs(...) 覆盖每个声明的策略。
用于兼容性分发的旧版回复辅助函数(如 createChannelTurnReplyPipeline、
dispatchInboundReplyWithBase 和 recordInboundSessionAndDispatchReply)
仍然可用。请勿在新的渠道代码中使用这些名称;新插件应从 message 适配器以及 openclaw/plugin-sdk/channel-outbound 上的回执和接收/发送生命周期辅助函数开始。
迁移入站授权的渠道可以使用运行时接收路径中的实验性 openclaw/plugin-sdk/channel-ingress-runtime 子路径。该子路径将平台查找和副作用保留在插件中,同时共享允许列表状态解析、路由/发送者/命令/事件/激活决策、编辑诊断和准入映射。在传递给解析器的描述符中保持插件标识规范化;不要序列化来自已解析状态或决策的原始匹配值。有关 API 设计、所有权边界和测试预期,请参阅 渠道入口 API。
如果您的渠道支持入站回复之外的输入指示器,请在渠道插件上公开
heartbeat.sendTyping(...)。Core 会在心跳模型运行开始之前使用已解析的
心跳传递目标调用它,并使用共享的输入保活/清理生命周期。当平台需要
显式停止信号时,添加 heartbeat.clearTyping(...)。
如果您的渠道添加了承载媒体源的消息工具参数,请通过
describeMessageTool(...).mediaSourceParams 公开这些
参数名称。Core 使用该显式列表进行沙盒路径标准化和出站媒体访问
策略,因此插件不需要为提供商特定的
头像、附件或封面图像参数处理共享核心的特殊情况。
最好返回一个以操作为键的映射,例如
{ "set-profile": ["avatarUrl", "avatarPath"] },这样不相关的操作就不会
继承另一个操作的媒体参数。对于在每个公开操作中
有意共享的参数,平面数组仍然有效。
如果您的渠道需要针对 message(action="send") 进行特定于提供商的调整,请优先使用 actions.prepareSendPayload(...)。将原生卡片、块、嵌入或其他持久化数据放在 payload.channelData.<channel> 下,让核心通过出站/消息适配器执行实际发送。仅将 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(...),以便核心无需使用解析器垫片即可获取提供商原生的会话和线程身份。
在渠道注册表启动之前需要相同解析的捆绑插件也可以暴露一个顶级 session-key-api.ts 文件,并带有匹配的 resolveSessionConversation(...) 导出。核心仅在运行时插件注册表尚不可用时才使用该引导安全表面。
messaging.resolveParentConversationCandidates(...) 仍然可用作传统兼容性回退方案,当插件只需要在通用/原始 ID 之上进行父级回退时。如果两个挂钩都存在,核心首先使用 resolveSessionConversation(...).parentConversationCandidates,并且仅当规范挂钩省略它们时才回退到 resolveParentConversationCandidates(...)。
审批和渠道功能
Section titled “审批和渠道功能”大多数渠道插件不需要特定于审批的代码。
- 核心拥有同聊天
/approve、共享审批按钮有效载荷和通用回退交付。 - 当渠道需要特定于审批的行为时,首选在渠道插件上使用一个
approvalCapability对象。 ChannelPlugin.approvals已被移除。请将审批 delivery/native/render/auth 事实放在approvalCapability上。plugin.auth仅用于登录/登出;核心不再从该对象读取审批 auth hooks。approvalCapability.authorizeActorAction和approvalCapability.getActionAvailabilityState是规范的审批 auth 接口。- 使用
approvalCapability.getActionAvailabilityState来处理同聊审批 auth 可用性。 - 如果您的渠道公开了原生执行审批,并且启动界面/原生客户端状态与同聊审批 auth 不同,请使用
approvalCapability.getExecInitiatingSurfaceState。核心使用该特定于执行的 hook 来区分enabled和disabled,决定启动渠道是否支持原生执行审批,并将该渠道包含在原生客户端回退指导中。createApproverRestrictedNativeApprovalCapability(...)会在常见情况下自动填充此项。 - 使用
outbound.shouldSuppressLocalPayloadPrompt或outbound.beforeDeliverPayload来处理特定于渠道的 payload 生命周期行为,例如隐藏重复的本地审批提示或在发送前输入指示器。 - 仅将
approvalCapability.delivery用于原生审批路由或回退抑制。 - 使用
approvalCapability.nativeRuntime来获取渠道拥有的原生审批事实。在热渠道入口点上通过createLazyChannelApprovalNativeRuntimeAdapter(...)保持其延迟加载,这可以按需导入您的运行时模块,同时仍允许核心组装审批生命周期。 - 仅当渠道确实需要自定义审批 payload 而不是共享渲染器时,才使用
approvalCapability.render。 - 当渠道希望在禁用路径回复中解释启用原生执行审批所需的确切配置旋钮时,请使用
approvalCapability.describeExecApprovalSetup。该 hook 接收{ channel, channelLabel, accountId };命名账户渠道应渲染账户作用域路径(例如channels.<channel>.accounts.<id>.execApprovals.*),而不是顶级默认值。 - 如果渠道可以从现有配置中推断出稳定的类似所有者的私信身份,请使用来自
openclaw/plugin-sdk/approval-runtime的createResolvedApproverActionAuthAdapter来限制同一聊天中的/approve,而无需添加特定于审批的核心逻辑。 - 如果自定义审批授权有意仅允许同聊回退,请从
openclaw/plugin-sdk/approval-auth-runtime返回markImplicitSameChatApprovalAuthorization({ authorized: true });否则核心会将结果视为显式审批者授权。 - 如果渠道拥有的原生回调直接解析审批,请在解析之前使用
isImplicitSameChatApprovalAuthorization(...),以便隐式回退仍然经过渠道的正常参与者授权。 - 如果渠道需要原生审批交付,请将渠道代码集中在目标标准化以及传输/呈现事实上。使用
createChannelExecApprovalProfile、createChannelNativeOriginTargetResolver、createChannelApproverDmTargetResolver和createApproverRestrictedNativeApprovalCapability,它们来自openclaw/plugin-sdk/approval-runtime。将渠道特定的事实放在approvalCapability.nativeRuntime之后,最好通过createChannelApprovalNativeRuntimeAdapter(...)或createLazyChannelApprovalNativeRuntimeAdapter(...),以便核心可以组装处理程序并拥有请求过滤、路由、去重、过期、网关订阅和路由到别处的通知。nativeRuntime被拆分为几个较小的接缝: - 当渠道同时支持会话发起的原生交付和显式批准转发目标时,请使用
openclaw/plugin-sdk/approval-native-runtime中的createNativeApprovalChannelRouteGates。该帮助器集中了批准配置选择、mode处理、代理/会话过滤器、账户绑定、会话目标匹配和目标列表匹配,而调用者仍拥有渠道 ID、默认转发模式、账户查找、传输启用检查、目标规范化和轮源目标解析。不要使用它来创建核心拥有的渠道策略默认值;显式传递渠道文档记录的默认模式。 createChannelNativeOriginTargetResolver默认对{ to, accountId, threadId }目标使用共享渠道路由匹配器。仅当渠道具有提供商特定的等效规则(例如 Slack 时间戳前缀匹配)时,才传递targetsMatch。- 当渠道需要在默认路由匹配器或自定义
targetsMatch回调运行之前对提供商 ID 进行规范化,同时保留用于交付的原始目标时,请将normalizeTargetForMatch传递给createChannelNativeOriginTargetResolver。仅当已解析的交付目标本身需要规范化时,才使用normalizeTarget。 availability- 账户是否已配置以及是否应处理请求presentation- 将共享批准视图模型映射到待处理/已解析/已过期的原生负载或最终操作transport- 准备目标并发送/更新/删除原生审批消息interactions- 用于原生按钮或反应的可选 bind/unbind/clear-action 钩子,以及一个可选的cancelDelivered钩子。当deliverPending注册进程内或持久化状态(例如反应目标存储)时,请实现cancelDelivered,以便在处理程序停止取消bindPending运行之前的投递,或者当bindPending未返回句柄时,可以释放该状态。observe- 可选的投递诊断钩子- 如果渠道需要运行时拥有的对象(例如客户端、令牌、Bolt 应用或 Webhook 接收器),请通过
openclaw/plugin-sdk/channel-runtime-context进行注册。通用的运行时上下文注册表允许核心从渠道启动状态引导由功能驱动的处理程序,而无需添加特定于审批的包装粘合代码。 - 仅当由功能驱动的接口尚不够具有表现力时,才使用较低级别的
createChannelApprovalHandler或createChannelNativeApprovalRuntime。 - 原生审批渠道必须通过这些辅助程序路由
accountId和approvalKind。accountId将多帐户审批策略限定在正确的机器人帐户范围内,而approvalKind使执行与插件审批的行为对渠道可用,而无需在核心中进行硬编码分支。 - 核心现在也拥有审批重定向通知。渠道插件不应从
createChannelNativeApprovalRuntime发送自己的“审批已转至私信或其他渠道”的后续消息;相反,应通过共享的审批功能辅助程序暴露准确的源 + 审批者私信路由,并让核心在向发起聊天发布任何通知之前汇总实际投递情况。 - 端到端保留已投递的审批 ID 类型。原生客户端不应 根据渠道本地状态猜测或重写执行与插件审批的路由。
- 不同的审批类型可以有意识地暴露不同的原生界面。
当前捆绑的示例:
- Slack 为 exec 和 plugin ID 保留了原生审批路由。
- Matrix 为 exec 和插件审批保留了相同的原生私信/渠道路由和用户体验,同时仍允许根据审批类型使用不同的身份验证。
createApproverRestrictedNativeApprovalAdapter仍然作为兼容性包装器存在,但新代码应该首选 capability 构建器并在插件上公开approvalCapability。
对于热渠道入口点,当您只需要该系列中的一部分时,首选更窄的运行时子路径:
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以及委托的 设置代理构建器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 之类的文件应导出渠道插件对象,而无需导入设置向导、传输客户端、套接字监听器、子进程启动器或服务启动模块。将这些运行时部件放在从 registerFull(...)、运行时设置器或惰性功能适配器加载的模块中。
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和openclaw/plugin-sdk/account-helpers用于多账户配置 和默认账户回退openclaw/plugin-sdk/inbound-envelope和openclaw/plugin-sdk/channel-inbound用于入站路由/信封 和记录分发连接openclaw/plugin-sdk/channel-targets用于目标解析助手openclaw/plugin-sdk/outbound-media用于媒体加载,以及openclaw/plugin-sdk/channel-outbound用于出站身份/发送代理 和负载规划- 从
openclaw/plugin-sdk/channel-core获取buildThreadAwareOutboundSessionRoute(...),当出站路由应保留 显式replyToId/threadId或在基础会话密钥仍匹配时 恢复当前:thread:会话。当提供商平台的 线程传递语义具有原生支持时,提供商插件可以覆盖 优先级、后缀行为和线程 ID 标准化。 openclaw/plugin-sdk/thread-bindings-runtime用于线程绑定生命周期 和适配器注册openclaw/plugin-sdk/agent-media-payload仅当仍然需要 传统的代理/媒体负载字段布局时openclaw/plugin-sdk/telegram-command-configTelegram 用于 Telegram 自定义命令 标准化、重复/冲突验证以及一个回退稳定的命令 配置契约
仅认证通道通常可以停留在默认路径:核心处理审批,插件只需暴露出站/认证功能。原生审批通道(如 Matrix、Slack、Telegram 和自定义聊天传输)应使用共享的原生辅助工具,而不是自行实现审批生命周期。
入站提及策略
Section titled “入站提及策略”将入站提及处理分为两层:
- 插件拥有的证据收集
- 共享策略评估
使用 openclaw/plugin-sdk/channel-mention-gating 进行提及策略决策。
仅在需要更广泛的入站
辅助工具集合时使用 openclaw/plugin-sdk/channel-inbound。
适合插件本地逻辑的情况:
- 回复机器人检测
- 引用机器人检测
- 线程参与检查
- 服务/系统消息排除
- 证明机器人参与所需的原生平台缓存
适合共享辅助工具的情况:
requireMention- 显式提及结果
- 隐式提及允许列表
- 命令绕过
- 最终跳过决策
首选流程:
- 计算本地提及事实。
- 将这些事实传入
resolveInboundMentionDecision({ facts, policy })。 - 在您的入站网关中使用
decision.effectiveWasMentioned、decision.shouldBypassMention和decision.shouldSkip。
import { implicitMentionKindWhen, matchesMentionWithExplicit, resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-inbound";
const mentionMatch = matchesMentionWithExplicit(text, { mentionRegexes, mentionPatterns,});
const facts = { canDetectMention: true, wasMentioned: mentionMatch.matched, hasAnyMention: mentionMatch.hasExplicitMention, implicitMentionKinds: [...implicitMentionKindWhen("reply_to_bot", isReplyToBot), ...implicitMentionKindWhen("quoted_bot", isQuoteOfBot)],};
const decision = resolveInboundMentionDecision({ facts, policy: { isGroup, requireMention, allowedImplicitMentionKinds: requireExplicitMention ? [] : ["reply_to_bot", "quoted_bot"], allowTextCommands, hasControlCommand, commandAuthorized, },});
if (decision.shouldSkip) return;api.runtime.channel.mentions 为已依赖运行时注入的内置渠道插件公开了相同的共享提及辅助程序:
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
如果您只需要 implicitMentionKindWhen 和
resolveInboundMentionDecision,请从
openclaw/plugin-sdk/channel-mention-gating 导入,以避免加载无关的入站运行时辅助程序。
使用 resolveInboundMentionDecision({ facts, policy }) 进行提及控制。
包和清单
创建标准插件文件。
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."}}}{"id": "acme-chat","kind": "channel","channels": ["acme-chat"],"name": "Acme Chat","description": "Acme Chat channel plugin","configSchema": {"type": "object","additionalProperties": false,"properties": {}},"channelConfigs": {"acme-chat": {"schema": {"type": "object","additionalProperties": false,"properties": {"token": { "type": "string" },"allowFrom": {"type": "array","items": { "type": "string" }}}},"uiHints": {"token": {"label": "Bot token","sensitive": true}}}}}configSchema验证plugins.entries.acme-chat.config。将其用于 不属于渠道账户配置的插件自有设置。channelConfigs验证channels.acme-chat,并且是配置架构、设置和 UI 表面在插件运行时加载之前使用的冷路径源。构建渠道插件对象
ChannelPlugin接口有许多可选的适配器表面。从最小配置开始——id和setup——并根据需要添加适配器。创建
src/channel.ts:import {createChatChannelPlugin,createChannelPluginBase,} from "openclaw/plugin-sdk/channel-core";import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core";import { acmeChatApi } from "./client.js"; // your platform API clienttype 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)?.[“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({ base: createChannelPluginBase({ id: “acme-chat”, setup: { resolveAccount, inspectAccount(cfg, accountId) { const section = (cfg.channels as Record
)?.[“acme-chat”]; return { enabled: Boolean(section?.token), configured: Boolean(section?.token), tokenStatus: section?.token ? “available” : “missing”, }; }, }, }),
// DM security: who can message the botsecurity: {dm: {channelKey: "acme-chat",resolvePolicy: (account) => account.dmPolicy,resolveAllowFrom: (account) => account.allowFrom,defaultPolicy: "allowlist",},},// Pairing: approval flow for new DM contactspairing: {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 deliveredthreading: { topLevelReplyToMode: "reply" },// Outbound: send messages to the platformoutbound: {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);},},},});```对于既接受规范顶级私信密钥又接受传统嵌套密钥的渠道,请使用 `plugin-sdk/channel-config-helpers` 中的辅助函数:`resolveChannelDmAccess`、`resolveChannelDmPolicy`、`resolveChannelDmAllowFrom` 和 `normalizeChannelDmPolicy` 将账户本地值置于继承的根值之前。通过 `normalizeLegacyDmAliases` 将相同的解析器与 doctor 修复配对,以便运行时和迁移读取相同的契约。createChatChannelPlugin 为您做什么
无需手动实现低级适配器接口,您只需传递声明式选项,构建器会将它们组合起来:
选项 它连接的内容 security.dm来自配置字段的范围限定私信安全解析器 pairing.text基于文本的私信配对流程,附带代码交换 threading回复模式解析器(固定、账户范围或自定义) outbound.attachedResults返回结果元数据(消息 ID)的发送函数 如果需要完全控制,您也可以传递原始适配器对象来代替声明式选项。
原始出站适配器可以定义
chunker(text, limit, ctx)函数。 可选的ctx.formatting携带传递时的格式化决策, 例如maxLinesPerMessage;请在发送前应用它,以便回复线程和分块边界由共享的出站传递解析一次。 当解析了原生回复目标时,发送上下文还包括replyToIdSource(implicit或explicit), 因此负载助手可以保留显式回复标签,而不会消耗隐式的一次性回复槽。连接入口点
创建
index.ts:import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";import { acmeChatPlugin } from "./src/channel.js";export default defineChannelPluginEntry({id: "acme-chat",name: "Acme Chat",description: "Acme Chat channel plugin",plugin: acmeChatPlugin,registerCliMetadata(api) {api.registerCli(({ program }) => {program.command("acme-chat").description("Acme Chat management");},{descriptors: [{name: "acme-chat",description: "Acme Chat management",hasSubcommands: false,},],},);},registerFull(api) {api.registerGatewayMethod(/* ... */);},});```CLI将渠道拥有的 CLI 描述符放在 `registerCliMetadata(...)`OpenClaw 中,以便 OpenClaw可以在不激活完整渠道运行时的情况下在根帮助中显示它们,而正常的完整加载仍然会获取相同的描述符以进行实际命令注册。保留 `registerFull(...)` 用于仅运行时的工作。如果 `registerFull(...)`RPC 注册了网关 RPC 方法,请使用特定于插件的前缀。核心管理员命名空间(`config.*`、`exec.approvals.*`、`wizard.*`、`update.*`)保持保留状态并始终解析为 `operator.admin`。`defineChannelPluginEntry` 会自动处理注册模式拆分。请参阅[入口点](/en/plugins/sdk-entrypoints#definechannelpluginentry) 了解所有选项。添加设置入口
创建
setup-entry.ts以便在新手引导期间进行轻量级加载:import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";import { acmeChatPlugin } from "./src/channel.js";export default defineSetupPluginEntry(acmeChatPlugin);```OpenClaw当渠道被禁用或未配置时,OpenClaw 会加载此入口而不是完整入口。它避免了在设置流程中引入繁重的运行时代码。有关详细信息,请参阅 [设置和配置](/en/plugins/sdk-setup#setup-entry)。将设置安全的导出拆分到 Sidecar 模块中的打包工作区渠道,如果它们还需要一个显式的设置时运行时设置器,可以使用来自 `openclaw/plugin-sdk/channel-entry-contract` 的 `defineBundledChannelSetupEntry(...)`。处理入站消息
您的插件需要从平台接收消息并将其转发到 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 the bundled Microsoft Teams or Google Chat plugin package.await handleAcmeChatInbound(api, event);res.statusCode = 200;res.end("ok");return true;},});}测试
在
src/channel.test.ts中编写同地测试:```typescript src/channel.test.tsimport { 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);});});``````bashpnpm test --/acme-chat/ ```
有关共享测试帮助程序,请参阅 [Testing](/zh/plugins/sdk-testing)。
<bundled-plugin-root>/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 和操作发现
inferTargetChatType, looksLikeId, resolveTarget
通过 api.runtime 实现的 TTS, STT, media, subagent
共享的入站事件生命周期:ingest, resolve, record, dispatch, finalize