Start refusals: a teammate's machine offers New Cloud Machine; Choose Folder on another machine opens its folders

Merged
TE
6d ago
+167-26

Leo, 2026-10-31: "let's work on improving this a LOT" and, on refusals, "we can't simply block always. we need to be able to get stuff running asap. lots and lots of bare prototypes so we can evolve our app as we go."

Stacked on #29145 (its commits show here until it lands; this lands after it). Fixes two follow-ups from its review:

Closes #29214. Closes #29213.

What changed

A teammate's Cloud machine says so (#29214). A teammate's link has no owner answer, so AgentSessionAttachClient.ownedConnection refused with agent_session.host_unverified before the start, and the card's New Cloud Machine action (mapped from agent_session.team_vm_blocked) never showed.

  • The Cloud link now keeps the Cloud API's exact vm_kind: "team" answer next to the owner one: CloudLinkSocket.callerIsTeammate (app link, from cloud.machine.connect), CloudAttachEndpoint.callerIsTeammate / CloudMachineLink.callerIsTeammate (legacy link). It is false whenever the same answer says owner, and false once the link ends.
  • AgentTabStore.runRemoteStart checks it first: on a teammate's link the start is refused as agent_session.team_vm_blocked ("%@ is a teammate's machine, so its owner starts chats there. Pick your own machine, or create one with New Cloud Machine.", the existing handlers.refusal.agentRemoteStart.teamVM, already translated in #29145), and nothing is sent to the machine. A host_unverified from the owner check on a link that turned out to be a teammate's while the start connected gets the same code (AgentRemoteStartRefusal.classified).
  • The owner check is unchanged. The teammate answer grants nothing; it only names the refusal. A non-owner still never gets a start, and a machine whose kind is unknown still gets host_unverified.

Choose Folder on another machine opens that machine's folder menu (#29213). The card's Choose Folder called workspace.chooseFolder, which the host refuses on a remote chat.

  • startRefusalRoute(action, remoteChat) (startRefusal.ts, one new function after the existing ones) sends a remote chat's Choose Folder to the composer's Location menu (openPicker("Location")). This Mac's open panel stays the fallback when the menu isn't mounted.
  • The remote project.list reply is marked machine: true, and the pane passes those folders to the composer as machineFolders. While the chat's own machine is picked, the Location menu lists them (its work folder and the folders under it, from #29145's agent-session-folders-v1). Before this, a remote chat's menu listed only folders its chats had used. A pick there becomes this chat's folder through workspace.useFolder (the remote useRemote path), not a new chat. Local panes and other machines' picks work as before.
  • SwitchNotice.tsx is untouched, and startRefusal.ts gets only the new function, so the "Share machine…" work on top of those files merges cleanly.

Risk: low. The start's owner gate is unchanged. The only new path refuses earlier, and the folder menu change applies only when the host marks a list as the machine's own.

Tests

  • Swift (AgentTabRemoteStartTests): a teammate's machine is refused as team_vm_blocked and its endpoint is never dialed; classified maps only host_unverified on a teammate's link; a team socket answer is a teammate's and an owner answer never is. The existing owner start tests (vm_kind: "owner", the work-folder fallback) still pass through the shared setup.
  • Web (startRefusal.test.ts): Choose Folder on a remote chat routes to the machine's folder menu, on this Mac to the panel, and New Cloud Machine to the Cloud connect.
  • cmux-ci compile-check --app and cmux-ci test --setup webviews (acpmux suite, tsc, biome): see the comments below.

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

Fixes two follow-ups from the #29145 review: a teammate's Cloud machine now refuses to start chats clearly and offers New Cloud Machine, and Choose Folder on a remote chat opens the other machine's folder menu instead of this Mac's.

Teammate's machine refusal (#29214)

  • A link whose Cloud API answer is vm_kind: "team" refuses the start as agent_session.team_vm_blocked before anything is sent; the failed card offers New Cloud Machine beside Retry.
  • The owner check is unchanged: only an owner answer ever sends a start, and host_unverified on a teammate's link becomes the teammate refusal.

Remote folder handling (#29213)

  • cmux-tui now serves agent-session-folders-v1 (the machine's work folder and the folders under it), so a remote start with no folder falls back to it instead of failing.
  • Choose Folder on a remote chat opens the composer's Location menu listing that machine's folders; a pick there becomes the chat's folder through workspace.useFolder.
  • A fresh chat whose session has not started yet routes Choose Folder by its connection's origin.