Skip to content

fix: @mention 白名单过滤 + 队列上下文修复 + thread bypass 智能判断 - #232

Merged
lishuceo merged 7 commits into
mainfrom
fix/mention-gate-and-queue-context
May 7, 2026
Merged

fix: @mention 白名单过滤 + 队列上下文修复 + thread bypass 智能判断#232
lishuceo merged 7 commits into
mainfrom
fix/mention-gate-and-queue-context

Conversation

@lishuceo

@lishuceo lishuceo commented May 6, 2026

Copy link
Copy Markdown
Owner

变更概述

  • @mention 白名单过滤:将 @mention 过滤从"没被拦住就放行"重构为"必须有明确 passReason 才放行",每条消息留 info 日志记录决策
  • 队列 accountId 上下文修复:修复 .finally() 回调继承前一个任务的 AsyncLocalStorage 上下文导致 bot 身份错乱(@土豆儿但张全栈回复)
  • Card action 补 runWithAccountId:审批/管道卡片按钮回调与 message handler 对齐
  • Thread bypass 智能判断:话题内只有创建者和 bot 时直接放行;多人时用 Qwen 带完整对话上下文(含发言者人名)判断消息在跟谁说话
  • 结果卡片底部加 hostname:pid 标识实例来源

测试计划

  • 27 个 getRespondReason 测试
  • 30 个 resolveMentionGate 测试(含双人/多人/bug 复现场景)
  • 9 个队列 accountId 上下文隔离测试
  • Qwen 集成测试:多人场景 4/4 正确(@别人后发消息→不回,追问 bot→回,回复第三人→不回,确认 bot 回复→回)
  • 飞书群内实测

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Progress

  • Read CLAUDE.md and PR context
  • Check previous review comments
  • Review changed files (router, event-handler, queue, thread-relevance)
  • Review test coverage
  • Post review summary

View job run

lishuceo and others added 5 commits May 6, 2026 20:43
将 @mention 过滤从"没被拦住就放行"改为"必须有明确 passReason 才放行"。
原来 shouldRespond 返回 false 时静默 return 不留日志,导致无法诊断
群聊话题内 bot 未被 @mention 却主动响应的问题。

- 新增 resolveMentionGate() 统一所有 @mention 决策逻辑
- 新增 getRespondReason() 替代 shouldRespond 返回放行原因
- 每条消息的过滤决策都留 info 日志 (passReason 或 blocked)
- 新增 29 个 mention-gate 测试 + 12 个 getRespondReason 测试

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
根因:task queue 的 .finally() 回调继承前一个任务的 AsyncLocalStorage
上下文。当 pm bot 的消息在 dev bot 任务完成后出队执行时,feishuClient
会用 dev 的凭证发送响应,导致"@土豆儿但张全栈回复"。

修复:在 QueueTask 中保存 accountId,processQueue 执行时用
runWithAccountId 恢复正确的上下文。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- queue.test.ts: 新增 3 个 accountId 存储/传递测试
- queue-context.test.ts: 新增 6 个 AsyncLocalStorage 上下文测试
  - 复现 bug:.finally() 继承外层上下文导致 pm 任务用 dev 凭证
  - 验证 fix:als.run() 包裹后每个任务获得正确上下文

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
审批/管道/记忆卡片按钮的回调 handleCardAction 此前未包裹
runWithAccountId,导致 feishuClient 走 defaultClient。
与 message handler 和 bot-added handler 对齐。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 话题内只有创建者和 bot 两人时直接放行,不走 Qwen
- 多人话题时,拉 fetchRecentMessages 构建对话上下文喂给 Qwen,
  让 Qwen 看清发言者身份判断消息在跟谁说话(之前只看一条文本)
- 新增 formatThreadContext 将话题消息格式化为 [人名]: 内容
- 结果卡片底部加上 hostname:pid 标识实例来源
- 更新 mention-gate 测试覆盖双人/多人场景

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lishuceo
lishuceo force-pushed the fix/mention-gate-and-queue-context branch from 13d5076 to aea9e10 Compare May 6, 2026 12:43
@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 5m 29s —— View job


Code Review Summary

Verdict: ⚠️ Issues Found (1 minor correctness issue)

Tasks

  • Read CLAUDE.md and PR context
  • Check previous review comments (none)
  • Review changed files (router, event-handler, queue, thread-relevance, message-builder)
  • Review test coverage
  • Post review summary

What's Good ✅

  • 白名单过滤重构:从"没拦住就放行"改为"必须有明确放行理由",每条消息都有 info 日志(@mention gate: passed/blocked),可观测性显著提升。getRespondReason 抽出来后单测覆盖完整(11 个用例覆盖 p2p / mentioned / commander / 边界)。
  • 队列 accountId 修复有理有据queue-context.test.ts 里专门写了 WITHOUT FIX 用例直接复现 .finally() 继承 AsyncLocalStorage 的行为,再写 FIX 用例证明 runWithAccountId 包裹后能正确切换上下文 — 这是教科书级别的回归测试。
  • Card action 回调handleCardActionrunWithAccountId(accountId, ...) 包裹,与 message handler 对齐,修复了卡片按钮回调里 bot 身份错乱。
  • 多人话题 Qwen 上下文增强RELEVANCE_PROMPT_WITH_CONTEXT + formatThreadContext 把"谁在跟谁说话"显式喂给 Qwen,超时从 2s 提到 3s 也合理。
  • Mention gate 测试覆盖全面:30 个用例覆盖单/多 bot、话题创建者、bug 复现场景,包括"@人类 但 bot 不该响应"的实际线上 bug。

Issues Found 🟡

formatThreadContext 在多 bot 场景下 bot 标签有歧义 (conf 80, 已留 inline comment)

  • 文件:src/feishu/event-handler.ts:559-568
  • 所有 senderType === 'app' 消息都标成 ${botName}(bot),但 botName 是当前判断中的 agent — 多 bot 共存的话题里,pm-bot 的回复会被打上 dev(bot) 标签,可能让 Qwen 误判"用户在跟当前 bot 的回复对话"。
  • 建议参考同文件 formatHistoryMessages(line 1755-1758) 用 selfBotOpenIds 区分 self/other bot 的做法。

其他观察(不阻塞)

  • humanSenders.size <= 1fetchRecentMessages 失败返回 [] 时也会触发 thread_bypass_exclusive(fail-open),但仅作用于已是 session 创建者的用户,风险可控。
  • resolveMentionGate 在每条话题非-@-mention 消息上都会做一次 fetchRecentMessages(API 调用),活跃话题的开销略增 — 当前体量应该无感,需要时可以考虑短期缓存。

整体评价

变更聚焦清晰:白名单化 + 上下文修复 + 智能分流三件事都在同一主题下,提交粒度合理(5 个 commits 各自独立可读),测试质量很高(特别是 WITHOUT FIX 那个反向证明)。Inline comment 里那个 bot 标签问题不阻塞合并,可以后续 PR 跟进。

Branch: fix/mention-gate-and-queue-context

Comment thread src/feishu/event-handler.ts
多 bot 场景下,pm-bot 的回复不再被误标为当前 bot 名字。
用 accountManager.getBotOpenId 区分 self vs 其他 bot。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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


Code Review Summary

Verdict: ✅ Approved — 1 previous issue fixed

Tasks

  • Check previous review comments (1 thread, already resolved)
  • Read CLAUDE.md and PR context
  • Review changed files (focus on formatThreadContext fix at src/feishu/event-handler.ts:549-575)
  • Verify the bot label fix is correct
  • Post review summary

Previous Issue Resolution

🟡 → ✅ formatThreadContext 多 bot 场景下 bot 标签歧义 (commit 96f13dd)

  • 函数签名添加 accountId: string 参数(src/feishu/event-handler.ts:553
  • 通过 accountManager.getBotOpenId(accountId) ?? '' 取当前 self bot 的 openId(L558
  • 标签按 senderId === selfBotOpenId 区分:self bot → ${botName}(bot),其他 bot → '其他bot'L562-567
  • 两个调用点都正确传入 accountId
    • 多 bot 模式下 thread bypassL495
    • 单 bot 模式下 thread session ownerL534
  • accountId 已存在于 MentionGateInput 接口(L459)和 resolveMentionGate 解构(L465),无需额外管线改动
  • formatHistoryMessages(L812) 中 accountManager.getAllBotOpenIds() 的 self/other 区分模式一致,符合代码库已有约定

新增检查(无新问题)

  • 队列上下文修复QueueTask.accountId 新字段(src/session/types.ts:95)和 enqueue 第 13 个参数(queue.ts:32),所有 3 个调用点(event-handler.ts:50, 977, 1168)均正确传入
  • router 重构getRespondReason 返回 RespondReason | undefinedrouter.ts:99-130),保留 shouldRespond wrapper 维持向后兼容,新签名清晰且测试覆盖完整
  • thread-relevance 增强RELEVANCE_PROMPT_WITH_CONTEXT 多人场景 prompt 与 formatThreadContext 的输出格式([name]: content)匹配;超时 2s→3s 合理(多人上下文 prompt 更长)

整体评价

变更聚焦清晰,先前 reviewer 的 inline 反馈已精准修复,方案与建议一致并复用了已有的 accountManager 接口。测试覆盖在主 PR 已建立(30 个 mention-gate / 9 个 queue-context / 多人场景 4/4),bot 标签修改是纯重构,行为正确性继承既有用例。建议合并。

Branch: fix/mention-gate-and-queue-context |

从 accountManager 和 chatBotRegistry 查找 bot 名字,
Qwen 看到的对话记录从 [其他bot] 变为 [土豆儿(bot)]。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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


Code Review Summary

Verdict: ✅ Approved — latest commit refines an already-approved change

Tasks

  • Check previous review comments and resolution status (no open inline threads)
  • Read CLAUDE.md and PR context
  • Review changed files (focus on new commit 316f697 since last review)
  • Verify previous fix correctness
  • Post review summary

What Changed Since Last Review

Only 1 new commit (316f697), 7 lines added in src/feishu/event-handler.ts:

const botNameMap = new Map<string, string>();
for (const acc of accountManager.allAccounts()) {
  if (acc.botOpenId) botNameMap.set(acc.botOpenId, acc.botName);
}
// ...
name = m.senderId === selfBotOpenId
  ? `${botName}(bot)`
  : `${botNameMap.get(m.senderId)
      ?? chatBotRegistry.getBots(chatId).find(b => b.openId === m.senderId)?.name
      ?? '其他bot'}(bot)`;

Now Qwen sees [土豆儿(bot)]: ... instead of [其他bot]: ... — strictly more information for cross-bot disambiguation, no regression risk.

Verification

  • Source lookups are correct: accountManager.allAccounts() returns BotAccount[] with botName: string (required, multi-account.ts:16) and optional botOpenId. chatBotRegistry.getBots(chatId) returns BotInfo[] with optional name (bot-registry.ts:14-20). Both fallbacks are type-safe.
  • Callsites unchanged: Two callers at event-handler.ts:495,534 already pass accountId (added in 96f13dd); this commit changes only the internal lookup logic.
  • Self-bot path unchanged: m.senderId === selfBotOpenId still uses botName (the current agent's display name) → ${botName}(bot). Self/other-bot distinction from prior commit still holds.
  • Fallback chain ordering is sensible: accountManager (system-configured bots) > chatBotRegistry (runtime-discovered bots) > '其他bot' (genuinely unknown).

Minor Observations (non-blocking)

  1. chatBotRegistry.getBots(chatId).find(...) runs inside the message loop, so it's O(N×M) per call. With the 1500-char content cap this is at most ~20 messages × a handful of bots — fine in practice. Could hoist into a Map alongside botNameMap if the message volume grows.
  2. The fallback string '其他bot(bot)' reads slightly redundant when neither registry knows the bot. Cosmetic — the case should be rare since acc.botName is always set.

Overall

变更是上轮 reviewer 反馈的一个补全:从"区分 self/other"进一步到"显示 other bot 的具体名字"。改动小而聚焦,类型安全,行为正确,与既有的 accountManager / chatBotRegistry 双源发现机制一致。建议合并。

Branch: fix/mention-gate-and-queue-context |

@lishuceo
lishuceo merged commit 5b9d5cc into main May 7, 2026
4 checks passed
@lishuceo
lishuceo deleted the fix/mention-gate-and-queue-context branch May 7, 2026 07:07
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