Agent chat: a finished turn folds only its work; every takeaway stays in sight

Merged
TE
13d ago
+504-90

A finished agent turn now folds only its work. Every reply that reads as a takeaway stays in sight, mid-turn or final, and the work between two takeaways folds under its own "Worked for" line between them.

Leo-ask: ask-0401fa4202
Leo-ask: ask-8c664026c1

Asks, verbatim

  • Lawrence: "this stuff needs to be collapsed"
  • Leo: "so first is his pic second is codex... but can we really be able to collapse this? maybe? is there a true final takeaway that we can keep? or some sort of consolidation?"
  • Leo (the ruling this PR builds): "we dont want end of turn takeaway responses to be folded in im pretty sure..."

What changed

webviews/src/agent-session/acpmux/conversation/turns.ts has one shared rule:

  • isTakeaway(row): an assistant reply over about three lines (more than 160 characters or more than 3 lines), or with a question, a list, a link, code or a second paragraph. Another agent's words always count. Short narration ("Let me check…", "Both audits still running.") is not a takeaway. The same predicate drives the running turn's note merging (mergeShortNotes), so live and finished turns agree.
  • foldSegments(opener, rows) splits a finished part (a prompt's or a steer's) into stretches: the work since the previous takeaway, then the takeaway. The part's final reply (the text after its last tool, e.g. "Pushed.") is always kept as well. So a turn reads [fold][takeaway][fold][takeaway] rather than one fold over everything.
  • Each stretch's fold is keyed worked-<row before it>: the prompt's id for the first stretch, as before, and the preceding takeaway's id after that. turnFoldFor (Find, shell reveal) uses foldSegments to open the fold that actually holds the row.
  • Compactions and "Subagent finished" rows now fold with the work. Errors and refusals (notice), pending permission asks, subagent groups, todo lists, background tasks and failovers stay outside the fold.
  • Running turns: nothing folds while the turn runs, as before. One small change: with the Codex-style runs (the default), the short-note merge (mergeShortNotes) now uses !isTakeaway. A note that has a question, a list or a link now stays its own paragraph between two runs instead of joining them. Before, only length, code and a second paragraph kept a note apart.
  • Builds on #23843 (merged): the final reply is never folded. It also covers the gap in that PR. In "long reply → tool → Pushed." the long reply and "Pushed." both stay visible, and the tool between them folds on its own.

Overlap: #24050 (tp/fold-animation, useAnchoredCollapse) animates the same .cv-worked rows. Nothing in this PR touches the toggle or its motion, so the hook will apply to every fold this one creates.

Before / after

Gallery entries agent-pane.transcript › takeaways-stay-open and takeaways-fold-open model a long turn on Lawrence's and Leo's screenshots, with synthetic text only: runs of commands, short narration, a compaction, two answers with a list and a question, then git push and "Pushed.".

Changelog

  • Changed: A finished agent chat turn folds only its work. Every substantive reply stays visible, and the work between two replies folds under its own "Worked for" line.

Tests

webviews/test/transcript-fold-takeaways.test.ts (new):

  • Leo's shape: tools ×4, an answer with a list, tools ×2, an answer. Both answers stay visible, with two separate folds.

  • Each fold opens to its own commands, in order.

  • Find opens the fold that holds the row.

  • Short narration between tools folds.

  • Long reply → tool → "Pushed.": both stay visible, and the tool folds between them.

  • Compactions and finished subagents fold; an error stays visible.

  • A running turn is unchanged.

  • A turn with only its answer gets no fold.

  • An empty last message never takes the answer's place.

  • isTakeaway cases.

  • Find opens the right fold past a prompt that failed to send mid-turn. This is a regression the review found; partFoldFor now ends a part where turnParts does.

  • A stopped turn's "You stopped after" sits on the last "Worked for" row of its last part, including when its last stretch has no work.

  • Find opens the right fold for a row after a summary that a steer continued. This came from the Codex review; partFoldFor now skips the summary that shapedTurn drops.

  • A ~~~ code block keeps a reply visible, as a backtick fence does. A note that only names a file inline ("Reading client.ts next.") still folds, on purpose: narration names files and calls all the time, and that narration is the work Lawrence asked to collapse.

Each of these four regression tests fails on the turns.ts before its fix and passes at the head (run on huge-blue).

webviews/test/agent-pane-history-paging.test.ts (react-apps-check shard 3): its 700-turn chat expected every compaction note on screen. Finished turns now fold their compaction with their work, which is Leo's rule ("fold only the work: ... compaction"). So the expectation changes, not the fold: only the streaming turn's compaction draws where it happened. The test passes at the head with the browser lane on huge-blue (3 pass).

webviews/test/transcript-live-runs.test.tsx: the ended-turn case now opens both of its folds, because the long text between its runs is a takeaway.

CI reds are base reds

These fail the same way on feat-cmux-next push f581234847 (https://github.com/manaflow-ai/cmux/actions/runs/39744860462 and https://github.com/manaflow-ai/cmux/actions/runs/39744860278):

  • Guard status
  • release-notary
  • cmux-next checks: native tab-switch timing on an unlit console
  • gallery health (0) and (1)

risk evidence is held for the review receipt.

Test reds on react-apps-check, one by one

I ran each file alone on huge-blue (CMUX_BROWSER_TESTS=1) twice in the same checkout: once at this branch, once with only this PR's files put back to the merge base 31afd278. The failures match:

  • agent-pane-history-paging.test.ts:724 (shard 3): ours. Fixed in fc5b5a6, where the expectation now follows Leo's rule that finished turns fold their compaction. 3 pass at the head.
  • changelog-pictures.test.tsx, "a change's picture opens enlarged on a click, and Close closes it": a base red. It fails the same way with the base files (2 pass, 1 fail) and also fails on #24221, #24267 and #24340, none of which touch the transcript.
  • composer-location-strip.test.ts:396 and :618 (no hairline across the seam, no gap in the card's corner, the 340 px folder cut): a base red. All three fail with the base files too (21 pass, 3 fail), and the same file fails on #24068 and #24267.
  • agent-pane-subagent-of-untitled.test.ts (shard 1): ours, now fixed. A finished turn now folds a finished subagent's line with the rest of its work, so the test clicks Worked for before the line. 4 pass at the head (0 before).
  • agent-pane-peer-chat, gallery-agent-pane-entries and agent-pane-adopted-tool-calls also fail with the base files in this checkout. The attribution comment lists the first two as seen on other PRs.

Bringing in feat-cmux-next

26bd8fa brings in feat-cmux-next. turns.ts conflicted with #24075 (consolidated tool rows). I kept this PR's segment loop and took base's change: a run that edited folds too (the !isEdit guard on foldRun is gone). These pass at the head: the fold, live-runs and consolidated-runs tests, group thought state, step rows and TurnRows (41 pass), plus subagent tray and agents pill. vp check on the changed files is clean.

Observed / Not run

  • Observed on huge-blue, in a sparse checkout of this branch:
    • bun test test/transcript- test/agent-pane- src/agent-session/acpmux has the same failure set as the base turns.ts. Those failures are environment ones in the sparse checkout (browser lanes and so on), and this PR adds none.
    • bun test test/transcript-fold-takeaways.test.ts test/transcript-worked-fold.test.ts: 19 pass.
    • vp check on the changed files: no errors.
    • tsc --noEmit: no errors in the changed files.
    • Layer, type-scale, motion, glyph and protocol checks pass.
  • Not run: the catalog-keys check, because the sparse checkout lacks the prototype catalogs. This PR adds no strings. Nothing ran on cosmic-gray.

No capture: webview-only transcript shaping. The gallery compare above is the visual proof.

Agent session: cosmic-gray

🤖 Generated with Claude Code