agent pane: a cmux_exec row says what its script did, and opens to JavaScript and a result tree

Merged
TE
5d ago
+1515-15

Leo, 2026-10-30: "these should be rendered better than just cmux called cmux exec we know what these are supposed to be"

Three cmux_exec scripts in a row each read "Called cmux · cmux exec", and an opened one showed the raw mcp__cmux__cmux_exec line, the script as one escaped JSON string and the response as escaped JSON. Now each row says what its script did, and opens to the script as JavaScript and the result as a tree.

What changed

  • Titles (conversation/cmuxExec.ts). A finished script's title comes from call_log in its answer ({calls, call_log, logs, result}, cmux-tui cli/mcp/code_mode.rs). A running script's title comes from the cmux.call("<name>") and cmux.<tool>( calls in its source, in the present tense.

    • One verb table: workspace_list "Listed workspaces", app_agent_open / app_agent_open_tab "Opened N agent sessions", tab_create_browser / workspace_open_url "Opened N browser tabs", cmux.docs / cmux_docs "Searched cmux tools [for ]". It also covers pane, tab and terminal lists, workspace_create and workspace_rename, tab_rename, tab_create_terminal, terminal_input_write, terminal_wait, terminal reads, pane_split, tab_close, tab_focus, workspace_status_set and notification_create.
    • A change outranks a lookup. Counts are aggregated and pluralized.
    • The workspace is named when the script or its result names exactly one. It is never named by id or by current: "Opened 12 agent sessions in Leads".
    • Other steps add "and N more steps".
    • A failed call counts only when none of its kind went through.
    • Fallbacks:
      • a generic <noun>_<verb> name reads in the MCP rows' words ("Read workspace");
      • otherwise "Ran cmux script (N calls): ", then "Ran cmux script";
      • a malformed response falls back to the script's own calls.
  • One row per script. Consecutive scripts no longer fold into one "×3" line (toolGroups.ts), so each reads by its own content.

  • Opened (McpCallRows.tsx CmuxExecDetails):

    • the script as JavaScript with real line breaks and coloured tokens (jsHighlight.ts, synchronous, the pane's ANSI code roles);
    • the tools it called, with counts and failures;
    • the result and any error as a key/value tree (valueTree.ts): YAML-like, text unescaped with its line breaks, nested JSON text parsed, host-injected context dropped;
    • console logs.

    A response with a number a double would round shows as it came. The mcp__… name and the request and response as they came are behind Raw (the existing toggle the sub-session cards use). They are out of the default view but one click away. Find reads what the opened row draws.

  • Known MCP tools covered. cmux_exec, cmux_docs, and the cmux server's own tools called directly (workspace_*, pane_*, tab_*, terminal_*, screen_*, window_*, notification_*, app_*): their shapes are ours. Tools the server folds in from other servers (cua_*, subsessions_*, secrets_*, forum_*, acpmux_*) keep their own rows and cards. Every other MCP server keeps the generic line, and no other tool's row changes. The running line (toolPaths.ts) uses the same title.

  • Cost. Titles, the script view and the opened parts are derived once per tool-call snapshot (WeakMaps on the immutable call, per language for titles). Nothing is derived per render or per streamed token. Nothing is parsed for a closed row beyond its title. This adds no O(rows) work per token.

  • 52 new strings in Localizable.xcstrings (21 languages, plurals per CLDR; non-English needs_review).

  • .github/design-surfaces.json maps the transcript's tool-row files to design/chat-transcript.md.

Spec: leo-workspace design/chat-transcript.md, section "cmux_exec and known MCP tools" (teamleaderleo/leo-workspace#1791).

Stills

Leo: "WE DONT NEED FUCKIGN SCREENSHOTS JUST LET ME TRY THE SHIT OUT". There are no stills; try the cmux script row in the nightly or dogfood build. The gallery state is agent-pane.cmux-exec-rows.

Tests

  • webviews/test/cmux-exec-rows.test.tsx (11 tests):
    • titles from call_log, from a running script and from a fallback;
    • aggregation and failure counting;
    • malformed responses;
    • direct cmux tools against folded servers' tools;
    • JS tokens and the value tree;
    • three rows reading distinctly;
    • the opened view: highlighted JS, a structured result, Raw keeping mcp__cmux__cmux_exec;
    • Find text.
  • Unchanged and green: agent-pane-mcp-tool-rows, toolGroups, ToolVerb, pane-english, agent-pane-locale, chat-find, subsessionCalls, transcript-density, tool-failure and subsession-disclosure.

Observed / Not run

  • Observed: the bun suites above, run locally (pass). scripts/web-lint.sh: no findings in changed files. Its remaining failures (design tokens, layers, fmt, lint) are all in webviews/src/pages/settings/* and are red on base ad871573723. prepush-gate.py: no new violations. test/gallery-coverage.test.ts has 3 failures that are red on base and do not name this entry.
  • Not run: a Mac build (no Swift changed). WebKit gallery stills (Chromium only).

Changelog

  • Changed: an agent's cmux script row says what it did ("Opened 12 agent sessions in Leads") and opens to the script and its result, not escaped JSON.

risk: normal (transcript rendering for one MCP server's tools only).

Size: about 1,450 lines, of which 52 are the string catalog's one-line entries, about 290 are the new test file and about 190 are the gallery entry. The renderer, its verb table, the tokenizer and the tree view are one feature with its tests, so this PR is not split.

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

Renders each cmux_exec row in the agent transcript so it says what its script did instead of "Called cmux · cmux exec", and opens to the script as highlighted JavaScript and the result as a tree rather than escaped JSON.

  • A finished script's title comes from the call_log in its result; a running one's from the cmux.call(...) calls in its source.
  • Consecutive scripts no longer fold into one ×N row.
  • An opened row shows the script as highlighted JavaScript, the calls with counts and failures, console logs, and the result and error as a YAML-like tree; the raw mcp__cmux__cmux_exec call stays behind the existing Raw toggle.
  • cmux_docs and the cmux server's own tools get the same titles; other MCP servers keep their current rendering.
  • Titles and opened parts are derived once per tool-call snapshot (WeakMaps), never per render or token, and Find reads what the opened row draws and highlights across its colored tokens.
  • A cut call log (call_log_truncated, or fewer entries than the reported count) never gives a wrong count: such a script's title says its total calls instead.
  • A number a double would round, in any JSON layer including a nested text block, shows the response as it came rather than parsed as a tree.
  • A direct tool's cached title is reused only while its workspace name matches, so a workspace named by the run's later identifiers still appears in the title.