cmux NEXT: Share Chat with Guest as one op (palette, tab menu, CLI, MCP)

TE
8d ago
+4828-11

HOLD: stacked on #26825 (the daemon's owner_token param on agent-session-share-create); lands after it. Only the top commit is this PR's.

Size: the size is mostly generated localization (3,150 lines). This branch also includes #26825 until that prerequisite lands; the share-operation logic is kept under about 800 lines.

Summary

Leo (2026-10-28), on Theo's T3 Code "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." Plan and inventory: manaflow-ai/cmuxterm-hq#4766; spec: leo-workspace design/chat-multiplayer.md sections 1 and 6.

This is the owner's entry point for the link share, on every surface, as one op:

  • agent.share (Share Chat with Guest… in the palette; Share with Guest… in an agent chat tab's right-click menu, under More; no default shortcut). It names a started agent chat by --target tab:<id>, --surface N, the chat whose agent ran the CLI, else the focused chat, and calls the daemon's agent-session-share-create.
  • CLI: cmux share chat [--surface N] [--ttl 24h] [--uses 1] [--json] prints the guest URL alone (so | pbcopy works); --json gives {share_id, url, expires_at, uses_left, session, surface}. cmux share list, cmux share revoke ID, cmux share kick ID GUEST are the family (agent.share.list|revoke|kick): CLI and MCP only; the share sheet (next PR) offers them in place.
  • MCP: each of the four is a cmux mcp serve tool (MCP follows the CLI decision).
  • A person's palette or menu run copies the link and shows a toast ("Guest link copied. It works once and expires in 24 hours."); the share sheet replaces that in the follow-up PR.
  • Errors keep the daemon's code (share.needs_sign_in, share.room_unreachable, share.room_refused, share.not_a_chat, share.limit, …) with a sentence a person can act on.

Owner token. The app mints a fresh cmux install token for each create with MacInstallIdentity.installToken() (CloudService.installIdentity, the ES256 install principal from /v1/auth/token, the same environment as the rest of cmux Cloud) and hands it to the daemon in that one request; nothing stores it. Signed out, the request goes without one and a room-bridged daemon answers share.needs_sign_in. The CLI never sees the token: cmux share chat runs the app's action, which adds it.

Layout. The logic lives in CmuxNextDaemonService (AgentShareActions, behind an AgentShareHost protocol); the App adds only AppServices+AgentShareHost.swift (one-line forwards) and one bind line. Typed requests are CmuxNextDaemon/Requests/Agents/AgentSessionShares.swift. The CLI's positional share revoke ID / share kick ID GUEST map to the actions' --share-id / --guest-id in cli/share_cli.rs.

The generated action exports (action-surfaces.json, actions.md) come from the PR autofix's Mac regeneration.

Testing

  • CmuxNextAppTests/AgentShareTests: every surface is declared on the one op (palette, CLI share chat, MCP, the tab menu, no shortcut; the family is CLI and MCP only); a started chat's tab menu offers it and a new chat's or a terminal's does not; --surface / tab target resolution and refusals; --ttl parsing (1 min to 7 d); daemon refusals keep their code; the answer carries the URL.
  • CmuxNextDaemonTests/AgentSessionShareRequestTests: wire shape of create/list/revoke/kick (snake_case, absent options left out, which the daemon's deny_unknown_fields needs) and decoding of create and list answers.
  • share_cli Rust unit tests: positional ids, other words untouched (cmux host share stays the tailnet share), share chat prints only the URL.
  • Ran: prepush-gate.py (no new violations), localization_catalog.py check (no new errors; all 21 locales filled). CI results to follow on this head.

Observed / Not run

Not run yet: the Swift suites and the Rust tests on CI/fleet at this head (results posted as a comment), and a live create against a room (needs #26825 and the room PR).

Localization audit: 10 new action/argument titles (ActionCatalog.xcstrings) and 14 refusal/notice strings (new CmuxNextDaemonService AgentShare.xcstrings), each with all 21 locales; no bare English in the changed Swift.

Risk: normal. New op and verbs only; the token goes only to the owner's own local daemon over its same-user unix socket (the daemon refuses share verbs on any other connection).

Changelog

Added: Share Chat with Guest in the command palette, an agent chat tab's menu, cmux share chat|list|revoke|kick and MCP

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 Share Chat with Guest as one operation on every surface: the command palette, an agent chat tab's right-click menu, cmux share chat (--surface N, --ttl, --uses, --json), and the matching MCP tool. One agent.share op names a started agent chat and returns a guest link that works once and expires in 24 hours by default; a palette or menu run copies it and shows a toast.

The app mints a fresh cmux install token per create and hands it to the local daemon in that one request; nothing stores it, and the daemon answers share verbs only on the owner's own unix socket.

Daemon

  • Create provisions the share in the hosted room before answering; a failed PUT retries with backoff (1 s doubling to 60 s, 10 minutes) then revokes the share.
  • 401 answers share.needs_sign_in; other 4xx share.room_refused; no answer share.room_unreachable.
  • Revoke and expiry queue the room's DELETE until it is acknowledged (2xx or 404/410); expiry also ends provisioning shares.
  • The room is CMUX_SHARE_ROOM_URL, else the build's (dev, staging, prod); standalone daemons have none and answer null URLs as before.