Tool group summary

A folded run of tool calls in the chat transcript is headed by one line. This page defines what that line says: a short account of what the calls did, built on the client from the tool calls already in the transcript, with a +N −M badge when the run changed files. It also covers whether a turn-level summary is worth adding.

Where the row comes from

assistant-bubble.tsx splits a turn's ordered blocks into segments. Each text block stands alone. Each run of consecutive thinking and tool blocks becomes one ExecutionStrip from execution-strip.tsx, and summarizeToolGroup() writes its header. Older records without ordered blocks go through LegacyRuntimeTrace, which feeds its calls to the same function. The collapsed row is a single 28px line, as the chat-turn visual spec requires.

Data each call carries

FieldSourceUse
toolblockCategory through presentTool() in tool-presentation.ts; this is the same categorisation the timeline rows use.
inputblock, full JSON of the call argumentsPaths (file_path, path), commands, search patterns, URLs. edit carries old_string/new_string, write carries content, and apply_patch carries the whole patch.
result, is_error, outcomeblockSuccess or failure. The edit result says (N replacements), and the apply_patch result says Added, Updated or Deleted.
runningmsg.tools[].statusWhich call is in progress.
turnFilesassistant message, written at finalize from the mutation journalAuthoritative per-file op (create/modify/delete) and added/removed for the whole turn. It is aggregated per path, has no tool-call id, and only exists once the turn settles.

So per-group stats cannot be read straight from turnFiles when two groups in a turn touch the same file. They can be derived from the arguments, and turnFiles fills the gaps.

Summary grammar

Phrases

Calls in a group are bucketed by kind. Each bucket yields one phrase, placed where its first call occurred, so the line reads in the order things happened. The first phrase is capitalised. Phrases are joined with ", " (Chinese ","). Thinking gives "Thought" only when the group has nothing else; otherwise it is left out, because every turn thinks.

Kind (tools)OneSeveralRunning
Command (bash, process, terminal_use, execute_code)Ran a commandRan 3 commandsRunning a command…
Created file (write to a new path, apply_patch Add File)Created pill-shadow.htmlCreated 2 filesCreating pill-shadow.html…
Edited file (edit, write over an existing file, apply_patch Update)Edited app.tsxEdited 4 filesEditing app.tsx…
Deleted file (apply_patch Delete File)Deleted old.cssDeleted 2 filesDeleting old.css…
Read (read, pdf)Read package.jsonRead 3 filesReading package.json…
List (list)Listed a folderListed 2 foldersListing a folder…
Code search (grep, glob, semble_*, lsp_*)Searched for "tl-toggle"Ran 3 searchesSearching for "tl-toggle"…
Web search (web_search)Searched the webSearched the web 2 timesSearching the web…
Fetch (web_fetch)Fetched docs.anthropic.comFetched 3 pagesFetching docs.anthropic.com…
Browser (web_use, agent_browser, playwright_browser)Operated the browserOperating the browser…
Sub-agent (agent, task, send_message)Ran sub-agent ReviewerRan 2 sub-agentsRunning sub-agent Reviewer…
Image (image_generate / image_analyze)Generated an imageGenerated 2 imagesGenerating an image…
Todos (todo_*)Updated todosUpdating todos…
Question (ask_user_question)Asked a questionAsking a question…
Memory (memory_*)Accessed memoryAccessing memory…
Program / skillRan program deploy, Used skill pdfRan 2 programsRunning program deploy…
Anything else (MCP, custom)Used linear_search (×2 adds " 2 times")Used linear_search and jira_create; Used 3 toolsUsing linear_search…

Names: one distinct file shows its basename. A command is never named in the header, because commands are long and the expanded row already shows them. A search pattern is quoted and cut to 24 characters. A fetch shows the host only. A bucket names its single target only when it has exactly one distinct target, so "Read a.ts" turns into "Read 2 files" once a second file appears. Repeated calls on the same file count once.

Numbers: English uses "a"/"an" for one and digits for two or more. Chinese always uses digits with a measure word (1 条命令, 3 个文件, 2 次).

Failures: if any call in the group failed, a muted red "· 1 failed" follows the label. A failed call still counts in its bucket, and failed file writes add nothing to the badge.

Running state

While the group is the live part of a streaming turn, completed buckets keep the past tense and the bucket of the call in progress switches to the present tense with a trailing "…". It moves to the end of the line, where the eye expects the newest step, for example "Read 2 files, running a command…". The shimmer stays. When the call finishes, the phrase settles back to the past tense in its first-occurrence position. If the group only has thinking so far, the label is "Thinking…".

Truncation

On screen the label is one line: the label span has min-width:0 with a CSS ellipsis, and the failure note, badge and chevron are flex:none, so they never drop off. There is no character budget, so the same label fits a narrow side pane and a wide window. The row has no hover tip of any kind: it is transcript content, not a control whose purpose needs explaining, and expanding it shows every call.

Change badge

The badge shows when at least one file-writing call in the group succeeded and every successful one has known counts. It sums those counts, shows zeros (+16 −0), and reuses the turn file card's .turn-files-diff pill. Counts per call:

This also decides create versus edit for write. Until turnFiles exists, a write counts as "Created" only if the turn has not read or edited that path before. When turnFiles says modify, it becomes "Edited".

Strings

Every phrase goes through the existing text(en, zh) from useTranslation(). Each bucket has one entry: a past and a present form, each in a one-target and a count variant. Chinese uses 了 for the past and 正在 for the present: "运行了 1 条命令,创建了 pill-shadow.html" and "读取了 2 个文件,正在运行命令…".

Where the summary is built

All of it is built on the client from structured tool data, as a pure function next to tool-presentation.ts. No model call and no backend change is needed. One optional backend refinement would make write exact while streaming and give exact per-group counts when two groups touch one file: stamp the tool-call id on each mutation-journal entry and send per-call {path, op, added, removed} with the tool result.

Preview

Each pair contrasts a category-count header with this design's header for the same calls. Both are collapsed rows at the transcript's 15px size.

Command, then a new file
Counts
Summary
Investigate, then fix one file
Counts
Summary
Web research
Counts
Summary
MCP and custom tools
Counts
Summary
A command failed
Counts
Summary
Running (streaming)
Counts
Summary
Summary
Long group in a narrow pane (hover shows the full label)
Counts
Summary

Turn-level summary

A single line at the top of a turn, such as "Read 9 files, edited 3 files, ran 6 commands · +40 −12", was considered.

The turn-level line is not built. Where a turn must collapse to one line elsewhere, such as the message minimap, a hover preview, or a sub-agent row in the parent turn, that surface calls the deterministic aggregate.

Implementation

Implementation status

Implemented as described. The optional backend refinement (tool-call ids on mutation-journal entries) is not built, so a write over an existing file shows its badge only once the turn settles.