cmux daemon: link shares for a guest (create, list, revoke, kick, redeem)

Merged
TE
9d ago
+747-26

Leo (2026-10-28), on T3 Code's "Share with guest": "we want to be having, be able to do this kind of flow for CMux multiplayer ... we can work on the agent chat multiplayer."

The flow: the owner creates a link that works once and expires in 24 h. A guest opens it in a browser, types a name and watches the chat live. Every message the guest sends waits for the owner's approval. Inventory and plan: manaflow-ai/cmuxterm-hq#4766. Design: chat-multiplayer.md section 4.2.

This is part 1 of 2: the daemon's share registry and its verbs. Part 2 is the guest binding: agent-session-proxy-bind {link_guest}, the guest's allowed verbs, and the one-shot expiry timer that closes guest connections. It is split out to keep each PR under 800 lines.

What it does

  • Registry. The daemon keeps every share in agent-session-shares.json, next to the session's registry. The file is owner-only and rewritten atomically, using the same writer and trusted reader as the agent-transfer records.
    • A share is pinned to the chat's acpmux session id, not to the surface number.
    • It records uses_left, state (provisioning|active|revoked|expired|closed), its guests (active|kicked|left, and each guest's attempt_id_sha256), and room, provision_key and pending_room_ops for the room bridge.
    • It stores only the secret's SHA-256.
    • At most 16 records are kept, which bounds the file.
    • A restart reads the file back, so kicks, revokes and spent uses still hold.
  • Verbs. They answer only the owner's own same-user unix connection. A remote relay, a WebSocket or any bound proxy link gets share.not_trusted (a teammate's link gets agent_session.not_trusted from the existing scope check).
    • agent-session-share-create {surface, ttl_secs? (default 86400, 60 to 7 days), uses? (default 1, 1 to 8)} returns {share_id, secret, url: null, expires_at, session, surface, uses_left}. share_id and secret are 128-bit base64url. The secret is returned only in this reply.
    • agent-session-share-list {surface?} lists the shares with no secret or hash. Each has its guests, state and expires_in_secs.
    • agent-session-share-revoke {share_id} and agent-session-share-kick {share_id, guest_id} persist revoked and kicked.
    • agent-session-share-redeem {share_id, secret, name, attempt_id}:
      • Checks run in this order: the share exists, the secret hash matches (constant time), the share is live and not expired, a use is left. The name must be one line of 1 to 64 characters, using the same rule as acpmux's author.rs.
      • On success it spends one use and creates a 64-bit guest_id. This happens atomically under the registry lock, and the change is saved before the reply.
      • Repeating a redeem with a recorded attempt_id returns the same guest and spends no use.
  • Expiry is checked on every use: a share past expires_at reads as expired.
  • Refactor: the teammate verb allowlist moved from agent_session_attach.rs into agent_session_proxy.rs (scope). scope also routes agent-session-of and the share verbs, so the god file shrinks (1325 to 1309 lines).

Error codes: share.unknown, share.secret, share.revoked, share.expired, share.provisioning, share.closed, share.used, share.kicked, share.name, share.limit, share.not_a_chat, share.not_trusted, share.unsaved (the write failed and the change was rolled back), share.bad_request.

Risk

risk:high: socket policy. These are new owner-only daemon verbs. They grant nothing yet, because no connection can act as a guest until part 2. A teammate's link keeps exactly its six verbs; the allowlist only moved. Not done here: a CLI (cmux share chat|list|revoke|kick; design PR 6). Until then, run cmux raw command --request-json '{"cmd":"agent-session-share-create","surface":N}'.

Tests

cargo test -p cmux-tui-core agent_session_shares (agent_session_shares_tests.rs):

  • create, list, kick, revoke, and refusals for a non-chat surface and bad params;
  • two concurrent redeems of a one-use link: exactly one wins and the other gets share.used; a wrong secret spends nothing; a retried attempt_id gets the same guest after the link is spent; names are validated;
  • expiry on use, with an injected clock;
  • shares, uses, kicks and revokes survive a reload from disk, and the file is mode 0600;
  • remote-relay, WebSocket and teammate-bound connections cannot create or redeem.

Results from cmux-ci follow in a comment.

Changelog

none

Agent session: agent session

šŸ¤– 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

Adds the daemon-side share registry for agent-session-share-* verbs, letting an owner create once-only, expiring links for an agent chat. Guests can redeem share links and receive a guest_id; the guest binding itself ships in a follow-up.

  • Shares persist in agent-session-shares.json (owner-only, atomic rewrite, directory synced before the reply) and survive restarts, including used-up limits, kicks, and revokes.
  • Verbs only work on the owner's own same-user unix connection; remote relays, WebSockets, and teammate-bound links are refused.
  • Redeem spends one use atomically and returns the same guest_id on a retried attempt_id; a kicked or left guest's retry stays refused. Expiry is checked on every use.
  • Max 16 shares, TTL 60s–7 days, uses 1–8; only the SHA-256 of the secret is stored.
  • Refactors the teammate verb allowlist into agent_session_proxy.rs (scope), shrinking agent_session_attach.rs.