Agent pane: an agent a shell call starts (codex exec, acpmux run) draws as a subagent

Merged
TE
12d ago
+1550-13

Stacked on #25040 (agentRuns.ts). #24977 (AgentCard) has landed, and this branch is rebased onto feat-cmux-next. Until #25040 lands, the diff here also includes its two files (agentRuns.ts and its test). Its own change is about 640 lines. The typed-data follow-up for acpmux run --parent (acpmux #25019) is #25062.

Leo, 2026-10-24, word for word, looking at a codex exec review that renders as a plain "Command" row (spinner, "5m 13s Running", a stop square; Session Info → Background tasks shows only { cat /Users/…/flee… 5m 13s):

"this is mildly interesting i think being able to do it like a cmux-next natively rendered thing of subsession/non-same-model-family subagent would be a cool depiction"

"plus supporting the situation better in acpmux"

What changes

A shell call that starts an agent from another model family is now that agent, a Subagent in the existing fold. Examples: codex exec …, claude -p …, acpmux run|new|exec …, sr codex …, and any of these through reach HOST '…', ssh, sh -c, timeout, nohup, env or the perl -e 'alarm N; exec @ARGV' wrapper. "Different family" means the harness the call runs (from the executable, or from acpmux's -m) differs from this chat's harness (summary.harness). So a Codex chat's own codex exec stays a command.

  • subagentFold.ts reduceRun: the call's tool_call (with rawInput.command, a string or Codex's argv) becomes a Subagent with id run:<callId> and its own group row. The Subagent carries:

    • the harness and the model plus effort ("gpt-6-astra xhigh");
    • kind "Codex · gpt-6-astra xhigh";
    • its name: the first line of the prompt (inline, heredoc or here-string), else the call's description;
    • the command, the acpmux session name and the host.

    The call's updates become the run's own view, so opening the run shows the Command row with everything it printed. When the call completes or fails, the run is done or failed, with its reply: Codex's final message after its tokens used log, or the output's tail. A call that goes to the background ("Output is being written to …") keeps running until acpmux's background_task_update ends it. A later Read, cat or tail of that output file sets the reply. A turn's end ends a foreground run whose end never arrived, the same way it does for Claude subagents.

  • direct.ts: the chat's harness goes to the fold. If a call is only recognized as a run once its command arrives in a later update, its earlier Command row is dropped (dropToolItem).

  • AgentRunCard.tsx (new) draws the run's group row with #24977's AgentCard, the same card sub-sessions use. It shows the harness mark, title, model (plus the host), state (the shared working spinner, then Done/Failed) and elapsed time. Once the run ends it shows the reply as Markdown cut to three lines (the sub-session card's Cut). Raw opens the command. Show output opens the run's view. For an acpmux run whose -n session is in the session list, Open chat opens it through SubsessionContext.open.

  • The tray above the composer, Session Info's Subagents section and the Subagents tab list the run like any subagent, with the harness's mark (AgentLine mark) and "Codex · gpt-6-astra xhigh". The swallowed call no longer shows under Background tasks. A run has no Stop or Restart button, since those are Claude's own.

  • runningTray.ts/subagentRows.ts: a run group (isRunGroup) keeps its card where it started, also in the tray's layout. It never opens into a list. model.ts estimates its height.

The fallback is the command text. hb-acpmux is adding typed parent/state/result data to acpmux run/new (--parent), and was asked for the event shape. A small follow-up PR will read that data first once it merges.

Gallery

agent-pane.agent-runs. Every row is folded through the real SubagentFold from the update shapes Claude Code recorded for these calls, with scrubbed paths and synthetic prompts and replies. Variants:

  • before: today's Command row.
  • card-running (shipped): running, with the spinner.
  • card-done (shipped): done with its reply, failed, and an acpmux run with Open chat.
  • group (not picked): the run as a plain subagent group card.
  • line (not picked): a tray line plus a one-line end.
  • summary: the Session Info rows.

I picked the card because it is the only variant that shows the model and the reply where the run happened, and it is the card sub-sessions already use.

Captured on huge-blue (Linux Chromium, w420) from the gallery at this head. before is the same recorded call drawn as the base draws it.

DarkLight
Before: the call as a Command row
After: running, its card and tray line
After: done with reply, failed, acpmux with Open chat
After: Session Info's Subagents
Not picked: group, line

Risk

Contained to the agent pane's webview. It only changes how shell calls that start another family's agent render. Every other call keeps its row, and the parser returns nothing for anything it can't read. No new timers: elapsed time uses useNow, which ticks only while a run runs and stops when the pane is hidden. No observers beyond Cut's existing one per card. The per-event cost is a Map lookup. The command is tokenized once per call, and calls that start no agent are remembered so they are never parsed again.

Tests

  • agentRunFold.test.ts: a run's start, end and reply; a failed run; a background run that ends through background_task_update and gets its reply from a later Read; a turn's end that leaves a background run running; a same-family call and a grep that stay commands; a Codex chat's claude -p.
  • agentRuns.test.ts (from #25040).
  • The webview CI jobs run them along with typecheck, lint and fmt. They couldn't run locally because huge-blue is out of memory.

Agent session: cosmic-gray

🤖 Generated with Claude Code