AGENTS.md@packages/client/src/components/tool-renderers · git:20260822.175327a · 2026-08-22 · sha256 676adc84dafa96b5
AGENTS.md@packages/client/src/components/tool-renderers git:20260822.175327aA
Immutable. This exact content is served forever at /api/v1/blob/676adc84dafa96b5.
# DOX — packages/client/src/components/tool-renderers
Files in this directory. One row per source file.
| File | Purpose |
|------|---------|
| `AgentToolRenderer.tsx` | Renders `Agent` tool (pi-dashboard-subagents). Card via `AgentCardShell`; maps `AgentDetails` status → running/complete/error/stopped; shows `PromptBlock`, `ResultBlock`, stats line, `CardControls` expand toggle + popout button; expanded body mounts `SubagentDetailView`. Exports `AgentToolRenderer`. Popout opens `SubagentDetailView` (mode=`popout`) inside the shell `ui:dialog` primitive (`flush`, size `lg`, no title; `onBack`→close), NOT `window.open` — fixes Electron/PWA/mobile popout breakage, parity with `flow-agent-detail`. Affordance disabled unless `agentId && session` resolved. See change: fix-subagent-live-detail-reliability (D4). Inline expand toggle (`toggleExpand`) fires `subagent_resync_request` on open when subagent still running + timeline empty (or map missing entry) — same `requestResyncIfStale` helper as popout `openDetail`; fixes inline expand showing "Subagent not found in this session." for running subagents after refresh/late-subscribe. See change: fix-subagent-inline-expand-resync. `PromptBlock` renders `text.slice(0,1000)` but sets `data-copy-text={text}` (full) so a transcript selection copies the whole prompt, not the DOM-capped prefix. See change: chat-copy-fidelity-intercept. The subagent error line marks `Error:` in `--severity-error-fg` and the message in `--text-secondary`. See change: repair-tool-error-surfaces. |
| `AskUserToolRenderer.tsx` | Renders `ask_user` tool. Method badge + question + options + parsed `User responded:` result. `method:"batch"` branches to `AskUserBatchRenderer` reconstructing per-question answers from `args.questions` + `toolDetails.results`. Exports `AskUserToolRenderer`. Error branch: icon `--severity-error-fg`, message `--text-secondary` (see change: repair-tool-error-surfaces). |
| `BashToolRenderer.tsx` | Renders `bash` tool. Shows `$ command` + timeout; strips ANSI via `stripAnsi` then `LinkifiedText` in result block; `ToolResultImages` for image output. Exports `BashToolRenderer`. |
| `CtxToolRenderer.tsx` | Single React renderer registered for all context-mode `ctx_*` MCP tools. Calls `parseCtxResult(toolName, result, isError)`, switches body layout on parsed `kind`. Shared header chip (`⚙ shell · N lines`, `▦ N cmds · N sections · N queries`, `🔍 N queries`, `🗂 N sections`, `🌐 host · N sections`) + error card. Body branches: execute/execute_file (args.code as syntax-highlighted CodeBlock + stdout + intent preview list), batch (Indexed Sections collapsible + per-query accordions), search (per-query accordions, "No results found" badge), index/fetch (compact one-liner + url link), insight (dashboard URL link button + log), error (severity-token card: `--severity-error-{bg,border}` chrome + `--severity-error-fg` label, body in `--text-secondary` on `--bg-code`; a structured runtime error renders `command` via `CodeBlock`, an `exit <n>` badge and labelled `stdout`/`stderr` sections, else falls back to the flat message; each stream section passes through `truncateOutputForDisplay` (last 200 lines + `«N earlier lines hidden»`, same helper as `BashOutputCard`, test-plan #X3); collapsible Received arguments), raw fallback (LinkifiedText, no JSON-args dump). Accordions default collapsed. Body `max-h-80 overflow-auto`. Never renders `JSON.stringify(args)`. See change: add-ctx-tool-renderer. |
| `EditToolRenderer.tsx` | Renders `edit` tool. Diff priority: `toolDetails.diff` → `oldText`/`newText` → `edits[]` text edits → hashline replace/append/prepend summaries. Mobile uses `HomegrownDiff`, desktop `RichDiff`. `OpenFileButton` for path. Exports `EditToolRenderer`. |
| `FileLink.tsx` | Inline clickable file ref inside tool output. Click routes via shared `useFileOpenRouting` hook (no external-editor branch). Title carries resolved abs path. Inline-only styling, preserves selection. See change: linkify-tool-output. Absolute tokens (`token.absolute`) skip cwd join — pass path through unchanged. Click routes via shared `useFileOpenRouting` hook. See change: unify-file-link-openability. `<button>` gets `draggable={false}` + inline `userSelect: "text"` so click-drag extends text selection (highlight + Ctrl+C / right-click Copy work); plain click still opens. See change: selectable-tool-output-links. Preview state hoisted to `FilePreviewProvider`; renders own `FilePreviewOverlay` only on no-provider fallback (`!hostManaged && previewTarget`). See change: fix-file-preview-survives-message-churn. Resolution now routes through `resolveLinkOrigin` (lib/link-origin.ts): worktree session re-roots an absolute parent-checkout path onto the worktree's own tree for tooltip + open + preview target; `openFile` gets the re-rooted target, not the raw token. See change: fix-worktree-link-origin. Routes rel-path click through `openInSplit` when split context present; else preview overlay. External-editor (`openEditor`) branch removed; title always `Preview <path>`. See change: split-editor-workspace, remove-external-editor-integration. Click now LAZILY resolves via `resolveFileMention(cwd, path)` (lib/resolve-mention-api): server-resolved abs path opened DIRECTLY across preview overlay AND split (G2 — split routes through resolve too, `absolute` tokens skip split), no `resolveLinkOrigin` on the resolved path (D4, no double re-root). `resolved:null` → inline not-found affordance (`data-not-found`/`aria-disabled`/line-through, NO open, G1); resolve request FAILURE → D5 fallback to client-side `resolveLinkOrigin` open (rejection caught). `resolveLinkOrigin` survives for tooltip + fallback only. Sends token `path` (normalized), not verbatim text (D1). See change: server-side-file-mention-resolution. Link colour now `text-[var(--link)]` (mode-aware) instead of the raw `text-blue-400`, which measured 1.80–2.52:1 on `--bg-code` in all 9 light themes; gated by `severity-contrast.spec.ts` #F8. See change: repair-tool-error-surfaces. |
| `GenericToolRenderer.tsx` | Fallback renderer. Renders raw JSON `args` verbatim + `LinkifiedText` result block + `ToolResultImages`. Exports `GenericToolRenderer`. |
| `index.ts` | Barrel. Re-exports `getToolRenderer`, `registerToolRenderer` from `registry.js`; types `ToolContext`, `ToolRendererProps`, `ToolRenderer` from `types.js`. |
| `lang-detect.ts` | Maps file extension/basename → Prism language id via `EXT_MAP`. Exports `detectLanguage(filePath)`. |
| `LinkifiedText.tsx` | Renders tokenised tool output. Memoises `tokenize(text)`. Maps tokens → `<UrlLink>` / `<FileLink>` / `React.Fragment`. Wraps in `ErrorBoundary` with plain-text fallback. See change: linkify-tool-output. |
| `make-tool-context.tsx` | `makeToolContext()` (the only production `ToolContext` builder) + `withDefaultFileLink()` for embedder contexts. Attaches the `fileLink` renderer that gates markdown linkification; hand-built contexts silently lose it. See change: cleanup-import-cycles. |
| `OpenFileButton.tsx` | Plain button opening a file in the internal Monaco pane. Renders null only when no cwd or no filePath. Click routes via shared `useFileOpenRouting` hook. Same fallback pattern as `FileLink` — overlay rendered locally only when no provider. Body click → `openInSplit` when split context present (this session); else deep-link `buildEditorUrl(sessionId, file, line)`; else preview overlay. Native-editor caret dropdown (`openEditor`) removed. See change: add-internal-monaco-editor-pane, split-editor-workspace, remove-external-editor-integration. |
| `parse-ctx-result.fixtures.ts` | Auto-captured real `ctx_*` result texts from session JSONL. Exports `ctxFixtures` object (`execute_stdout`, `execute_intent`, `search`, `search_no_results`, `index`, `fetch`, `insight`, error variants, etc.). Source of truth for `parse-ctx-result.test.ts`. `err_runtime_fenced` / `err_runtime_plain` cover the structured vs. flat runtime-error paths (see change: repair-tool-error-surfaces). |
| `parse-ctx-result.ts` | Pure parser, no React. Exports `parseCtxResult(toolName, result, isError) → CtxResult` typed union (kinds: error, execute, batch, search, index, fetch, insight, raw) + `stripNoise` + `parseExecutionShape`. A `runtime` error additionally carries the context-mode execution shape when its body has one — `preamble` / `command` / `language` / `exitCode` / `stdout` / `stderr` (`CtxExecutionShape`, every field optional; `preamble` keeps prose above the dump so no text is dropped); a plain-sentence body leaves them undefined and the renderer falls back to flat `message`. Extraction never throws on a partial body. A `stdout:`/`stderr:` header opens a section only when preceded by a blank line (or start-of-body) and not already opened, so a line reading `stderr:` INSIDE stdout's own output cannot split the section (test-plan #E9). (see change: repair-tool-error-surfaces). Strips leading `⚠️ context-mode v…` upgrade banner. Classifies errors validation/timeout/runtime; validation captures `Received arguments:` JSON into receivedArgs. Per-tool parse arms return `{ kind: "raw", text }` on header miss; never throws. Fixtures in `parse-ctx-result.fixtures.ts` (real session captures); tests in `parse-ctx-result.test.ts`. See change: add-ctx-tool-renderer. |
| `ReadToolRenderer.tsx` | Renders `read` tool. Path + offset/limit subtitle + `OpenFileButton`; `SyntaxHighlighter` with `detectLanguage` theme via `getSyntaxTheme`; `ToolResultImages` for image reads. Exports `ReadToolRenderer`. |
| `registry.ts` | Tool renderer registry. `Map` of tool name → renderer (read/edit/write/bash/Agent/ask_user/ctx_*). `getToolRenderer` falls back to `CtxToolRenderer` for unmapped `ctx_*` names, else `GenericToolRenderer`. Exports `getToolRenderer`, `registerToolRenderer`. |
| `ToolResultImages.tsx` | Shared tool-renderer component. Renders inlined `type:"image"` tool-result blocks as `data:` URL thumbnails (max 512px) + `ImageLightbox`. Used by `ReadToolRenderer`, `BashToolRenderer`, `GenericToolRenderer`. See change: inline-agent-screenshot-artifacts. |
| `types.ts` | Defines `ToolContext` (`cwd`, `sessionId`, `session`, `send?`; `editors` field removed — change: remove-external-editor-integration), `ToolRendererProps` (`toolName`, `args`, `status`, `result`, `images`, `context`, `toolDetails`), `ToolRenderer` component type. `send?: (msg: BrowserToServerMessage) => void` lets renderers message the server (e.g. subagent resync request); populated in `App.tsx` `toolContext`. See change: fix-subagent-live-detail-reliability. Adds optional `fileLink?: FileLinkRenderer` — injected so `MarkdownContent` depends on the contract, not the concrete `FileLink` (cut the upward edge). Optional by necessity: `ToolContext` is re-exported from `chat-embed` as a published surface. Module stays value-free. See change: cleanup-import-cycles. |
| `UrlLink.tsx` | Thin `<a target="_blank" rel="noopener noreferrer">` wrapper. Rejects non-`http(s)://` hrefs (defense in depth even if tokenizer bypassed). See change: linkify-tool-output. `<a>` gets `draggable={false}` so drag-select crosses link instead of starting native link-drag. See change: selectable-tool-output-links. `onClick`→`useLoopbackLinkOpen()`: plain click of a loopback URL opens the internal live-server split viewer; LAN/external + modifier/middle-click keep the tab. See change: open-loopback-links-in-split-viewer. Link colour now `text-[var(--link)]` (mode-aware) instead of the raw `text-blue-400`, which measured 1.80–2.52:1 on `--bg-code` in all 9 light themes; gated by `severity-contrast.spec.ts` #F8. See change: repair-tool-error-surfaces. |
| `useFileOpenRouting.ts` | Shared file-open routing hook. Used by `FileLink` + `OpenFileButton`. Routes clicks to the `FilePreviewOverlay` (external-editor `openEditor` branch removed — change: remove-external-editor-integration). Reads `FilePreviewContext` via `useContext` (nullable). Provider present → `hostManaged:true`, dispatch to `ctx.open` (hoisted overlay). Provider absent → leaf-local `useState` fallback, exposes `hostManaged`/`previewTarget`/`closePreview`. No longer owns unconditional preview state. See change: fix-file-preview-survives-message-churn. |
| `WriteToolRenderer.tsx` | Renders `write` tool. Path + `OpenFileButton`; `SyntaxHighlighter` with `detectLanguage` for `args.content`; running placeholder + result line. Exports `WriteToolRenderer`. |