cli: add a caller selector and make current mean the caller inside cmux

Merged
AU
11d ago
+664-150

Summary

Inside cmux, current resolved to the workspace and pane the person had focused, not to the caller. An agent in its own workspace that ran cmux pane current split --right or cmux workspace current rename … split or renamed whatever the person was looking at. cmux identify already reported the caller (.caller.workspace, .caller.pane), but no selector could name it: cmux pane caller get returned selector.not_found.

After this change:

  • caller is a selector for workspace, screen, pane, tab and terminal scopes. It names the object the command runs in, using the same caller system.identify reports. For a cmux terminal (CMUX_TUI_TERMINAL_ID), the CLI follows the terminal up through its tab, pane and screen to its workspace in the daemon. For an agent session (CMUX_AGENT_SESSION), it takes the place the owning app reports in system.identify. caller fails without a caller, and fails under --socket, --session or --machine, which name another session or machine.
  • current names the caller's object whenever a caller is known on its own session. It still names the focused object in these cases:
    • No caller is known, or the caller can't be found: a shell outside cmux, a stale terminal id, an app that doesn't answer.
    • The request is routed with --socket, --session or --machine.
    • It sits below an explicit parent that isn't the caller's, or above an explicit id or name.
    • The target is a browser.
  • Browser creation (pane current tab create browser) now runs after these lookups, so the new tab lands in the caller's pane.
  • Help and docs: the cmux <scope> --help SELECTORS text, cmux-tui/spec/cli.md and the cmux / cmux-workspace skills describe both selectors.

Why resolve this in the CLI

The daemon's resource connection is anonymous: it can't tell which terminal or agent sent a request, so its current can only mean "focused". The CLI already reads the caller from the environment for system.identify and for status. It also already had a pre-request lookup step for the caller's workspace (Resolve::CallerWorkspace).

This PR generalizes that step into Resolve::Caller. Requests still carry only ids, so idempotency fingerprints don't change and the daemon protocol is untouched. Focus behavior of split, rename and the other commands is unchanged; the lookups only read.

Not covered here (follow-up)

cmux agents … builds plain unary requests and refuses pre-request lookups, so its current still names the focused object. cmux agents now refuses caller instead of sending it as a name. The MCP tools resolve current to the caller on their own since https://github.com/manaflow-ai/cmux/pull/25315 (mcp/caller_scope.rs); this PR does not change that path.

Testing

  • tests/cli/caller_selector.rs runs the real CLI against a headless daemon. The caller's terminal sits in workspace alpha while workspace beta is focused. The tests check:
    • workspace|pane|terminal caller show return the caller's objects.
    • workspace current rename renames alpha, not beta.
    • pane current split --right lands in alpha.
    • Without a caller (no terminal in the environment, or --socket), current is still beta's pane.
    • Focus never moves.
  • Unit tests in cli/resolve.rs cover the edge rules: no caller or a lost caller, --socket and --machine routing, an explicit parent that isn't the caller's, an explicit id below current, a browser target, and an agent chat with no terminal.
  • Focused hosted command: ./scripts/verify-cmux-tui-hosted.sh --filter caller_selector (red/green); the final green run used --filter caller to include the unit tests.

Observed / Not run

  • Red, test-only commit df932d4 on base 3cc9733 (https://github.com/manaflow-ai/cmux/actions/runs/39504165011): both caller_selector tests fail. workspace caller show returns selector.not_found ("no workspace matches "caller""), and workspace current rename renamed the focused workspace instead of the caller's (ws_ff81… ≠ ws_7538…).
  • Green, head: af4b87e (https://github.com/manaflow-ai/cmux/actions/runs/39530274989, --filter caller): lint (rustfmt, clippy, god-file ratchet), MSRV, Linux and macOS tests all pass. Executed: the three caller_selector:: integration tests, the three cli::resolve::tests caller tests, and the existing caller-related CLI and daemon tests matched by the filter.

Not run: no tagged GUI dogfood. The agent-chat path (CMUX_AGENT_SESSION → app system.identify) is covered only by unit tests on the mapping and by reading; no live app answered it. cmux agents keeps current as the focused object (follow-up above).

Changelog

Added: caller selector, and current now means the caller's own workspace, pane, tab or terminal when the cmux CLI runs inside cmux (focused only outside it)

Proof

CLI behavior only, covered by the daemon-backed tests above; no UI change.

🤖 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 current inside cmux so it resolves to the caller's own workspace, screen, pane, tab, and terminal instead of the focused ones, and adds a caller selector that names the object the command runs in. Previously an agent in its own workspace that ran cmux pane current split or cmux workspace current rename acted on whatever the person was looking at, and cmux pane caller get returned selector.not_found.

Behavior

  • caller resolves the caller's terminal (or agent session via the app's system.identify) through the daemon before the request is sent, and fails with selector.not_found without a caller or selector.invalid under --socket/--session/--machine.
  • current stays the focused object when no caller is known or findable, when routed with --socket/--session/--machine, below a parent that isn't the caller's, above an explicit id or name, and for browser targets.
  • Browser tab creation now runs after these lookups so the new tab lands in the caller's pane.
  • The daemon protocol and request fingerprints are unchanged; lookups only read.
  • cmux agents and the MCP tools keep current focused and refuse caller.