消息路由架构
Moltbot 的消息路由和会话键管理
概述
消息路由系统负责将来自不同渠道的消息路由到正确的 Agent,并管理会话键(Session Key)的构建和解析。它支持多 Agent 配置、绑定规则和复杂的会话隔离策略。
设计目标
- 灵活路由: 支持多种匹配规则(peer、guild、team、account)
- 会话隔离: 不同类型的消息使用独立的会话键
- 多 Agent: 支持多个独立的 Agent 实例
- 绑定规则: 细粒度的路由控制
- Identity Links: 跨渠道用户身份关联
路由解析流程
主路由函数
文件: src/routing/resolve-route.ts
export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentRoute {
// 输入:
// - channel: 通道 ID (whatsapp, telegram, discord...)
// - accountId: 账号 ID (多账号场景)
// - peer: { id, name, kind } 消息发送者信息
// - guildId: Discord guild ID
// - teamId: Microsoft Teams team ID
// 输出:
// - agentId: 路由到的 Agent ID
// - channel: 通道 ID
// - accountId: 账号 ID
// - sessionKey: 会话键
// - mainSessionKey: 主会话键别名
// - matchedBy: 匹配类型
}
路由匹配优先级
路由匹配类型
export type ResolvedAgentRoute = {
agentId: string;
channel: string;
accountId: string;
sessionKey: string;
mainSessionKey: string;
matchedBy:
| "binding.peer"
| "binding.guild"
| "binding.team"
| "binding.account"
| "binding.channel"
| "default";
};
Session Key 构建
主会话键
文件: src/routing/session-key.ts
export function buildAgentMainSessionKey(params: {
agentId: string;
mainKey?: string | undefined;
}): string {
const agentId = normalizeAgentId(params.agentId);
const mainKey = normalizeMainKey(params.mainKey);
return `agent:${agentId}:${mainKey}`;
}
// 示例:
// buildAgentMainSessionKey({ agentId: "main" })
// → "agent:main:"
// buildAgentMainSessionKey({ agentId: "main", mainKey: "custom" })
// → "agent:main:custom"
对等会话键(Peer Session)
用于 DM 和群组消息的会话键。
export function buildAgentPeerSessionKey(params: {
agentId: string;
mainKey?: string | undefined;
channel: string;
peerKind?: "dm" | "group" | "channel" | null;
peerId?: string | null;
identityLinks?: Record<string, string[]>;
dmScope?: "main" | "per-peer" | "per-channel-peer";
}): string {
const peerKind = params.peerKind ?? "dm";
if (peerKind === "dm") {
const dmScope = params.dmScope ?? "main";
let peerId = (params.peerId ?? "").trim();
// 处理 identity links
const linkedPeerId = dmScope === "main" ? null
: resolveLinkedPeerId({
identityLinks: params.identityLinks,
channel: params.channel,
peerId,
});
if (linkedPeerId) peerId = linkedPeerId;
peerId = peerId.toLowerCase();
// per-channel-peer scope
if (dmScope === "per-channel-peer" && peerId) {
const channel = (params.channel ?? "").trim().toLowerCase() || "unknown";
return `agent:${normalizeAgentId(params.agentId)}:${channel}:dm:${peerId}`;
}
// per-peer scope
if (dmScope === "per-peer" && peerId) {
return `agent:${normalizeAgentId(params.agentId)}:dm:${peerId}`;
}
// main scope: 回退到主会话
return buildAgentMainSessionKey({
agentId: params.agentId,
mainKey: params.mainKey,
});
}
// group/channel scope
const channel = (params.channel ?? "").trim().toLowerCase() || "unknown";
const peerId = ((params.peerId ?? "").trim() || "unknown").toLowerCase();
return `agent:${normalizeAgentId(params.agentId)}:${channel}:${peerKind}:${peerId}`;
}
DM Scope 详解
DM Scope 示例
| dmScope | Channel | Peer ID | Session Key | 说明 |
|---|---|---|---|---|
main | +1234567890 | agent:main: | 所有 DM 共享主会话 | |
per-peer | +1234567890 | agent:main:dm:+1234567890 | 每个手机号独立会话 | |
per-channel-peer | +1234567890 | agent:main:whatsapp:dm:+1234567890 | 每个渠道和手机号独立会话 | |
per-peer | telegram | 123456789 | agent:main:dm:123456789 | Telegram 用户独立会话 |
线程会话键
线程支持
某些渠道(如 Telegram、Discord)支持线程化回复。
export function resolveThreadSessionKeys(params: {
baseSessionKey: string;
threadId?: string | null;
parentSessionKey?: string;
useSuffix?: boolean;
}): { sessionKey: string; parentSessionKey?: string } {
const threadId = (params.threadId ?? "").trim();
if (!threadId) {
return { sessionKey: params.baseSessionKey, parentSessionKey: undefined };
}
const normalizedThreadId = threadId.toLowerCase();
const useSuffix = params.useSuffix ?? true;
const sessionKey = useSuffix
? `${params.baseSessionKey}:thread:${normalizedThreadId}`
: params.baseSessionKey;
return { sessionKey, parentSessionKey: params.parentSessionKey };
}
线程会话键示例
// 基础会话:agent:main:telegram:dm:123456789
// Thread ID: 12345
resolveThreadSessionKeys({
baseSessionKey: "agent:main:telegram:dm:123456789",
threadId: "12345",
useSuffix: true,
});
// → { sessionKey: "agent:main:telegram:dm:123456789:thread:12345" }
Identity Links
跨渠道身份关联
Identity Links 允许将不同渠道的用户关联为同一身份。
配置结构:
{
agents: {
defaults: {
identityLinks: {
"telegram:123456789": ["whatsapp:+1234567890", "discord:987654321"],
"whatsapp:+1234567890": ["telegram:123456789"]
}
}
}
}
Identity 解析
export function resolveLinkedPeerId(params: {
identityLinks?: Record<string, string[]>;
channel: string;
peerId: string;
}): string | undefined {
if (!params.identityLinks) return undefined;
const sourceKey = `${params.channel}:${params.peerId}`;
const linkedPeers = params.identityLinks[sourceKey];
if (!linkedPeers) return undefined;
// 查找第一个匹配目标渠道的链接
for (const linkedPeer of linkedPeers) {
const [targetChannel, targetPeerId] = linkedPeer.split(":");
return targetPeerId;
}
return undefined;
}
绑定规则
绑定配置
{
agents: {
bindings: [
{
agentId: "work",
match: {
peer: "telegram:123456789"
}
},
{
agentId: "personal",
match: {
guild: "123456789098765432"
}
},
{
agentId: "support",
match: {
accountId: "workbot@telegram.com"
}
},
{
agentId: "team",
match: {
teamId: "19:xxxxxxxxx@thread.tacv2"
}
},
{
agentId: "channel-specific",
match: {
channel: "whatsapp"
}
}
]
}
}
绑定匹配逻辑
// Peer 匹配
function matchesPeer(match: RouteMatch, peer: RoutePeer): boolean {
if (!match.peer) return false;
return normalizePeerId(match.peer) === normalizePeerId(peer.id);
}
// Guild 匹配 (Discord)
function matchesGuild(match: RouteMatch, guildId: string): boolean {
if (!match.guildId) return false;
return match.guildId === guildId;
}
// Team 匹配 (Microsoft Teams)
function matchesTeam(match: RouteMatch, teamId: string): boolean {
if (!match.teamId) return false;
return match.teamId === teamId;
}
// Account 匹配
function matchesAccount(match: RouteMatch, accountId: string): boolean {
if (!match.accountId) return false;
return match.accountId === accountId;
}
// Channel 匹配
function matchesChannel(match: RouteMatch, channel: string): boolean {
if (!match.channel) return false;
return match.channel === channel;
}
路由上下文
Tool Context 构建
export type ToolContext = {
currentChannelId?: string;
currentThreadTs?: string;
hasRepliedRef?: boolean;
channelConfig?: any;
groupConfig?: any;
};
文件: src/channels/dock.ts
// Telegram Thread Context
threading: {
resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first",
buildToolContext: ({ context, hasRepliedRef }) => {
const threadId = context.MessageThreadId ?? context.ReplyToId;
return {
currentChannelId: context.To?.trim() || undefined,
currentThreadTs: threadId != null ? String(threadId) : undefined,
hasRepliedRef,
};
},
}
消息流转
完整路由流程
代码路径引用
| 功能 | 文件路径 |
|---|---|
| 路由解析 | src/routing/resolve-route.ts |
| Session Key 构建 | src/routing/session-key.ts |
| Identity 解析 | src/routing/identity-links.ts |
| 路由上下文 | src/routing/tool-context.ts |
| Channel Dock | src/channels/dock.ts |
| 工具上下文 | src/channels/plugins/types.plugin.ts |