Agent pane: sub-session calls render as live sub-session cards, not JSON

Merged
TE
12d ago
+725-207

Leo, 2026-10-24, word for word, looking at how a sub-session start renders in the chat:

"I don't know if we're, like, hand-rolling and reinventing the wheel. I don't know if we're doing it in a good way, or if we could do it in better ways. We can get some guys to research that, but look, regardless, I feel like we should be able to render this a hell of a lot better, you know?"

Root cause

The transcript already had sub-session cards (SubsessionCallCard.tsx), but subsessionCalls.ts only recognised the cmux-subsessions server. In code mode the cmux server serves that server's tools as its own, so the call arrives as mcp__cmux__subsessions_start_session (server cmux, tool subsessions_<tool>, cli/mcp/upstream.rs prefix()). Every such call fell back to the generic MCP row, which showed "Called cmux · subsessions start session" with raw Request/Response JSON.

What changes

  • subsessionCalls.ts reads both names (Claude Code's mcp__cmux__subsessions_* and Codex's {server: "cmux", tool: "subsessions_*"}), and now also reads list_sessions, the start's prompt/model/harness and each row's harness.
  • A new subagents/AgentCard.tsx draws a sub-session as a glass card: its harness mark, name, model, live state (the working spinner, never a dot; "Waiting on you"; Done; Failed), what its turn does now, and Open chat (it opens beside this chat without taking focus).
  • start_session: the card, with the prompt as rendered Markdown cut to 3 lines and Show prompt.
  • wait_for_sessions / list_sessions: one card per sub-session with its state; a finished one's last reply as Markdown, cut to 3 lines with Show more.
  • send_message: → name: <first line> · <delivery>, which opens to the whole message as Markdown.
  • The raw JSON is only behind Raw, which reuses the generic MCP row's Request/Response view (McpCallDetails).
  • Live state comes from the existing session-list store (useSubsessionInfo, useSyncExternalStore) per card. There is no polling and no timer. A session changing redraws only its own card, and each event costs O(1) per card.

I checked with tt-workflows: their workflow card is per workflow and opens the panel, so there is no per-agent overlap with #24874.

Before / after

Gallery agent-pane.subsession-cards, variants recorded and recorded-wait. They use the exact shapes of a mcp__cmux__subsessions_start_session and a wait_for_sessions result that Claude Code recorded on 2026-10-24, with synthetic names and text. The same gallery file was rendered on the base and on this head (Linux Chromium, w420).

Gallery: agent-pane.subsession-cards/recorded, agent-pane.subsession-cards/recorded-wait

Before (base)After
start_session (recorded), dark
start_session (recorded), light
wait_for_sessions (recorded), dark
wait_for_sessions (recorded), light

Before: the start reads "Called cmux · subsessions start session", and opening it shows the raw JSON. The wait is one more generic row folded into the finished turn. After: the start is net-retries' card with its Claude mark, model, the working spinner, what it does now, Open chat and its prompt as Markdown. The wait is two cards: review done with its reply as Markdown, and net-retries still working.

Risk

Small and contained. It only changes how the transcript renders calls to the sub-session tools; no protocol, daemon or native code changes. If a call can't be read (a failure, a refusal, an unknown shape), it still falls back to the generic MCP row, as before. The model.ts height estimate for these cards grows to match.

Tests

  • subsessionCalls.test.ts: the mcp__cmux__subsessions_* form (Claude and Codex shapes, from a recorded result), list_sessions, and other cmux tools still keeping the generic row. 40/40 pass in the touched suites.
  • On huge-blue: bun test src/agent-session test gives the same 403 failures on head and base, with none only on head. Typecheck, lint, fmt, design-token, catalog-key and type-scale checks pass.

Agent session: cosmic-gray

🤖 Generated with Claude Code