cmux NEXT CLI: start, read, nudge and interrupt GUI agent chats; agents screenshot panes and tabs

Closed
AU
6d ago
+1206-48

Fixes #23026. See also #23016 and https://github.com/manaflow-ai/cmux/pull/23085.

Agents, a Chief turn included, can now do from the CLI and MCP what a person does with Cmd-N and typing into the chat: start an ordinary GUI agent chat (harness, model, effort, mode, permissions, folder) in a given or new workspace, then read, prompt, nudge and interrupt it. Nothing moves the person's focus.

What changed

  • cmux agents chat new [--workspace <ws>|--pane <pane>] [--harness] [--model] [--effort] [--mode] [--policy] [--cwd] [--name] [-- <prompt>], MCP agents_chat_new.
    • The chat starts in the app's own acpmux. The new read-only control method agent.acpmux reports that socket (ControlMethod.agentAcpmux in CmuxNextControl; AppControl only wires it). So a Chief turn, whose own acpmux is its home's, gets an ordinary chat too, never an a1/a2 subagent the app cannot attach.
    • The chat carries CMUX_WORKSPACE_ID of its workspace.
    • The app shows it without focus: in the given pane, or the workspace's focused pane, through agent.openSessionTab; or in a new workspace through agent.openSessionWorkspace.
    • The first prompt is the chat's own message, as typed.
    • The receipt names the chat, tab, pane and workspace. opened: false with open_error means the chat runs but no tab shows it; pending means the app is still opening it.
  • agents chat state <chat> (MCP agents_chat_state): status, running, pending permissions, queued, harness, model, mode.
  • agents chat interrupt <chat> (MCP agents_chat_interrupt): cancels the running turn.
  • agents chat continue <chat> [text]: a nudge, a send whose text defaults to "Continue.".
  • Reaching chats in the app's acpmux: send, read, state and interrupt find a chat in the caller's acpmux, else in the app's. The session-scope check is the same either way: the chat must run in a workspace of this cmux session. A send always goes through the daemon: agent-message policy, verified sender, holds and limits. Never the caller's own chat.
  • agent.openSessionTab (base, https://github.com/manaflow-ai/cmux/pull/23492) now answers tab, pane, workspace and workspace_key, so a script finds the chat tab it opened.
  • Fixed: agents palette open and chat new sent the app their receipt label agents as the origin, which the app refuses ("origin must be cli, mcp, script or remote"; part of #23016's usage.invalid). They now send cli, or mcp from MCP.
  • Docs: a cmux docs guide, "Start, prompt and drive another agent chat in the app", so agents find these verbs through cmux_docs; plans/cmux-next/cli-gui-parity.md (the GUI-vs-CLI audit and the remaining gaps); plans/cmux-next/mcp.md.

Defaults I chose (no human decision was available)

  • First prompt as the chat's own: chat new's first prompt has no agent-message provenance, as cmux acp new PROMPT already allows. Recommendation posted on #13742: https://github.com/manaflow-ai/cmux/issues/13742#issuecomment-6390595526. The alternative there: only a Chief turn's first prompt is the chat's own.
  • Policy limit: --policy accepts ask, approve-reads or deny-all. approve-edits and approve-all stay a person's choice in cmux. With no --mode, acpmux applies the machine's configured default for the harness, as cmux acp new does.
  • A retry starts another chat: agents_chat_new takes no idempotency key.
  • No tab, no chat: when the app cannot make the new workspace, the chat is ended (_acpmux/kill), since nothing could reach it.

Superseded by the base during the merge

  • Agent pane and tab screenshots: base #23983 (browser pages blacked out) replaced this branch's earlier screenshot change.
  • Opening a session's chat in a pane: base agent.openSessionTab (https://github.com/manaflow-ai/cmux/pull/23492) replaced this branch's --pane/--workspace arguments on agent.openSessionWorkspace.

Tests

  • cmux-tui/crates/cmux-tui/tests/cli/agents_chat_tools.rs (a real daemon and acpmux):
    • an_agent_starts_a_chat_in_a_workspace_and_drives_it_without_the_app: env, harness, the first prompt as the chat's own, state, continue, interrupt, --pane, and the refusals (own chat, out of scope, unknown workspace, both flags, --policy approve-all).
    • a_caller_with_its_own_acpmux_starts_and_drives_a_chat_of_the_apps_acpmux: a Chief-like caller (its own acpmux socket dead, a fake app naming the real one) starts, sends to through the daemon, reads, checks and interrupts a chat.
  • Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/AgentSessionInUnshownPaneTests.swift (real app services and daemon): agent.openSessionTab in a pane no window shows answers its ids and leaves the window, and in a shown pane keeps the person's selected tab.

Observed / Not run

  • Rust, run on the rbx builder after the merge:
    • cargo test -p cmux-tui --test cli agents_chat: 8/8.
    • --test cli agent_message: 1/1.
    • --lib cli::agent cli::mcp cli::docs cli::app: 219/219.
    • cargo clippy -p cmux-tui --all-targets --no-deps -D warnings: clean.
    • It reran at the final head with the new ghostty-next pointer; the result is in the PR comments.
  • Swift, cmux-ci steps:
    • 961d1cc0399b99732114d717 at 12a01ed: ActionOriginTests, CLISurfaceParityTests and the earlier app test passed.
    • 11a078fad0a720e9466382f2 at 2be0f35: exit 0.
    • The step at the final head is in the PR comments.
  • GUI dogfood: on tagged builds of this branch, in an isolated tagged app launched without focus:
    • A real Claude chat, started in a workspace no window showed, answered.
    • The window stayed on Home and cmux never became the active app.
    • chat state showed the chat; interrupt ended a long turn as cancelled; continue started a new turn that completed.
    • Not run: the Chief-flow GUI check after the app-acpmux change. The person's running copy of the tag blocked a second instance; the Rust test above covers that flow.
  • Hosted cmux-tui verification: three focused runs timed out while still queued, with no verdict.
  • publish to R2: on the earlier head it failed with GitHub's API rate limit for attestation (infrastructure).

No capture: no visible UI changes. These are CLI/MCP verbs, a control method and an action's result fields.

Changelog

  • Added: cmux agents chat new|state|interrupt|continue start and drive ordinary app agent chats from the CLI and MCP (a Chief included) without taking focus.

šŸ¤– Generated with Claude Code