Skip to content

feat(ui): dedicated chat components for tool search/select/query - #430

Open
Zerlight wants to merge 7 commits into
masterfrom
ruocheng/code-412
Open

feat(ui): dedicated chat components for tool search/select/query#430
Zerlight wants to merge 7 commits into
masterfrom
ruocheng/code-412

Conversation

@Zerlight

@Zerlight Zerlight commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

Search-type tool calls get their own chat presentation (Closes CODE-412):

  • Search rows (Grep/Glob and agent-native search tools) render a humanized query + scope instead of raw JSON, with expressive per-kind icons.
  • ToolSearch renders its result rows inline; select-style calls keep neutral wording when no result rows exist.
  • Search context is preserved across pending/running/failed states.
  • Codex MCP tool titles — live app-server events and rollout replay (history-tools.ts) — normalize to the shared mcp__<server>__<tool> slug so server identity survives into these rows.
  • Engine git fixtures set commit.gpgsign=false so integration tests pass on signing machines.

Verification

  • pnpm check:ci (0 errors) and full pnpm exec vitest run (2741 passed) at this tip.
  • New unit coverage: tool-search.test.tsx, tool-call-metadata.test.tsx, tool-result-content.test.ts, codex-mcp-tools.test.ts, codex-history.test.ts.
  • Mock showcase extended with the new search/ToolSearch cases — renderable end-to-end via the webview dev:mock conversation showcase.

Checklist

  • pnpm check:ci and pnpm test both pass (plus cargo fmt / clippy / test for Rust changes)
  • I ran the affected surface and observed the change working
  • If a wire message changed: WIRE_PROTOCOL_VERSION is bumped (no wire change in this PR)
  • New code and assets are my own work, or their origin and license compatibility are noted above
  • Docs and comments are updated where behavior changed

Copilot AI lite review requested due to automatic review settings August 8, 2026 02:35
@linear-code

linear-code Bot commented Aug 8, 2026

Copy link
Copy Markdown

CODE-412

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the chat transcript UI for tool calls by introducing dedicated rendering for “search-like” tool calls (including Claude’s ToolSearch) and by normalizing Codex MCP tool titles to the shared mcp__<server>__<tool> slug so MCP server identity is consistently preserved in the UI and in history replays.

Changes:

  • Add ToolSearch-specific parsing + rendering (inline tool-name rows, humanized headers, preserved context across states).
  • Humanize generic search tool headers to summarize counts (matches/files) while keeping raw queries in the expandable body only.
  • Normalize Codex MCP tool-call titles (live adapter + history replay) to mcp__<server>__<tool>, including plugin-app namespace unwrapping; update mocks/tests and fix git fixture signing config for integration tests.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/presentation/ui/src/tool-utils.ts Adds toolCallSearchCounts, adjusts search header/body behavior, and integrates ToolSearch handling into body visibility logic.
packages/presentation/ui/src/chat/tool-search.tsx New ToolSearch result renderer that displays loaded tool names inline with MCP server suffix.
packages/presentation/ui/src/chat/tool-result-preview.tsx Routes ToolSearch calls to the dedicated renderer; updates search preview card icon/behavior for empty-output searches.
packages/presentation/ui/src/chat/tool-result-content.ts Adds toolSearchPresentation parsing to classify ToolSearch output into rows vs. prose message.
packages/presentation/ui/src/chat/tool-kind-icons.ts Updates the search kind icon to TextSearchIcon.
packages/presentation/ui/src/chat/tool-call-item.tsx Humanizes search headers (ToolSearch verbs + generic search counts), suppresses search metadata badges in the body.
packages/presentation/ui/src/chat/activity-run.tsx Swaps activity “explore” icon to TelescopeIcon.
packages/presentation/ui/src/chat/tests/tool-search.test.tsx Adds unit tests covering ToolSearch header/body behavior across states and MCP name splitting.
packages/presentation/ui/src/chat/tests/tool-result-content.test.ts Adds unit tests for toolSearchPresentation parsing behavior.
packages/presentation/ui/src/chat/tests/tool-call-metadata.test.tsx Updates tests to validate search header summarization/count extraction and query placement.
packages/presentation/i18n/src/locales/en.ts Adds localized strings for ToolSearch header verbs and search count summaries.
packages/presentation/i18n/src/locales/zh-cn.ts Adds localized strings for ToolSearch header verbs and search count summaries (zh-CN).
packages/host/engine/tests/integration/git-mutations.test.ts Disables GPG signing in the git fixture to make commits reliable on signing-enabled machines.
packages/host/agent-adapter/src/native/codex/history-tools.ts Normalizes rollout/history MCP function calls into mcp__<server>__<tool> titles.
packages/host/agent-adapter/src/native/codex/adapter.ts Normalizes live Codex MCP tool-call titles to the shared MCP slug and unwraps plugin namespace.
packages/host/agent-adapter/src/tests/codex-mcp-tools.test.ts Adds tests for Codex MCP slug normalization and plugin namespace unwrapping.
packages/host/agent-adapter/src/tests/codex-history.test.ts Adds tests ensuring history replay uses the same MCP slug format as live events.
packages/client/workbench/src/mock/data/showcase.ts Updates mock showcase tool bursts to exercise new search/ToolSearch presentation paths.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +135 to +139
/** Rollout MCP rows are `function_call`s whose sibling `namespace` is `mcp__<server>` (observed
* with a stray trailing `__` on some rows); `name` is the bare tool. Plugin apps namespace as
* `mcp__codex_apps__<app>` with one leading `_` on the tool name — surface the app as the
* server, like the live adapter does. Built-ins carry no namespace or a non-`mcp__` one.
* (Verified against real 0.131–0.146 rollouts, 2026-08.) */
Comment on lines +1284 to +1286
// Emit the shared `mcp__<server>__<tool>` slug — the UI's server/tool join key — instead
// of codex's raw `server.tool`. Plugin apps all mount under the one `codex_apps` server
// with the plugin in the tool's first segment; surface the plugin as the server.
Comment on lines +5 to +7
/** A ToolSearch settle: the loaded tools as one inline line (the humanized header already says
* what happened, so the body is only the result). MCP slugs shed their `mcp__<server>__` envelope
* like tool headers do, keeping the server as a muted suffix. */

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[!IMPORTANT]

The new ToolSearch rendering is solid, but the toolCallHeaderSummary search-case deletion strips header context from search tools that don't emit match counts — WebSearch collapses to a bare WebSearch label. Verified by rendering through ToolCallItem.

Reviewed changes

This PR adds dedicated presentation for Claude Code's deferred-tool ToolSearch calls and normalizes Codex MCP tool titles.

  • New ToolSearchResult component (packages/presentation/ui/src/chat/tool-search.tsx) plus toolSearchPresentation() in tool-result-content.ts, splitting select: mode from keyword mode and rendering mcp__server__tool as tool (server).
  • tool-call-item.tsx grows a header branch computing toolSearch / searchCounts and swaps in ToolCaseIcon; search-kind rows now suppress generic metadata.
  • tool-utils.ts adds toolCallSearchCounts() and changes toolCallHeaderSummary's case 'search' from returning the query to break; SearchRows drops its result-count badge.
  • Codex adapter titles change from ${server}.${tool} to mcp__${server}__${tool}, with a new codexMcpToolName() handling the rollout-JSONL replay path.
  • i18n keys added to both zh-cn.ts and en.ts with matching key sets and valid ICU plurals.

ℹ️ The Codex title change is safe — confirming so it isn't re-litigated

I traced whether mcp__${server}__${tool} could break anything downstream that keys off toolCall.title. It can't: the engine reads title only for approval toolTitle (session-event-processor.ts:150), Codex MCP tools announce as kind: 'other' with no approval path, and deduplication is by toolCallId throughout conversation.ts. codexToolSettle spreads ...existing, so the announce title survives the settle. No action needed — noting it as verified negative evidence.

ℹ️ Nitpicks

The PR bundles three loosely-related concerns — the UI search presentation, the Codex MCP title normalization, and the commit.gpgsign=false git fixture. They're each small, so this isn't worth splitting retroactively, but the title only describes the first.

Separately, the description promises "humanized query + scope" in the header, while the implemented behavior shows counts and removes the query. Worth reconciling the description with what shipped, since the two read as contradictory to a future reader.

Finally, the "I ran the affected surface and observed the change working" checklist item is unchecked — and the search-header path above is exactly the surface where a manual pass would have caught the regression.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

break;
}
// Search queries are raw machine strings (regexes, select: lists) — the localized header
// composes counts via toolCallSearchCounts instead, and the query stays in the body card.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleting the label = toolCallSearchQuery(toolCall) assignment removes the only header context for any search tool that doesn't produce numMatches/numFiles. Those rows now render a bare tool name with nothing else.

I verified this empirically by rendering a WebSearch-shaped ToolCall through ToolCallItem:

contextSummary: undefined
searchCounts:   undefined
HEADER TEXT:    "WebSearch"

Before this PR that header showed the search query. Now the user sees WebSearch with no indication of what was searched for.

The justifying comment — "Search queries are raw machine strings (regexes, select: lists)" — is true for Grep/Glob/ToolSearch but false for WebSearch, whose query is human prose ("linkcode release notes") and whose rawOutput carries durationSeconds, never numMatches/numFiles. So neither the query nor a count reaches the header.

The SearchRows badge removal in tool-result-preview.tsx compounds it: for these tools no count survives in the body either.

Technical details

Two independent paths lead here.

1. WebSearch is classified as kind: 'search'. toolKindFromName() in packages/host/agent-adapter/src/util.ts tests SEARCH_TOOL_NAME_RE = /search|grep|glob|find/ before FETCH_TOOL_NAME_RE = /fetch|web|http|browser/, so WebSearch matches the search branch and never reaches fetch. It arrives at the UI as { title: 'WebSearch', kind: 'search' } and flows through this changed arm.

2. toolCallSearchCounts is record-only. It calls recordValue(toolCall.rawOutput), which rejects both strings and arrays. Adapters whose search rawOutput is a bare string or an array of results (Pi, opencode) return undefined for the same reason, so this isn't limited to WebSearch — it's every search-kind tool outside Claude's Grep/Glob shape.

A targeted fix: keep the query as the fallback label when counts are unavailable, rather than dropping it unconditionally.

case 'search': {
  if (toolCallSearchCounts(toolCall)) break;
  label = toolCallSearchQuery(toolCall);
  break;
}

That preserves the intended behavior for Grep/Glob/ToolSearch (counts win, query moves to the body card) while leaving WebSearch and non-record-output adapters with a meaningful header.

@lucas77778 lucas77778 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking findings:

  1. packages/presentation/ui/src/tool-utils.ts:203-206 removes the query from every generic search header, while ToolCallItem only substitutes counts when rawOutput has a supported count envelope. WebSearch is classified as kind: search by toolKindFromName, and Pi/OpenCode-style searches also commonly have no numMatches/numFiles, so their collapsed rows become a bare tool name (and all in-progress searches are indistinguishable). I reproduced the resulting shape as {kind:"search", headerSummary:undefined, counts:undefined} for a WebSearch call with query linkcode release notes. Keep toolCallSearchQuery as the fallback summary (or append counts to it), while retaining the dedicated ToolSearch branch that hides select: queries; add coverage for an output-less generic search and a counted search.

  2. packages/host/agent-adapter/src/native/codex/adapter.ts:1287-1299 embeds the raw Codex MCP server name in mcp__<server>__<tool>, but mcpToolName splits on the first __. LinkCode accepts arbitrary non-empty custom MCP names, and the pinned Codex CLI accepts repo__prod (verified with an isolated CODEX_HOME: codex mcp add repo__prod -- /bin/true and codex mcp list --json both succeeded). A call from server repo__prod, tool search_files is emitted as mcp__repo__prod__search_files and rendered as server repo, tool prod__search_files, defeating the stated identity-preservation goal. Preserve a non-slug fallback for delimiter-containing Codex names (live and history), or introduce an unambiguous representation; add live/history parser cases for such a valid name.

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.

3 participants