feat(channels)!: remove message prefix filtering - #11571
Conversation
BREAKING CHANGE: messagePrefix no longer filters or rewrites channel messages. Existing sender, group, mention and pairing policies still apply.
Standalone message-prefix removal verificationTested removal commit: A 34-case local script drives the actual shared channel runtime with paired configurations: a retained legacy
Standalone package checks: 658 DingTalk tests and 188 CLI configuration/catalog tests passed. The shared base and eight other channel source/test trees are byte-for-byte identical to the previously validated trees, whose 2,824 tests passed (base 1,302; DWS 377; QQ 311; Feishu 288; GitHub 209; WeCom 142; Weixin 97; GitLab 61; Telegram 37). Full build, bundle and type checking passed. The later upstream update only changed the separate review workflow/CLI implementation; CLI build and type checking plus the bundle were repeated after that update and passed. Changed-file ESLint, Prettier and diff checks passed. Bundled version and channel-help smoke checks used a temporary home/runtime directory. The globally installed CLI is 0.10.0 and has no channel subcommand, so this is a local runtime test-script fallback. No live channel was connected, no external message was sent and no existing service was restarted. Hosted CI/review results are separate from these local results. The DingTalk mention parser is unchanged from upstream; #11566 remains a separate follow-up that must merge after this removal. |
|
Thanks for the PR — this one is unusually disciplined mechanically, and the thing I can't settle is not the code. Template looks good ✓ (all required sections, bilingual, reviewer test plan filled in rather than stubbed). Problem: this is a feature removal, not a bug fix, and there's no linked issue — so I went and found the history myself. The feature landed five days ago in #10817 (+3109/−144 across 41 files), closing #10816, a P3 feature request you also filed. The cost being removed here is observed and quantifiable rather than theoretical: it needed three Direction: this is where I need a human, and it's the reason I'm not approving. Size: cross-package (ten channel packages plus Approach: the scope is right and I could not find any fat in it. I checked completeness mechanically instead of by eyeball: the set of files on Risk: no elevated risk signals — the Stage 1e revert-history path patterns matched nothing in this diff. Continuing to the code review so whoever picks this up has the full picture in one place, but the direction question above is the one that decides it. 🙏 中文说明感谢贡献!这个 PR 在机械执行层面异常干净,而我无法定论的部分不在代码本身。 模板完整 ✓(必填章节齐全、中英双语、Reviewer Test Plan 是认真填写的而非占位)。 问题: 这是一次功能移除而不是 bug 修复,且没有关联 issue——所以我自己去查了历史。该功能五天前才通过 #10817 落地(41 个文件,+3109/−144),关闭的是 #10816,一个同样由你提出的 P3 功能请求。这里要移除的成本是已观测、可量化的,不是理论性的:它需要三个 方向: 这是我需要人来定夺的地方,也是我不批准的原因。 规模: 跨包改动(十个渠道包加 方案: 范围是对的,我没能找出任何多余部分。我用机械方式而非肉眼核对了完整性: 风险: 无升级风险信号——Stage 1e 的回撤历史路径模式在本 diff 中未命中任何文件。 我会继续做代码审查,让接手的人能在一个地方看到完整信息;但真正决定这个 PR 的是上面的方向问题。🙏 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
🩺 serve daemon A/BBuilt the PR base vs this PR head
|
| field | PR base (before) | this PR (after) |
|---|---|---|
activeWorkStaleMs |
10 |
8 |
— Qwen Code · serve A/B
Code reviewIndependent proposal first. From the title and the "Why it's needed" section alone, before reading the diff, I would have done three things: opened an issue recording the decision to remove a setting that shipped in v0.23.2 one day earlier; deleted the rewriting layer first ( No Critical blockers found. I looked hard for the three failure modes a ten-adapter removal invites, and none are present:
One thing I verified rather than assumed — the config compatibility claim. The PR body says an unchanged legacy key may remain but can't be added or changed. That's true, and it falls out of existing machinery rather than new code: Suggestions, not blockers:
Files changed (17 of 32 shown — production and docs; the other 15 are their collocated tests)
Testing evidenceThis is an unattended CI run, so per the gate's rules I did not build or execute anything from this PR — the review above is static, read from the diff and from Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Reading that honestly: Not verified: runtime dispatch behaviour on any live channel. I did not execute PR code, the ubuntu unit suite had not landed at review time, and no sandboxed lane has been run. The author's own evidence, attributed as such and not counted as mine: a comment on this PR reports a 34-case local script against the shared channel runtime (22 passed / 12 expected failures on the upstream baseline, 34/34 on the new build), plus 658 DingTalk and 188 CLI tests, full build, bundle and typecheck. That is a serious and specific write-up, and the baseline-versus-new pairing is the right shape for a removal. But it is the author's claim about a local run I cannot inspect or re-run, and one part of it is not externally checkable at all — the statement that nine channel trees are "byte-for-byte identical to the previously validated trees" refers to the author's own earlier branches, not to Sandboxed verification would settle what neither my static read nor the pending ubuntu suite can: 中文说明代码审查 先说我自己的方案。 只看标题和「Why it's needed」、在读 diff 之前,我会做三件事:先开一个 issue 记录「移除一个一天前才随 v0.23.2 发布的配置项」这个决定;先只删改写正文那一层( 未发现 Critical 阻断问题。 我重点找了「十个渠道适配器同时移除」最容易出的三类问题,都不存在:
有一处我是去核实而不是想当然的——配置兼容性主张。 PR 描述说「旧键原样保留可以,但不能新增或修改」。这是成立的,而且它是靠既有机制自然达成的,没有新增代码: 建议(非阻断):
测试证据 这是一次无人值守的 CI 运行,因此按 gate 规则我没有构建或执行本 PR 的任何代码——上面的审查是静态的,读的是 diff 以及隔离 worktree 里的 如实解读: 未验证: 任何真实渠道上的运行时分发行为。我没有执行 PR 代码,审查时 ubuntu 单测尚未出结果,也没有跑过任何沙箱验证通道。 作者自述的证据,明确归属于作者、不计入我的证据: 本 PR 上的一条评论报告了一个针对共享渠道运行时的 34 例本地脚本(上游基线 22 通过 / 12 例预期失败,新构建 34/34 通过),外加 658 个 DingTalk 测试、188 个 CLI 测试,以及完整的 build、bundle 和 typecheck。这是一份认真且具体的说明,基线与新版本配对也正是移除类改动该有的形状。但它是我无法检查也无法重跑的本地运行的作者主张,其中有一处完全无法外部核验——「九个渠道树与先前已验证的树逐字节一致」指的是作者自己先前的分支,而不是 沙箱验证能解决我的静态阅读和尚未出结果的 ubuntu 单测都解决不了的问题: — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 3/5 — the code review came back clean and I could not break it; the score is capped by policy, because removing a setting that shipped in stable v0.23.2 the day before is a public-contract call that belongs to a maintainer, not to this gate. Stepping back from the diff, here's what I actually think. The execution is the easy part to judge and it holds up. I went in expecting to find the usual damage a ten-adapter removal leaves behind — an orphaned export, a positional argument shifted into the wrong slot, a local whose only reader was deleted, one adapter quietly left behind. I found none of it, and I checked by enumeration rather than impression: the files on What I can't settle is whether the removal is right, and the reason I can't settle it changed while I was looking. This started as "no linked issue, breaking change, no deprecation window" — all still true. Then I checked what else you have open, and there are 14 PRs, with a whole channels stack in flight: #11570 removing DingTalk response aggregation, #11566 fixing DingTalk mention text (which this PR's description says must merge after it), #11561 adding a shared output policy, #11429 enhancing DingTalk status cards. Read alone, this PR looks like deleting a five-day-old feature for no recorded reason. Read as one move in that stack, it looks like an inbound-routing boundary being cleared out to make room for a different design. I can see the shape of that from here but I can't see the intent, and the difference between those two readings is the entire decision. That is not something a gate should resolve by inference, and it's the honest reason this is a 3 and not a 4. So, concretely, for whoever picks this up — three questions, and only the first one is about code:
On the evidence: Two things I'm deliberately not doing, so nobody waits on the wrong signal:
One housekeeping note: I tried to resolve an accountable maintainer deterministically and got nothing — 中文说明Confidence: 3/5 —— 代码审查结论是干净的,我没能找出问题;分数被政策封顶,因为移除一个前一天才随 stable v0.23.2 发布的配置项属于公共契约决策,该由维护者定夺,而不是由这个 gate 定夺。 从 diff 里退一步说,我真实的看法是这样的。 执行层面最容易判断,而它站得住。我原本预期会看到「十个适配器同时移除」通常会留下的破坏——孤立的导出、位移到错误参数位的实参、唯一读取方被删掉的局部变量、某个被悄悄漏掉的适配器。这些一个都没有,而且我是靠枚举而不是印象核对的: 我无法定论的是这次移除是否正确,而且我无法定论的原因在查看过程中发生了变化。一开始是「没有关联 issue、破坏性变更、没有废弃过渡期」——这些依然成立。然后我去看了你还有哪些 PR 在开着:有 14 个,其中一整套 channels 的工作正在推进——#11570 移除 DingTalk 的响应聚合、#11566 修复 DingTalk mention 文本(本 PR 描述说它必须在此之后合并)、#11561 新增共享的 output policy、#11429 增强 DingTalk 状态卡片。单独看,这个 PR 像是在没有任何记录理由的情况下删掉一个五天前才加的功能;放到那一摞里看,它像是在为另一套设计腾地方而清掉一条入站路由边界。我在这里能看出那个形状,但看不出意图,而这两种解读之间的差别就是整个决策本身。这不是一个 gate 应该靠推断去解决的问题,也是它诚实地停在 3 分而不是 4 分的原因。 所以,具体给接手的人三个问题,其中只有第一个是关于代码的:
关于证据: 有两件事我是刻意不做的,免得有人等错信号:
一个流程性说明:我尝试用确定性方式解析出一位负责的维护者,但没有得到结果—— — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
Approved at head 60eeddc2.
Required CI is green — Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox) and web-shell E2E Smoke completed successfully; only review-pr (the reviewer's own job) is still running.
This is a breaking removal across eight packages, so the bar I held it to was "nothing left hanging" rather than "the hot paths look fine":
- Every removed name has zero references at this head:
messagePrefix,message-prefix,prefixedCommand,configuredMessagePrefix,bypassMessagePrefix,messagePrefixText,displayTextOffset,startsWithMessagePrefix/stripMessagePrefix. The shared module, its 240-line test file, both baseindex.tsexports and the three option fields inChannelOptions/adapter inputs are gone together, and per-adapter deletions (telegram,feishu,qqbot,github,gitlab,wecom,weixin,dws) removed the call sites rather than leaving them fed by defaults. - The two
WeakSetbookkeepers inChannelBaseexisted only to remember which envelopes had been prefix-checked or prefix-rejected, so deleting them takes no other behavior with it; the comment that survived in their place still records the part that matters independently of the prefix — a synthetic media placeholder is never recorded as quoted group history, so it cannot come back as if a member had typed it. - Legacy settings are handled honestly rather than swept under a migration: a stored
messagePrefixis now just unknown configuration data, whichconfig-utilstolerates by iterating declared fields, and there are three tests pinning the compatibility surface — the base preserving an unchanged legacy key,config-utilsaccepting it as unknown data, andchannel-settings-storeboth preserving it across an unrelated save and rejecting a managed write of it (Channel field "messagePrefix" is not manageable.). No config migration is claimed, and none is needed for that to be true. - The help and permission strings are the part I checked most closely, because a wrong command in a user-facing hint is a silent usability break. Every
prefixedCommand(...)wrapper became the bare slash command, so/help,/clear confirm,/approve [request-id],/deny [request-id],/session use <name>and the multi-pending-request guidance all now name something the runtime actually accepts — and the removed validation (field "messagePrefix" must be a string) went with the field it validated.
The breaking consequence is stated where it should be, including the security-relevant half: a deployment that used the prefix to narrow what reaches the agent now admits everything its remaining sender/group/mention/pairing policies allow. feat(channels)! is the right tag for that.
No new Critical found. Two non-blocking notes: docs/users/features/channels/overview.md deletes the whole prefix section without a "previously configured keys are ignored now" line for operators who still have one set, which pairs with the same choice in the sibling cleanup; and this PR intentionally excludes the DingTalk mention-body parser fix from #11566, so whichever of the two merges second should re-check that messagePrefixText-shaped adapter hook is really gone rather than re-introduced by the other.
yiliang114
left a comment
There was a problem hiding this comment.
Reviewed the PR-owned surface at head 60eeddc2 (32 files, +339/-1644). No blocking finding. Approving, with three non-blocking notes — the second one is a control-integrity delta worth accepting consciously rather than by omission.
A ! PR that removes a filter from every inbound-message path at once deserves one question above all others: was that filter an authorization control? It was not. applyMessagePrefix short-circuited return true when the prefix was unset, and the config key defaulted to undefined, so out of the box nothing was filtered. What is being withdrawn is a traffic-narrowing control, and the PR body says so. The gates that actually decide whether a message reaches the model are untouched and still run in the same order: groupGate.check (ChannelBase.ts:6150, now the first statement of preflightInbound), dmGate.check (:6187), sender/pairing policy, and the mention_required group handling with recordPendingGroupHistory.
The removal is uniform — no adapter kept a private copy. Base had the shared helper plus four adapter-private extensions (dws-channel.ts with its own dwsMessagePrefix field and three stripMessagePrefix call sites; FeishuAdapter.ts with stripLeadingMentionNames(); GithubAdapter.ts stripping inside comment aggregation; QQChannel.ts deriving messagePrefixText/displayTextOffset). At head, git grep for messagePrefix, MessagePrefix, prefixedCommand, displayTextOffset and message_prefix across packages/ returns zero hits, versus 8 non-test hits at base. The surviving alreadyPrefixed references (types.ts:145, ChannelBase.ts:2033/6808/6920, QQChannel.ts:2630/2718/2872, GithubAdapter.ts:388) are a different, still-live feature — the per-turn [senderName] attribution marker — correctly retained rather than half-removed. I also checked the two adapter simplifications that could have drifted behaviour and found them equivalent: dws documentNotification now parses from text instead of rawText, which are identical once stripping is gone; Telegram's msg.caption ternaries collapse to the no-prefix branch, which was already the default.
The managed settings path does push back, which is the part most likely to be missed in a removal: assertSharedField (channel-settings-store.ts:124-148) dropped 'messagePrefix' from its allowed list, so the key now falls through to Channel field "messagePrefix" is not manageable., and the rewritten tests pin both halves — an unchanged stored key round-trips untouched while adding or changing it rejects with channel_settings_invalid_config. channel-registry.ts removed the field descriptor and docs/users/features/channels/overview.md deleted the row and both explanatory paragraphs.
Test deletions are replacements, not holes. checks a prepared envelope once and rejects before preparation became rejects disallowed groups before preparing media and still calls handlePreparedInbound(rejected, prepare) twice before asserting prepare was not called, so rejection idempotence survives on the group-gate path; requires the prefix on a pairing first contact too became offers pairing on first contact with ordinary text; DingTalk's five prefix tests were rewritten to assert the surviving syntheticText contract rather than deleted. No surviving public or protected ChannelBase method lost its only test — the three removed helpers (prefixedCommand, configuredMessagePrefix, wasMessagePrefixRejected) went with their call sites.
Three notes
1. The raw settings.json path is silent where the managed path is not. parseChannelConfig no longer reads the key (config-utils.ts:513 removed optionalPlainStringField(name,'messagePrefix',…)), the ChannelConfig field is gone (types.ts:57), and nothing emits a notice. So an operator who set messagePrefix: "/review" to keep a bot quiet upgrades and gets no log line, no warning, no startup failure — and from that moment every message clearing the remaining gates dispatches a run, with the raw prefix now reaching the model verbatim. The PR's own new test proves the inertness: it.each(['hello','/review /new','@Qwen /review inspect this'])('preserves %s when an old config still contains messagePrefix', …) asserts bridge.prompt is called with the text unchanged and bridge.discardSession is not called, so /review /new is no longer rewritten into the local reset command. Blast radius is bounded by the group gate (the deleted test had to set groupPolicy: 'open' explicitly to let ambient chatter through), so this is not a flood — but it is a behaviour change with zero operator-visible signal on the one path that does not reject. A one-line stderr deprecation notice from parseChannelConfig when it discards the key would make the two paths consistent.
2. Deleting the prefix bypass also deleted Telegram's only @OtherBot guard. TelegramAdapter.ts buildEnvelope (head :496-558) lost the allowRegisteredCommandBypass parameter and the whole bypassMessagePrefix computation, and with it the block whose own comment read: "/cancel@OtherBot is addressed to a different bot. parseCommand strips the suffix, so without this check the bypass would let a command meant for someone else past the prefix gate and run it here — cancelling our own request, for instance." In a Telegram group hosting two bots, /cancel@OtherBot can now cancel our running request. To be fair about scope: the bypass was inert whenever messagePrefix was unset, so this matches long-standing behaviour for every non-prefix deployment and is a declared consequence of the removal, not a new defect. But it is a control-integrity delta rather than mere extra noise, and the fix is cheap and strictly better than what was there — move the @OtherBot check into parseCommand/command dispatch, where it protects all configurations instead of only prefixed ones.
3. Two public exports of the published @qwen-code/channel-base package were removed (index.ts base lines 43-46: startsWithMessagePrefix, stripMessagePrefix), and the ! PR body's breaking-change note mentions only the config key. Nothing in the monorepo is affected — all five consumers (dws-channel.ts:13, FeishuAdapter.ts:14, GithubAdapter.ts:31, QQChannel.ts:22, TelegramAdapter.ts:15) had their imports removed in this PR — but an out-of-tree channel plugin importing either symbol fails to compile after upgrade with no migration note to point at. One line in the breaking-change section naming the removed exports would close it.
Cross-PR check
#11570 modifies packages/channels/dingtalk/src/DingtalkAdapter.test.ts too and shares this merge-base, so I checked the pair: git merge-tree --write-tree --name-only pr-11570-review pr-11571-review exits 0 with no conflicted-file section, and the two edit disjoint regions of that file. Landing order between them does not matter. The ordering constraint this PR states is its own and unrelated: it must merge before #11566.
CI at head: green apart from review-pr. No review threads. I did not run the suite locally — no node_modules in this checkout — and I did not read config-utils.test.ts (+10/-11), so I cannot confirm whether a test covers the real settings.json → parse → construct chain for a legacy key; the new ChannelBase.test.ts case constructs legacyConfig and passes it straight to createChannel, which exercises the constructor only.
中文说明
在 head 60eeddc2 上审了本 PR 自有的范围(32 个文件,+339/-1644)。没有阻塞项。 批准,附三条非阻塞说明——第二条是一个值得有意识接受、而不是靠遗漏接受的管控完整性变化。
一个在所有入站消息路径上一次性移除过滤器的 ! PR,最该回答的问题只有一个:那个过滤器是不是授权控制?不是。applyMessagePrefix 在前缀未设置时直接 return true,而该配置键默认 undefined,所以开箱状态下什么都没被过滤。被撤回的是一个收窄流量的控制,PR 正文也是这么说的。真正决定消息能否到达模型的门禁未被触碰、且顺序不变:groupGate.check(ChannelBase.ts:6150,现在是 preflightInbound 的第一句)、dmGate.check(:6187)、发送者/配对策略,以及带 recordPendingGroupHistory 的 mention_required 群处理。
移除是统一的——没有 adapter 留私拷贝。 base 有共享 helper 加四处 adapter 私有扩展(dws-channel.ts 自己的 dwsMessagePrefix 字段与三处 stripMessagePrefix;FeishuAdapter.ts 的 stripLeadingMentionNames();GithubAdapter.ts 在评论聚合里剥离;QQChannel.ts 推导 messagePrefixText/displayTextOffset)。head 上对 messagePrefix、MessagePrefix、prefixedCommand、displayTextOffset、message_prefix 在 packages/ 下 grep 零命中(base 有 8 处非测试命中)。存活的 alreadyPrefixed 引用(types.ts:145、ChannelBase.ts:2033/6808/6920、QQChannel.ts:2630/2718/2872、GithubAdapter.ts:388)属于另一个仍然存活的特性——每回合的 [senderName] 归属标记——是正确保留而非半移除。我也查了两处可能造成行为漂移的 adapter 简化,结论是等价:dws 的 documentNotification 改为从 text 而非 rawText 解析,而剥离去掉后两者相同;Telegram 的 msg.caption 三元式收敛到无前缀分支,而那本来就是默认行为。
托管设置路径确实会拒绝,这是移除类改动最容易漏的一环:assertSharedField(channel-settings-store.ts:124-148)把 'messagePrefix' 从允许列表里去掉,所以该键现在落到 Channel field "messagePrefix" is not manageable.,且重写后的测试钉住了两半——已存键原样往返,新增或修改则以 channel_settings_invalid_config 拒绝。channel-registry.ts 移除了字段描述符,docs/users/features/channels/overview.md 删掉了表格行与两段说明。
测试删除是替换,不是留洞。 checks a prepared envelope once and rejects before preparation 变成 rejects disallowed groups before preparing media,仍然两次调用 handlePreparedInbound(rejected, prepare) 后断言 prepare 未被调用,所以拒绝幂等性在 group-gate 路径上存活;requires the prefix on a pairing first contact too 变成 offers pairing on first contact with ordinary text;DingTalk 的五个前缀测试被改写为断言存活的 syntheticText 契约而非删除。没有存活的 ChannelBase 公有/受保护方法失去唯一测试——被删的三个 helper(prefixedCommand、configuredMessagePrefix、wasMessagePrefixRejected)与其调用点一起消失。
三条说明
1. 裸 settings.json 路径是沉默的,而托管路径不是。 parseChannelConfig 不再读该键(config-utils.ts:513 移除了 optionalPlainStringField(name,'messagePrefix',…)),ChannelConfig 字段已删(types.ts:57),且没有任何提示。所以为了「让 bot 安静」而设过 messagePrefix: "/review" 的运维者升级后不会看到任何日志行、告警或启动失败——而从那一刻起,凡是过了剩余门禁的消息都会派发一次运行,且原始前缀会原样到达模型。本 PR 自己的新测试证明了这种惰性:it.each(['hello','/review /new','@Qwen /review inspect this'])('preserves %s when an old config still contains messagePrefix', …) 断言 bridge.prompt 收到未改动的文本、且 bridge.discardSession 未被调用,所以 /review /new 不再被改写成本地 reset 命令。影响范围被 group gate 限住(被删的那个测试必须显式设 groupPolicy: 'open' 才能放进环境噪声),所以不是洪水——但这是一次在唯一不会拒绝的路径上、零运维可见信号的行为变化。让 parseChannelConfig 在丢弃该键时输出一行 stderr 弃用提示,两条路径就一致了。
2. 删掉前缀 bypass 也删掉了 Telegram 唯一的 @OtherBot 守卫。 TelegramAdapter.ts 的 buildEnvelope(head :496-558)失去了 allowRegisteredCommandBypass 参数与整段 bypassMessagePrefix 计算,连同那段自带注释的代码:「/cancel@OtherBot 是发给另一个 bot 的。parseCommand 会剥掉后缀,所以没有这道检查,bypass 会让本意给别人的命令越过前缀门禁并在这里执行——比如取消我们自己的请求。」 在同时挂了两个 bot 的 Telegram 群里,/cancel@OtherBot 现在能取消我们正在跑的请求。就范围而言要说公道话:bypass 在 messagePrefix 未设置时本来就是惰性的,所以这与所有无前缀部署的长期行为一致,是移除的既定后果而非新缺陷。但它是管控完整性的变化,不只是多了些噪声,而且修法便宜且严格优于原状——把 @OtherBot 检查移进 parseCommand/命令派发,它就能保护所有配置而不只是设了前缀的那些。
3. 已发布的 @qwen-code/channel-base 包有两个公开导出被移除(index.ts base 第 43-46 行:startsWithMessagePrefix、stripMessagePrefix),而 ! PR 正文的破坏性变更说明只提到配置键。monorepo 内不受影响——五个消费者(dws-channel.ts:13、FeishuAdapter.ts:14、GithubAdapter.ts:31、QQChannel.ts:22、TelegramAdapter.ts:15)的 import 都在本 PR 里删掉了——但一个 import 了这两个符号的树外 channel 插件升级后会编译失败,且没有迁移说明可指。在破坏性变更那节点名这两个被移除的导出即可。
跨 PR 检查
#11570 也改 packages/channels/dingtalk/src/DingtalkAdapter.test.ts 且共享同一 merge-base,所以我查了这一对:git merge-tree --write-tree --name-only pr-11570-review pr-11571-review 退出 0 且无冲突文件段,两者改的是该文件的不相交区域。两者之间的合并顺序无所谓。 本 PR 自己声明的顺序约束与之无关:它必须先于 #11566 合并。
head 上的 CI:除 review-pr 外全绿。无 review 线程。我没有在本地跑套件——这个 checkout 没有 node_modules——也没有读 config-utils.test.ts(+10/-11),所以无法确认是否有测试覆盖「真实 settings.json → parse → construct」链路上的遗留键;新的 ChannelBase.test.ts 用例是构造 legacyConfig 后直接传给 createChannel,只锻炼了构造函数。
What this PR does
Removes configurable message-prefix filtering from channels. Eligible messages now follow the normal sender, group, mention and pairing policies without requiring or stripping an extra prefix. Command help and permission instructions use direct slash commands, and channel settings no longer offer the removed option.
Why it's needed
Prefix matching rewrote message bodies in the shared runtime and in several channel-specific paths, with extra handling for command menus, media, notifications and history. Removing the feature eliminates those filtering and rewriting paths. With an old
messagePrefix: "/review"setting, ordinaryhellois now delivered, and/review /newremains the received text instead of being rewritten into a local reset command.Reviewer Test Plan
How to verify
/reviewtext. Confirm normal dispatch without prefix filtering or clipping, subject to each platform's existing text normalization./help,/new, and permission replies. Confirm that help displays direct commands, session reset works normally, and permission ownership and shared-session confirmation remain enforced.Evidence (Before & After)
Tested on
Environment (optional)
Node.js 22 and an isolated worktree. Current runtime verification exercises the shared channel runtime with a local test bridge, without a live provider or external messages. The global CLI is an older version without channel commands, so it cannot exercise this runtime path.
Risk & Scope
messagePrefixfiltering is removed. Existing stored keys no longer affect runtime behavior. Deployments that previously used the prefix to narrow traffic will now receive messages allowed by their remaining channel policies.Linked Issues
No linked issue. This PR must merge before #11566; the DingTalk mention change is deliberately excluded.
中文说明
本次改动
移除 channel 的可配置消息前缀过滤功能。符合条件的消息直接按发送者、群聊、mention 和配对策略处理,不再要求额外前缀,也不裁剪该前缀。命令帮助与权限提示直接展示斜杠命令,渠道设置不再提供已删除的选项。
原因
原有前缀匹配会在公共运行时及多个渠道内部改写正文,并为命令菜单、媒体、通知和群历史增加特殊处理。删除这个功能后,这些过滤和改写路径一并移除。即使旧配置还保留
messagePrefix: "/review",普通hello也会正常传递,/review /new会保留为收到的正文,不会被裁剪成本地重置命令。Reviewer 验证方式
/review文本,确认经过平台原有文字正常化后正常传递,不被前缀过滤或裁剪。/help、/new和权限回复,确认帮助展示裸命令,会话重置正常,权限归属和共享会话确认仍有效。修复前后证据
测试平台与环境
macOS 已测试,Windows 和 Linux 未在本地测试。使用 Node.js 22、隔离 worktree、公共渠道运行时及本地测试 bridge;当前验证没有连接真实渠道或发送外部消息。全局 CLI 是没有 channel 命令的旧版本,无法覆盖该运行时路径。
风险与范围
messagePrefix过滤。已有旧字段不再影响运行时行为;原先用前缀缩小消息范围的部署,升级后将接收剩余渠道策略允许的消息。