SIGN IN SIGN UP

Claude agent host: Phase 8.5 — rich tool-call rendering (#317184)

* Claude agent host: roadmap status sync + Phase 8.5 (rich tool-call rendering)

- Mark Phases 5, 6, 7, 8 as DONE (implementations have shipped; just
  catching the headings up to reality).
- Insert Phase 8.5 — Rich tool-call rendering parity with Copilot.
  Today the Claude permission card for Bash reads 'Run shell command'
  with no command shown; Bash/Grep/Glob rows render in the generic
  renderer instead of the dedicated terminal/search renderers. This
  phase ports Copilot's getInvocationMessage / getPastTenseMessage /
  getToolKind / getShellLanguage / getToolInputString shape into
  claudeToolDisplay.ts and wires them through the permission, mapper,
  and replay paths.

Phase 6.5 (Fork) intentionally stays Deferred.

* Claude agent host: Phase 8.5 plan (super-planner + grilling)

Adds phase8.5-plan.md alongside roadmap.md's Phase 8.5 section.

Synthesized from a 3-model council (GPT-5.5, Claude Opus 4.6,
GPT-5.3-Codex) and refined through a grill-with-docs session.

Locked decisions:
- D1: getClaudeToolKind is a TOOL_ROWS column (single source of truth).
- D2: add Agent row to TOOL_ROWS; delete SUBAGENT_TOOL_NAMES.
- D3: defensive Record<string, unknown> access; no per-tool exported
  types.
- D4: pastTenseMessage is success-aware (tool_result.is_error).
- D5: live mapper mirrors Copilot's stash-on-start/reuse-on-complete
  pattern, with state encapsulated in a new ClaudeToolCallRegistry
  class (replaces today's bare maps on ClaudeMapperState).
- D6: _meta single-write on Start; reducer carries to Complete; replay
  emits on its single terminal action (asymmetry by design).
- D7: MCP tools get toolKind: undefined.
- D8: one big per-tool snapshot table covering all 5 helpers.
- D9: getClaudeInvocationMessage('Task', ...) owns the Task description
  fallback; Phase 12's site reduces to a plain helper call.
- D10: _meta stays flat (no per-kind namespacing).

Steps cover claudeToolDisplay (helpers + columns), claudeCanUseTool
(permission card rich invocation + _meta), sessionPermissions (forward
_meta through pending->ready), claudeMapSessionEvents (registry
migration + _meta on Start + success-aware past tense),
claudeReplayMapper (parity), claudeSubagentSignals (inner tool
parity), and the snapshot + behavior tests.

* Claude agent host: Phase 8.5 implementation (rich tool-call rendering)

Brings tool-call rendering parity with Copilot:

- Rich invocation/past-tense messages per tool (Bash 'Running `git status`'
  → 'Ran `git status`', Read 'Reading [README.md](...)' → 'Read [README.md](...)',
  Grep/Glob with patterns, file links for path-bearing tools, subagent
  descriptions for Task/Agent).
- `_meta.toolKind` ('terminal'/'search'/'subagent') stamped at the tool-open
  seam to drive the workbench's specialized renderers; reducer carries it
  forward through every state transition (D6 in plan).
- New `ClaudeToolCallRegistry` encapsulates per-session tool-call attribution
  + input accumulation + computed start-info, mirroring Copilot's
  'stash on start, reuse on complete' pattern.

Critical bug fix bundled in:

- Emit `SessionToolCallReady` at `content_block_stop` so auto-allowed
  tools (which the Claude SDK runs without invoking `canUseTool`) transition
  Streaming → Running and the subsequent `SessionToolCallComplete` is
  accepted by the reducer instead of dropped. Without this, every
  auto-allowed tool widget rendered empty after completion.

D6 parity fix for inner subagent tools:

- `claudeSubagentSignals` now calls `registry.seedParsedInput()` for inner
  tool_use blocks (which arrive pre-parsed on synthesized assistant
  messages rather than via input_json_delta), so the live tool_result
  handler emits rich past-tense text matching the replay path instead of
  falling back to '{displayName} finished'.

Also fixes a pre-existing Phase 10 stub:

- `ClaudeAgent.onClientToolCallComplete` is now a benign no-op. The
  AgentSideEffects autorun fires this hook for EVERY server-dispatched
  SessionToolCallComplete envelope (including normal SDK tool completions),
  so the previous `throw new Error('TODO: Phase 10')` corrupted every
  tool flow. Client (MCP) tool registration via `setClientTools` still
  throws since Phase 10 hasn't landed.

Tests:
- New: `claudeToolCallRegistry.test.ts` (lifecycle + seedParsedInput coverage).
- Snapshot: `claudeToolDisplay.test.ts` covers every tool row × all helpers.
- Mapper: `claudeMapSessionEvents.test.ts` Test 9.5 asserts the new
  content_block_stop Ready emission.
- Subagent: `claudeSubagentSignals.test.ts` extended to assert rich
  past-tense for inner-tool completion (D6 parity).
- Agent: `claudeAgent.test.ts` asserts onClientToolCallComplete is a no-op.

Verified live in the Agents window: NEW Claude [Local] chat with
'Run `git status` and then read README.md' produces both tool widgets
in the expanded thinking block with command + output visible.

* Address PR review feedback

- claudeCanUseTool: drop the redundant '?? JSON.stringify(input)' fallback.
  getClaudeToolInputString already wraps stringify in try/catch and
  returns undefined on failure; the outer call was re-running the same
  stringify that just failed (would throw on non-serializable input).

- sessionPermissions.createToolReadyAction: drop the state._meta
  forwarding. The reducer's SessionToolCallReady branch derives _meta
  via tcBase(tc) from the prior state, so the action-level _meta was
  never read.

- claudeToolCallRegistry.finalize: preserve the raw inputBuffer as
  toolInput when JSON.parse fails or yields a non-object. Without this,
  malformed payloads rendered an empty input section in the UI.
  Test updated to assert raw-buffer preservation.

- claudeToolDisplay.formatPathAsMarkdownLink + WebFetch link: escape
  the link label via escapeMarkdownLinkLabel. File names containing
  ']' or '\\' would otherwise break out of the [...] label and could
  cause malformed rendering / injection.

- claudeAgent.integrationTest (Phase 7 §5.3 Read tool round-trip):
  expected signal sequence updated for the Phase 8.5 mapper's new
  SessionToolCallReady emission at content_block_stop. The mapper-side
  Ready (auto-allow path) now lands between toolCallDelta and the
  permission card's pending_confirmation.
T
Tyler James Leonhardt committed
1e4d8bd8967be11ae2878cafe076c86846601aa1
Parent: 85bd57c
Committed by GitHub <noreply@github.com> on 5/18/2026, 11:19:24 PM