cmux NEXT agent chat: the running line is the turn's last row, and its clock never stops

Closed
TE
15d ago
+405-59

Leo (2026-10-19): "when a chat is thinking, it has very little responsiveness. It just only has that thinking thing, but it doesn't do anything a lot of the time."

Builds on #21631 (the working mark and the call in flight on the line), which landed as c5061bd; feat-cmux-next is merged in, so the diff is this PR's own.

Why the line looked dead

Three things in liveTurn (conversation/turns.ts) made a running turn look stalled while the agent was working:

  1. The clock stopped whenever text was the latest row. The line held at the text's start, so "Working for 8s" sat still while the agent went on thinking or writing a tool call's input. Claude Code reports a tool call only when its whole input has streamed (acpmux reads the call from the finished assistant line), so a long Write or Edit was a frozen label for as long as the file took to write.
  2. The line was over the turn's work. After a few tool rows and some text it scrolled off the top, and the bottom of the chat, where the reader looks, had nothing live.
  3. A turn that had only streamed text drew no line. If the agent wrote "Let me check" and then thought for a minute, nothing showed the turn was still running.

Before the first output the line read a bare "Thinking" with no time.

Now

  • The live line is the turn's last row from send to the end of the turn, as in Claude desktop: the working mark, what the agent is doing now, and the turn's time.
    • "Thinking 12s" before any output. While a thought streams in it reads "Working for", since the thought labels itself.
    • The call in flight (from #21631), e.g. "bun test src/net 1m 4s".
    • "Working for 1m 4s" otherwise, including while the answer streams.
  • The clock never stops while the turn runs.
  • It keeps one row id from the Thinking line the prompt opened, so it doesn't leave and come back when the first output arrives. There's no rule under it.
  • The first "Thinking" line (the older placement, and while a prompt waits) shows the time since the prompt, on this computer's clock (peer clock offset applied).
  • When the turn ends, "Worked for" folds the work as before.

Debug Settings "Running turn's status line at the bottom" (agentPane.debug.liveLineBelow, on by default) turns it off: the line goes back over the turn's work, with its clock held while text streams. A browser host that sends no value gets the new line.

No new strings: "Thinking" and "Working for" already exist, and the time uses formatDuration.

Testing

  • test/transcript-live-line-below.test.tsx (new, commit 1 alone is red): shapes live turns with turnView and renders WorkingFor and Thinking: Thinking with a time before output; the line under a streaming answer, ticking; text after work doesn't hold the clock; the call in flight named on the last row; a streaming thought reads Working for; one row id from send to first output, no divider; the toggle off keeps the old shape; the store's default.
    Review follow-ups (468e586) are covered there too: shell commands run mid-turn and prompts held for the turn both go above the line; a thought streaming in reads "Working for" so "Thinking" isn't shown twice; the forget-rows fast path still applies with the line last.
  • test/agent-pane-peer-chat.test.ts: the line now reads "Thinking" before the peer's tool call in the same place, so the test waits for "Run make".
  • test/agent-pane-agents-pill.test.ts: the pill's text now also shows on the line, so the locator takes the first match, and clearance is measured against the last transcript row.
  • newChatNoShift gallery play: checks the prompt bubble doesn't move and the line sits under the answer.
  • Ran on Huge Blue (Linux, CMUX_BROWSER_TESTS=1 for the browser lanes): transcript-live-line-below 11/11, transcript-working-line 5/5, transcript-live-runs 4/4, agent-pane-peer-chat 1/1, agent-pane-working-counts 4/4, agent-pane-follow-live 6/6, turn footer/opener/chat column 13/13, static-status-loops 8/8, agent-pane-agents-pill 4/4. The new test file alone, on the base: red (the module it imports doesn't exist).
  • vp check (format, lint, types) passes. CI web and size checks are green at c5b471a.
  • Other reds are on the base too: the react-apps lanes that fail here (New Tab strip, new-tab-warm-first-frame, narrow-layouts, history-paging, chat-find) fail the same way on the base on Huge Blue; defaults-snapshot gives the same output on the base; the cmux-next concurrency findings are in files this PR doesn't touch.
  • Swift: the tunable and its two tests (AgentPaneLiveLineTests) compile and run in CI only.

Captures

Gallery agent-pane.transcript at w420, from the gallery advisory run (artifact gallery-advisory-22363-468e586…), dark and light:

  • working-thinking: the prompt, then "Thinking 12s" with the moving dots as the last row.
  • working-after-text: a Read, the streamed answer, then "Working for 1m 30s" under the answer.
  • working-line-above (toggle off): the old placement, "bun test src/net 2m" over the turn's work with its rule.

🤖 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

Running agent turns now keep their status line pinned to the bottom of the transcript, where it stays visible and its timer keeps ticking while text streams, matching Claude desktop.

  • Before any output the line reads "Thinking" with the time since the prompt; a streaming thought labels itself, so the line reads "Working for" instead of repeating "Thinking."
  • The line names the in-flight tool call beside the turn's time and stays last under held prompts and shell commands run mid-turn.
  • Debug Settings agentPane.debug.liveLineBelow is on by default; a browser host that sends no value gets the new line. Turn it off to restore the previous placement and timing behavior.
  • The animated working mark respects Reduce Motion and Static status indicators.

Builds on the working mark and call-in-flight from #21631, now merged into feat-cmux-next.