cmux-tui: agent message unsend and pump edge cases (agent messaging 4b)

Merged
TE
23d ago
+252-32

Follow-up to #20744 (agent messaging 4/6, bead cx-55d27) for five review findings in agent.message.unsend and the daemon's delivery pump.

  • A stale revision no longer drops acpmux's answer. Unsend checks expected_revision once, under the delivery lock, before it asks acpmux to withdraw anything. It used to check again at commit time. Any unrelated state commit during a withdraw (each can take up to 10 s) then failed the unsend after acpmux had already withdrawn the prompt. The commit now skips the second check. The replay and key checks still run atomically inside it.
  • A prompt that may still reach acpmux isn't called unsent. Take a queued delivery that already had a hand-over (attempts > 0). If acpmux answers withdraw.not_found, the prompt can still be in flight inside acpmux and run later. That recipient now gets a retryable failed (error.reason acpmux_unavailable), and the delivery stays queued. If the pump can't record an attempt or a delivery for a hand-over that went out, it remembers that delivery in memory, and unsend treats it as tried.
  • Later messages don't overtake a waiting one. For the rest of a pass, the pump skips a recipient whose delivery is waiting (never accepted, Unsettled), the same way it skips an unreachable one. A stuck session now costs one accept timeout per pass instead of one per queued message.
  • acpmux reasons fit the catalog. The red run showed the catalog counts max_length in UTF-8 bytes: a long refusal reason made the daemon reject its own unsend result. error.acpmux_reason is now cut to 128 bytes and error.message to 1024 bytes, on a character boundary. An empty reason becomes withdraw.refused.
  • cmux agent message unsend exits 1 on failed. It still prints the result first. unsent, retracted and too_late exit 0. spec/cli.md now says so.

No spec JSON changes, so the catalog sha is unchanged.

Verification

The fleet runs scripts/ci/cmux-tui-rust-check.sh test agent_message_pump (macOS) at each commit:

  • Red, test commit 9b7458509626 (step d541ed86c81888af86229642): the five new tests failed, each for the reason it targets, and the 14 existing tests passed. The failures were a revision.conflict after acpmux withdrew; unsent for an in-flight queued prompt; prompt order [first, second, first]; the result rejected for an acpmux_reason over the byte limit; and exit 0 for a failed CLI unsend.
  • Green, head 87bda38ae305 (step 9c7155e648377451a48354ab): 19 passed, 0 failed. Intermediate fix commit bda3564cdd32 (step bbb4e2f312d52a3021c9b95a) passed 18 and failed only the long-reason test. That run showed the byte limit, fixed in 87bda38.
  • Clippy (-D warnings), head 87bda38ae305 (step 9efbaf6b66691ba273ecdd4c): green.

Not covered by a test: the attempt/delivery-record failure path. A failed write is hard to reproduce at the daemon boundary.

Changelog

  • Fixed: Taking back an agent message no longer fails after acpmux already withdrew it, no longer reports a prompt that may still run as unsent, and cmux agent message unsend exits 1 when it fails.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes five review findings in agent message unsend and the daemon's delivery pump so outcomes match what acpmux may still do and what the catalog allows.

  • A stale revision no longer loses an unsend: the revision is checked once, before acpmux is asked to withdraw, and the later commit no longer re-checks it.
  • A queued prompt that may still reach acpmux is reported as retryable failed, not unsent. Hand-overs that went out but could not be noted count as tried.
  • The pump skips a waiting recipient for the rest of a pass, so later messages never overtake it and a stuck session costs one accept timeout per pass.
  • acpmux refusal reasons are cut to 128 UTF-8 bytes and error messages to 1024, on character boundaries; an empty reason becomes withdraw.refused.
  • cmux agent message unsend exits 1 when the outcome is failed, after printing the result; unsent, retracted, and too_late exit 0.