fix: 可靠信箱:Codex→Claude 确认制 at-least-once 投递(修复 #223)/ reliable mailbox: acknowledged at-least-once Codex→Claude delivery (fixes #223)#239
Conversation
…)/ reliable mailbox: acknowledged at-least-once Codex→Claude delivery (fixes raysonmeng#223) 核心改动 / Core changes: - 入箱先于 Channel 推送;推送只是延迟优化 / queue synchronously before Channel push; push is a latency optimization - 不可变投递代 ID(与源 ID 分离);get_messages 非破坏性、稳定有序 / immutable delivery IDs separate from source IDs; non-destructive stably-ordered get_messages - 新增 ack_messages;get_messages 支持可选 ack_ids;ack_ids 上限跟随信箱容量 / new ack_messages tool; optional ack_ids on get_messages; ack cap follows mailbox capacity - 未确认推送 FIFO 有界重试(默认 3 次,60s 起指数退避),ACK/逐出/耗尽取消定时器 / bounded FIFO retries (3 attempts, exponential from 60s), timers cancelled on ACK/eviction/exhaustion - 源 ID 冲突指纹去重 + 碰撞别名;迟到 ACK 不误删复用 ID 的新消息 / source-ID fingerprint dedupe + collision aliases; late ACKs cannot delete newer reuses - 边界:100 条 / 4 MiB UTF-8 / 2048 去重项 / 20 分钟 TTL,溢出与超大有可观察警告;超大消息 best-effort 推送并明确不可恢复(ack_required:false)/ bounds with observable warnings; oversized messages pushed best-effort with an honest not-retained preamble - 系统与预算恢复消息共用信箱;ack_resume 收敛全部兄弟投递 / system + budget-resume messages share the mailbox; ack_resume retires all siblings - 指令面全部迁移到新契约(MCP instructions、collaboration-content 注入块、CLAUDE.md/AGENTS.md、plugin README、marketplace、kickoff、双语 README)/ all instruction surfaces migrated to the new contract - 新增 reliable-mailbox 单测套件与 E2E 测试计划;bundles 按 ADR 重建提交 / new unit suite + E2E test plan; bundles rebuilt per ADR Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
|
关于红色的 CLA check / About the red CLA check 中文:CLA 已签署(见上方 bot 的 "All contributors have signed the CLA ✍️ ✅" 确认)。该 check 仍显示失败,是 workflow 的签名存储步骤在报错: English: The CLA is signed (see the bot's "All contributors have signed the CLA ✍️ ✅" confirmation above). The check still shows failed because the workflow's signature-storage step errors out: |
Summary
中文:为 #223 实现"可靠信箱":每条 Codex→Claude 消息在 Channel 推送之前同步进入 adapter 内存信箱,Channel 推送只是延迟优化而非投递证明。
get_messages变为非破坏性读取(稳定顺序、稳定 ID),新增ack_messages工具:只有 Claude 显式确认"已处理"的 ID 才会被移除;未确认的推送按 FIFO 有界重试。空闲会话不再静默丢失 Codex 回复——最坏情况下消息安全地留在信箱里,Claude 一旦活跃即可取回。English: Implements a "reliable mailbox" for #223: every Codex→Claude message enters the adapter's in-memory mailbox synchronously before its Channel push — the push is a latency optimization, not proof of receipt.
get_messagesbecomes non-destructive (stable order, stable IDs), and a newack_messagestool removes only IDs Claude explicitly confirms as fully processed; unacknowledged pushes get bounded FIFO retries. An idle session can no longer silently lose a Codex reply — worst case, the message stays safely in the mailbox until Claude is next active.Motivation
中文:#223:push-only 投递下,发往空闲 Claude 会话的 Codex 回复会静默丢失(#29 的回归,无队列兜底)。已合并前的两个尝试各有缺口:先推后入队有竞态且成功推送会重复处理;仅源侧改动未同步 bundle 且缺测试。本 PR 从 master(
a3e927f)全新实现。English: #223: with push-only delivery, a Codex reply sent to an idle Claude session was silently lost (a regression of #29, no queue fallback). Prior attempts each had gaps (queue-after-push races and double-processing of successful pushes; source-only changes with stale bundles and no tests). This PR is a fresh implementation on master (
a3e927f).Changes
get_messages非破坏性、稳定有序;支持可选ack_ids顺带确认上一轮结果 / Non-destructive, stably orderedget_messages; optionalack_idsto confirm a previous batch in the same call.ack_messages工具;Channel 内容与 meta 都携带稳定 ID 与直接 ACK 指引(ack_required/ack_tool)/ Newack_messagestool; Channel content + meta carry the stable ID and a direct-ACK instruction.ack_required:false)/ Bounds + observability: 100 messages / 4 MiB UTF-8 / 2048 dedupe entries / 20-min TTL (all env-configurable); overflow and oversized omissions emit observable warnings; an oversized message is not admitted — it is pushed best-effort with an honest "not retained, do not acknowledge" preamble (ack_required:false).ack_ids单次上限跟随信箱容量(max(100, maxBufferedMessages)),保证 drain 提示的"一次确认全部 ID"始终可执行 / The per-callack_idscap follows mailbox capacity, keeping the drain epilogue's "ack all pending IDs" instruction always executable.ack_resume(或对任一兄弟投递的ack_messages)收敛该 resume 的全部兄弟尝试并调用既有 resume 处理器 / System + budget-resume messages use the same mailbox;ack_resume(orack_messageson any sibling) retires all sibling attempts and invokes the existing resume handler.collaboration-content.ts注入块、仓库CLAUDE.md/AGENTS.md、plugin README、marketplace 描述、kickoff 消息、双语 README(zh-CN 全部中文化)/ All instruction surfaces migrated: MCP server instructions, thecollaboration-content.tsinjected blocks, repoCLAUDE.md/AGENTS.md, plugin README, marketplace description, kickoff message, both READMEs (zh-CN fully in Chinese).src/unit-test/reliable-mailbox.test.ts与docs/test-plans/issue-223-reliable-mailbox.md;plugin bundles 已按 ADR 重建提交 / New unit suite + E2E test plan; plugin bundles rebuilt and committed per the bundle ADR.语义边界(诚实声明)/ Semantics & non-goals (honest scope):
get_messages是活跃后的恢复路径)。README/plugin README 的 Limitations 均已如实写明。Testing
bun run typecheckpassesbun test srcpasses — full suite 1673 pass / 0 fail (109 focused mailbox/delivery/resume tests, 321 assertions)bun run checkexit 0 (typecheck + tests +verify:plugin-sync+ plugin version alignment)smoke:built+smoke:packpass (18 files packed, all required artifacts)AGENTBRIDGE_SAFE=1, disposable repo, Claude Code 2.1.211 / Codex CLI 0.144.5)Live reproduction (manual steps) / 手工复现步骤:
get_messagesreturns the sentinel; a secondget_messagesreturns the same stable delivery ID (non-destructive). / 空闲未唤醒(Codex→Claude replies silently lost when Claude is idle (push-only regression of #29; no queue fallback) #223 症状)。激活后第一次get_messages拿到哨兵;第二次返回同一稳定 ID(非破坏性)。ack_messageswith exactly that ID → acknowledged=1; the next poll no longer contains the sentinel; unrelated messages remain. / 精确确认该 ID 后,下一次轮询不再包含哨兵;无关消息保留。docs/test-plans/issue-223-reliable-mailbox.md: daemon restart / Codex reconnect with the same adapter → mailbox survives; adapter process restart → mailbox lost (documented, P2 not claimed). / 重启边界见测试计划:同 adapter 下 daemon 重启/Codex 重连信箱存活;adapter 进程重启信箱丢失(如实记录,不声称 P2)。Known remaining findings (recommended follow-ups)
中文:本 PR 经过多轮多智能体对抗式审查(每个发现均经独立反驳验证)。以下已确认、但刻意未在本 PR 内修复的问题,建议作为后续改动处理——它们都涉及需要 maintainer 拍板的设计取舍,不影响上述 P0/P1 保证:
English: This PR went through multiple adversarial multi-agent review rounds (every finding independently verified against refutation). The following are confirmed but deliberately not fixed here — each involves a design trade-off that deserves maintainer sign-off, and none breaks the P0/P1 guarantees above:
system_turn_started(⏳)/system_turn_completed(✅)与 daemon 断连/重连通知现在与普通消息一样享受重试与"确认前一直保留"。长会话中它们会累积占用 100 条容量,且过期的 ⏳ 可能在 ✅ 之后被重推(指令要求 Claude 见 ⏳ 不回复 → 可能卡住协作节奏)。建议:通知分级——瞬态通知不重试 + 取代语义(✅ 收敛对应 ⏳,reconnected 收敛 disconnected,或短 TTL),可执行指令保持可靠信箱待遇。/ Turn/connectivity notices now get the same retry + retain-until-ack treatment as real messages: they accumulate toward the 100-slot cap in long sessions, and a stale ⏳ can be re-pushed after its ✅ (instructions tell Claude not to reply while ⏳ is latest → possible stall). Suggestion: notice classes — ephemeral notices get no retry + supersession (✅ retires its ⏳; reconnected retires disconnected; or a short TTL), while actionable directives keep full mailbox treatment.maxBufferedBytes同时是单条上限与全信箱上限:一条恰好 4 MiB 的可入箱消息可一次逐出全部其余待确认条目。建议:推送成功过的条目在逐出时保留墓碑;单条与全箱上限解耦。/ Overflow eviction deletes the dedupe tombstone even when a push already succeeded, so a daemon at-least-once replay re-admits under a NEW delivery ID → possible double-processing. AlsomaxBufferedBytesis both the per-message and whole-mailbox cap: one exactly-4 MiB admission can evict every other pending entry. Suggestion: retain tombstones on eviction for entries with a resolved push; decouple the two caps.ack_resume落地时若 daemon 已有一条 resume 重推在途,该迟到兄弟会在本地收敛之后入箱,且没有自动收敛路径(adapter 对 resume 不武装重试、daemon 收到 ack 后停止重推)——它会一直留箱直到手动确认。建议:adapter 维护一个带 TTL 的"已确认 resumeId"记忆,迟到兄弟直接丢弃或入箱即收敛。/ If a daemon resume re-push is in flight whenack_resumelands, the late sibling is admitted after local retirement and nothing retires it automatically — it lingers until manually acked. Suggestion: a TTL'd memory of acked resumeIds in the adapter; drop or admit-then-retire late siblings.docs/test-plans/issue-223-reliable-mailbox.md的手工计划覆盖。若希望自动化,需要进程级集成测试。/ The daemon-restart/Codex-reconnect survival unit tests simulate via setter re-registration; real process boundaries are covered by the manual test plan. Process-level integration tests would be needed to automate them.后续 / Future work: P2(跨 adapter 重启的持久化信箱)建议独立改动:daemon 侧带版本的存储、0700 目录/0600 文件、原子写入 + fsync、有界压缩、崩溃安全的 ACK 记录。/ P2 (persistence across adapter restarts) as a separate change: daemon-owned versioned store, 0700 dir / 0600 files, atomic write+fsync, bounded compaction, crash-safe ACK records.
Compatibility notes
get_messages语义变化:从破坏性排空改为非破坏性 + 显式 ACK。所有随仓库分发的指令面已在本 PR 内迁移;已初始化项目需重跑abg init以刷新 CLAUDE.md/AGENTS.md 注入块。/get_messageschanges from destructive drain to non-destructive + explicit ACK. All in-repo instruction surfaces are migrated in this PR; already-initialized projects should re-runabg initto refresh their injected blocks.message_id现为稳定投递 ID,新增source_message_id、ack_required、ack_tool等);AGENTBRIDGE_MODE保持忽略 + 一次性警告。/ New Channel meta fields (message_idis now the stable delivery ID; addssource_message_id,ack_required,ack_tool);AGENTBRIDGE_MODEstays ignored with the one-time warning.Checklist
Prepared with Claude Code (multi-agent adversarial review + live verification). 按仓库惯例欢迎 Codex 交叉 review。/ Cross-review per repo convention welcome.
🤖 Generated with Claude Code