acpmux: a person's Stop of a chat on a peer holds the other agents' messages there

TE
7d ago
+415-29

Leo's asks:

i literally can't stop you i clicked the stop button and it didnt do anything so this is still busted didnt we say we were going to fix this in acpmux?

this is so awkward the stop button doesnt really work and resume doesnt really appear!?

What was wrong

The person's Stop on a local chat holds the other agents' messages queued behind the stopped turn (hub/cancel.rs person_stop, merged in #27685 and before it). A chat that runs on an ssh peer didn't get that. The daemon forwarded the Stop as a bare session/cancel, and the peer daemon's link is never the person on the peer (hub/person.rs), so the peer ran a plain cancel. The stopped turn ended and the next agent message started a turn right away. To the person, Stop looked like it did nothing.

The change

  • This daemon forwards the person's Stop with _meta.acpmux.personStop: true. It covers both the notification and the request forms (requests.rs, peer_forward.rs).
  • Only a peer that lists the new personStop feature in its initialize gets the field (peer.rs). This is the same capability check the other peer features use. An older peer gets the plain cancel it always took: no error, and its behavior is the same as before.
  • A peer accepts personStop only from a peer daemon's own link: PeerToken auth, Control::Peer, not forwarded for its Web client (via: web) or for a paired machine (via: device). It then runs the same person_stop it runs for its own person, so the hold and the escalation (#27685) behave the same as on a local chat (server/requests/person_stop.rs).
  • Any personStop a client writes into its own cancel is dropped before the forward. Only this daemon's word that its person pressed Stop goes through.

The field can only hold agent messages back, which narrows what runs. The cancel itself is something the link could already send.

peer.rs is over its god-file budget on base. The handshake's feature reads now share one set helper, so the file doesn't grow.

Tests

cmux-tui/crates/acpmux/tests/peer_person_stop.rs:

  • Two real daemons. B runs an until-cancel turn with an agent message queued behind it. A's person presses Stop on b/lane, as a notification and as a request. B logs queue_paused with reason: "stop" holding the agent message, and that message is still queued and starts no turn after the stopped one ends.
  • A fake peer records what A forwards. A peer that lists personStop gets the field; one that doesn't gets a plain cancel. A non-person client's Stop never carries it, even when the client writes the field itself.

Command: cargo test -p acpmux --test peer_person_stop (cmux-ci, Linux 32 vCPU).

Observed / Not run

  • Observed: the two cmux-ci runs above.
  • Not run: a live Mac app against a real ssh peer. This is daemon-only; the pane already sends the same session/cancel.

Review (gpt-6.1-sol, high): SHIP at 24c837b119ee0150d9296b230fa1ed0817d2b625. No findings; it noted the fixed 500 ms check in the e2e test as a minor robustness point.

The owner Stop of a chat started on the peer itself (remote_guard.rs check_cancel_ownership refuses it today) is a separate change on its own PR.

risk: normal. Daemon-only, and the field can only narrow what runs.

Changelog

  • Fixed: Stop on a chat that runs on another machine now holds the other agents' queued messages, as it does for a chat on this Mac.

Agent session: agent session

🤖 Generated with Claude Code