From 9bc681fa6e65ed757ed9fae890f16c281723dd99 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 30 Jul 2026 22:58:57 +0100 Subject: [PATCH] fix(site/src/pages/AgentsPage/components/ChatElements/tools): delete unreachable switch arms from ToolLabel and ToolIcon (#27697) Deletes unreachable switch arms from `ToolLabel` (14 arms) and `ToolIcon` (1 arm). No behaviour change; the deleted arms could never be reached, so the rendered output is identical. ## Reachability proof `ToolLabel` has exactly two call sites: 1. `Tool.tsx` `GenericToolRenderer` (line 950), reached when a tool name has no `toolRenderers` entry or when its registered renderer delegates to `GenericToolRenderer`. 2. `AdvisorTool.tsx` (line 72), which hardcodes `name="advisor"`. Dispatch in `Tool.tsx`: subagent names (`spawn_agent`, `wait_agent`, `message_agent`, `interrupt_agent`, plus legacy `spawn_subagent`, `close_agent`) route to `SubagentRenderer`; everything else hits `toolRenderers[name] ?? GenericToolRenderer`. None of the subagent names appear in either switch. Every registered renderer was checked for delegation: | ToolLabel arm | Shadowing renderer | Delegates? | | --- | --- | --- | | `execute` | `ExecuteRenderer` -> `ExecuteTool` | No | | `process_output` | `ProcessOutputRenderer` -> `ProcessOutputTool` | No | | `read_file` | `ReadFileRenderer` -> `ReadFileTool` | No | | `write_file` | `WriteFileRenderer` -> `WriteFileTool` | No | | `edit_files` | `EditFilesRenderer` -> `EditFilesTool` | No | | `create_workspace` | `CreateWorkspaceRenderer` -> `CreateWorkspaceTool` | No | | `start_workspace` | `StartWorkspaceRenderer` -> `StartWorkspaceTool` | No | | `list_templates` | `ListTemplatesRenderer` -> `ListTemplatesTool` | No | | `read_template` | `ReadTemplateRenderer` -> `ReadTemplateTool` | No | | `read_skill` | `ReadSkillRenderer` -> `ReadSkillTool` | No | | `read_skill_file` | `ReadSkillFileRenderer` -> `ReadSkillTool` | No | | `chat_summarized` | `ChatSummarizedRenderer` -> `ChatSummarizedTool` | No | | `propose_plan` | `ProposePlanRenderer` -> `ProposePlanTool` | No | | `computer` | `ComputerRenderer` -> `ComputerTool` | No | Kept arms: - `process_signal`: `ProcessSignalRenderer` IS a registry key but delegates to `GenericToolRenderer`, so the arm stays reachable. Kept. - `advisor`: hardcoded by `AdvisorTool.tsx`. Kept. - `process_list`, `attach_file`: no registry entry, not subagent names, so they fall through to `GenericToolRenderer`. Kept. - `default`: covers MCP tools and any unregistered name. Kept. `ToolIcon` is rendered by `ToolCall.LeadingIcon` / `ToolCall.Header iconName`, and every dedicated per-tool component passes its own fixed name (`execute`, `process_output`, `read_file`, `write_file`, `edit_files`, `list_templates`, `read_template`, `read_skill`, `chat_summarized`, `ask_user_question`, `propose_plan`, `computer`, `start_workspace`, `list_agents`, `create_workspace`, `advisor`), so those arms are reachable and kept. `thinking` is passed directly from `StreamingOutput.tsx` and `ConversationTimeline.tsx`, and `chat_summarized` covers `list_agents` via the shared `BotIcon` arm. `read_skill_file` is the only arm whose renderer (`ReadSkillFileRenderer`) renders `ReadSkillTool` with the hardcoded `iconName="read_skill"`, so nothing ever passes `read_skill_file` to `ToolIcon`. That arm alone is deleted. No exports, helpers, or imports became dead (verified with knip, which passes clean). ## Delta and tests - Line delta: -119 (ToolLabel -118, ToolIcon -1), 0 insertions. - Unit (`--project=unit src/pages/AgentsPage`): 1500 passed, 2 skipped before and after. - Storybook (`--project=storybook src/pages/AgentsPage`): 949 passed, 2 failed before and after, identical failures both runs: `AgentChatPageView.stories.tsx > Scroll To Bottom Button Works With Inverse Scroll` and `Tool.stories.tsx > MCP Tool Completed`. Both reproduce on unmodified main (MCP Tool Completed also fails in isolation on main), so they are pre-existing and unrelated. - `tsc --noEmit`, `biome check --error-on-warnings`, and knip all pass. Generated by Coder Agents. --- .../ChatElements/tools/ToolIcon.tsx | 1 - .../ChatElements/tools/ToolLabel.tsx | 118 ------------------ 2 files changed, 119 deletions(-) diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx index 16e9bc148b..913b8d0bc8 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx @@ -81,7 +81,6 @@ export const ToolIcon: React.FC<{ return ; case "read_file": case "read_skill": - case "read_skill_file": return ; case "write_file": case "edit_files": diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolLabel.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolLabel.tsx index 078aa5d9ae..4b465e7b03 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolLabel.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolLabel.tsx @@ -12,21 +12,6 @@ export const ToolLabel: React.FC<{ const parsedResult = asRecord(result); switch (name) { - case "execute": { - const command = parsed ? asString(parsed.command) : ""; - if (command) { - return ( - - {command} - - ); - } - return Running command; - } - case "process_output": - return ( - Reading process output - ); case "process_signal": { const signal = parsed ? asString(parsed.signal) : ""; const processId = parsed ? asString(parsed.process_id) : ""; @@ -66,69 +51,6 @@ export const ToolLabel: React.FC<{ } case "process_list": return Listing processes; - case "read_file": - return Reading file…; - case "write_file": { - const path = parsed ? asString(parsed.path) : ""; - if (path) { - return ( - - {path} - - ); - } - return Writing file; - } - case "edit_files": { - const files = parsed?.files; - if (Array.isArray(files) && files.length === 1) { - const path = asString((files[0] as Record)?.path); - if (path) { - return ( - - {path} - - ); - } - } - return Editing files; - } - case "create_workspace": { - const wsName = parsedResult ? asString(parsedResult.workspace_name) : ""; - if (wsName) { - return Created {wsName}; - } - return Creating workspace; - } - case "list_templates": { - const count = parsedResult - ? ((parsedResult.count as number | undefined) ?? 0) - : 0; - return ( - - {count === 0 - ? "Listing templates…" - : count === 1 - ? "Listed 1 template" - : `Listed ${count} templates`} - - ); - } - case "read_template": { - const templateRec = parsedResult - ? asRecord(parsedResult.template) - : undefined; - const tmplName = templateRec - ? asString(templateRec.display_name) || asString(templateRec.name) - : ""; - return ( - - {tmplName ? `Read template ${tmplName}` : "Reading template…"} - - ); - } - case "chat_summarized": - return Summarized; case "attach_file": { const attachedName = (parsedResult ? asString(parsedResult.name) : "") || @@ -139,52 +61,12 @@ export const ToolLabel: React.FC<{ {`Attached ${attachedName}`} ); } - case "computer": - return Screenshot; - case "propose_plan": { - const path = parsed ? asString(parsed.path) || "PLAN.md" : "PLAN.md"; - const filename = getPathBasename(path) || "PLAN.md"; - return {filename}; - } case "advisor": return ( Advisor ); - case "read_skill": { - const skillName = parsed ? asString(parsed.name) : ""; - return ( - - {skillName - ? parsedResult - ? `Read skill ${skillName}` - : `Reading skill ${skillName}…` - : "Reading skill…"} - - ); - } - case "read_skill_file": { - const skillName = parsed ? asString(parsed.name) : ""; - const filePath = parsed ? asString(parsed.path) : ""; - const label = - skillName && filePath - ? `${skillName}/${filePath}` - : skillName || filePath || "skill file"; - return ( - - {parsedResult ? `Read ${label}` : `Reading ${label}…`} - - ); - } - case "start_workspace": { - const wsName = parsedResult ? asString(parsedResult.workspace_name) : ""; - return ( - - {wsName ? `Started ${wsName}` : "Starting workspace…"} - - ); - } default: { const displayName = mcpSlug ? humanizeMCPToolName(mcpSlug, name) : name;