ZON
Best Minds Board Private
report-20260317-095539.html
OpenClaw · Feishu · Retrospective

群聊主时间线 / Topic 回复复盘

这次问题的本质不是“会不会发消息”,而是 普通群默认应该继续显示在主时间线,同时 Topic / Thread 场景又必须可显式保留。 本地把群聊默认改成 chat_id 直发,解决了眼前 UX; 但真正更像 upstream 会接受的形状,是把这层能力收敛成 groupDeliveryMode + groupSessionScope + replyInThread

Andrej Karpathy · simulate the maintainer, not your preference Charity Majors · trust runtime behavior over config intent OpenClaw maintainers · keep generic semantics, avoid Feishu-specific hacks Feishu API semantics · root_id / thread_id / reply_to_id are not interchangeable
核心目标
1
普通群不自动塌进话题
本地止血
有效
群聊默认改为主时间线直发
上游现状
半成品
已有配置原语,但还没完整收口
已验证 open issues
4
#35518 #35598 #35478 #39765
推荐提交单元
1
兼容式配置,不提硬编码

Direct Answer

你的真实目标不是“永远不要话题”,而是 默认普通群继续显示在主时间线;只有明确配置时才保留 Topic/Thread。 所以这次复盘的结论是: 本地硬改有效 配置化兼容更值钱 把硬编码原样提 upstream 风险高

What You Wanted

群里继续展示

正常群聊多轮沟通应该留在主时间线,不要因为回复 API 选型错误,视觉上突然变成话题。

What Worked Locally

im.message.create(chat_id)

对普通群是有效止血,因为它直接绕过了 reply metadata 带来的 topic 化行为。

What Is Upstream-Worthy

显式 delivery mode

更好的抽象是把“发到哪里”和“会话如何分组”拆开,而不是继续把两者混在 replyInThread 里。

Timeline

Timeline · March 2026 2026-02-28 上游合并 PR #27325: replyInThread 2026-03-05 open issues 暴露 #35518 / #35598 / #35478 2026-03-16 本地用户问题暴露 普通群回复变成话题 2026-03-16 本地热修 群聊默认改成 chat_id 直发 2026-03-17 兼容式收敛 groupDeliveryMode + tests
真正重要的分水岭不是“有没有修复”,而是从“热修写死”转成“兼容式配置”的那一刻。

Four Principles

要点 1

先区分“普通群”和“显式 topic 群”,不要把两个场景混成同一个默认回复语义。

要点 2

把 session scope、delivery mode、reply target 拆开,才有可能兼容普通群和话题群。

要点 3

本地 hotfix 的价值在于验证用户体验,而不是证明它已经是最好的通用抽象。

要点 4

真正值得 upstream 的,是最小兼容补丁;不要把 ACP topic binding 和普通群默认行为混在一起提。

Sequence

Sequence A · Before User OpenClaw Feishu UI 群内发消息 携带 root_id / reply metadata 选择 im.message.reply 回复落入 topic / 话题视图 主时间线可见性下降
错误不在“会不会发”,而在“把普通群误当成 thread 目标处理”。这正是用户体感最差的一段。
Sequence B · Normal Group, Main Timeline User OpenClaw Feishu UI 普通群消息 groupDeliveryMode=direct skip reply metadata in messages im.message.create(chat_id) 继续显示在主时间线
这就是当前更合理的普通群默认行为。注意:它解决的是“投递模式”,不是 session scope。
Sequence C · Explicit Topic / Thread User OpenClaw Feishu UI 话题内继续追问 group_topic + replyInThread=enabled reply to root / keep thread topic continuity preserved 继续留在同一话题
这条路径依然需要被支持,所以不能把“永远主时间线直发”做成全局硬编码。
Sequence D · Decision Split Inbound message arrives 先判断是否显式 topic / thread,再决定发到主时间线还是回复 API。 Branch 1 · Normal group delivery=direct send via create(chat_id) Branch 2 · Explicit topic replyInThread / group_topic* keep root + thread continuity
真正稳的抽象,是先分流场景,再决定投递模式;而不是让单个布尔值承受全部语义。

Supplemental Views

Score · Which layer matters most now Local UX fix 91 Upstream fitness 71 Config clarity 78 Remaining risk 43
这张分数图的意思很简单:本地 UX 目标已经接近完成,但 upstream 仍有语义和边界问题。
Matrix · Scope vs Risk 低通用性 高通用性 低风险 高风险 全局硬编码主时间线 兼容式 delivery mode 现有 groupSessionScope / replyInThread
真正值得往 upstream 推的,是右下角那块:通用性高,但不会把 topic 场景一起打坏。
Risk · What can still go wrong 把 threadId 当成 messageId 使用,回复目标语义会错。 streaming card 如果不统一,也会和文本回复出现分叉。 bot.test.ts 的本地运行依赖仍不完整,回归验证要补。
剩余风险并不神秘,已经被收敛到三个非常具体的工程点。
Architecture · Separate concerns Session scope group / topic / sender Delivery mode reply / direct Reply target reply_to_id / root_id
这张架构图就是这次复盘最核心的一句话:三件事要分开,不要再混成一个布尔值。
Feedback Loop · How this should mature 用户体感 本地修正 配置抽象 上游提交
最佳节奏不是“发现问题后直接提大 PR”,而是先让用户体感闭环,再抽象成最小通用补丁。

Upstream Reality

官方现在已经有的东西

  • 主干 Feishu 代码里已经存在 groupSessionScopereplyInThread
  • 官方文档明确写了 ACP 对 Feishu 重点支持的是 DM 和 topic conversations。
  • PR #27325 已经把 replyInThread 合进主干。

官方现在还没完全收口的东西

  • Issue #35518:线程内应该跟随 reply_to_id,而不是总回 root。
  • Issue #35598:ACP topic 出站忽略 threadId,结果掉回 group root。
  • Issue #35478:DM topics / threads 也要可选 scope 与 thread reply。
  • PR #39765 仍在推进 Feishu ACP topic bindings。
判断项 这次结论 为什么重要
这是不是纯配置问题 不是 因为当前上游虽然已有配置位,但行为语义还没完整收敛。
本地硬改是不是无价值 不是 它证明了普通群的真实 UX 诉求,但它只是局部 workaround。
能不能直接把硬改提 PR 不建议 maintainer 更关心 generic semantics,不喜欢把 Feishu 特殊行为硬塞成默认。
最佳单位是什么 兼容式配置 PR 让普通群与 topic 群都能被清晰表达,避免重复造轮子。

Local Refactor

这轮本地实现已经从“直接写死群聊直发”进一步收敛成配置兼容版: schema 新增 groupDeliveryMode: "reply" | "direct", 普通群可以显式主时间线直发,显式 topic 场景仍然保留 groupSessionScopereplyInThread

Normal Group

推荐默认

{
  "groupDeliveryMode": "direct",
  "groupSessionScope": "group",
  "replyInThread": "disabled"
}

这代表:普通群继续显示在主时间线,session 也不按 topic 切分。

Explicit Topic Group

显式保留话题

{
  "groupDeliveryMode": "reply",
  "groupSessionScope": "group_topic_sender",
  "replyInThread": "enabled"
}

这代表:这是一个明确依赖 topic continuity 的群,不应该被普通群默认覆盖。

已做

  • 新增 groupDeliveryMode schema 与 channel config 暴露。
  • 把群聊“主时间线直发”与“topic/thread 继续回复”从逻辑上拆开。
  • 修正 streaming card 路径,避免文本直发而卡片又悄悄走 reply。

刻意没做

  • 没有去碰 ACP outbound 的 threadId 语义补丁。
  • 没有把 threadId 强行当成 replyToMessageId
  • 没有把你本地 workaround 原样包装成 upstream patch。

剩余风险

  • bot.test.ts 还受缺失的 plugin-runtime-mock.js 影响,当前目录单独跑不通。
  • 上游文档与主干代码的语义解释仍然存在差距。
  • 如果未来 maintainer 更想复用现有字段,可能会对新字段命名提出收缩意见。

Decision Matrix

层级 现在怎么做 值不值得 upstream
本地普通群止血 默认 groupDeliveryMode=direct 值,但应以配置化形式提,不是硬编码。
显式 topic 群 group_topic* + replyInThread=enabled 值,这是现有上游能力真正合理的落点。
ACP outbound threadId 单独处理 值,但必须避开“threadId 当 message id”的语义坑。
全局永不话题 不建议 不值,这会伤害真正依赖 topic continuity 的群。

Recommended Next Step

如果下一步要对外提交,只提一件事: 把“普通群主时间线直发”做成兼容式 delivery mode。 不要把 ACP topic binding、threadIdreply_to_id、 normal group 默认行为混在同一个 PR 里。

更像 maintainer 会接受的版本

  • 小补丁:只讨论 group outbound delivery mode。
  • 保持现有 groupSessionScopereplyInThread 不被破坏。
  • 配套测试覆盖 normal group / explicit topic / streaming card 三条路径。

最不该做的版本

  • “不管怎样都发主时间线”。
  • “只要有 threadId 就一律复用成 reply target”。
  • “把本地用户体验热修直接等价成通用语义”。

Sources

这次复盘的结论很明确:你要解决的是“普通群继续显示在主时间线,但 topic/thread 场景仍应被显式支持”。本地把群聊默认改成 chat_id 直发是有效止血,但更像 upstream 会接受的形状,是把它收敛成兼容式配置:groupDeliveryMode + groupSessionScope + replyInThread,而不是继续写死。
— One small system