Skip to main content

消息路由与安全管线

ChannelMessageRouter 是 Channel 系统的核心中枢,负责接收来自所有 Channel 插件的入站消息,依次经过安全管线检查、触发规则匹配,最终路由到 Agent 处理,并将响应格式化后发回原始平台。

源码位置: packages/desktop/app/main/services/capabilities/integrations/channel/ChannelMessageRouter.ts

完整处理管线

sequenceDiagram
participant Plugin as Channel Plugin
participant Registry as ChannelPluginRegistry
participant Router as ChannelMessageRouter
participant RL as RateLimiter
participant IS as InputSanitizer
participant PG as PromptGuardian
participant UP as UserPermissionService
participant Gate as ChannelPermissionGate
participant Bridge as ChannelMagiBridge
participant Agent as MagiService

Plugin->>Registry: emitMessage(InboundMessage)
Registry->>Router: emit('message', ChannelMessage)

Note over Router: 安全管线开始

Router->>RL: check(channelId, senderId)
alt 频率超限
RL-->>Router: { allowed: false }
Note over Router: 静默丢弃
end

Router->>IS: sanitize(content)
IS-->>Router: { content, modified, warnings }
Note over Router: 替换净化后的内容

Router->>PG: review(content, context)
alt 检测到注入
PG-->>Router: { allowed: false, deflectionMessage }
Router->>Registry: sendMessage(deflectionMessage)
Note over Router: 消息被拦截
end

Router->>UP: checkPermission(channelId, senderId, name)
alt 用户被禁止
UP-->>Router: { allowed: false, role: 'blocked' }
Router->>Registry: sendMessage("You have been blocked")
Note over Router: 消息被拦截
end

Router->>Gate: processReply(channelId, chatId, senderId, content)
alt 是权限确认回复
Gate-->>Router: consumed = true
Note over Router: 消息被确认机制消费
end

Note over Router: 安全管线结束,进入触发匹配

Router->>Router: shouldTrigger(msg, config)
alt 未匹配触发规则
Note over Router: bufferMessage (最多 50 条)
else 匹配触发规则
Router->>Registry: emit('routeToAgent', payload)
Bridge->>Agent: handleMessage(incoming)
Agent-->>Bridge: { response }
Bridge->>Router: sendResponse(channelId, chatId, text, type)
Router->>Router: stripInternalTags + splitMessage
Router->>Registry: sendMessage(chunk)
Registry->>Plugin: plugin.sendMessage(chatId, chunk)
end

安全服务注入

Router 通过 setter 方法注入安全服务,每个安全层都是可选的:

setRateLimiter(limiter: RateLimiter): void
setSanitizer(sanitizer: InputSanitizer): void
setPromptGuardian(guardian: PromptGuardian): void
setUserPermissions(service: UserPermissionService): void
setPermissionGate(gate: ChannelPermissionGate): void

未注入的安全层会被跳过。这允许在不同部署场景下灵活配置安全级别。

触发匹配逻辑

shouldTrigger(msg, config) 的判断流程:

1. allowFrom 检查(所有模式通用)
├── allowFrom 为空或包含 "*" → 通过
├── senderId 在白名单中 → 通过
└── senderId 不在白名单中 → 返回 false(不触发、不缓存)

2. 私信检查
└── !msg.isGroup → 返回 true(私信始终触发)

3. 触发模式检查
├── 无配置 或 mode === 'all' → true
├── ignoreBot && msg.isFromMe → false
├── mode === 'dm_only' → !msg.isGroup
├── mode === 'mention' → content.includes(`@${mentionName}`)
└── mode === 'keyword' → keywords.some(kw => content.toLowerCase().includes(kw.toLowerCase()))

关键细节:

  • allowFrom 检查优先于触发模式,且对私信和群组都生效
  • 私信始终触发(在 allowFrom 检查之后),不受触发模式影响
  • ignoreBot 检查在触发模式匹配之前
  • 关键词匹配不区分大小写
  • mentionName 默认值为 'Clawia'

消息缓冲

未触发的消息不会被丢弃,而是被缓存在内存中:

private messageBuffer = new Map<string, ChannelMessage[]>();

缓冲键: ${channelId}:${chatId}(每个 Channel 实例 + 聊天会话一个缓冲区)

缓冲行为:

  • 每个缓冲区最多 50 条消息(FIFO 淘汰)
  • 触发时,缓冲区中所有消息 + 触发消息一起发送给 Agent
  • 发送后清空该缓冲区
  • Router 关闭时调用 clearBuffers() 清空所有缓冲区

消息格式化

群组消息以 XML 格式封装后发送给 Agent:

<channel_messages>
<message sender="Alice" channel="discord" channel_id="ch-001" chat="general" time="2026-03-19T10:00:00Z">你好</message>
<message sender="Bob" channel="discord" channel_id="ch-001" chat="general" time="2026-03-19T10:00:05Z">嗨!</message>
<message sender="Alice" channel="discord" channel_id="ch-001" chat="general" time="2026-03-19T10:00:10Z">@Clawia 请帮忙看一下</message>
</channel_messages>

私信/DM 消息不使用 XML 封装,直接以原始文本作为 prompt。

响应处理

内部标签清除

Agent 回复中的 <internal>...</internal> 标签会被移除:

function stripInternalTags(text: string): string {
return text.replace(/<internal>[\s\S]*?<\/internal>/g, '').trim();
}

这允许 Agent 在回复中包含仅供内部使用的元信息,不会泄露到外部平台。

消息分片

当回复超过平台的消息长度限制时,自动拆分为多条:

function splitMessage(text: string, maxLength: number): string[]

分片策略(优先级从高到低):

  1. 在换行符处断开
  2. 在空格处断开
  3. maxLength 处硬切

平台长度限制

ChannelMessageRouter 维护每个平台的消息长度上限:

平台最大长度
discord2,000
telegram4,096
slack4,000
qqbot1,500 (类型定义中) / 2,000 (Router 默认)
line5,000
email100,000
whatsapp65,000
matrix65,536
msteams28,000
mattermost16,383
twitch500
irc512

如果插件清单中声明了 maxMessageLength,会覆盖默认值。

附件发送

Router 也支持发送附件到 Channel:

async sendAttachment(
channelId: string,
chatId: string,
attachment: AttachmentInput,
): Promise<void>

调用链:Router.sendAttachment()Registry.sendAttachment()plugin.sendAttachment()。如果插件未实现 sendAttachment,会抛出错误。

配置管理

// 设置/更新触发配置
setTriggerConfig(channelId: string, config: ChannelTriggerConfig): void
removeTriggerConfig(channelId: string): void

// 设置平台消息长度限制
setMaxLength(pluginType: string, maxLength: number): void

// 清空所有消息缓冲区
clearBuffers(): void

下一步