Agent chat: a stopped subagent's line says why (stopped by you, interrupted by Send now) and offers Restart

Merged
TE
13d ago
+1301-17

Leo: "UI ALSO NEEDS TO BE BETTER". A subagent's end line in the agent chat read Subagent stopped: <name> · 17m 6s: fine as info, but it never said why the subagent stopped, and there was no way to run it again.

What changes

  • The reason. A stopped (cancelled) subagent's line now adds why it stopped, when the pane knows: Subagent stopped: Check the docs · stopped by you · 1m 36s, or · interrupted by Send now ·. The reason comes from data the pane already has:
    • a foreground subagent that stops while a Send now is cancelling the turn (the queue_order record that sets interrupting in direct.ts). acpmux records this, so the reason survives a reload;
    • a foreground subagent that stops after this pane's Stop on the turn (stopAfterSeq), or any subagent this pane stopped from its own Stop button (_acpmux/subagent_stop). Both read stopped by you. These are known only to the live pane: the reasons survive the fold's rebuilds, but a page reload loses them, because acpmux records no cause for a cancel;
    • anything else (Claude's own TaskStop, another client's or agent's cancel, a Codex interrupt, a background subagent whose turn stopped) shows no reason. An unknown reason is left blank, never guessed.
    • "finished" and "failed" need no extra segment: those lines already read Subagent finished: and Subagent failed:. Claude's task_notification status maps through acpmux's adapter (failed → failed, stopped → cancelled, else completed) and arrives as the line's state.
  • Restart. On hover or keyboard focus, a stopped, failed or lost subagent's line offers Restart. It uses the same compact icon button as the message hover row (#23769): Retry icon, "Restart" as the tooltip title, and "Ask the agent to run this subagent again with the same prompt" under it. Neither acpmux nor Claude Code has an op that resumes a stopped subagent, so Restart sends the chat the prompt the person would type: Run the Explore subagent "Check the docs" again with the same prompt: followed by the original prompt. The original prompt is the prompt acpmux already records on subagent_spawned, and the send goes through the composer's chat.send, as Retry does. It shows only for the chat's own subagents with a recorded prompt and a connected chat. No new backend op.
  • All 6 new strings are translated into all 21 languages.

Tests

  • subagents/subagentFold.test.ts: reason mapping (Send now, Stop, pane Stop request, background subagent, stopRunning, rebuild survival, running again forgets, unknown → none, finished/failed carry none).
  • subagents/restart.test.ts: when Restart is offered; the prompt text (with and without kind; braces in the prompt kept as is).
  • test/subagent-tray.test.tsx: the rendered line with each reason and with none; Restart wiring (click → restartSubagent(agent); none for finished or no-prompt lines; none without the action).

Before / after (gallery, synthetic fixtures)

agent-pane.subagent-tray: failed-line now shows a failure, both reasons and an unknown one; the new restart-on-hover variant shows the focused Restart button. Live side by side: scripts/gallery-compare.sh tp/subagent-stopped-reason --entry agent-pane.subagent-tray (tailnet).

BeforeAfter
beforeafter
before (the variant is new: the old line, no Restart)after

Tests run on cmux16 at the head: 33 pass, 0 fail; vp check finds no type errors in 2284 files.

What the base already fixed (vs Leo's nightly 03960f5)

Nothing for this item: #23104, #24031, #24087 and #23962 changed when subagents end and how the tray counts them, but the stopped line still had no reason and no Restart.

Web only (no Swift). risk:normal.

Agent session: cosmic-gray

🤖 Generated with Claude Code