fix(desktop): give tool rows the names, arguments and results the UI renders - #821
Merged
Conversation
…renders
Tool rows still looked nothing like the TUI's after the payload-shape fix:
plain untitled entries, no icons, no diffs, and — for anything that returned
text — no output at all. The renderer keys every per-tool formatter off its
own tool vocabulary (read_file/terminal/edit_file/…) and reads output from
named result fields, while the agent speaks Claude Code's (Read/Bash/Edit/…)
and returns plain text. Nothing matched, so every tool fell through to the
unlabelled generic path, and a plain-string result was dropped outright by
the renderer's parseMaybeJsonObject.
The gateway now adapts all three, mirroring the TUI's own summarization
(ui-tui/src/gatewayClient.ts toolContext/formatToolResult) so both surfaces
describe a run the same way:
- Names map onto the renderer's vocabulary, which is what turns on its
titles ("Read file", "Ran command"), icons and tones. Unknown tools pass
through and keep the generic treatment.
- The one argument worth showing rides along as `context`, in the TUI's
priority order — a Grep row shows its pattern rather than its search path.
`path` is aliased in alongside `file_path` because that is the spelling
the file formatters look for.
- Results land where each tool family reads them: `content` for a read,
`output` for a shell run, `inline_diff` for an edit — rebuilt from the
structuredPatch display envelope so an edit renders as a real diff with
add/remove counts, as it does in the TUI. Glob/Grep get a count chip, a
web search its "3 results / 2.4s". Failures show a cleaned reason with
the model-facing markup stripped, and no diff.
Testing: the wire payloads are frozen into a fixture that the Python suite
generates and ui-desktop's new gateway-tool-contract.test.ts renders through
the real renderer, asserting what a user actually sees. That pairing is the
point — this class of bug is invisible to either language alone, since each
side happily tests a field the other never reads. Writing it immediately
caught two more live mismatches: the argument context outranking a Glob's
matches, and a diff that never reached the view.
Full Python suite green; ui-desktop typecheck clean.
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #820. Tool rows still looked nothing like the TUI's: plain untitled entries, no icons, no diffs, and — for anything that returned text — no output at all.
Why
The desktop renderer keys every per-tool formatter off its own tool vocabulary (
read_file,terminal,edit_file, …) and reads a tool's output from named result fields. The agent speaks Claude Code's vocabulary (Read,Bash,Edit) and returns plain text. Nothing matched:TOOL_META→ the unlabelled generic path: no title, no icon, no tonefirstStringField(args, ['path','file','filepath'])never seesfile_pathresultis discarded by the renderer'sparseMaybeJsonObject, so finished tools rendered emptyWhat changed
All three are adapted in the gateway, mirroring the TUI's own summarization (
ui-tui/src/gatewayClient.tstoolContext/formatToolResult) so both surfaces describe a run the same way.Read· (empty)Bash· (empty)$ ls src/transcript · ANSI outputEdit· (empty)Glob· (empty)3 fileschipWebSearch· (empty)3 results·2.4sBash· (empty)Unknown tools (
Task, MCP tools) pass through unchanged and keep the generic treatment, which is the right fallback.Details worth calling out:
contextin the TUI's priority order, so a Grep row shows its pattern rather than its search path.pathis aliased in alongsidefile_pathbecause that is the spelling the file formatters look for.inline_diffis rebuilt from thestructuredPatchdisplay envelope, so it renders as a real diff with add/remove counts — the TUI treats the diff AS the result, and now so does the desktop.<tool_use_error>,<sandbox_violations>) stripped before display.Testing
The wire payloads are frozen into
gateway-tool-events.fixture.json, generated by the Python translator and rendered through the real renderer by the newui-desktop/src/lib/gateway-tool-contract.test.ts, which asserts what a user actually sees.That pairing is the point. This class of bug is invisible to either language alone — each side happily tests a field the other never reads, which is precisely how the blank rows shipped. Writing the test immediately caught two more live mismatches that the Python tests were happy with: the argument
contextoutranking a Glob's matches on the renderer's generic path, and a diff that never reached the view.tsc --noEmit: clean🤖 Generated with Claude Code