Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • 消息路由架构

消息路由架构

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 示例

dmScopeChannelPeer IDSession Key说明
mainwhatsapp+1234567890agent:main:所有 DM 共享主会话
per-peerwhatsapp+1234567890agent:main:dm:+1234567890每个手机号独立会话
per-channel-peerwhatsapp+1234567890agent:main:whatsapp:dm:+1234567890每个渠道和手机号独立会话
per-peertelegram123456789agent:main:dm:123456789Telegram 用户独立会话

线程会话键

线程支持

某些渠道(如 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 Docksrc/channels/dock.ts
工具上下文src/channels/plugins/types.plugin.ts