diff --git a/docs/design/tool-use-summary/unified-tool-output.md b/docs/design/tool-use-summary/unified-tool-output.md new file mode 100644 index 00000000000..41f3ee2dc26 --- /dev/null +++ b/docs/design/tool-use-summary/unified-tool-output.md @@ -0,0 +1,68 @@ +# Unified Tool Output Rendering + +## Background + +The TUI previously had two rendering modes for tool results: + +- **Compact mode** (Ctrl+O): collapsed completed tool results into a one-line summary +- **Normal mode**: showed full tool results inline, causing excessive vertical noise + +Users had to manually toggle between modes. Most of the time, completed tool results (file contents, search results, etc.) added no value to the conversation flow. + +## Design + +### Core Principle + +**One unified mode**: tool rendering is determined by tool category, not by a user-toggled mode. Information-gathering tools (read/search/list) are collapsed into a summary; mutation tools (edit/write/command/agent) always render individually with full results. + +### Semantic Summary (`buildToolSummary`) + +Instead of showing raw tool names and counts (`ReadFile x 3`), generate human-readable summaries using a count-based format: + +| Scenario | Output | +| ------------------ | --------------------------------------------- | +| Single tool | `Read 1 file` / `Ran 1 command` | +| Multiple same-type | `Read 3 files` | +| Mixed types | `Ran 1 command, read 3 files, edited 2 files` | +| Active (executing) | `Reading 1 file` (present progressive) | +| Completed | `Read 1 file` (past tense) | + +### Tool Categories + +| Category | Display Names | Past Verb | Active Verb | Collapsible | +| -------- | ---------------------------- | --------- | ----------- | ----------- | +| read | ReadFile, Read File(s) | Read | Reading | Yes | +| edit | Edit, NotebookEdit | Edited | Editing | No | +| write | WriteFile | Wrote | Writing | No | +| search | Grep, Glob | Searched | Searching | Yes | +| list | ListFiles, Read Directory | Listed | Listing | Yes | +| command | Shell | Ran | Running | No | +| agent | Agent, Workflow, SendMessage | Ran | Running | No | +| other | (everything else) | Used | Using | No | + +### Rendering Rules + +1. **Type-based partition**: tools are split by `isCollapsibleTool()` — collapsible tools (read/search/list) render as a `CompactToolGroupDisplay` summary line; non-collapsible tools (edit/write/command/agent/other) render individually via `ToolMessage` +2. **Memory-only groups** have a dedicated rendering path (read/write counts badge) that takes priority, but only when all ops succeed (`!hasErrorTool && every status === Success`) +3. **Result collapse**: only collapsible tools with `Success` status have their text/ANSI output collapsed. Non-collapsible tools (including MCP tools, WebFetch, etc.) always show results. Canceled tools keep partial output visible +4. **Tool names** render bold regardless of status, providing consistent styling across both `CompactToolGroupDisplay` and individual `ToolMessage` paths +5. **Force-expand conditions**: when any tool in a group is confirming, errored, user-initiated, in a focused shell, or a terminal subagent, ALL tools render individually (no partition) with results forced visible only for the triggering tools (errored, confirming, terminal subagent) — successful siblings keep normal collapse behavior +6. **`tool_use_summary`** items (LLM-generated semantic summaries) render unconditionally alongside `CompactToolGroupDisplay`'s mechanical count — they serve different purposes (semantic context vs tool count) +7. **Memory badge**: rendered in both the all-collapsible path and the mixed path when memory ops are present in a non-memory-only group + +### Key Changes + +| File | Change | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CompactToolGroupDisplay.tsx` | Added `buildToolSummary()` with count format, `isCollapsibleTool()`, removed border styles | +| `ToolMessage.tsx` | `shouldCollapseResult` gated on `isCollapsibleTool()` and `Success` only; `isDim` removed | +| `ToolGroupMessage.tsx` | Type-based partition replaces `showCompact`; `forceShowResult` simplified to `forceExpandAll`; height budget accounts for collapsible summary row | +| `MainContent.tsx` | Removed `mergedHistory` alias, `absorbedCallIds`, `summaryByCallId`, cross-group merging | +| `HistoryItemDisplay.tsx` | `tool_use_summary` renders unconditionally (removed `summaryAbsorbed` gate) | +| `mergeCompactToolGroups.ts` | `compactToggleHasVisualEffect` no longer triggers on `tool_group` (compact mode has no effect on tool rendering) | + +## Alternatives Considered + +1. **Keep two modes with improved summaries**: Rejected — unnecessary cognitive overhead for users +2. **Per-tool summary (Gemini CLI style)**: Each tool gets its own summary arrow. Rejected — still too verbose for large tool batches +3. **Phased rollout**: Rejected — user preference for single implementation pass diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx index 821bc39a0a3..4d0efe6e646 100644 --- a/packages/cli/src/ui/AppContainer.test.tsx +++ b/packages/cli/src/ui/AppContainer.test.tsx @@ -3443,7 +3443,7 @@ describe('AppContainer State Management', () => { ); }); - it('calls refreshStatic on Ctrl+O when history contains a tool_group', () => { + it('skips refreshStatic on Ctrl+O when history contains only tool_group (no visual effect)', () => { mockedUseHistory.mockReturnValue({ history: [ { type: 'user', id: 1, text: 'run ls' }, @@ -3483,7 +3483,7 @@ describe('AppContainer State Management', () => { expect(handler).toBeDefined(); handler!(ctrlOKey); - expect(mockStdout.write).toHaveBeenCalledWith( + expect(mockStdout.write).not.toHaveBeenCalledWith( ansiEscapes.clearTerminal, ); }); diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.tsx index 3d6871c1057..69a1fcecbd6 100644 --- a/packages/cli/src/ui/components/HistoryItemDisplay.tsx +++ b/packages/cli/src/ui/components/HistoryItemDisplay.tsx @@ -57,7 +57,6 @@ import { BtwMessage } from './messages/BtwMessage.js'; import { MemorySavedMessage } from './messages/MemorySavedMessage.js'; import { DiffStatsDisplay } from './messages/DiffStatsDisplay.js'; import { GoalStatusMessage } from './messages/GoalStatusMessage.js'; -import { useCompactMode } from '../contexts/CompactModeContext.js'; import { useSettings } from '../contexts/SettingsContext.js'; import { useThoughtExpanded } from '../contexts/ThoughtExpandedContext.js'; import { useThinkingViewer } from '../contexts/ThinkingViewerContext.js'; @@ -76,21 +75,6 @@ interface HistoryItemDisplayProps { activeShellPtyId?: number | null; embeddedShellFocused?: boolean; availableTerminalHeightGemini?: number; - /** - * When the item is a `tool_group`, an optional short LLM-generated label - * summarizing the batch. Replaces the generic "Tool × N" line in compact - * mode. Computed by the parent from `tool_use_summary` history items. - */ - compactLabel?: string; - /** - * When the item is a `tool_use_summary`, true if a sibling tool_group has - * absorbed this label via its compact-mode header. The standalone `●