如何免费推广一个网站手机网站 推广
如何免费推广一个网站,手机网站 推广,做网站文字编辑好不好,网站后台上传附件系列目标#xff1a;读完全系列后#xff0c;你能在 OpenClaw 上做二次开发#xff0c;也能从零搭建类似的系统。
本文核心问题#xff1a;WhatsApp 来了一条消息#xff0c;OpenClaw 怎么知道该交给哪个 Agent 的哪个会话处理#xff1f;从一个具体的麻烦说起
假设你用 …系列目标读完全系列后你能在 OpenClaw 上做二次开发也能从零搭建类似的系统。本文核心问题WhatsApp 来了一条消息OpenClaw 怎么知道该交给哪个 Agent 的哪个会话处理从一个具体的麻烦说起假设你用 OpenClaw 管理以下账号WhatsApp 个人号1-555-personalWhatsApp 商务号1-555-businessTelegram 机器人 A个人助手Telegram 机器人 B工作助手Discord 服务器有 #general、#dev、#ops 三个频道你有两个 Agentpersonal处理私人事务有访问日历、联系人的权限work处理工作事务有访问代码仓库、服务器的权限现在WhatsApp 个人号收到消息 → 应该交给personalWhatsApp 商务号收到消息 → 应该交给workTelegram 机器人 A 收到消息 → 应该交给personalDiscord #ops 频道的消息 → 应该交给workDiscord #general 频道 → 应该交给personalDiscord #dev 频道来自管理员角色的消息 → 应该交给专门的devopsAgent同一个 Discord不同频道、不同角色的人应该路由到不同的 Agent。这是一个真实的路由问题。OpenClaw 是如何设计它的路由系统来解决这个问题的第一个问题如何统一接入不同的消息平台WhatsApp、Telegram、Discord、Slack、Signal、iMessage……每个平台的 API 完全不同。如果为每个平台单独写一套接入代码那么核心路由逻辑就会陷入「if platform ‘whatsapp’: … elif platform ‘telegram’: …」的噩梦。OpenClaw 的解法是Channel 插件接口ChannelPlugin——一个统一的对接协议每个平台实现这个协议核心路由代码只和协议打交道。// src/channels/plugins/types.plugin.tsexporttypeChannelPluginResolvedAccountany,Probeunknown,Auditunknown{id:ChannelId;// 平台 ID如 whatsapp、telegrammeta:ChannelMeta;// 展示信息名称、文档路径、图标capabilities:ChannelCapabilities;// 支持哪些能力群组、投票、媒体、编辑消息…config:ChannelConfigAdapter;// 必须读取/解析平台配置security?:ChannelSecurityAdapter;// 可选dmPolicy、allowFrom 等安全策略outbound?:ChannelOutboundAdapter;// 可选发送消息的实现pairing?:ChannelPairingAdapter;// 可选二维码配对流程groups?:ChannelGroupAdapter;// 可选群组管理gateway?:ChannelGatewayAdapter;// 可选注册额外的 Gateway RPC 方法agentTools?:ChannelAgentToolFactory;// 可选提供给 Agent 的工具如 whatsapp-login// ... 更多可选 Adapter};这是一种分层可选协议的设计核心字段id、meta、capabilities、config是必须的其余功能按需实现。一个简单的 webhook 通道可以只实现outbound一个功能完整的平台可以实现全部 Adapter。这类似于 Go 的接口组合设计——每个 Adapter 是一个小接口插件按需组合。插件如何注册// src/channels/plugins/index.tsexportfunctionlistChannelPlugins():ChannelPlugin[]{constregistryrequireActivePluginRegistry();returnregistry.channels.map((entry)entry.plugin).sort(/* 按 meta.order 排序 */);}插件注册表PluginRegistry在 Gateway 启动时初始化支持四个来源优先级从高到低config配置文件指定的插件路径workspacepnpm workspace开发模式下extensions/*global全局 npm 安装的插件bundled随核心内置的插件同一个 channel ID 只保留优先级最高的版本避免冲突。第二个问题消息到达后如何唯一标识一个会话路由的核心任务是给定「来自 WhatsApp 商务号、发自联系人 Alice 的消息」找到或创建对应的AI 会话。这个「找到」是通过SessionKey会话键实现的。SessionKey 是一个字符串唯一标识一个 AI 对话上下文。看看测试用例从里面读出格式规律agent:main:main # 最简单的主会话 agent:main:direct:15551234567 # 按联系人隔离的 DM 会话 agent:main:whatsapp:direct:15551234567 # 按渠道联系人隔离 agent:main:telegram:tasks:direct:7550356539 # 按账号渠道联系人隔离 agent:main:discord:channel:1468834856187203680 # Discord 某个频道的会话 agent:main:discord:group:987654321 # Discord 群组会话 agent:main:discord:channel:c1:thread:t1 # Discord 线程会话 agent:main:cron:job-1 # 定时任务会话 agent:main:subagent:worker # 子 Agent 会话格式规律agent:agentId:rest其中rest可以是main主会话direct:peerId私信按发送人隔离channel:direct:peerId私信按渠道发送人隔离channel:accountId:direct:peerId按账号渠道发送人隔离channel:chatType:peerId群组/频道会话SessionKey 就是 AI 对话的坐标系。同一个 SessionKey 意味着同一段对话历史同一个 Agent 实例。dmScope控制 DM 会话的隔离粒度默认情况下所有私信无论来自哪个人、哪个平台都进入同一个主会话agent:main:main。这对「单用户单助手」场景是合理的——你自己就是唯一的使用者。但如果你想让 OpenClaw 同时服务多个人家庭成员、团队成员就需要隔离不同人的对话历史。这时候用dmScope配置// src/config/types.base.tsexporttypeDmScope|main// 默认所有 DM → 同一个主会话|per-peer// 按发送人隔离agent:main:direct:peerId|per-channel-peer// 按渠道发送人agent:main:channel:direct:peerId|per-account-channel-peer// 按账号渠道发送人最细粒度测试用例直接说明了效果// src/routing/resolve-route.test.tstest(dmScope controls direct-message session key isolation,(){// per-peer 模式{dmScope:per-peer,expected:agent:main:direct:15551234567},// per-channel-peer 模式{dmScope:per-channel-peer,expected:agent:main:whatsapp:direct:15551234567},})插曲你在不同平台上是「同一个人」吗有个微妙的问题Alice 在 Telegram 的 ID 是111111111在 Discord 的 ID 是222222222222222222。如果你用per-peer或per-channel-peer隔离了会话那 Alice 从 Telegram 发的消息和从 Discord 发的消息会进入不同的会话。但你知道 Telegram-111111111 和 Discord-222222222222222222 是同一个 Alice。这时候用identityLinks配置# openclaw.ymlsession:dmScope:per-peeridentityLinks:alice:# 规范名称-telegram:111111111-discord:222222222222222222效果// src/routing/resolve-route.test.tstest(identityLinks applies to direct-message scopes,(){// Telegram 消息 → alice 的会话{channel:telegram,peerId:111111111,expected:agent:main:direct:alice},// Discord 消息 → 同一个 alice 的会话{channel:discord,peerId:222222222222222222,expected:agent:main:discord:direct:alice},})identityLinks 的本质在生成 SessionKey 之前把平台原始 ID 替换成规范身份名称。两条来自不同平台的消息经过身份链接后落到同一个 SessionKey因此使用同一段对话历史。核心问题七级路由优先级现在来到最复杂的部分。前面说的dmScope和identityLinks只解决了「同一个 Agent 内如何隔离会话」的问题。但我们还没解决开头的那个大问题WhatsApp 商务号的消息怎么路由到workAgent而不是personalAgent这是通过Binding绑定规则实现的。Binding 是一个条件-结果对// src/config/types.agents.tsexporttypeAgentBinding{agentId:string;// 路由目标 Agentmatch:{channel:string;// 必须哪个渠道accountId?:string;// 可选哪个账号* 任意账号peer?:{kind:ChatType;id:string};// 可选特定联系人/群组/频道guildId?:string;// 可选Discord 服务器 IDteamId?:string;// 可选Slack 工作区 IDroles?:string[];// 可选Discord 角色 IDs};};多条 Binding 组成一个列表配置文件中的bindings: [...]。当消息到达时resolveAgentRoute函数按照七级优先级从最具体到最宽泛依次匹配// src/routing/resolve-route.ts — tiers 数组consttiers[{matchedBy:binding.peer,/* 1. 精确联系人/群组/频道匹配 */},{matchedBy:binding.peer.parent,/* 2. 线程父频道继承 */},{matchedBy:binding.guildroles,/* 3. Discord 服务器 角色 */},{matchedBy:binding.guild,/* 4. Discord 服务器范围 */},{matchedBy:binding.team,/* 5. Slack 工作区范围 */},{matchedBy:binding.account,/* 6. 账号级匹配 */},{matchedBy:binding.channel,/* 7. 渠道级通配accountId**/},];// 以上七级都没匹配 → default使用默认 Agent优先级从高到低第一个匹配的就是结果。用真实配置验证回到文章开头的场景对应的 Binding 配置是这样的bindings:# Tier 6 - account 级WhatsApp 账号分流-agentId:personalmatch:channel:whatsappaccountId:1-555-personal-agentId:workmatch:channel:whatsappaccountId:1-555-business# Tier 1 - peer 级Discord #ops 频道-agentId:workmatch:channel:discordpeer:{kind:channel,id:1111111}# #ops 频道 ID# Tier 3 - guildroles管理员角色 → devops Agent-agentId:devopsmatch:channel:discordguildId:999999roles:[admin-role-id]# Tier 4 - guild 级Discord 服务器其他情况 → personal-agentId:personalmatch:channel:discordguildId:999999验证规则对应测试用例中的优先级消息来源匹配 Tier路由结果WhatsApp 个人号任意联系人Tier 6:binding.accountpersonalWhatsApp 商务号任意联系人Tier 6:binding.accountworkDiscord #ops 频道Tier 1:binding.peerworkDiscord #general管理员发消息Tier 3:binding.guildrolesdevopsDiscord #general普通成员Tier 4:binding.guildpersonal注意 Discord #ops 的例子即使消息来自管理员也会先匹配 Tier 1精确频道而不是 Tier 3角色。更具体的规则永远优先。线程继承Tier 2Discord 的线程是一个特殊情况。假设 #parent-channel 有 Binding 指向agent-A但线程本身没有单独的 Binding// src/routing/resolve-route.test.tstest(thread inherits binding from parent channel when no direct match,(){constrouteresolveAgentRoute({cfg:{bindings:[{agentId:adecco,match:{channel:discord,peer:{kind:channel,id:parent-channel-123}}}]},channel:discord,peer:{kind:channel,id:thread-456},// 线程本身parentPeer:{kind:channel,id:parent-channel-123},// 父频道});expect(route.matchedBy).toBe(binding.peer.parent);expect(route.agentId).toBe(adecco);})线程继承了父频道的 Binding——这符合直觉你在 #ops 下开的线程当然还是由workAgent 处理。路由的内部实现缓存 懒加载resolveAgentRoute每收到一条消息就会调用一次。如果每次都遍历所有 Binding对高并发场景多个群组同时活跃可能成为瓶颈。OpenClaw 的解法是两层缓存// src/routing/resolve-route.ts// WeakMap以 Config 对象为键Config 不变时复用缓存constevaluatedBindingsCacheByCfgnewWeakMapOpenClawConfig,EvaluatedBindingsCache();// 内层以 channel\taccountId 为键缓存过滤后的 Binding 列表typeEvaluatedBindingsCache{bindingsRef:OpenClawConfig[bindings];byChannelAccount:Mapstring,EvaluatedBinding[];};第一次路由时getEvaluatedBindingsForChannelAccount遍历所有 Binding过滤出属于当前channel accountId的子集缓存起来。后续同渠道同账号的消息直接取缓存不再全量遍历。当配置变更Config 对象引用变化时WeakMap 的键失效缓存自动失效。不需要显式清除——这是 WeakMap 的自动 GC 特性优雅地解决了缓存失效问题。把这一切串起来从一条 WhatsApp 消息到 AI 开始思考完整的路径是1. WhatsApp 渠道收到消息 ↓ 2. 渠道调用 resolveAgentRoute(cfg, channelwhatsapp, accountId1-555-business, peer...) ↓ 3. 七级优先级匹配找到 agentIdwork, matchedBybinding.account ↓ 4. buildAgentSessionKey(...) 生成 SessionKey (dmScopeper-peer 时 → agent:work:direct:15551234567) ↓ 5. Gateway 用 SessionKey 找到或创建对应的 Agent 会话 ↓ 6. 消息送入 AgentAI 开始执行 ↓ 7. AI 回复通过同一个渠道发回 WhatsApp 商务号每一步的实现都能对应到具体文件步骤 2-3src/routing/resolve-route.ts:resolveAgentRoute步骤 4src/routing/session-key.ts:buildAgentPeerSessionKey步骤 5Gateway 的 Session Manager第三篇文章的主题总结问题解法关键代码不同平台如何统一接入ChannelPlugin对接协议src/channels/plugins/types.plugin.ts如何唯一标识一个 AI 会话SessionKey 格式体系src/routing/session-key.ts同一 Agent 内如何隔离多用户对话dmScope四种模式src/config/types.base.ts:DmScope同一个人在不同平台如何共享对话identityLinks身份链接src/routing/session-key.ts:resolveLinkedPeerId如何把消息路由到正确的 Agent七级优先级 Bindingsrc/routing/resolve-route.ts:tiers高频路由如何高效WeakMap channel-account 双层缓存src/routing/resolve-route.ts:evaluatedBindingsCacheByCfg下一篇深入 Agent 执行引擎Agent 收到消息之后AI 是如何「思考」的工具调用、沙盒执行、流式输出pi-embedded-runner 的执行循环是如何运转的源码路径src/routing/|src/channels/plugins/| 核心文件resolve-route.ts、session-key.ts、types.plugin.ts