fix(lark): 修复入群主动开工话题路由 - #636
Conversation
普通群 shared 模式缺少首条消息锚点, 导致主动开工回复平铺。保留 chat-scope 会话复用语义, 并让首轮输出稳定归入同一话题。
deepcoldy
left a comment
There was a problem hiding this comment.
首次 review(Claude)— 结论:无阻塞(LGTM)
在 review worktree 拉取 PR head 3e956a6b 完成本地验证(fork PR 无 CI,全部本地跑)。base 恰为当前 master tip fd455bcf,trial-merge 干净,diff 仅 2 文件。
改动逻辑(白话)
普通群配 shared 模式时,回复本应「折」进一个话题串里;但 bot 入群主动开工(bot.added)这条事件没有入站消息 id,于是当时创建的 chat-scope 会话没有可见话题根,输出只能平铺到群顶层。本 PR 在这条路径上,先发一条本地化开工消息作为「话题根」,并把它绑成首轮的 turnId,让卡片 / 流式输出 / 最终回复都稳定落进同一话题串。
关键点:
- 仅当
mode==='group'且有效模式(含群级覆盖chatReplyModes[chatId])解析为shared时才发种子;否则sharedReplyRootId=undefined。 - 会话仍是 chat-scope、锚点仍是 chatId——复用群共享会话语义不变。
beginReplyTargetTurn(ds, seed, seed)把种子登记为首轮 reply target。- 种子存进
ds.pendingTurnId,让延迟 fork(选仓库卡点击 / 自动 worktree 提交)经commitRepoSelection读取并带上同一 turnId;两条即时 fork 路径显式传{turnId}后清空pendingTurnId。
我核实过的正确性
- 无 split-brain(#631 的失败模式):种子会话登记在
sessionKey(chatId);后续顶层消息经regularGroupRouting→{scope:'chat', anchor:chatId}→isSessionOwner(chatId)=true→复用(handleThreadReply),不会 fork 新会话。用一次性 stateful 探针驱动真实decideRouting+ownership 语义验证通过。与 #631 有本质区别:#631 把种子登记在 thread-key 上、chat-routed 回复找不到它才分裂;这里种子始终在 chatId 上。 pendingTurnId是既有机制:新话题路径本就用pendingTurnId(daemon.ts:15414),消费点在commitRepoSelection(card-handler.ts:492/517)。本 PR 是复用成熟机制,不是新造。sendMessage契约:返回非空 id 或 throw,绝不返回空串 →sharedReplyRootId有值即合法。beginReplyTargetTurnscope≠chat 时 early-return,对话题群路径是 no-op(无害)。noteTurnReceived只管 reaction,不碰currentReplyTarget,种子绑定能存活到 fork。- 错误面:种子发送在 dedup 守卫(
activeSessions.has(dsKey))之后——重复bot.added不会重发;handleBotAdded在 dispatcher 层被 try/catch 包住,种子发送 throw 只 log 不崩 daemon,且 master 的话题群路径本就有同款await sendMessagethrow 面。
影响面
- 仅改普通群
shared入群主动开工路径。chat模式(不发种子、fork 传false)与话题群路径(仍 thread-scope 种子、fork 传false)字节级不变——测试已覆盖。 - 未触碰 CLI 适配器 / PTY·Tmux 后端 / 平台路径;turn 元数据对所有 CLI 同构。codex-app 的
pendingCodexAppText保留。
一个小 nit(非阻塞,P3-观感)
种子在 replyInvalidWorkingDirs 之前发送。若工作目录配错,用户会看到「🚀 已加入本群,开始工作…」后面紧跟一条「工作目录不存在」错误。略微别扭但不影响功能——且 master 话题群路径是同款顺序(种子先于校验),属既有一致行为,不是本 PR 引入的回归。若想优化可把种子挪到工作目录校验之后,但不必阻塞合码。
验证记录
pnpm build✅pnpm vitest run --project unit test/group-join-shared-routing.test.ts→ 5/5 ✅(隔离单跑)- event-dispatcher 全套 + 新测试 → 241/241 ✅
- 相关套件 auto-start / session-reply-thread-anchor / reply-mode-command / relay-target-routing / reply-target-fallback → 全绿
- 一次性 split-brain 探针(驱动真实 decideRouting + ownership)→ 通过,验证后已删除
git status干净;basefd455bcf= 当前 master tip;trial-merge 无冲突
已 @codex 做复审。未获申晗确认前不合码。
|
To use Codex here, create a Codex account and connect to github. |
双审收敛 — 代码无阻塞(LGTM),合入前 1 个提交规范项Claude(首审)+ codex(复审)独立核对 head
独立验证(两侧):build ✅;首审套件 + event-dispatcher/repo-select/auto-worktree 补充套件全绿;codex 在最新 master( 合入前规范项(唯一待处理)head commit subject 带 emoji 前缀 残余验证缺口真实飞书 未获申晗确认前不合码。 |
✅ 已合码(申晗授权,admin squash-merge)
合前 + 合后验证
残余真实飞书 |
问题
普通群配置
regularGroupReplyMode=shared时,bot.added事件没有入站消息 ID。入群主动开工因此只创建了 chat-scope 会话,却没有可供飞书回复的可见话题根,输出会平铺到群里。改动
shared模式(含群级覆盖)先发送本地化开工消息,作为可见话题根影响面
shared模式下的入群主动开工路径chat模式和原生话题群路径保持不变验证
pnpm vitest run --project unit test/group-join-shared-routing.test.ts test/session-reply-thread-anchor.test.ts test/auto-start.test.ts test/reply-mode-command.test.ts test/relay-target-routing.test.ts(5 个文件、65 个用例通过)pnpm test(完整 unit suite 通过)pnpm buildgit diff --checkpnpm daemon:restart(本机 daemon 重启后在线;已切回 canonical checkout)未在真实飞书新群触发
bot.added,避免为了验证擅自创建群或重新邀请机器人。