Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt

Use this file to discover all available pages before exploring further.

频道轮次内核是共享的入站状态机,它会把规范化的平台事件转换为智能体轮次。渠道插件提供平台事实和投递回调。核心负责编排:摄取、分类、预检、解析、授权、组装、记录、分发和收尾。 当你的插件位于入站消息热路径上时使用它。对于非消息事件(斜杠命令、模态框、按钮交互、生命周期事件、反应、语音状态),让它们保持插件本地处理。内核只负责可能变成智能体文本轮次的事件。
内核通过注入的插件运行时以 runtime.channel.turn.* 访问。插件运行时类型从 openclaw/plugin-sdk/core 导出,因此第三方原生插件可以像内置渠道插件一样使用这些入口点。

为什么需要共享内核

渠道插件会重复相同的入站流程:规范化、路由、门控、构建上下文、记录会话元数据、分发智能体轮次、完成投递状态。没有共享内核时,对提及门控、仅工具可见回复、会话元数据、待处理历史或分发收尾的变更都必须逐个渠道应用。 内核有意将四个概念分开:
  • ConversationFacts:消息来自哪里
  • RouteFacts:哪个智能体和会话应处理它
  • ReplyPlanFacts:可见回复应发送到哪里
  • MessageFacts:智能体应看到什么正文和补充上下文
Slack 私信、Telegram 话题、Matrix 线程和 Feishu 话题会话在实践中都会区分这些概念。把它们当作一个标识符会随着时间推移产生偏差。

阶段生命周期

无论渠道如何,内核都会运行相同的固定流水线:
  1. ingest — 适配器将原始平台事件转换为 NormalizedTurnInput
  2. classify — 适配器声明此事件是否可以启动智能体轮次
  3. preflight — 适配器执行去重、自回声、水合、防抖、解密、部分事实预填充
  4. resolve — 适配器返回完整组装的轮次(路由、回复计划、消息、投递)
  5. authorize — 将私信、群组、提及和命令策略应用到组装后的事实
  6. assemble — 通过 buildContext 从事实构建 FinalizedMsgContext
  7. record — 持久化入站会话元数据和最后路由
  8. dispatch — 通过缓冲的分块分发器执行智能体轮次
  9. finalize — 即使发生分发错误,适配器 onFinalize 也会运行
当提供 log 回调时,每个阶段都会发出结构化日志事件。请参阅可观测性

准入类型

轮次被门控时,内核不会抛出异常。它会返回一个 ChannelTurnAdmission
类型何时
dispatch轮次被准入。智能体轮次运行,并执行可见回复路径。
observeOnly轮次端到端运行,但投递适配器不发送任何可见内容。用于广播观察者智能体和其他被动多智能体流程。
handled平台事件已在本地消费(生命周期、反应、按钮、模态框)。内核跳过分发。
drop跳过路径。可选的 recordHistory: true 会把消息保留在待处理群组历史中,以便未来提及时有上下文。
准入可以来自 classify(事件类别表明它不能启动轮次)、来自 preflight(去重、自回声、缺少提及但记录历史),也可以来自 resolveTurn 本身。

入口点

运行时公开三个首选入口点,让适配器可以在匹配渠道的层级选择接入。
runtime.channel.turn.run(...)             // adapter-driven full pipeline
runtime.channel.turn.runPrepared(...)     // channel owns dispatch; kernel runs record + finalize
runtime.channel.turn.buildContext(...)    // pure facts to FinalizedMsgContext mapping
为了 Plugin SDK 兼容性,两个较旧的运行时辅助函数仍然可用:
runtime.channel.turn.runResolved(...)      // deprecated compatibility alias; prefer run
runtime.channel.turn.dispatchAssembled(...) // deprecated compatibility alias; prefer run or runPrepared

run

当你的渠道可以把入站流程表达为 ChannelTurnAdapter<TRaw> 时使用。适配器提供 ingest、可选 classify、可选 preflight、必需 resolveTurn 和可选 onFinalize 回调。
await runtime.channel.turn.run({
  channel: "tlon",
  accountId,
  raw: platformEvent,
  adapter: {
    ingest(raw) {
      return {
        id: raw.messageId,
        timestamp: raw.timestamp,
        rawText: raw.body,
        textForAgent: raw.body,
      };
    },
    classify(input) {
      return { kind: "message", canStartAgentTurn: input.rawText.length > 0 };
    },
    async preflight(input, eventClass) {
      if (await isDuplicate(input.id)) {
        return { admission: { kind: "drop", reason: "dedupe" } };
      }
      return {};
    },
    resolveTurn(input) {
      return buildAssembledTurn(input);
    },
    onFinalize(result) {
      clearPendingGroupHistory(result);
    },
  },
});
当渠道只有少量适配器逻辑,并且适合通过钩子拥有生命周期时,run 是合适的形状。

runPrepared

当渠道有复杂的本地分发器,包含预览、重试、编辑或线程引导,并且这些逻辑必须由渠道拥有时使用。内核仍会在分发前记录入站会话,并公开统一的 DispatchedChannelTurnResult
const { dispatchResult } = await runtime.channel.turn.runPrepared({
  channel: "matrix",
  accountId,
  routeSessionKey,
  storePath,
  ctxPayload,
  recordInboundSession,
  record: {
    onRecordError,
    updateLastRoute,
  },
  onPreDispatchFailure: async (err) => {
    await stopStatusReactions();
  },
  runDispatch: async () => {
    return await runMatrixOwnedDispatcher();
  },
});
富渠道(Matrix、Mattermost、Microsoft Teams、Feishu、QQ Bot)使用 runPrepared,因为它们的分发器会编排平台特定行为,而这些行为不应让内核知晓。

buildContext

一个纯函数,将事实包映射为 FinalizedMsgContext。当你的渠道手写部分流水线,但希望上下文形状保持一致时使用它。
const ctxPayload = runtime.channel.turn.buildContext({
  channel: "googlechat",
  accountId,
  messageId,
  timestamp,
  from,
  sender,
  conversation,
  route,
  reply,
  message,
  access,
  media,
  supplemental,
});
在为 run 组装轮次的 resolveTurn 回调内部,buildContext 也很有用。
已弃用的 SDK 辅助函数(如 dispatchInboundReplyWithBase)仍会通过组装轮次辅助函数桥接。新的插件代码应使用 runrunPrepared

事实类型

内核从你的适配器消费的事实与平台无关。在交给内核之前,请把平台对象转换为这些形状。

NormalizedTurnInput

字段用途
id用于去重和日志的稳定消息 ID
timestamp可选的纪元毫秒
rawText从平台接收的正文
textForAgent可选的清理后智能体正文(去除提及、修剪输入)
textForCommands可选的用于 /command 解析的正文
raw需要原始对象的适配器回调使用的可选透传引用

ChannelEventClass

字段用途
kindmessagecommandinteractionreactionlifecycleunknown
canStartAgentTurn如果为 false,内核返回 { kind: "handled" }
requiresImmediateAck对需要在分发前 ACK 的适配器提供提示

SenderFacts

字段用途
id稳定的平台发送者 ID
name显示名称
usernamename 不同时的用户名
tagDiscord 风格的判别符或平台标签
roles角色 ID,用于成员角色允许列表匹配
isBot当发送者是已知机器人时为 true(内核用于丢弃)
isSelf当发送者是已配置的智能体自身时为 true
displayLabel用于信封文本的预渲染标签

ConversationFacts

字段用途
kinddirectgroupchannel
id用于路由的会话 ID
label信封的人类可读标签
spaceId可选的外层空间标识符(Slack 工作区、Matrix homeserver)
parentId当这是线程时的外层会话 ID
threadId当此消息位于线程内时的线程 ID
nativeChannelId与路由 ID 不同时的平台原生渠道 ID
routePeer用于 resolveAgentRoute 查找的对端

RouteFacts

字段用途
agentId应处理此轮次的智能体
accountId可选覆盖值(多账号渠道)
routeSessionKey用于路由的会话键
dispatchSessionKey与路由键不同时,分发时使用的会话键
persistedSessionKey写入持久化会话元数据的会话键
parentSessionKey分支/线程会话的父级
modelParentSessionKey分支会话的模型侧父级
mainSessionKey直接对话的主私信所有者固定键
createIfMissing允许记录步骤创建缺失的会话行

ReplyPlanFacts

字段用途
to写入上下文 To 的逻辑回复目标
originatingTo原始上下文目标(OriginatingTo
nativeChannelId用于交付的平台原生渠道 id
replyTarget当不同于 to 时,最终可见回复目的地
deliveryTarget更底层的交付覆盖目标
replyToId引用/锚定的消息 id
replyToIdFull当平台同时提供两者时,完整格式的引用 id
messageThreadId交付时的线程 id
threadParentId线程的父消息 id
sourceReplyDeliveryModethreadreplychanneldirectnone

AccessFacts

AccessFacts 携带授权阶段需要的布尔值。身份匹配保留在渠道中:内核只消费结果。
字段用途
dm私信允许/配对/拒绝决策以及 allowFrom 列表
group群组策略、路由允许、发送者允许、允许列表、提及要求
commands跨已配置授权器的命令授权
mentions是否可以检测提及,以及是否提到了智能体

MessageFacts

字段用途
body最终信封正文(已格式化)
rawBody原始入站正文
bodyForAgent智能体看到的正文
commandBody用于命令解析的正文
envelopeFrom信封的预渲染发送者标签
senderLabel渲染后发送者的可选覆盖值
preview用于日志的短版脱敏预览
inboundHistory当渠道保留缓冲区时的近期入站历史条目

SupplementalContextFacts

补充上下文涵盖引用、转发和线程引导上下文。内核会应用已配置的 contextVisibility 策略。渠道适配器只提供事实和 senderAllowed 标志,以便跨渠道策略保持一致。

InboundMediaFacts

媒体采用事实形态。平台下载、鉴权、SSRF 策略、CDN 规则和解密保留在渠道本地。内核会把事实映射到 MediaPathMediaUrlMediaTypeMediaPathsMediaUrlsMediaTypesMediaTranscribedIndexes

适配器契约

对于完整的 run,适配器形态如下:
type ChannelTurnAdapter<TRaw> = {
  ingest(raw: TRaw): Promise<NormalizedTurnInput | null> | NormalizedTurnInput | null;
  classify?(input: NormalizedTurnInput): Promise<ChannelEventClass> | ChannelEventClass;
  preflight?(
    input: NormalizedTurnInput,
    eventClass: ChannelEventClass,
  ): Promise<PreflightFacts | ChannelTurnAdmission | null | undefined>;
  resolveTurn(
    input: NormalizedTurnInput,
    eventClass: ChannelEventClass,
    preflight: PreflightFacts,
  ): Promise<ChannelTurnResolved> | ChannelTurnResolved;
  onFinalize?(result: ChannelTurnResult): Promise<void> | void;
};
resolveTurn 返回一个 ChannelTurnResolved,也就是带有可选准入类型的 AssembledChannelTurn。返回 { admission: { kind: "observeOnly" } } 会运行该轮次,但不会产生可见输出。适配器仍然拥有交付回调;该回调只是会在该轮次中变成空操作。 onFinalize 会在每个结果上运行,包括分发错误。用它清理待处理的群组历史、移除 ack 反应、停止 Status 指示器,并刷新本地状态。

交付适配器

内核不会直接调用平台。渠道会向内核传入一个 ChannelTurnDeliveryAdapter
type ChannelTurnDeliveryAdapter = {
  deliver(payload: ReplyPayload, info: ChannelDeliveryInfo): Promise<ChannelDeliveryResult | void>;
  onError?(err: unknown, info: { kind: string }): void;
  durable?: false | DurableInboundReplyDeliveryOptions;
};

type ChannelDeliveryResult = {
  messageIds?: string[];
  receipt?: MessageReceipt;
  threadId?: string;
  replyToId?: string;
  visibleReplySent?: boolean;
};
deliver 会针对每个已缓冲的回复分块调用一次。在消息生命周期迁移期间,组装后的渠道轮次交付默认由渠道拥有:省略 durable 字段意味着内核必须直接调用 deliver,且不得通过通用出站交付路由。只有在渠道经过审计,证明通用发送路径保留旧交付行为之后,才设置 durable,包括回复/线程目标、媒体处理、已发送消息/自回显缓存、Status 清理,以及返回的消息 id。durable: false 仍然是“使用渠道拥有的回调”的兼容写法,但未迁移的渠道不需要添加它。当渠道拥有平台消息 id 时返回这些 id,以便分发器保留线程锚点并编辑后续分块;较新的交付路径也应返回 receipt,以便恢复、预览收尾和重复抑制可以从 messageIds 迁移出去。对于仅观察轮次,返回 { visibleReplySent: false },或使用 createNoopChannelTurnDeliveryAdapter() 使用 runPrepared 且拥有完全由渠道拥有的分发器的渠道没有 ChannelTurnDeliveryAdapter。这些分发器默认不是 durable。它们应保留直接交付路径,直到它们明确选择加入新的发送上下文,并具备完整目标、可安全重放的适配器、receipt 契约和渠道副作用钩子。 公共兼容性辅助函数,例如 recordInboundSessionAndDispatchReplydispatchInboundReplyWithBase 和直接私信辅助函数,在迁移期间必须保持行为不变。它们不应在调用方拥有的 deliverreply 回调之前调用通用 durable 交付。

记录选项

记录阶段会包装 recordInboundSession。大多数渠道可以使用默认值。通过 record 覆盖:
record: {
  groupResolution,
  createIfMissing: true,
  updateLastRoute,
  onRecordError: (err) => log.warn("record failed", err),
  trackSessionMetaTask: (task) => pendingTasks.push(task),
}
分发器会等待记录阶段完成。如果记录抛出异常,内核会运行 onPreDispatchFailure(当提供给 runPrepared 时)并重新抛出。

可观测性

当提供 log 回调时,每个阶段都会发出结构化事件:
await runtime.channel.turn.run({
  channel: "twitch",
  accountId,
  raw,
  adapter,
  log: (event) => {
    runtime.log?.debug?.(`turn.${event.stage}:${event.event}`, {
      channel: event.channel,
      accountId: event.accountId,
      messageId: event.messageId,
      sessionKey: event.sessionKey,
      admission: event.admission,
      reason: event.reason,
    });
  },
});
记录的阶段:ingestclassifypreflightresolveauthorizeassemblerecorddispatchfinalize。避免记录原始正文;使用 MessageFacts.preview 作为短版脱敏预览。

哪些内容保留在渠道本地

内核拥有编排。渠道仍然拥有:
  • 平台传输协议(Gateway 网关、REST、websocket、轮询、webhook)
  • 身份解析和显示名称匹配
  • 原生命令、斜杠命令、自动补全、模态框、按钮、语音状态
  • 卡片、模态框和自适应卡片渲染
  • 媒体鉴权、CDN 规则、加密媒体、转写
  • 编辑、反应、脱敏和在线状态 API
  • 回填和平台侧历史获取
  • 需要平台特定验证的配对流程
如果两个渠道开始需要同一个辅助函数来处理其中某项,请提取一个共享的 SDK 辅助函数,而不是把它推入内核。

稳定性

runtime.channel.turn.* 是公共插件运行时表面的一部分。事实类型(SenderFactsConversationFactsRouteFactsReplyPlanFactsAccessFactsMessageFactsSupplementalContextFactsInboundMediaFacts)和准入形态(ChannelTurnAdmissionChannelEventClass)可通过来自 openclaw/plugin-sdk/corePluginRuntime 访问。 向后兼容规则适用:新的事实字段是增量添加的,准入类型不会重命名,入口点名称保持稳定。需要非增量变更的新渠道需求必须经过插件 SDK 迁移流程。

相关