Skip to content

feat(lark): 完善回复卡排版与可配置语义布局 - #1057

Merged
deepcoldy merged 18 commits into
masterfrom
feat/lark-reply-card-layout
Aug 31, 2026
Merged

feat(lark): 完善回复卡排版与可配置语义布局#1057
deepcoldy merged 18 commits into
masterfrom
feat/lark-reply-card-layout

Conversation

@sensuossss

@sensuossss sensuossss commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

改动内容

回复卡排版底座与 Card 2.0 字段清理

  • 统一普通回复卡的 Card JSON 2.0 配置:botmux send、daemon final fallback 与 contextual reply 共用 width_mode: fill 和一致的 footer builder。
  • 将顶层 ATX H1/H2 提升为独立 heading-2 组件,并将全卡提升数量限制为 6;H3–H6、Setext 与超额标题保持加粗回退。
  • 提升后的标题携带 botmux_md_h{level}_{seq} 元素 id:飞书回读会剥掉 text_size,parser 据此 id 重建 #/## 行,跨会话回读保留完整层级,跨 bot 重发可再次提升(与 botmux_reply_footer 同款存活载体)。
  • 保持列表、代码块和 pipe 表格的结构化渲染;原生 table 经飞书归一化后,quoted / history 仍可还原完整 Markdown 表格及 code_span 单元格。
  • 修正其余 Card 2.0 字段:
    • /adopt 选择卡改用 width_mode: fill
    • 回复页脚及语音按钮空占位使用合法的 notation
    • parser 同时兼容新字号、缺省字号与历史 notation_small_v2
  • 更新内置 botmux-send 指南,提供结果摘要、进度更新、方案对比、风险/待确认、交接说明五类可选写作配方;配方可不用、混搭或改名,短消息仍使用自由 Markdown。

五档语义 layout

新增可选参数:

botmux send --layout <result|progress|risk|blocked|handoff>
layout 卡头前缀 默认卡头色 默认标签
result 结果 green
progress 进度 blue
risk 需要确认 orange 需要你 / red
blocked 受阻 red 需要你 / red
handoff 交接 indigo
  • 档位只来自显式 --layout,不会根据正文猜测状态。
  • 正文存在首个顶层 ATX H1/H2 时,卡头标题为「前缀 · 标题」,并从正文取走该行;重复前缀只保留一次。
  • 卡头只是薄壳,正文继续复用现有 Markdown、图片、表格和 footer builder。
  • 非法、缺值或重复的 --layout 会提示并回退普通回复卡,不阻断发送。
  • 自定义原始卡、语音、斜杠回复、文档评论和纯视频路径显式忽略 layout,保持原行为。
  • 不新增 compare / diff 档,不做假按钮、进度条或任意卡片 JSON 注入。

按 Bot 配置 replyStyle

新增稀疏配置 replyStyle

  • recipes:是否注入内置写作配方
  • layout:是否允许 --layout
  • themedefault / minimal / vivid
  • recipePrompt:用自定义文本替换内置配方区
  • layoutColors:按档覆盖 Card 2.0 卡头色
  • layoutTags:按档继承、隐藏或自定义标签

配置遵循以下约束:

  • 缺省值不写回 bots.json;恢复默认后整块 replyStyle 自动移除。
  • recipePrompt 上限 4096 个 Unicode code point;单档标签上限 32 个。
  • 非法枚举、超限值和控制字符逐项忽略、记录告警并回退缺省,不让外观配置阻断发送。
  • layoutColors 只改变卡头;标签色固定跟随语义,不随卡头覆盖漂移。
  • minimal 去掉彩色卡头,vivid 为五档增加语义标签,default 保持定稿观感。

Dashboard 回复风格设置

  • 在 Bot Defaults 的消息卡片页新增「回复风格」区域:
    • 写作配方、layout 两个开关
    • 三档主题
    • 配方文本
    • 五档卡头颜色
    • 标签三态:跟随主题 / 隐藏 / 自定义
  • 保存时只提交稀疏 override;颜色“跟随主题”和标签“继承”不落盘,显式隐藏保留空字符串。
  • API 使用 32 KiB 请求上限;坏 JSON、非法 envelope 和非对象 replyStyle 返回 400,超限返回 413,null 保留显式清空语义。
  • RMW 持久化后同步 live registry,新建或重启的 worker 无需重启 daemon 即可读取新配置。
  • 配方文本与标签输入会在进入 React state updater 前同步取值并按 code point 截断,避免 synthetic event 生命周期导致页面崩溃。

影响面评估

  • 发送链路:排版底座影响所有 CLI / backend 经 Lark 普通回复卡走的显式 botmux send、daemon final fallback 与 contextual reply;三条路径复用同一 Card 2.0 配置和 footer builder。五档卡头只在显式、有效的 --layout 请求上生效。
  • 读取链路:影响 Lark interactive card 的 quoted / history 文本提取;新增 header title、text_tag_list 与 CardKit 归一化 native table 的对等回读,确保跨会话读取不丢标题、标签、正文或表格。
  • CLI / relay 边界:CLI 对用户输入 fail-soft,非法 layout 回退普通卡;sandbox host 只对伪造或篡改的 relay 请求执行五档 allowlist 硬拒绝,与用户侧发送语义分层。
  • 会话快照:自有 worker 在 spawn 时冻结归一化后的 BOTMUX_REPLY_STYLE。已运行 worker 保持原快照,新建或重启 worker 读取新配置;Riff/Mojo 在用户 env 合并后再冻结,共享持久后端在会话边界清理该变量,避免跨 Bot 漂移。
  • adopt / restore-adopt:保持非侵入,不向已运行的外部 CLI 注入 env、skill 或个性化指南;其指南 loader 固定回到出厂默认。adopt 会话里的 botmux send --layout 仍按该 session 的 larkAppId 读取 live registry / bots.json,正常渲染卡头。
  • Dashboard:配置只进入私有 Bot Defaults,不进入公开 roster payload;写盘保留其它 Bot 字段及凭证,响应不暴露密钥。
  • 平台与兼容性:schema 1.0 流式卡继续使用原有字段;其它 IM、CLI 适配器以及 PTY/Tmux 核心收发协议不变。

测试验证

提交态聚焦回归

  • 终态提交:b9bb09ff455385a0a1c4b3e6878bc0748016fc43
  • bun run test -- <18 个受影响测试文件>
    • 18 files passed
    • 975 passed,6 skipped
  • bun run build
    • domain audit、TypeScript、脚本类型检查、Dashboard bundle 与 dist audit 全部通过。
  • git diff --check
    • 通过。

全量回归

  • bun run test
    • 18,786 passed,17 skipped
    • 另有 6 条环境相关失败
      • 2 条:root 环境下 chmod 权限语义与普通用户不同
      • 2 条:bwrap 环境下依赖 symlink / MCP 隔离前提不成立
      • 1 条:机器级公开 URL / redirect 注入环境影响
      • 1 条:bwrap 环境下 DSH 子进程启动依赖缺失
    • 以上失败均不经过本次回复卡、消息解析、replyStyle、Dashboard 或指南接线路径;对应聚焦回归均通过。

Live 验收

  • 五档真实回复卡均已在飞书客户端发送并完成视觉检查:
    • result:绿色「结果」
    • progress:蓝色「进度」
    • risk:橙色「需要确认」+ 红色「需要你」
    • blocked:红色「受阻」+ 红色「需要你」
    • handoff:indigo「交接」
  • quoted 对五档卡头标题和标签均可读回;带 native table 的样卡可还原完整 pipe 表。
  • Dashboard 实际操作覆盖开关、主题、配方文本、颜色和标签三态:
    • 配方文本与自定义标签输入不再触发页面卸载
    • 自定义值保存后按稀疏结构落盘
    • 恢复默认后二次保存,bots.jsonreplyStyle 整块移除
  • 已通过 switch:here 部署当前 checkout 并重启 daemon 完成上述验收。

五档样卡说明

五档卡头都只表达明确、由调用方选择的语义;正文仍是自由 Markdown:

  • result:任务完成并交付结果
  • progress:长任务到达值得同步的里程碑
  • risk:存在风险或需要用户拍板
  • blocked:任务失败或被硬阻塞,需要人介入
  • handoff:将任务正式交给下一处理人

Dashboard 截图

桌面(900 × 900) 窄屏(390 × 1400)
Dashboard 回复风格设置(桌面) Dashboard 回复风格设置(窄屏)

明确不做

  • Diff 左右分栏
  • --layout diff / --layout compare
  • 插件化卡片模板
  • 自动从正文猜测状态
  • 私聊按读者覆盖主题
  • 假按钮、进度条或任意 JSON 主题注入

@sensuossss
sensuossss requested a review from deepcoldy as a code owner August 28, 2026 06:25
@sensuossss sensuossss changed the title feat(lark): 优化回复卡片结构化排版 feat(lark): 优化回复卡片排版与 Card 2.0 兼容 Aug 28, 2026
@deepcoldy

Copy link
Copy Markdown
Owner

感谢这个 PR,字段修复这块的价值很实在 —— 我查了官方文档确认 notation_small_v2 / heading_2_v2 在 Card JSON 1.0 和 2.0 的枚举里都不存在(文档明写未知值静默回落 normal),所以「JSON 改了、客户端没变化」确实是真问题;width_mode: fillwrapAdoptCard 那处 1.0/2.0 混用也都改对了。表格经 CardKit 归一化后跨会话回读的还原我实测有效,反向变异(禁用 extractTableMarkdown)会让 3 条用例变红,测试是有牙的。

本地验证:bun run build 通过;card-builder / md-card / message-parser / builtin-skills / skill-feedback-card 共 481 条全绿。

一个问题想请你确认,正好和这个 PR 自带 backlog 文档里定的「quoted / history 回读必须对等」硬约束相关 —— 表格那条达标了,但标题这条我实测是退化的。我建了 master 对照 worktree 跑同一个探针:

输入: # 执行结果 / 核心链路已验证。 / ### 细节 / 补充说明。

master 回读: "**执行结果**\n\n核心链路已验证。\n\n**细节**\n\n补充说明。"
本 PR 回读: "执行结果\n核心链路已验证。\n\n**细节**\n\n补充说明。"

原因是 heading-2 的层级只存在于 text_size 字段,而 extractElementText 只取 content、没有读 text_size。两个后果:

  1. 比改动前更差:以前至少留着 **加粗**,现在是裸文本。副作用是 H3 反而保住了 **细节**,回读文本里三级标题比一级标题更显眼。
  2. 段落粘连:标题与紧随正文用单个 \n 相连,markdown-it 重新解析会并成同一个 paragraph(接列表/代码块/表格没问题,接散文和接标题会粘),跨 bot 接力时下游读到的是糊在一起的一段。

关于新增的 round-trip 用例:它断言的是 toContain('执行结果') 这类存在性。我把三种可能结果(本 PR 的裸文本、master 的加粗、理想的 # 前缀)都代进那条断言,结果全部为 true —— 也就是它对「层级还在不在」没有区分力,这刚好是 backlog 文档自己写下的那条教训。

一个可能的修法(我在本地试过,extractElementText 的 markdown 分支):

const m = typeof el.text_size === 'string' && /^heading-([0-4])$/.exec(el.text_size);
parts.push(m ? `${'#'.repeat(Math.max(1, Number(m[1]) || 1))} ${text}` : text);

实测回读变成 ## 变更\n已完成三处修改。,重新解析能拿到 heading_open,五种邻接场景的粘连都消失,481 条测试仍全绿。但有个副作用需要你判断buildContextualReplyCard 的卡片标题也是 heading-2,回读会多出 ## 前缀(## 本地轮次 · Codex)—— 要么接受,要么给标题元素加 element_id 加以区分。另外建议把断言一并改成能区分层级的(断 # 或至少断加粗),否则修完仍然没有回归保护。

另有一条不阻断的小观察:MAX_PROMOTED_CARD_HEADINGS = 6 超额后是静默降级,标题较多的长报告会出现「前 6 个大标题、之后忽然变加粗」的视觉断层。

还有一点我没能自证:backlog 文档里提到「飞书会把标题收成 **加粗**」,我在本地 builder→parser 全链路里没找到任何一步会重新加粗。如果这条在真机上成立,上面的严重度可以降一档 —— 这需要真机 quoted --raw 才能定,我这边无法验证,所以按我实测到的结果报告,可能有偏差,请以你的真机结果为准。

以上是自动评审的初步意见,可能有误判,最终以维护者审阅为准

@deepcoldy

Copy link
Copy Markdown
Owner

交叉复审补充(第二遍自动评审,含真机验证)

我们按流程做了第二遍交叉复审。首审指出的回读退化方向是对的,但真机证据把修法的位置改变了,补充如下(同样以维护者审阅为准):

1. 飞书读回会剥掉 text_size,parser 侧修法在生产路径上不生效。
botmux send --card-file 真机发送带 text_size: "heading-2" 元素的 schema 2.0 卡片后 quoted --raw 回读:

  • text_size 被剥掉(markdown 元素只剩 tag / content / element_id);
  • 标题既不是加粗也不是标题,是裸文本
  • config.width_mode: "fill" 原样保留。

并且换 card_msg_content_type=user_card_content("原始 JSON" 变体)再取一次,text_size 同样不存在。把首审建议的 extractElementText 补丁打上后喂真实回读 JSON,# 还原不会触发——该补丁只在单测的合成 round-trip(本地 builder 输出直通 parser)里生效。

2. backlog 文档「飞书会把标题收成 **加粗**」这条可以证伪了。
加粗其实来自 botmux 自己 master builder 在发送前做的 #** 转换(用安装版 CLI 发 # 标题 后回读为 **标题** 可复现);飞书服务端只剥字段、不会重新加粗。文档那条大概是把自家 builder 的行为记到了飞书头上。

3. 一个可能两全的修法(回读侧已真机验证):把 ATX 标记直接写进提升标题的 content
发送 content: "# 标题甲"(保留 text_size: "heading-2")后回读,# 被飞书原样保留(首尾会各包一个换行,trim 掉即可)。此时不需要任何 parser 改动,回读即得 # 标题甲\n\n散文,markdown-it 直接解析出 heading_open,层级与段落分离全部保住,且对所有读回路径(实时事件 / REST 两个变体)一视同仁。显示侧 widget 对 ATX 的处理(静默剥除而非字面显示)与文档现有说法一致,但建议真机肉眼确认一次渲染效果再定。
注意:这同样会让 buildContextualReplyCard 的卡片标题回读带上 ## 前缀,取舍与首审所述相同(接受或用 element_id 区分)。

4. 回归建议:round-trip 用例的 fixture 应直接采用真机 quoted --raw 的归一化形态(text_size 已剥、元素带服务端 element_id),而不是 builder 输出——否则测试永远测不到飞书真实剥字段的行为,这也正是 parser 侧补丁漏过真机路径的原因。

以上是自动交叉评审的补充意见,可能有误判,最终以维护者审阅为准

@deepcoldy

Copy link
Copy Markdown
Owner

更正我上一条评论里的修法建议 —— 它是错的,请不要采用。

交叉复审补充了真机证据,我独立复核后确认:飞书服务端在回读时会剥掉 text_size。用 quoted --raw 读一张真实发出的 2.0 卡片,元素只剩 tag / content / element_id

发送: {tag:"markdown", text_size:"heading-2", content:"标题甲"}
回读: {content:"标题甲", element_id:"_2", tag:"markdown"}      ← text_size 不存在

card_msg_content_type=user_card_content 取「原始 JSON」变体,同样没有。

因此我上一条给的补丁(在 extractElementText 里读 el.text_size 还原 #在生产路径上是空转:条件永远为假。它只能让单测里的合成 round-trip(builder 输出直通 parser)变绿 —— 而这恰恰是本 PR backlog 文档自己写下的那条教训。我的对照实验建在 builder→parser 直通上,漏掉了「飞书服务端归一化」这一层,为此致歉。

结论因此升级而非撤销:层级不是在 parser 侧丢的,而是在服务端就永久丢了 ⟹ 任何 parser 侧修法都救不回来,修法必须落在发送侧。

顺带更正 backlog 文档第 15 行的两处事实(都经真机验证):

  • 「飞书剥掉 text_size」✅ 成立
  • 「剥掉 width_mode」❌ 不成立 —— config.width_mode: "fill" 回读原样保留
  • 「把标题收成 **加粗**」❌ 不成立 —— 飞书回来是裸文本;加粗其实来自我们自己 builder 发送前的 #** 转换(走真实 botmux send# 标题,回读得 **标题**,可复现)。这条是把自家行为记到飞书头上了。

发送侧有两条候选,都已真机验证可行

  1. 把 ATX 写进 content:发 content: "# 标题"text_size 保留或不保留都行)→ 回读 "\n# 标题\n"# 被原样保留,markdown-it 直接解析出 heading_open,不需要任何 parser 改动。残留不确定:客户端是否把字面 # 显示出来 —— 这需要真人肉眼确认,我们都只能读 API。

  2. 用自定义 element_id 做锚点(我另外验证的,可能更干净):element_id 不会被剥。同一张卡里自动 ID 被重编为 _2/_3,但自定义 id 原样保留:

发送: {tag:"markdown", element_id:"botmux_h_1", text_size:"heading-2", content:"标题丙"}
回读: {content:"标题丙", element_id:"botmux_h_1", tag:"markdown"}   ← id 在

这样正文不必内嵌字面 #(无显示风险),parser 按 id 还原层级即可。它还能顺手解决 buildContextualReplyCard 的副作用:卡片标题与正文标题用不同 id(如 botmux_card_title / botmux_body_heading),parser 就能分别处置,不必二选一。

另外一个回归测试上的请求:无论采用哪条修法,回归 fixture 请改用真机归一化形态(无 text_size、自动 element_id 被重编、ATX 内嵌时首尾多一个换行),不要用 builder 的原始输出。否则测试照样全绿、线上照样丢层级 —— 这也是我这轮判断出错的直接原因。

以上仍是自动评审意见,最终以维护者审阅为准。上一条评论中关于字段修复(notation / heading-2 / width_mode: fill 合法,两个 _v2 枚举不存在)和表格回读的部分不受本次更正影响,仍然成立。

@deepcoldy

Copy link
Copy Markdown
Owner

给选项 2 补一条仓内证据和一个设计注意项(不改变上面的结论,供作者选择时参考):

  • 选项 2 有生产先例:页脚机制 botmux_reply_footer(message-parser.ts 的 footer 过滤)今天就是靠「自定义 element_id 活过飞书归一化」在生产上工作的——我们探针的回读里该 id 原样保留。所以「按 element_id 锚定还原层级」不是引入新机制,是走一条已被生产验证的轨道,风险比看起来低。
  • 注意项(碰撞):parser.ts 现有注释已写明「公开 element_id 不是所有权证明,第三方卡可能撞名」。页脚的对策是 id + 内容精确标记双校验;标题元素没法内嵌可见标记。可接受的折中:撞名后果只是回读多一个 # 前缀(低危),或在 id 之外加内容形态校验。请作者权衡。

以上仍是自动评审意见,最终以维护者审阅为准。

@sensuossss sensuossss changed the title feat(lark): 优化回复卡片排版与 Card 2.0 兼容 feat(lark): 完善回复卡排版与可配置语义布局 Aug 28, 2026
@deepcoldy

Copy link
Copy Markdown
Owner

增量复审(第三轮自动评审,针对 b9bb09ff4 的 4 个新 commit)

先说结论:新增的这套可配置回复风格,我这边 0 阻断,质量比我预期的高。但上一轮的原始阻断仍然存在,所以整体建议仍是合前修。

新增部分我实测认可的地方

  • 配置校验逐项 fail-soft 且边界精确:超长 tag(第 33 码点触发,emoji 按 Unicode 码点正确计数,恰好 32 放行)、控制字符、非字符串、非法颜色、未知 layout 键、handoff=grey 禁令、recipePrompt 含 NUL —— 每条都被拒并给出具体警告,没有一例抛错或污染。原型污染尝试无效。
  • 反向变异有牙:禁用 tag 长度校验 → 红;禁用 handoff=grey 禁令 → 红;从 SESSION_TURN_MARKER_ENV_KEYS 摘掉 BOTMUX_REPLY_STYLE → 红(跨会话污染防线确实承重)。
  • 公共层改动是正确的加法sandbox.ts--layout 加入 RELAY_FLAGS_VAL同时校验了取值枚举(不是裸放行);child-env.ts 把新 env 同时登记进注入列表与按会话清理列表 —— 这正是 per-bot env 隔离的要求,两处都做到了。
  • Dashboard 写接口权限正确PUT /api/bots/:appId/reply-style 不在 PUBLIC_READ_PATHS,且该白名单的公开豁免只对 GET/HEAD 生效,写操作仍在 token 门后;daemon 侧有 hasExactSafeJsonKeys 严格键校验、请求体上限(413)、原子 RMW 落盘。
  • --layout 是 opt-in:不传不套卡头,传非法值 fail-soft 警告并按普通卡发送 ⟹ 不影响现有回复行为。

本地验证:bun run build 通过;reply-card-style / reply-style-guide / md-card / message-parser / card-builder / bot-registry / dashboard-reply-style(+proxy) / dashboard-ipc / builtin-skills809 测试全绿(1 skipped)。

但上一轮的阻断没有被修

默认路径(不传 --layout)的回读退化与上轮逐字一致:

输入: # 执行结果 / 核心链路已验证。 / ### 细节
回读: "执行结果\n核心链路已验证。\n\n**细节**"

buildCardBodyElementstext_size: 'heading-2' 那段提升逻辑本轮未改动,parser 侧也没有按 ATX 或 element_id 还原层级 —— 我们上轮提的两条发送侧修法都没有采用。H1 回读仍是裸文本(比改动前的 **加粗** 更差),且与紧随正文用单 \n 相连、markdown-it 重解析会并成同一段。

--layout 只救到第一个标题,不构成修复(走 cli.ts:9129-9135 的真实接线复核):

--layout result 回读: "[卡片: 结果 · 执行结果]\n核心链路已验证。\n变更\n改了三处。"
                        ↑ 第一个 H1 靠卡头存活          ↑ 第二个 H2「变更」仍是裸文本

extractFirstReplyCardHeading 顾名思义只取第一个,正文里其余 H1/H2 仍走原路径。而且它要求 Agent 显式传 flag,默认路径完全未变。

两条不阻断的观察(上轮提过,仍在)

  1. H1 与 H2 渲染成完全相同的大小(都是 heading-2/20px)。看注释是刻意避免 H1 压垮卡片,属设计取舍;但本轮新增 --layout 后更明显:第一个 H1 进了卡头,正文里剩下的 H1 和 H2 视觉上完全无法区分。若不打算区分,建议在指南里明说「H1/H2 视觉同级」。
  2. 提升标题的组件数线性翻倍(6 个标题 = 12 个组件),上限 6 封住了失控,但超额静默降级、无提示。

一个流程上的建议

这轮 +2619 行 / 45 文件把评审面扩大了一个量级,却没解决最初那个问题。个人建议:要么先补上发送侧那一条修法,要么把 --layout 这套拆成独立 PR —— 否则本来最小、最该先合的字段修复(notation / heading-2 / width_mode: fill)会被一个大功能拖住。这只是建议,怎么切由你和维护者定。

另外声明我这轮的覆盖边界:Dashboard 前端那 224 行 bot-defaults-page.tsx 我只核了后端接口的校验与权限,前端交互没有细看,也没有做真机 UI 验证。

以上是自动评审的初步意见,可能有误判,最终以维护者审阅为准

@deepcoldy

Copy link
Copy Markdown
Owner

交叉复审补充:两个覆盖边界的缺口已补

  1. --layout 只救第一个标题」已用真实接线 + 真机双重确认。 我按 cli.ts:9129-9135 的实际组装(extractFirstReplyCardHeadingbuildReplyLayoutHeaderbuildImageCardElements)程序化复刻卡片并以原生卡片 JSON 真发,quoted --raw 回读经本 PR parser 输出:
"[卡片: 结果 · 执行结果]\n核心链路已验证。\n变更\n改了三处。\n\n**细节**\n\n补充说明。"

第一个 H1 靠卡头存活,第二个 H2「变更」仍是裸文本且与正文粘连——与上条评论的判断一致。默认路径的 text_size: 'heading-2' 提升逻辑在新 head(md-card.ts:811/1150)逐字保留。

  1. Dashboard 前端 224 行已补看,无新问题
  • dangerouslySetInnerHTML/手动 innerHTML,全部受控组件,无 XSS 面;
  • 码点钳制正确:HTML maxLength 按 UTF-16 计,clampUnicodeCodePoints 在 model 空间按 Unicode 码点钳到与 daemon 归一化一致的 32,emoji/增补平面字符行为一致;
  • 稀疏序列化语义正确(inherit 省略 / hidden 显式 '' / custom trim),前端不预丢越界值、交给 daemon 规范校验并回显 warnings;
  • 代理链路 PUT /api/bots/:appId/reply-style 位于全局认证门之后,PUBLIC_READ_PATHS 豁免仅对 GET/HEAD 生效,写操作必过 token 门;body 上限在代理层与 daemon 层双重强制(content-length 预检 + 流式累计),413 语义一致。
  • 我这边相关 8 个测试文件 509 条全绿。

维持上条评论的结论:新增功能 0 阻断,PR 整体合前修(发送侧修法仍未落地)。

以上仍是自动交叉评审意见,最终以维护者审阅为准

作为 --layout 终态实现规格:五档卡头与标签定稿、按 bot 开关和预设主题、本轮排除 Diff/对比档。
recipePrompt、layoutColors、layoutTags 及对应 Dashboard 控件改为本轮范围;本轮之后只留 Diff 与私聊按人覆盖。
header.title 为前缀或「前缀 · 首个 H1/H2」;重复前缀时只保留前缀;被取走的标题必须能从 live header 回读。
按终态拍板采用 B 形态:header.title 只写五档前缀,不消费 H1;指南建议 --layout 时不要重复前缀词。
终裁改回 {前缀} · {首个 H1/H2}、取走正文行并防重复;去掉 B 形态指南。header 回读仍用 live 归一化测例。
fdd47f2 的 B 改判已撤销;title 规则以派生 A 为准,文档内容与 0aeafbc 对齐。
result=green、progress=blue、risk/blocked=red、handoff=indigo;非法 text_tag 色回退 neutral,发送不失败。
用户输入非法 --layout 不失败;host 校验只拒绝伪造 relay,与 --response-kind 同门。
飞书服务端回读时会剥掉 markdown 组件的 text_size,提升后的 H1/H2
在 quoted/history 里退化成与正文粘连的裸文本。发送侧给提升标题打
botmux_md_h{level}_{seq} 元素 id(与 botmux_reply_footer 同款、经
归一化仍存活的生产载体),parser 据此重建 `#`/`##` 行——跨会话回读
完整还原层级,且跨 bot 重发时可再次提升为标题组件。

Claude-Session: https://claude.ai/code/session_01H9M72GnFuikeDokYmY7i9f
@sensuossss
sensuossss force-pushed the feat/lark-reply-card-layout branch from b9bb09f to 3b709c7 Compare August 31, 2026 10:03
# Conflicts:
#	test/dashboard-bot-payload.test.ts

@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.

自动评审(Claude)批准:申晗已授权合并。

本轮验证摘要(详见前述 3 条评审评论):

  • 核心阻断已由 3b709c7f8 修复——element_id 载体(botmux_md_h{level}_{seq})在飞书归一化剥掉 text_size 后仍存活,跨会话回读完整还原 #/## 层级;真机端到端验证通过(真发→quoted --raw→喂本分支 parser)。
  • 与 master 的冲突(test/dashboard-bot-payload.test.ts 期望 key 列表)已解为并集,可数判据:67 keys / unique 67,两侧独有 key 均在位。
  • 合并树上 build 通过、13 个相关测试文件 917 条全绿;反变异(删 bot-payload.tsreplyStyle 真字段)2 条变红,证明断言承重。
  • CI bun-test 腿的失败已归因为既有环境性问题:与 master 基线做失败集差集,本 PR 无任何独有失败(PR 33 个 / master 35 个),且两边红的是同一条 buildReplyCardFooter 用例(该用例在 vitest 下本地通过,属 bun runner 侧既有差异)。

以上为自动评审意见,最终以维护者审阅为准。

@deepcoldy
deepcoldy merged commit 71a9b45 into master Aug 31, 2026
7 of 9 checks passed
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🚀 Released in v3.18.11

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