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