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>
agentforce314
added a commit
that referenced
this pull request
Aug 9, 2026
… Desktop The Electron app now builds, runs, tests, and packages natively on Windows 10/11. Most of the machinery shipped with the upstream import and needed verification rather than porting; this commit fixes what actually broke and adds the missing gates. Verified live on Windows 11, end to end - `npm run dist:win:nsis` → release/ClawCodex-0.17.0-win-x64.exe (unsigned; icon + version metadata stamped via rcedit; node-pty conpty binaries staged; install-stamp pins the backend commit). - Silent NSIS install → %LOCALAPPDATA%\Programs\ClawCodex → app boot → backend spawned from the shared %USERPROFILE%\.clawcodex\clawcodex venv (the install.ps1 layout; resolveClawCodexHome honors an existing ~/.clawcodex) → healthy loopback gateway → a real DeepSeek (deepseek-v4-flash) chat turn driven through the app's own JSON-RPC gateway (session.create → prompt.submit → reply streamed back). - Packaged-app Playwright spec: 4/4 against the real win-unpacked binary. - scripts/test-desktop.mjs bundle validation passes. Fixes - src/entrypoints/desktop_cli.py: spawn the shutil.which-resolved npm.cmd — Popen never PATHEXT-searches a bare `npm` on Windows, so `clawcodex desktop` died with FileNotFoundError. Docstring named the wrong env var (CLAWCODEX_DESKTOP_CLAWCODEX_ROOT is what main.ts reads). - electron/update-relaunch.ts + main.ts: the updater's release-dir resolvers used the upstream `apps/desktop/` layout, which never matches this repo's `ui-desktop/` — in-app updates always ended in "reinstall the GUI" and the mac bundle swap never found the rebuilt .app. The relaunch handoff (a bash script) is now explicitly skipped on Windows in favor of the honest manual-restart state. - scripts/stage-native-deps.mjs: dropped the hashbang — a `#!` line in a module IMPORTED by vitest tests lands mid-source after the SSR wrapper and kills collection of both packaging test files with "SyntaxError: Invalid or unexpected token". - src/themes: the legacy "nous" alias key points at the same theme object, so Object.values() listed the clawcodex theme twice in every picker and /skin (and made the registry-order test unpassable). BUILTIN_THEME_LIST now dedupes by identity and listAllThemes consumes it; a regression test pins that the alias still resolves. - src/components/onboarding tests: still asserted the upstream "Nous Portal" featured provider; realigned to this product's FEATURED_ID (anthropic) with equal assertion strength. - package.json `fmt`: single quotes are literal under cmd.exe — prettier matched nothing on Windows. Double quotes work everywhere. - src/lib/gateway-tool-contract.test.ts: import-group spacing error that shipped in #821 broke `npm run lint` (its one error). - e2e/launch-packaged-app.spec.ts: the sandboxed packaged boot terminates at first-run onboarding by design (isolated config has no providers); the overlay heuristic now recognizes that state instead of timing out. Windows test enablement (electron + ui vitest projects green) - electron project: 934 passed / 0 failed. POSIX mode-bit asserts gated; EBUSY-tolerant async rm where a probe child still holds the cwd; ssh fixtures normalize host separators; mux lifecycle tests pin mux:true (Windows OpenSSH has no ControlMaster, so the constructor defaults it off there); bash relaunch-script lint feeds stdin on Windows (MSYS mangles C:\ argv paths); drive-qualification fix for rootless POSIX fixtures under path.resolve. - ui project: 3596 passed / 0 failed, twice consecutively. Beyond the theme/onboarding fixes above, the load flakes' mechanism was a starved worker's first test dying mid-import at the 15s deadline, its abandoned render then leaking into the NEXT test's DOM ("Found multiple elements" cascades); widened the two existing centralized knobs (testTimeout 15s→30s, RTL asyncUtilTimeout 5s→10s) — success still resolves the instant a node appears. CI - New "Desktop (os)" job on ubuntu-latest + windows-latest: npm ci, typecheck (3 tsconfigs), vitest run (both projects). Packaging and Playwright stay local/release-time. Docs: ui-desktop/README status refreshed (works end to end, packaging on both OSes), root README news entry, CHANGELOG under [Unreleased]. Co-Authored-By: Claude Fable 5 <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