cmux-next: open a remote machine's agent chat as a sidebar workspace (agent-session-adopt)
Summary
Open a chat that already runs on another machine (cmux16, huge-blue; for example one cmux agent open huge-blue/NAME started) as a sidebar workspace bound to that machine's daemon.
- Daemon (cmux-tui): new
agent-session-adopt {surface, session}(agent-session-adopt-v1). It binds a session of this machine's own acpmux to an unbound agent tab of this store.- Gates: the same as
agent-session-start(a trusted local non-remote client, the host gate that refuses team VMs and unverified Cloud VMs, and a tab of this store with no session yet). - It refuses with
agent_session.unknown_sessionany id that this machine's_acpmux/sessionsdoes not list as its own: a peer's mirrored chat, the Home Chief's, or an unknown id. - It answers
{surface, session, conversation, family, mode, asking}.
- Gates: the same as
- Policy (b), per the coordinator: a chat in a mode that does not ask (bypass) is adopted, but only on enrolled or trusted hosts (the start's host gate), and the workspace shows a visible "bypass" badge.
- App (Swift): follows in this PR.
Risk: high. This adds a new socket verb that binds sessions and lets a non-asking session open on a trusted host.
Takes back #23163: nothing is taken back. This PR moves AgentTabRemoteClient (with #23163's children()) unchanged from CmuxNextApp/AgentTabRemoteHost.swift to CmuxNextAgentPane/AgentTabRemoteClient.swift, so the agent pane can use it.
Changelog
Added: open an agent chat running on another machine as a sidebar workspace.
Observed / Not run
No capture: the only new visual is the "Bypass" warning on the row of a chat adopted from another machine's acpmux. A capture mini has no second machine with a running non-asking chat to adopt, so no tour reaches it. AgentBypassBadgeTests covers the row content.
Head 211a40356ce919dea9a7771a2dc2498b1a3bdff8: two fixes, test first.
- Adopt refuses without the agent page.
cmux agent open S --host <machine>took the adopt path, which skipped thecanHostChatcheck that the local open makes. In a build with no agent page it made a workspace on that machine and bound the session into a tab that could not show it. Adopt and open now share one check (requireChatPage), which refuses before any workspace is made. A Debug-onlyCMUX_NEXT_AGENT_PANE_NONE=1makes the agent tabs resolve no page, so a test can run as such a build. - The unknown-session refusal names the session id. It said "Check the session name with acpmux ls", but adopt matches the exact session id only. It now says session id in English and in all 20 translations of the catalog (21 locales), each marked translated.
Command: cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg=Packages/macOS/CmuxNext --arg=AgentSessionAdoptNoPageTests,AgentBypassBadgeTests --env CMUX_CI_XCODE_APP=/Applications/Xcode_26.6.app
- Red on the test head
5d87ec570ee584bf138073cc77723011cfce9ad7, job22e656453201c8696521bccd:anotherMachinesChatIsRefusedWithoutTheAgentPageAndMakesNoWorkspacefailed with no refusal, a started work item ("The agent service on Move VM did not start.") and a workspace made on the other machine.anAdoptsOwnRefusalsSayWhatHappenedfailed because the text said "session name". (An earlier run atb5122f739d9a, job3da86b8af641d14e631e6cdf, showed that the fleet's test build carries the bundled page, which is why the seam was added.) - Green on
211a40356ce9, jobb7ffea143d44bd85549928fe: both suites passed, 7 of 7 tests, none skipped. cmux-ci compile-check --appon211a40356ce9, jobfaa4a6d49cb59a9e93882737: ok.cmux-tui spec inventory / clippy (changed cmux-tui packages)(run 39630147344) is red only on two "redundant clone" errors atcmux-tui-core/src/server/rows_tests.rs:373and:416. This PR does not touch that file. It is a base red, fixed on base by #23557.
Head 169626f5634d10b51b859852f46fcb31b85da827: one merge of feat-cmux-next e323f77325da, then a test-only fix.
Merge conflicts, with both sides kept:
AgentSessionWorkspace: the remote adopt path plus #22809'sgroupargument, which now also goes to the local open. The listed-key reuse now applies to adopt too. Base'sDaemonStore.firstPane(mirroring:)replaces this branch's duplicatemirroredPane(of:)in AgentSessionWorkspace, CloudMove and CloudBringBack, and the duplicate file is deleted.AgentTabRemoteClient: this branch moved it toCmuxNextAgentPane. It now carries #21258's observemodeandisReadOnly, and #23163'schildren().AgentSessionAttachClient: attach sendsmode.command(attach or observe) withmode_eventsand the bypass notes. The private mode read is renamedchatMode()so it does not clash with themodeproperty. Adopt is refused in observe mode.DaemonEndpointcapability list:agentSessionAdoptplus base's observe, children, cloudAttachments and cloudSelfHost.- cmux-tui
agent_session_attach/agent_session_stream: the adopt and children modules, the mode-read fields and the children set. Asession_changedof the session or of a followed descendant streams. Only the session's own mode change re-reads its mode.
Fleet, on the merged head e0f0a4808e44 unless noted:
cmux-ci compile-check --app, job9f4181e7d0cd8737d040c953: ok (app compiled and linked).- Swift suites, step
99113f1332b59dff06e1a59f(the fleet coalesced the per-suite keysr22871-<Suite>-e0f0a4808e44into one build): passed for AgentBypassBadgeTests (6), AgentTabHostRefusalTests (4), AgentSessionAttachClientObserverTests (2), RemoteChildSessionsTests (4), RemoteAcpmuxObserverTests (1), CloudMoveTests and CloudBringBackTests (12). - cmux-tui
test agent_session, step7ad611affb284732b92a9baf: red. 15agent_session_attach_testsfailed because the attach's new mode read (_acpmux/web_modes) went unanswered by the tests' fake acpmux and showed up as the next frame. Fixed in169626f5634d: the fake answers the read and skips it like the watch. - cmux-tui
test agent_sessionon169626f5634d, step9cec0891204f1fa16b33ef79: 94 of 96 passed, including every adopt, start, attach, observe and children test. Two did not pass:terminal_respawn::a_respawned_terminal_resumes_its_agent_sessionfails the same way on basee323f77325da(step9da1003cc84a96e2051d2360), so it is not this PR's.agent_input_hint::terminal_input_into_an_agent_suggests_agent_messagefailed in the full run and passed alone, so the gate marked it flaky.
PR checks on 169626f5634d:
cmux-next guards / defaults-snapshotis red at "Menu rows run registered actions" over FileExplorer files this PR does not touch. It fails the same way on the feat-cmux-next push run 39617831647, so it is a base red.pr-capture-checkfailed because the description had no capture line. The note above addresses it.
Not run: no live adopt of a real remote chat end to end, and no capture.
Agent session: cosmic-gray
š¤ Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Opens a chat that already runs on another machine's acpmux as a sidebar workspace bound to that machine's daemon, via the new agent-session-adopt verb in the cmux-tui daemon and the cmux-next app.
- Binds a session only when that machine's own acpmux lists it at the moment of the call; refuses peer-mirrored chats, the Home Chief's, and unknown or vanished ids, and never starts acpmux for an adopt.
--host NAME --group Gopens into that machine's group of that name, joining any same-named group that can hold the row and making it when missing; a host that names none or more than one machine is refused before the workspace is made, and the refusal says why. - The merge with
feat-cmux-nextkeeps adopt and the base's observe, children, andgroupfeatures, reusing base'sAgentSessionWorkspace.joinfor the group (the branch's duplicateSidebarEdits.group/joinGroupare removed) and base'sis_chief; an adopt in observe mode is refused, and the attached client carries the observe mode and children support. - Adopts bypass-mode sessions only on enrolled or trusted hosts (same host gate as
agent-session-start), reportingasking: falsefor the app's bypass badge. - Non-asking chats show a "Bypass: acts without asking" warning first on their workspace row, which no reporter's entry can drop or hide. The badge follows the chat: a new
agent-session-modeverb reads a bound tab's mode, the attach reply carries it, and an attachment withmode_eventshears pushes when the mode changes. Mode reads are bounded and run only while the connection serves them; a stale connection's read is dropped, and the re-read lock uses acquire-release ordering so a concurrent mode change is never lost. - Moves
mirroredPaneandAgentTabRemoteClientintoCmuxNextBridgeandCmuxNextAgentPaneto keep the App target flat. Adopt shares the local open's agent-page check, so without the page it is refused before any workspace is made on the other machine; the unknown-session refusal names the exact session id. - Any failed adopt closes the workspace it made outright, kept out of Recently Closed: the workspace, its terminal, and the agent tab are created before
agent-session-adoptruns, and a timeout, closed connection, malformed reply, or failed agent-tab request would otherwise leave them on the other machine. The close retries through later drops until the daemon answers (or the machine leaves the app), re-asking a late reply only within the late-reply budget, retrying a lost link only on a new connection, and ending on a timeout past the budget so it always terminates; it closes the terminal too, so a daemon without batch-close-v1 does not keep the PTY. The whole create runs under the workspace-repair claim, the close runs only after that claim fails and releases the key withendClosing, so no key is left settling, and the tab request is sent under a fixed mutation id so a late reply replays. - Adds CLI tests covering binding, refusals (peer, chief, unknown, team VM), the dual-adopt error, the host-name gates, mode reads and updates, and the failed-adopt cleanup, plus app tests for the bypass badge and the no-page refusal (a Debug-only
CMUX_NEXT_AGENT_PANE_NONEkey simulates a build without the page).