cmux NEXT: Share Chat with Guest as one op (palette, tab menu, CLI, MCP)
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'sagent-session-share-create.- CLI:
cmux share chat [--surface N] [--ttl 24h] [--uses 1] [--json]prints the guest URL alone (so| pbcopyworks);--jsongives{share_id, url, expires_at, uses_left, session, surface}.cmux share list,cmux share revoke ID,cmux share kick ID GUESTare 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 servetool (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, CLIshare 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;--ttlparsing (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'sdeny_unknown_fieldsneeds) and decoding of create and list answers.share_cliRust unit tests: positional ids, other words untouched (cmux host sharestays the tailnet share),share chatprints 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](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.
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 4xxshare.room_refused; no answershare.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.