会话和绑定
将提供商的会话 ID 映射到 OpenClaw 会话,并负责与之配套的路由和绑定规则。这是构建频道插件指南的一部分。
会话对话语法¶
如果你的平台在会话 ID 中存储了额外的作用域,请在插件中通过 messaging.resolveSessionConversation(...) 保留该解析逻辑。这是将 rawId 映射到基础会话 ID、可选线程 ID、显式 baseConversationId 以及任意 parentConversationCandidates 的规范钩子。当你返回 parentConversationCandidates 时,请按从最窄父级到最宽/基础会话的顺序排列它们。
messaging.resolveParentConversationCandidates(...) 是一个已弃用的兼容性回退,适用于只需要在通用/原始 ID 之上提供父级回退的插件。如果两个钩子都存在,核心会优先使用 resolveSessionConversation(...).parentConversationCandidates,只有当规范钩子省略它们时,才会回退到 resolveParentConversationCandidates(...)。
需要在频道注册表启动之前执行相同解析的内置插件,可以暴露一个顶层 session-key-api.ts 文件,并提供匹配的 resolveSessionConversation(...) 导出(参见 Feishu 和 Telegram 插件)。核心仅在使用运行时插件注册表尚不可用时,才使用该引导安全接口。
当插件代码需要规范化类似路由的字段、比较子线程与其父路由,或从 { channel, to, accountId, threadId } 构建稳定的去重键时,请使用 openclaw/plugin-sdk/channel-route。该辅助函数会以与核心相同的方式规范化数字线程 ID,因此请优先使用它,而不是临时使用 String(threadId) 比较。具有提供商特定目标语法的插件应暴露 messaging.resolveOutboundSessionRoute(...),以便核心获得提供商原生的会话和线程标识,而无需解析垫片。
由所有者派生的心跳路由会向该解析器传递 deliveryPurpose: "heartbeat-owner"。插件可以使用它来解析缺失的操作员投递上下文,同时不会放宽其他出站调用方的目标要求。
会话路由所有权¶
当通用路由匹配无法复现频道的已配置和运行时绑定规则时,请实现 messaging.resolveConversationRouteOwner(...)。解析器会接收当前配置、账户和已记录的会话标识,包括当投递 target 与路由对端不同时提供的 target。它必须复用与入站路由相同的优先级和提供商标识语法。
所有权检查是同步且只读的。不要刷新绑定存活状态、执行网络请求,或推断缺失的提供商事实。返回:
- 对于由代理拥有的路由,返回
{ kind: "agent", agentId }。 - 对于由插件拥有的运行时绑定,返回
{ kind: "plugin", pluginId, fallbackAgentId }。fallbackAgentId是在该插件没有活动的入站认领处理器时使用的路由。 - 当权威所有者状态暂时不可用且调用方应重试时,返回
{ kind: "unavailable" }。 - 当提供的标识无效或无法授权时返回
null,包括已被移除或禁用的账户。该账户保留的会话历史不应导致活动账户列表失败。 - 返回
undefined以委托给核心的通用所有者解析。
将暂时不可用与 null 区分开来:适配器重启并不能证明先前绑定的会话没有所有者。
对于这种可用/不可用区分,请使用 inspectConversationBinding(...) 及其来自 openclaw/plugin-sdk/conversation-binding-inspection-runtime 的 ConversationBindingInspection 结果。此公共检查辅助函数是同步的、只读的,并且不会刷新绑定存活状态。
账户范围的会话绑定支持¶
当频道支持通用当前会话绑定时,请设置 conversationBindings.supportsCurrentConversationBinding。createChatChannelPlugin(...) 默认将此静态能力设置为 true。其监视器拥有自定义绑定适配器的频道还必须设置 bindingStore: "adapter";当该适配器不可用时,核心会采取失败关闭策略,而不是读取或写入通用绑定行。仅使用旧版 createManager 的插件保留相同的适配器拥有行为。
如果支持情况因已配置账户而异,请同时实现 conversationBindings.isCurrentConversationBindingSupported({ accountId })。核心仅在静态能力启用后评估此同步钩子。返回 false 会使该账户的通用当前会话能力、绑定、查找、列表、touch 和 unbind 操作不可用。省略该钩子会将静态能力应用于每个账户。
请从已加载的账户配置或运行时状态中解析答案。此钩子仅控制通用当前会话绑定;它不会替代已配置的绑定规则或插件拥有的会话路由。契约测试应通过 openclaw/plugin-sdk/channel-core 导出的 ChannelPlugin["conversationBindings"] 契约,至少覆盖一个受支持账户和一个不受支持账户。
绑定 ID 是频道和账户本地的。SessionBindingService.touchAsync(bindingId, at?, scope?) 和 unbind({ bindingId, reason, scope }) 接受可选的 { channel, accountId } 作用域以选择该所有者。对于单个变更,请将现有绑定的 conversation 作为作用域传递。例如,要分离一个已解析的绑定:
await getSessionBindingService().unbind({
bindingId: binding.bindingId,
scope: binding.conversation,
reason: "manual",
});
从 openclaw/plugin-sdk/session-binding-runtime 导入 getSessionBindingService。对于活动更新,请 await service.touchAsync(binding.bindingId, at, binding.conversation)。仅在有意进行全局清理或现有遗留跨频道操作时省略作用域。作用域不会更改绑定 ID,也不需要新的适配器方法。
刷新相同的目标会话和目标类型会保留省略的运行时元数据。替换其中任一项会开始全新的目标元数据,因此新会话不能继承先前的插件所有者、代理或标签。请将会话传输细节和显式生命周期设置与目标元数据分开。
使用 resolveThreadBindingLifecycle(...)(来自
openclaw/plugin-sdk/thread-bindings-session-runtime)处理标准的空闲和最大存活时间过期。具有不同旧版时间戳约定的插件可以在同一子路径上向
resolveThreadBindingExpiry(...) 传入准备好的 inactivityExpiresAt 和 maxAgeExpiresAt 值。它会选择较早的截止时间及其原因;在截止时间相同时优先选择空闲过期;省略的截止时间视为禁用。插件仍负责时间戳验证和时长默认值。
在投影绑定记录时,保留不透明的插件所有权元数据。
插件拥有的目标不需要 OpenClaw agent id;在解析 agent 之前,使用
openclaw/plugin-sdk/conversation-binding-runtime 中的
isPluginOwnedSessionBindingRecord(...) 将它们与 agent 拥有的目标区分开。
对于具有无作用域会话键(例如 global)的 agent 拥有目标,保留
metadata.agentId,以便路由保持绑定的所有者。agent 作用域的目标键优先于冲突的元数据。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw