cli: add a caller selector and make current mean the caller inside cmux
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:
calleris a selector for workspace, screen, pane, tab and terminal scopes. It names the object the command runs in, using the same callersystem.identifyreports. 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 insystem.identify.callerfails without a caller, and fails under--socket,--sessionor--machine, which name another session or machine.currentnames 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,--sessionor--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> --helpSELECTORS text,cmux-tui/spec/cli.mdand thecmux/cmux-workspaceskills 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.rsruns the real CLI against a headless daemon. The caller's terminal sits in workspacealphawhile workspacebetais focused. The tests check:workspace|pane|terminal caller showreturn the caller's objects.workspace current renamerenamesalpha, notbeta.pane current split --rightlands inalpha.- Without a caller (no terminal in the environment, or
--socket),currentis stillbeta's pane. - Focus never moves.
- Unit tests in
cli/resolve.rscover the edge rules: no caller or a lost caller,--socketand--machinerouting, an explicit parent that isn't the caller's, an explicit id belowcurrent, 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 callerto include the unit tests.
Observed / Not run
- Red, test-only commit
df932d4on base3cc9733(https://github.com/manaflow-ai/cmux/actions/runs/39504165011): bothcaller_selectortests fail.workspace caller showreturnsselector.not_found("no workspace matches "caller""), andworkspace current renamerenamed 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 threecaller_selector::integration tests, the threecli::resolve::testscaller 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
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
callerresolves the caller's terminal (or agent session via the app'ssystem.identify) through the daemon before the request is sent, and fails withselector.not_foundwithout a caller orselector.invalidunder--socket/--session/--machine.currentstays 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 agentsand the MCP tools keepcurrentfocused and refusecaller.