Conversation
终态提示(`xpi.timeout.unclassified`)当前**零标识**:只说「没有做出选择,消息未被执行」, 不说是哪一条消息。后果不是不方便,是**受损方判不出自己是不是受损方**—— 在忙会话期间发过多条消息的提议方,无法知道被丢的是哪一条,也就无法精确补发; 机器人提议方更是只能全部重发或全部不发。 实测复现(同一群、`botmux history` 时刻换算为 UTC): - `00:50:58` 提议方 A 发出一条 - `00:51:01` 提议方 B(另一个 bot)发出一条 - `00:51:02` 提示到达,@ 的是 A 一秒之内两个不同的提议方各发过消息,而这张提示既不说消息、也不说提议方 ⇒ 「这张说的是 A 那条还是 B 那条」在收件人视图里**逐字同形**。 改法: - 新增纯函数 `crossPrincipalDroppedMessageDigest(record)`,从记录里取出 `turnId` 与首条正文的摘录(折叠空白、超长截断)。记录不含消息时返回 `undefined`,调用方原样返回原文,不改变任何既有措辞。 - `notifyCrossPrincipalTerminal` 的**两处** `xpi.timeout.unclassified` 调用点 追加这一行。⚠️ 其余 11 处终态提示调用点**未改**——其中一处 (「独立会话已创建;因共用工作目录,将在当前任务结束后串行执行。」)是 **成功**提示,把这段话无差别塞进 helper 会让成功提示也带上「未送达」字样。 - 摘录取自提议方自己刚刚在同一个会话里发过的正文,回显它不泄露任何新信息。 未做(明确声明,不是遗漏): - 不含条数。`CrossPrincipalInterruption.messages` 在全仓**只有一个写入点** (`cross-principal-interruption-store.ts` 建记录那一行,`grep -rn 'messages.push' src/` 的 6 处命中无一属于它),记录 id 又是 `sha256(sessionId, message.turnId)` ⇒ 每条消息各自成记录,条数**结构上恒为 1**。印一个恒为 1 的数会读起来像信息。 - 不动隔离守卫本身,也不改重试次数——那是另一个决定,不该搭在一次措辞修复里。 测试:`test/cross-principal-interruption-store.test.ts` 新增 4 例,含两组阴性对照 (恰好等于截断长度时**不**加省略号;记录无消息时返回 `undefined`)。 实测读数: - `bun run build` 通过。 - `bun x vitest run --project unit test/cross-principal-interruption-store.test.ts` → 8 passed。 - 同批相关文件 `cross-principal-choice` / `cross-principal-interruption-gate` / `xpi-shared-cwd-admission` 全绿(52 passed)。 - `test/daemon-ordinary-ingress-failure-notice.test.ts` 在本机报 `TypeError: undefined is not an object (evaluating 'z.string')`,**改动前的基线 同一读数**(`git stash` 后重跑逐字相同)⇒ 与本次改动无关,未处理。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
感谢补充消息身份,方向和切分(纯函数 + 在调用点包裹、不进 helper)都很干净。本地基于最新 master 复验编译与测试均通过,新增用例变异测试有效。有一处同族遗漏建议在本 PR 内一并处理,另附几个小建议。 建议修改: 与本 PR 修改的两处
两处各包一层本 PR 已有的 其余终态提示语义不同,可另开 follow-up、不阻塞本 PR:共享目录队列满的 backpressure 提示(「建议已确认,但队列已满,请稍后重新发起」)、owner 明确拒绝的提示。 小建议
以上为自动评审的初步意见,最终以维护者审阅为准。 |
…s; turn-only fallback for empty excerpt - wrap both xpi.timeout.still_busy call sites in withDroppedMessageIdentity - use xpi.terminal.dropped_turn_only when the digest excerpt is empty/whitespace - simplify staged() test helper; add whitespace-only arm Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks — all addressed in f0f7ce6:
Backpressure / explicit-reject notices left for a follow-up as suggested. Note: I did not run a full-repo 🤖 Generated with Claude Code |
|
新 commit 已复审:上一轮意见均已落实,谢谢快速响应。
本地基于最新 master rebase 零冲突,类型检查与 XPI 相关测试全部通过。 一个不阻塞的小建议:PR 描述「测试 / 实测读数」一节还是首版的数字(新增 4 例 / 8 passed / 52 passed),当前实际为新 describe 5 例(文件内 9 例)、四个 XPI 文件合计 53 passed,合并前顺手刷新即可。 以上为自动评审的初步意见,最终以维护者审阅为准。 |
问题
跨主体终态提示(
xpi.timeout.unclassified)零标识:只说「没有做出选择,消息未被执行」,不说是哪一条消息。后果不是不方便,是受损方判不出自己是不是受损方。
实测复现(同一群,时刻取自
botmux history,换算为 UTC)一秒之内两个不同的提议方各发过消息,而这张提示既不说是哪一条消息,也不说提议方。
⇒ 「这张说的是 A 那条还是 B 那条」在收件人的视图里逐字同形,A 无法判定自己那条到底有没有送达,也就无法精确补发。
对人类提议方这是困扰;对机器人提议方这是无法自动恢复——只能全部重发(重复投递)或全部不发(静默丢失),没有第三种。
改法
crossPrincipalDroppedMessageDigest(record):从记录里取出turnId与首条正文的摘录(折叠空白、超长截断为 60 字符 +…)。记录不含消息时返回undefined,调用方原样返回原文。notifyCrossPrincipalTerminal的两处xpi.timeout.unclassified调用点追加这一行。新增的一行(中/英):
刻意没做的(声明,不是遗漏)
notifyCrossPrincipalTerminal(调用点未改(grep -n共 13 行=1 处定义+12 个调用点;接 helper 的 4 处=xpi.timeout.unclassified×2、xpi.timeout.still_busy×2。初版此处写「11」是把定义行算成了调用点,已订正)。 其中一处(独立会话已创建;因共用工作目录,将在当前任务结束后串行执行。)是成功提示——把这段话无差别塞进 helper 会让成功提示也带上「未送达」字样。所以这一行加在调用点上,不加在 helper 里。CrossPrincipalInterruption.messages在全仓只有一个写入点(建记录那一行;grep -rn 'messages.push' src/的 6 处命中无一属于它),记录 id 又是sha256(sessionId, message.turnId)⇒ 每条消息各自成记录,条数结构上恒为 1。印一个恒为 1 的数会读起来像信息,所以去掉了。测试
test/cross-principal-interruption-store.test.ts新增一个 describe(dropped cross-principal message digest),5 例;该文件合计 9 例(基线ba847cae5d19上这个文件只有cross-principal interruption durable identity一个 describe、4 例)。五例逐条:names the message by the turn the proposer already knowscollapses newlines so the excerpt stays on one line of the noticetruncates only past the limit, so short messages are quoted whole—— 正好等于截断长度(60 字符)时不加省略号,超过 1 个字符才加yields an empty excerpt for a whitespace-only body, so the caller can drop the quote(阴性对照,f0f7ce6e补)returns undefined when the record carries no message, so the notice is left unchanged(阴性对照,提示原文不变)计数口径:
grep -cE '^[[:space:]]+it\('按 describe 分组,5 + 4 = 9;同一口径在下面那三个未改动的文件上给 12 + 17 + 15 = 44,与 vitest 报的 44 相等 ⇒ 这把尺子在同一个对象上跟运行器对过,不是只对自己自洽。实测读数
环境:macOS arm64 / bun 1.4.0 / vitest 4.1.11 / node v26.3.0。被测提交
f0f7ce6e2a91,=本 PR 当前 head(读数与出门的那份是同一个对象,不是本地另一棵树)。读数时刻 2026-09-20T13:28:14Z ~ 13:30:07Z(北京 21:28 ~ 21:30)。CI(GitHub Actions,head
f0f7ce6e2a91)9 项全绿:build、test、test (1/3)、test (2/3)、test (3/3)、bun-test、bun-binary、bun-binary-musl、bun-binary-darwin。这一节首版的数字为什么不只是「旧」
首版这一节写的是
3 passed (3)/52 passed (52),命令行里列的是不含cross-principal-interruption-store.test.ts的三个文件。今天量出来:那三个文件在基线和 head 上都是 12 + 17 + 15 = 44 例,且两处逐字未变。44 ≠ 52,而 44 + 8 = 52,8 正是636b3638(本 PR 首个提交)那时 store 文件的例数 ⇒ 那个 52 是一次四文件的读数,被写在了一条三文件的命令下面。所以不是「数字过时」,是印出来的命令产生不出它旁边那个数;现在四个文件是同一条命令里的四个参数,53 = 44 + 9。test/daemon-ordinary-ingress-failure-notice.test.ts在本机报TypeError: undefined is not an object (evaluating 'z.string')。这是改动前的基线同一读数——
git stash后在未改动的树上重跑,报错逐字相同 ⇒ 与本 PR 无关,未处理。相关:#1449(同一条投递路径上的另一半,日志侧)。
更新(f0f7ce6e,回应 review)
xpi.timeout.still_busy也接上withDroppedMessageIdentity。xpi.terminal.dropped_turn_only(未送达的消息:turn {turnId}/Not delivered: turn {turnId}),不再印出空的「开头「」」。staged()测试辅助简化为单条消息;新增全空白摘录一臂。本地bun test test/cross-principal-interruption-store.test.ts9/9,test/dashboard-i18n*.test.ts全过。tsc --noEmit未在本地跑」,这一格现在补上了:package.json的build脚本里串着tsc与bun run typecheck:scripts,上面的build_rc=0就包含这两步(不是--noEmit,是会 emit 的那次全量类型检查);typecheck:test-mocks不在build链里,未单独跑。CI 的build项同为绿。🤖 Generated with Claude Code