Skip to content

fix(lark): 修复入群主动开工话题路由 - #636

Merged
deepcoldy merged 1 commit into
deepcoldy:masterfrom
hyperdai:codex/fix-shared-autostart-topic
Jul 28, 2026
Merged

fix(lark): 修复入群主动开工话题路由#636
deepcoldy merged 1 commit into
deepcoldy:masterfrom
hyperdai:codex/fix-shared-autostart-topic

Conversation

@hyperdai

Copy link
Copy Markdown
Contributor

问题

普通群配置 regularGroupReplyMode=shared 时,bot.added 事件没有入站消息 ID。入群主动开工因此只创建了 chat-scope 会话,却没有可供飞书回复的可见话题根,输出会平铺到群里。

改动

  • 对普通群的有效 shared 模式(含群级覆盖)先发送本地化开工消息,作为可见话题根
  • 保留 chat-scope 会话和 chatId 锚点,继续复用普通群共享会话
  • 将话题根同时绑定为首轮 turnId,使卡片、流式输出和最终回复稳定落在同一话题
  • 仓库选择和自动 worktree 的延迟首轮继续携带该 turnId
  • 增加入群主动开工路由回归测试

影响面

  • 仅改变普通群 shared 模式下的入群主动开工路径
  • 普通群 chat 模式和原生话题群路径保持不变
  • 不涉及 CLI 适配器、PTY/Tmux 后端或平台路径逻辑;各 CLI/后端复用相同 turn 元数据

验证

  • 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 build
  • git diff --check
  • pnpm daemon:restart(本机 daemon 重启后在线;已切回 canonical checkout)

未在真实飞书新群触发 bot.added,避免为了验证擅自创建群或重新邀请机器人。

普通群 shared 模式缺少首条消息锚点,
导致主动开工回复平铺。保留 chat-scope 会话复用语义,
并让首轮输出稳定归入同一话题。
@hyperdai
hyperdai requested a review from deepcoldy as a code owner July 28, 2026 10:26

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

首次 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,让卡片 / 流式输出 / 最终回复都稳定落进同一话题串。

关键点:

  1. 仅当 mode==='group' 且有效模式(含群级覆盖 chatReplyModes[chatId])解析为 shared 时才发种子;否则 sharedReplyRootId=undefined
  2. 会话仍是 chat-scope、锚点仍是 chatId——复用群共享会话语义不变。
  3. beginReplyTargetTurn(ds, seed, seed) 把种子登记为首轮 reply target。
  4. 种子存进 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 有值即合法。
  • beginReplyTargetTurn scope≠chat 时 early-return,对话题群路径是 no-op(无害)。
  • noteTurnReceived 只管 reaction,不碰 currentReplyTarget,种子绑定能存活到 fork。
  • 错误面:种子发送在 dedup 守卫(activeSessions.has(dsKey)之后——重复 bot.added 不会重发;handleBotAdded 在 dispatcher 层被 try/catch 包住,种子发送 throw 只 log 不崩 daemon,且 master 的话题群路径本就有同款 await sendMessage throw 面。

影响面

  • 仅改普通群 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 干净;base fd455bcf = 当前 master tip;trial-merge 无冲突

@codex 做复审。未获申晗确认前不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy

Copy link
Copy Markdown
Owner

双审收敛 — 代码无阻塞(LGTM),合入前 1 个提交规范项

Claude(首审)+ codex(复审)独立核对 head 3e956a6b,代码结论一致:无阻塞 bug

  • shared 判定正确(仅有效 shared 才发 seed;chat 与原生话题群路径不变)
  • 会话仍 sessionKey(chatId)/scope=chat,seed 只作可见话题根 + 首轮 turnId,不重现 feat(lark): 普通群 chat-topic 隔离原生话题 + 默认改 chat-topic/私聊 chat #631 split-brain(我用一次性探针驱动真实 decideRouting+ownership 验证;codex 独立核对 per-turn map 落点一致)
  • repo picker / auto-worktree 延迟 fork 继续消费 pendingTurnId;即时 fork 两路显式携带 seed turnId
  • 不触及 CLI adapter / PTY·Tmux·Riff 后端 / 平台路径

独立验证(两侧):build ✅;首审套件 + event-dispatcher/repo-select/auto-worktree 补充套件全绿;codex 在最新 master(216da4a6,PR #635 已合入后)trial-merge 无冲突 + 完整 pnpm test 11059 passed。

合入前规范项(唯一待处理)

head commit subject 带 emoji 前缀 🐛 fix(lark): …,不符 AGENTS.md/CLAUDE.mdtype(scope): 中文描述 规范。PR 标题本身已正确。
⚠️ 本仓库 squash 合并的 title 策略是 COMMIT_OR_PR_TITLE,本 PR 仅 1 个 commit → squash 默认标题会取 commit subject(含 emoji)。因此若走 squash 合并,需管理员手动把 squash title 改成干净的 PR 标题;或请作者 amend commit message(只改 message、tree 不变,无需重做功能复审)。

残余验证缺口

真实飞书 bot.added 未做手工回归(未擅自建群/重邀 bot)。

未获申晗确认前不合码。

@deepcoldy
deepcoldy merged commit 06231bb into deepcoldy:master Jul 28, 2026
@deepcoldy

Copy link
Copy Markdown
Owner

✅ 已合码(申晗授权,admin squash-merge)

  • merge 方式:squash,pin sha=3e956a6b(首审+复审核对的确切 head)
  • master 合并 commit:06231bb1,subject 已清理为 fix(lark): 修复入群主动开工话题路由 (#636)(去掉 emoji 前缀,符合 type(scope): 中文描述 规范 — 复审提的唯一规范项已在合码时一并解决)
  • 作者归属保留(hyperdai)

合前 + 合后验证

  • 合前:PR head 仍 3e956a6b(未动);对当前 master 216da4a6fix(desktop): 修复 dashboard v2 兼容误判 #635 合入后)trial-merge 无冲突,同 2 文件 diff;build + 276 tests ✅
  • 合后:在真实 merged master 06231bb1 复跑 pnpm build ✅ + group-join-shared-routing/event-dispatcher 241/241 ✅
  • worktree 已还原,本地临时分支已删

残余

真实飞书 bot.added 手工回归仍未做(未擅自建群/重邀 bot);未发版、未部署 live——各需另行授权。

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.

2 participants