Skip to content

fix: 多 bot 话题中 @ 某 bot 时其他 bot 不再抢答#262

Merged
lishuceo merged 2 commits into
mainfrom
fix/thread-bypass-mention-other-bot
Jun 14, 2026
Merged

fix: 多 bot 话题中 @ 某 bot 时其他 bot 不再抢答#262
lishuceo merged 2 commits into
mainfrom
fix/thread-bypass-mention-other-bot

Conversation

@lishuceo

Copy link
Copy Markdown
Owner

问题

单实例多 bot 模式下,在一个话题里 @ 某个 bot,其他 bot 也会跟着响应

根因(有日志铁证)

抓到真实双响应消息 @张全栈 接下来做啥(在土豆儿创建的话题里),同一条消息两个 account 收到的文本不同:

account 收到的 text gate 判定
dev(张全栈本人) 接下来做啥(@已剥离) mentioned
pm(土豆儿) @张全栈 接下来做啥(@未剥离) thread_bypass ✗ 抢答

飞书 open_id 按 app 隔离——同一个 bot 在不同 app 下 open_id 不同。accountManager.getAllBotOpenIds() 存的是每个 bot 用自己 app 拉到的「自视 open_id」。当用户 @ 张全栈时,土豆儿的 app 收到的那条 mention 是「张全栈在 pm-app 视角下的 open_id」,不在 knownBotIds 里 → anyBotMentioned = falseevent-handler.ts!anyBotMentioned 那道闸失效 → 土豆儿作为话题创建者命中 thread_bypass 抢答。

每个 bot 唯一能可靠识别的,就是它自己的 open_id。

修复

把 thread_bypass 的闸从「是否 @ 了已知 bot」改成「是否 @ 了非自己」:

  • 只要消息 @ 了除本 bot 以外的任何对象(哪怕跨 app 没识别成 bot),就说明在叫别人 → 不 bypass。
  • 「@我」走 mentioned 路径照常响应;「完全不 @」走 bypass 照常响应;唯独「@别人」不再抢答。
  • 单 bot 路径此前完全没有 mention 闸,一并补上(否则 DevBot 在自己话题里被人 @ 另一个独立服务 bot 时也会抢答)。

测试

  • mention-gate.test.ts 新增 2 个用例,精确复现 @张全栈 接下来做啥 跨 app 漏判场景(多 bot + 单 bot 各一)。
  • 两个用例在修复前均失败(走 thread_bypass 抢答),修复后通过。
  • 全量回归 npx vitest run:1686 passed,唯一 1 个失败(memory/quality.test.ts)在 base 上同样失败,与本改动无关。

后续(可选,二期)

更深的修复是用 union_id(跨 app 稳定)做 bot 互认,让 @张全栈 在土豆儿侧也能被正确剥离,顺带修掉「prompt 里混进 @张全栈 字面量」这个次要问题。本 PR 先以最小改动止血。

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 2m 46s —— View job


PR Review: 多 bot 话题 @ 某 bot 时其他 bot 不再抢答

  • 读取 CLAUDE.md 与 PR 背景
  • 审查 event-handler.ts 改动
  • 审查 mention-gate.test.ts 改动
  • 验证边界情况与正确性
  • 输出审查结论

✅ Approved — 改动正确、最小、且有精准回归覆盖

根因分析(飞书 open_id 按 app 隔离,knownBotIds 只含「自视 open_id」,导致 anyBotMentioned 跨 app 漏判)非常扎实,修复方向也对:把判据从「是否 @ 了已知 bot」改成「是否 @ 了非自己」是唯一可靠的口径,因为每个 bot 唯一能确信的就是它自己的 open_id。

逐路径核对了行为,均正确:

场景 mentions 结果
话题创建者,完全不 @ [] mentionsOtherParty=false → bypass 放行 ✓
@ 自己(本 bot) [botOpenId] !== botOpenId 为 false → bypass / mentioned 响应 ✓
@ 别人(含跨 app 未识别) [other] mentionsOtherParty=true → 不 bypass ✓(本 PR 修复点)
@ 自己 + @ 别人 [self, other] 多 bot 走 getRespondReason 返回 mentioned;单 bot 走 mentionedBot 分支返回 mentioned

几个我特意确认过的边界,都没问题:

  • @所有人:飞书的 @ALL mention 通常不带 open_id,!!m.id.open_id 为 false,不会被算作「@别人」→ 不会误伤话题内的 @ALL bypass。
  • 变量同名 mentionsOtherParty:多 bot / 单 bot 分支各自作用域,无 shadowing 冲突。
  • 单 bot 路径补闸的位置:放在 if (!threadId) return undefined 之后,作用域正确(仅话题内生效),且在 mentionedBot → 'mentioned' 之后,不影响「@我也被 @ 别人」时的正常响应。

🟡 一点可选的观察(非阻塞,confidence ~70,不计入正式 finding)

本 PR 修的是 thread_bypass 这道闸。多 bot 模式下还有一条独立的 commander 路由(getRespondReasoncommanderBotOpenId 分支,router.ts:119):当用户 @ 了一个跨 app 未被识别的 bot 时,mentionedBotIds 为空 → 会落到 commander 检查 → 若本 bot 恰好是 commander,仍会响应

这与本 PR 想达到的「@ 别人时不抢答」在 commander 这条路径上并不完全一致。不过:

  1. 这是 getRespondReason 的既有行为,非本 PR 引入;
  2. commander 的设计语义本就是「兜底总响应」,是否算 bug 取决于产品预期。

建议在 PR 描述提到的**二期(union_id 跨 app 互认)**里一并考虑——届时跨 app 的 @ 能被正确识别为 bot,commander 路径也会自然收敛。本 PR 作为止血的最小改动无需处理。

测试

mention-gate.test.ts 新增的 2 个用例(多 bot + 单 bot)精确复现了 @张全栈 接下来做啥 的跨 app 漏判场景,断言 toBeUndefined()(不抢答),覆盖到位。符合 CLAUDE.md「bug fix 需附带回归测试」的要求。

注:本环境无运行测试的权限(npx vitest run 需批准),未能本地复跑;以 PR 描述声明的「1686 passed,唯一失败的 memory/quality.test.ts 在 base 上同样失败、与本改动无关」为准。

整体评价:小而准的修复,根因清晰、注释把「为什么这么改」讲透了,可以合并。

lishuceo and others added 2 commits June 15, 2026 01:08
多 bot 共处一个话题时,用户 @ 某个 bot,其他 bot 也会响应。

根因:飞书 open_id 按 app 隔离,同一 bot 在不同 app 下 open_id 不同。
thread_bypass 的闸用 `!anyBotMentioned`(是否 @ 了已知 bot)判定,而
getAllBotOpenIds() 只存各 bot 用自己 app 拉到的「自视 open_id」。当用户
@ 了另一个 bot 时,本 bot 的 app 收到的那条 mention 是「对方在本 app 视角
下的 open_id」,不在 knownBotIds 里 → anyBotMentioned 漏判 → 话题创建者
bot 误以为没人被 @ 而走 bypass 抢答。

修复:改用「是否 @ 了非自己」作为不 bypass 的判据。每个 bot 唯一能可靠
识别的就是自己的 open_id —— 只要消息 @ 了除本 bot 外的任何对象,就说明在
叫别人,不 bypass。同时给单 bot 路径补上此前缺失的同款 mention 闸。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 多 bot: 创建者 bot 看到 @ 了跨 app open_id 未识别的对象 → 不 bypass
- 单 bot: 创建者 bot 看到 @ 了另一独立服务 bot → 不 bypass

两个用例在修复前均会失败(走 thread_bypass 抢答),修复后通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lishuceo lishuceo force-pushed the fix/thread-bypass-mention-other-bot branch from 0792d42 to fedca02 Compare June 14, 2026 17:09
@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 1m 6s —— View job


PR Review: 多 bot 话题中 @ 某 bot 时其他 bot 不再抢答

  • 读取 CLAUDE.md 与 PR 背景
  • 审查 event-handler.ts 改动(多 bot + 单 bot 两条路径)
  • 审查 mention-gate.test.ts 新增用例
  • 验证边界(空 open_id、@ALL、@自己+@别人、accessor 存在性)

✅ Approved — 根因准确,修复最小且有精准回归

根因分析无懈可击:飞书 open_id 按 app 隔离,knownBotIds 只含各 bot 的「自视 open_id」,所以「@ 了另一个 bot」在本 app 视角下 anyBotMentioned 会漏判。把判据从「@ 了已知 bot」改成「@ 了非自己」是唯一可靠的口径 —— 每个 bot 唯一能确信的就是它自己的 open_id。

逐路径核对,行为均正确:

场景 mentions 结果
创建者,完全不 @ [] mentionsOtherParty=false → bypass 放行 ✓
@ 自己 [self] 多 bot 走 getRespondReasonmentioned;单 bot 在 mentionedBot 分支已提前返回 ✓
@ 别人(含跨 app 未识别) [other] mentionsOtherParty=true → 不 bypass ✓(本 PR 修复点)
@ 自己 + @ 别人 [self, other] 多 bot:getRespondReason 命中自己返 mentioned;单 bot:mentionedBot=true 提前返回 ✓

我额外确认过的边界,都没问题:

  • 空 open_id / @所有人:!!m.id.open_id 守卫挡掉了 @all(通常不带 open_id)和空值,不会误把它算作「@别人」,因此话题内的 @ALL bypass 不受影响。
  • botOpenId accessor:多 bot 用 accountManager.getBotOpenId(accountId),单 bot 用 feishuClient.botOpenId(client.ts:31 getter 存在),两者均 ?? '' 兜底。
  • 单 bot 补闸位置:放在 if (mentionedBot) return 'mentioned' 之后、if (!threadId) return undefined 之后,作用域正确——走到这里本 bot 必然未被 @,所以任一带 open_id 的 mention 都是「@别人」,注释也讲清了这一点。
  • 变量同名 mentionsOtherParty:多 bot / 单 bot 分支各自作用域,无 shadowing。

🟡 一点可选观察(非阻塞,confidence ~70)

本 PR 修的是 thread_bypass 这道闸。多 bot 模式下还有一条独立的 commander 兜底路由(router.tsgetRespondReason):当用户 @ 的 bot 跨 app 未被识别时,mentionedBotIds 为空 → 落到 commander 检查 → 若本 bot 恰是 commander 仍会响应。这是 getRespondReason 的既有行为(非本 PR 引入),且 commander 语义本就是「兜底总响应」。建议留到 PR 描述提到的**二期(union_id 跨 app 互认)**一并收敛——届时跨 app 的 @ 能被正确识别为 bot,这条路径会自然收口。本 PR 作为止血无需处理。

测试

mention-gate.test.ts 新增 2 个用例(多 bot + 单 bot)精确复现了 @张全栈 接下来做啥 的跨 app 漏判,断言 toBeUndefined()(不抢答),覆盖到位,符合 CLAUDE.md「bug fix 需附带回归测试」。

注:本环境无运行测试权限,未本地复跑;以 PR 描述「1686 passed,唯一失败的 memory/quality.test.ts 在 base 上同样失败、与本改动无关」为准。

小而准的修复,根因清晰、注释把「为什么这么改」讲透了,可以合并。

@lishuceo

Copy link
Copy Markdown
Owner Author

Acknowledged(🟡 commander 路径观察)— 确认这是 getRespondReason 的既有兜底行为、非本 PR 引入,且仅在「群配置了 commander + @ 跨 app 未识别 bot」时触发。与本 PR「最小止血」范围一致,归入二期 union_id 跨 app 互认一并收敛(届时跨 app 的 @ 能被正确识别为 bot,commander 路径会自然收口)。本 PR 不扩大改动。感谢精准复核 ✅

@lishuceo lishuceo merged commit aec76ac into main Jun 14, 2026
4 checks passed
@lishuceo lishuceo deleted the fix/thread-bypass-mention-other-bot branch June 14, 2026 17:20
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.

1 participant