Skip to content

fix(desktop): render tool calls in the transcript instead of blank "Tool" rows - #820

Merged
agentforce314 merged 1 commit into
mainfrom
fix/desktop-tool-rendering
Aug 9, 2026
Merged

fix(desktop): render tool calls in the transcript instead of blank "Tool" rows#820
agentforce314 merged 1 commit into
mainfrom
fix/desktop-tool-rendering

Conversation

@agentforce314

Copy link
Copy Markdown
Owner

Tool activity reached the desktop transcript as contentless rows labelled "Tool", and raw content-block JSON leaked into assistant bubbles. The transcript looked nothing like the TUI's tool output.

Three mismatches against what the renderer actually reads (ui-desktop/src/lib/chat-messages.ts):

sent read by renderer effect
(no name) payload?.name || "tool" every row labelled "Tool"
output payload.result finished tools rendered empty
is_error payload.error failures looked like successes

Changes

  • Tool names carry across frames. A tool_result names only its tool_use_id, so DesktopSession now keeps an id → name map populated from the assistant frame's tool_use blocks and consumed when the result arrives.
  • Renderer field names. tool.complete sends result/error. The agent's trimmed display envelope (tool_use_result: structuredPatch, filePath, duration_s, inline_diff, summary, preview) is forwarded as well — that is what lets an Edit row show a diff instead of raw text, and what puts the elapsed time on a long-running row.
  • No JSON in prose. Payloads where the UI expects text ran through coerceGatewayText, which JSON-stringifies an object it cannot read — that is how a bare {"type":"tool_use",…} block ended up printed inside an assistant bubble. New as_text() flattens content blocks and keeps only the text ones, so a tool block can never reach the transcript as prose.

Testing

  • New tests/server/test_desktop_translate.py pins the payload contract: name carried across the tool_usetool_result pair, result/error field names, display envelope forwarded, and as_text flattening a tool block to the empty string.
  • Full Python suite: 9892 passed, 15 skipped.
  • Verified live against a real agent turn — labelled Read/Glob/Write rows with their output and duration, a rendered approval bar, and no tool_use JSON anywhere in the transcript.

🤖 Generated with Claude Code

…ool" rows

Tool activity reached the desktop transcript as contentless rows labelled
"Tool", and raw content-block JSON leaked into assistant bubbles. Three
mismatches against what the renderer actually reads (lib/chat-messages.ts):

- `tool.complete` sent no `name`, so `upsertToolPart` fell back to the
  literal "tool". A tool_result names only its tool_use_id, so the session
  now carries an id -> name map across the assistant/user frame pair.
- It sent `output`/`is_error`; `toolResult()` reads `result`/`error`, so
  every finished tool rendered empty. The agent's trimmed display envelope
  (`tool_use_result`: structuredPatch, filePath, duration_s, inline_diff,
  summary, preview) is forwarded too, which is what lets an Edit row show
  a diff rather than raw text.
- Prose payloads passed content blocks through untouched, and the
  renderer's coerceGatewayText JSON-stringifies an object it can't read.
  `as_text()` flattens blocks and keeps only the text ones, so a tool_use
  block can no longer print itself into the transcript.

Verified live against a real agent turn: labelled Read/Glob/Write rows with
output and duration, and no tool_use JSON anywhere in the transcript.

Co-Authored-By: Claude <noreply@anthropic.com>
@agentforce314
agentforce314 merged commit 8b9f925 into main Aug 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant