feat(card): 支持完成卡受约束后续提案 - #1199
Conversation
|
感谢这个 PR — 分层很清楚:Human Decision kernel 与 Ask 复用、可见字段的白名单校验、先 durable 落盘再 ACK、 自动评审跑下来有 2 个建议阻塞项 + 2 个非阻塞项,其中两个阻塞项会叠加成同一个确定性后果,想请你看一下。 🔴1 重启恢复跑在 session 恢复之前,把它自己声称覆盖的窗口打空
此时 实测(复刻 daemon 顺序 + 真实 store): 而 PR 描述里写的是「覆盖 accept 已落盘、dispatch 尚未开始的重启恢复」—— 这条路径目前恒失败。 建议:把这段 recovery 移到 🔴2
|
更正 + 一条新增发现上一条评论里我写了「全量套件里唯一红文件 原因是我这边的取数问题:捕获测试输出时用了 补做完整对照后,其中一条是本 PR 引入的回归,这是上一条评论遗漏的。 🔴5(新增)
|
| 文件 | base 58dd61966 |
PR f909c501a |
|---|---|---|
initial-passthrough-ownership |
8 / 8 全绿 | 1 failed / 7 passed |
机制(已定位到行):该用例是源码文本断言 —— test/initial-passthrough-ownership.test.ts:109-118 在 handleThreadReply 的源码区间里逐字匹配三条字符串。本 PR 为加入 continuation 抑制,改动了其中两处:
daemon.ts:19300—if (!prepared) learnFromMentions(…)→if (!prepared && !ctx.completionProposalContinuation) learnFromMentions(…)daemon.ts:19377—if (!prepared) {(emitHookEvent('thread.reply')所在块)→if (!prepared && !ctx.completionProposalContinuation) {
实测三条断言的命中情况:resolveNonsupportMessage 仍命中,learnFromMentions 与 emitHookEvent 正则双双落空,合并为 1 个 failing case。
定性:这不是功能缺陷,而是护栏用例没有同步更新。 改动本身是合理的(合成的 continuation turn 本就不应学习 mentions、不应触发 inbound hook,这正是 PR 设计的抑制点之一);问题在于这条用例用逐字源码串钉住了旧写法。
建议:更新断言以容纳新的守卫条件,而不要直接删除断言 —— 它守的是「prepared 重投不重复学 mentions / 不重复发 hook」这条真实约束,删掉会让该约束失去覆盖。
其余 27 个红文件的归属(已全部落定)
- 4 个:
history-range-hint/preset-export-cli/whiteboard-cli/workflow-c0-isolation—— 我的 review worktree 未执行bun run build,beforeAll报dist/cli.js missing。补 build 后 69 / 69 全绿。 - 12 个:
e2e-browser/feishu-*—— 缺FEISHU_TEST_GROUP_URL/MIDSCENE_MODEL_NAME/MIDSCENE_MODEL_API_KEY,需真实浏览器与模型密钥。 - 9 个:同样受缺
dist/污染,补 build 后转绿 —— 其中workflow-cli由 14 failed → 16 / 16 全绿,worker-codex-app-turn-routing由 4 failed → 8 / 8 全绿,npm-binary-distribution由 2 failed → 35 / 35 全绿。 - 2 个:
mojo-launcher-env-quarantine、multi-bot-session.e2e—— 双树结果相同,非本 PR 引入。前者的根因是本机以 root(uid 0) 运行:用例用chmodSync(file, 0o000)/chmodSync(dir, 0o500)期望读写被拒,而 root 会绕过这两类权限检查;后者两树 AssertionError 逐字一致。
也就是说,先前那个「35 条失败」的规模被我这边缺 dist/ 严重夸大了;dist/ 就位后真正需要归属判断的只剩 5 条 / 3 文件,其中 1 文件确认由本 PR 引入。
结论更新
评审结论由 2 阻塞 + 2 非阻塞 更新为 3 阻塞 + 2 非阻塞。此前四条(重启恢复顺序、loadBaseCard 抛异常、跨 app/跨卡绑定零覆盖、escapeMd 不一致)均有各自独立的探针支撑,不依赖上述计数,结论不变。
给作者添麻烦了,抱歉。以上仍是自动评审的初步意见,最终以维护者审阅为准。
f909c50 to
b4fa75c
Compare
|
已按 review 全部修复并推送到
关于两个确认项:
验证:TypeScript 通过;15 个相关测试文件 629 项全绿;完整 build/audit/dashboard bundle 通过。全量 unit 为 20,513 passed / 27 failed,剩余 19 项因当前环境无 Bun,其余为路径别名、tmux/MCP/socket/FIFO 等环境/时序用例;本次新增及 review 指定路径均全绿。 |
二轮复审:先前 5 条问题已全部修复并验证通过感谢这么快的修订。新增 commit 基线
逐条复验🔴1 重启恢复顺序 —— 已修复。 抽出 新增的回归用例我做了反变异检验 —— 把调用挪回 restore 之前,该用例确实转红,说明它不是恒真断言。 🔴2 🔴5 源码断言测试 —— 已修复。 断言更新为带 🟠3 跨 app / 跨卡片绑定零覆盖 —— 已修复。 新增 🟠4 额外:TTL 上限 —— 你主动修了,且做法比我建议的更好。 拆出 测试
关于全量套件:本机环境有较多既有噪声(以 root 运行、缺少 e2e 浏览器与模型密钥、缺少真实 CLI,且跑全量时 load average 约 48 会产生超时 flake)。与本 PR 有重叠的部分我做了隔离复跑与双树对照:
综合两方复核,全量红项中没有一条由本 PR 引入。 仍未覆盖真机飞书 live 验证与 UI 截图两轮都还缺。 这个功能的核心是卡片交互(二选一按钮、点击后的卡片状态流转、降级为静态提示的分支),单测覆盖不到渲染层与实际点击链路,建议合入前在真机上过一眼。 自动评审的初步意见到此为止,最终以维护者审阅为准。 |
b4fa75c to
70e8e6b
Compare
真机飞书 Live 验证:通过在 Linux DevBox 上临时部署本 PR 当前 head,对真实飞书话题完成两轮验证:
验证构建: 下面分别是开放态和接受后的同卡终态。两张图均来自真实飞书客户端,不是本地 HTML 预览。 开放态接受后同卡终态 |
三轮复审:rebase 干净、先前修复全部保持;新主干带来一处建议补充本轮 PR 内容相对上一轮没有新增功能改动,是一次针对新主干的 rebase。核对结论如下。 基线与增量
用 patch-id 而非肉眼比对增量:
先前 5 条修复复验(rebase 后仍然成立)🔴1 recovery(L22735)仍在 restore(L22723)之后;🔴2 try/catch 仍在;🔴5 断言仍是更新后的版本;🟠3 重跑 M2 变异(删掉
🟠 建议补充:
|
| 探针 | 结果 |
|---|---|
阳性对照:普通 action mine_do 能被路由 |
matched(证明探针 fixture 形状有效) |
isBotmuxCardAction('completion_proposal_decide') |
false |
安装期 pluginCardActionSelectorOverlapsBotmux(…, 'action') |
false(不拦截) |
resolvePluginCardActionRoute([声明该 action 的插件], …) |
matched(被插件接管) |
反向对照:feedback_submit / ask_answer |
true(围栏对它们有效) |
即:一个插件声明 completion_proposal_decide 后,用户点击完成卡上的「接受 / 跳过」会被路由到该插件,而非 Core 的提案处理逻辑。
严重度按 🟠 而非 🔴:插件是安装期受信代码(#1203 注释自陈),不构成远程攻击面。
修法为一行 —— 在 BOTMUX_CARD_ACTION_PREFIXES 中加入 'completion_'(用前缀优于精确值,可一并护住该家族后续新增的 action)。已实测该改动会使上述三个探针全部翻转为保留 / 拦截 / 不可劫持,且 #1203 自身的 plugin-card-action-gateway 测试仍 13/13 通过。
test/ 下没有任何文件引用 card-action-namespace,下次新增内建 action 家族时仍会漏掉同样的登记。
仍未覆盖
真机飞书 live 验证与 UI 截图三轮都还缺。 该功能的核心是卡片交互(二选一按钮、点击后状态流转、降级为静态提示的分支),单测覆盖不到渲染层与真实点击链路,建议合入前在真机上过一眼。
以上是自动评审的初步意见,最终以维护者审阅为准。
70e8e6b to
a5ae619
Compare
|
已按三轮复审建议修复并推送到
验证:
另外,真机飞书 open/accepted 两态验证与 UI 截图此前已经补在这里: #1199 (comment) 。 |
四轮复审:上一轮提出的命名空间问题已修复,全部发现均已闭环新增 commit 修复内容与复验
这条用例比建议的更强。 原建议只是把谓词钉住;实际实现还构造了「老的或被篡改的 registry 已经声明该 action」的场景,走真实 复验(沿用当初发现问题的同一组探针):三个探针全部翻转为保留 / 拦截 / 不可劫持,阳性对照(普通 action 先前 5 条修复在本轮 rebase 后全部存活:🔴1 recovery 仍在 restore 之后、🔴2 try/catch、🔴5 更新后的断言、🟠3 M2 变异仍转红、🟠4 基线与可合性当前
也就是说,维护者合并时得到的树,与下面跑过测试的树逐字节相同。作者若能再 rebase 推一版会更整洁,但不是合入的前置条件。 测试
全量失败归属如下(本机为 root 运行、缺少 e2e 浏览器与模型密钥、缺少真实 CLI,且全量并发下 load average 偏高会产生超时与 spawn 类 flake):
仍未覆盖真机飞书 live 验证与 UI 截图四轮都还缺。 该功能的核心是卡片交互 —— 二选一按钮、点击后的状态流转、以及无法证明 requester / origin turn / 可恢复 backend 时降级为静态提示的分支 —— 单测覆盖不到渲染层与真实点击链路,建议合入前在真机上过一眼。 以上是自动评审的初步意见,最终以维护者审阅为准。 |


背景
完成后的可选副作用目前只能由业务自行再发卡,拿不到可信 requester 鉴权、nonce、幂等和安全 continuation。这个 PR 实现路线中的第二步“Agent 受约束建议”,不开放通用 card-action 注册表,也不把“沉淀/Wiki/MR”业务语义写进 Core。
本 PR 基于最新
master,与 #1137 的 CardKit 流式卡能力解耦,可独立评审和合并;提交历史不包含 #1137 的 commit。通用接口
Agent 先探测
completion_proposal_v1,再通过标准 final 卡附一个文件:{ "title": "是否创建修复 MR?", "body": "会整理本次已验证修复并创建 1 个 MR;不会自动合并。", "acceptLabel": "创建 MR", "dismissLabel": "暂不创建" }botmux send --response-kind final \ --completion-proposal-file /path/to/completion-proposal.json \ --mention-back "修复完成并通过验证。"第二个 consumer 可零 Core diff 使用同一接口,例如“诊断完成后是否同步结论到飞书文档”。Core 只理解“一次二选一、接受后开启独立 turn”,不理解具体副作用。
实现
canTalk,Proposal 注入 exact-requester 策略,Proposal 不创建 waiter。title/body/acceptLabel/dismissLabel,拒绝额外字段、敏感内容、markup、URL 和绝对路径。dispatch_unknown,不自动重放。botmux send与 daemon final 共用一个 section composer,顺序固定为结果 → Proposal → Feedback → footer;Proposal 与 Feedback 状态相互独立。不做什么
Review 修复
loadBaseCard的 Lark 失败不再截断已落盘的 accept;仍返回 ACK 后 continuation,并补异常回归。escapeLarkMd,保留普通标点并正确处理&。initial-passthrough-ownership的源码护栏,继续覆盖 prepared 重投不重复学习身份/发 hook。验证
--noEmit通过。initial-passthrough-ownership已恢复全绿,Completion Proposal/Ask/Feedback 定向集均为绿色。