Skip to content

feat(channels): route messages to prefix-specific sessions - #12595

Merged
qqqys merged 1 commit into
QwenLM:mainfrom
qqqys:feat/channel-message-routes-main
Sep 24, 2026
Merged

qqqys merged 1 commit into
QwenLM:mainfrom
qqqys:feat/channel-message-routes-main

Conversation

@qqqys

@qqqys qqqys commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds shared messageRoutes configuration so one Channel connection can run multiple prefix-selected workflows with separate conversation sessions and first-turn instructions. An optional defaultMessageRoute names the route for ordinary messages; without it, unmatched chat messages are ignored. The longest matching prefix wins and is removed before dispatch.

Route identity is retained through queued turns, session recovery, group history, and scheduled loops. DWS native document/todo triggers remain independent of IM prefix routing, and Telegram attachments and QQ message formatting preserve the routed message body.

Why it's needed

A single connection often serves more than one purpose, such as reviewing changes and answering repository questions. Prefix routing lets operators provide focused instructions for each workflow without running multiple connections or mixing their conversation sessions.

Reviewer Test Plan

How to verify

  1. Configure /review and /QA with different instructions. Send both prefixes in the same chat: each should create a separate session, strip its prefix, and inject only its own instructions on the first turn.
  2. Leave the default route unset and send ordinary text: it should not start a session or run a command. Set the default to /QA: ordinary text should reuse the QA session unchanged.
  3. Queue multiple review messages while also sending a QA message. The collected review messages should remain in their original route; clearing one route should not clear the other.
  4. Restore persisted sessions and run a route-owned scheduled loop. The route identity and instructions should survive. Group history should not cross routes.
  5. Verify captioned Telegram attachments, QQ messages with ordinary-text prefixes, and DWS messages after leading mentions. Existing sender/group access policies must continue to apply.

Evidence (Before & After)

Before: one shared instruction set and conversation route per configured session scope.

After: deterministic adapter tests and a built-output integration smoke verify configuration parsing, distinct route sessions, default routing, unmatched-message filtering, first-turn instructions, collect dispatch, and persisted-session recovery. No external IM or model calls were made.

Validation: full workspace build and typecheck; targeted channel-base, DWS, Telegram, QQ, CLI configuration, and settings-management tests; ESLint on changed TypeScript files.

Tested on

OS Status
macOS ✅ Local build, typecheck, targeted tests, built-output smoke
Windows ⚠️ Not tested locally
Linux ⚠️ Not tested locally

Environment (optional)

Local workspace builds with fake transport and bridge implementations; no production service changes.

Risk & Scope

  • Main risk or tradeoff: routing is applied before command and memory-intent handling, so shared commands need a matching prefix when no default route is configured.
  • Not validated / out of scope: real provider delivery, live model behavior, and route-specific models. Existing Channel memory keeps its chat/thread scope; routes are not an authorization sandbox.
  • Breaking changes / migration notes: existing configurations without routes retain their behavior. messageRoutes is currently incompatible with multiSession. No legacy messagePrefix API is introduced. Adapter-generated media placeholders and native provider events remain outside prefix routing.

Linked Issues

None.

中文说明

变更内容

新增通用 messageRoutes 配置,让单个 Channel 连接通过不同前缀运行不同工作流,分别使用独立会话和首轮指令。可选 defaultMessageRoute 指定普通消息使用的路由;不设置时忽略未匹配的聊天消息。优先匹配最长前缀,并在分发前移除前缀。

路由信息会保留到排队消息、会话恢复、群聊历史和定时任务中。DWS 原生文档与待办触发不受 IM 前缀过滤影响,Telegram 附件和 QQ 消息格式也会保留路由处理后的正文。

变更动机

同一个连接常常同时承担评审代码和回答仓库问题等职责。前缀路由允许为每个工作流设置针对性指令,不必运行多个连接,也不会混用各工作流的会话上下文。

评审验证计划

验证方式

  1. 为 /review 和 /QA 配置不同指令,在同一个聊天中分别发送:应建立独立会话、移除前缀,并仅在首轮注入对应指令。
  2. 不设置默认路由时发送普通文本:不应创建会话或执行命令。将默认路由设置为 /QA 后,普通文本应原样进入 QA 会话。
  3. 在评审进行中继续发送评审和 QA 消息:收集的评审消息应保留原路由;清理一路会话不应清理另一路。
  4. 恢复持久化会话并运行属于某一路由的定时任务:路由和指令应保留,群聊历史不应跨路由混入。
  5. 验证带说明文本的 Telegram 附件、普通文本前缀的 QQ 消息和前置提及后的 DWS 消息;已有发送者与群聊权限仍应生效。

前后对比与证据

之前:每个配置的会话范围使用一套指令和会话路由。

之后:确定性的适配器测试和构建产物集成验证覆盖配置解析、独立路由会话、默认路由、未匹配过滤、首轮指令、collect 分发和持久化会话恢复。未调用外部 IM 或模型。

验证包括全工作区构建和类型检查,channel-base、DWS、Telegram、QQ、CLI 配置及设置管理的定向测试,以及修改的 TypeScript 文件的 ESLint 检查。

测试平台

平台 状态
macOS ✅ 本地构建、类型检查、定向测试及构建产物集成验证
Windows ⚠️ 未在本地测试
Linux ⚠️ 未在本地测试

测试环境

本地工作区构建,使用模拟传输与桥接实现,未修改生产服务。

风险与范围

  • 主要风险或取舍:路由先于命令和记忆意图处理,因此未配置默认路由时,共享命令也必须带匹配的前缀。
  • 未验证或不包含:真实平台投递、在线模型行为以及按路由指定模型。Channel 记忆保留现有聊天/线程范围,路由不是权限沙箱。
  • 兼容说明:未配置路由时保持原行为。当前不能与 multiSession 同时使用,不引入旧 messagePrefix API。适配器生成的媒体占位文本和原生平台事件不参与前缀路由。

关联 Issue

无。

@qqqys

qqqys commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

Validation report

  • Full workspace build and typecheck passed; the final QQ-only adjustment was rebuilt separately.
  • Targeted suites passed: channel base/router/loop/matcher 926, DWS 295, Telegram 41, QQ 97, CLI/configuration/settings 250 (1,609 tests total).
  • ESLint and formatting checks passed for changed TypeScript files.
  • Built-output integration smoke passed: actual CLI configuration parsing into the built channel base, with a fake bridge, confirmed unmatched-message filtering before session creation, separate prefix-selected sessions, first-turn instructions, default-route reuse, collect dispatch, route identity persistence, and lazy session restoration.
  • Source self-review covered configuration validation, adapter preparation, queue/history behavior, session target persistence, and scheduled-loop route ownership.

Scope: macOS local verification with fake transports and no model calls. This is not a claim of real provider delivery or hosted CI success. No production Channel was restarted or reconfigured.

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM at 76d30e2 per maintainer request. Zero open threads; all lanes green except two notes:

  • web-shell E2E Smoke shows red, but it's the pre-#12593 timeout: the run hit the old 20-minute wall and was cancelled during cleanup with 183 passed / 1 flaky / 0 failed — the merge-base of this branch predates the 30-minute bump that just landed on main. The PR's diff is entirely channels/serve (no web-shell client surface), so this lane cannot be exercising it. It'll go green after the next merge from main (or a re-run once the branch carries the new timeout).
  • review-pr is still queued (bot infrastructure, not a gate).

The branch will need a merge from main anyway to pick up the timeout fix before this lane can pass.

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking findings.
Approval blockers: none.

Checked: message-routes.ts algorithm (longest-prefix sort, mention-stripping loop, whitespace-plus-non-empty guard); ChannelBase.applyMessageRoute WeakSet idempotency between preflightInbound and recordPendingGroupHistory; SessionRouter.routingKey with routeKey nests the existing scoped key in JSON.stringify([baseKey, routeKey]) — unrouted keys unchanged; groupHistoryKey appends route only when present; ChannelLoopStore.sameTarget includes messageRoute; config-utils.parseMessageRoutingConfig validates all edge cases; TelegramAdapter msg.caption ? envelope.text : '' preserves route-stripped text through async download; QQChannel routeText preserves non-bot mentions, alreadyPrefixed suppressed when routes configured; bypassMessageRoutes set on DWS todo/document provider events.

Cross-check against prior reviews: prior reviewer noted activateManagedSession does not thread messageRoute — confirmed; not a current bug (constructor throws on the only code path that would reach that call while routes are configured), but agree a comment recording the invariant would help future composability.

Not run: local vitest suite (no working tree). Author validation reports 1,609 targeted tests passing; serve A/B shows no response changes.

Reviewed with AI assistance.

@qqqys
qqqys added this pull request to the merge queue Sep 24, 2026
Merged via the queue into QwenLM:main with commit 7aac490 Sep 24, 2026
73 of 75 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants