Agent pane: live presence from acpmux (who has the chat open, who types)
Leo, 2026-10-26: "we can still do the regular chat multiplayer". Earlier: "also chief chats we need multiplayer". Relayed: "we can just send some codexes to do whatever" and "for the agent chat side, we should be able to just... do stuff. unless lawrence is super actively workin on stuf rn"
Design: https://github.com/manaflow-ai/cmuxterm-hq/pull/4389, section 5. This is the first of two PRs split out of #25303 to keep each under ~800 lines. This one makes the pane read live presence; the next makes the composer report typing.
What
The agent pane shows live presence from acpmux:
-
the faces in the header show who else has the chat open;
-
the line over the composer shows who is typing.
-
multiplayer/presenceClient.ts:- It reads presence from the attach reply's
presenceand from each_acpmux/presence, for the shown chat only. - This pane's own id is left out: the attach reply's
you.idwhen the host daemon names one, else acpmux'sowner. - Typing sends
_acpmux/typing {sessionId, on}at the first keystroke and again every 5 s while it lasts. Typing turns off on send, on empty text, after 5 s idle, on a chat switch and on close. A rate-limited off is retried a bounded number of times, never after the pane leaves the chat. - A keystroke from a chat still on screen while the next one attaches says nothing to the next one.
- The draft text never crosses, and an acpmux that answers no
presencegets nothing.
- It reads presence from the attach reply's
-
multiplayer/LivePresence.tsx: the header faces and the typing line read a store of their own (shownPresence), keyed by the shown chat. A teammate typing redraws no transcript. Every attach and leave sets the store, so a chat that opens with nobody else never draws faces kept from before. -
direct.ts:- routes
_acpmux/presence; - calls
attached()after attach anddetached()on a switch and on close; - a send stops typing;
typing()sits behind thechat.typingaction.
- routes
-
App.tsx: the header and the composer slot drawLivePresenceStackandLiveTypingLine, with the snapshot's presence only as a fallback until a client sets one.
The Swift pane allowlist for _acpmux/typing and _acpmux/view is already on base (#25375).
Risk
Low. Nothing is sent or drawn until acpmux answers presence on attach. Timers:
- one idle timer: stops when typing goes off, on detach and on close;
- one bounded stop retry: up to
STOP_RETRIEStries, cancelled on typing again and on detach.
Per-event cost is O(participants). No O(rows) work.
Tests
Run on cmux-ci (Linux): the multiplayer folder and composer-switch-keeps-draft pass; tsc and vp fmt are clean. Among them:
presenceClient.test.ts: the own id; another chat's presence; a switch's stale clear; a refused off retried a bounded number of times; a refused off for the chat before is not sent again after the switch.livePresence.test.tsx: the faces draw only over the chat the presence is for, and a chat that opens with nobody else draws no faces kept from before.
Each fix in this code was checked on CI to fail without it. It was reviewed through six Codex rounds as part of #25303, and its last finding is fixed here.
Captures come with the live two-person capture on landed code.
Agent session: agent session
š¤ Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.