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.
This commit is contained in:
Danielle Maywood
2026-07-30 22:58:57 +01:00
committed by GitHub
parent 218829d444
commit 9bc681fa6e
2 changed files with 0 additions and 119 deletions
@@ -81,7 +81,6 @@ export const ToolIcon: React.FC<{
return <TerminalIcon className={base} />;
case "read_file":
case "read_skill":
case "read_skill_file":
return <FileTextIcon className={base} />;
case "write_file":
case "edit_files":
@@ -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 (
<code className="truncate font-mono text-xs text-content-primary">
{command}
</code>
);
}
return <span className="truncate text-[13px]">Running command</span>;
}
case "process_output":
return (
<span className="truncate text-[13px]">Reading process output</span>
);
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 <span className="truncate text-[13px]">Listing processes</span>;
case "read_file":
return <span className="truncate text-[13px]">Reading file…</span>;
case "write_file": {
const path = parsed ? asString(parsed.path) : "";
if (path) {
return (
<code className="truncate font-mono text-xs text-content-primary">
{path}
</code>
);
}
return <span className="truncate text-[13px]">Writing file</span>;
}
case "edit_files": {
const files = parsed?.files;
if (Array.isArray(files) && files.length === 1) {
const path = asString((files[0] as Record<string, unknown>)?.path);
if (path) {
return (
<code className="truncate font-mono text-xs text-content-primary">
{path}
</code>
);
}
}
return <span className="truncate text-[13px]">Editing files</span>;
}
case "create_workspace": {
const wsName = parsedResult ? asString(parsedResult.workspace_name) : "";
if (wsName) {
return <span className="truncate text-[13px]">Created {wsName}</span>;
}
return <span className="truncate text-[13px]">Creating workspace</span>;
}
case "list_templates": {
const count = parsedResult
? ((parsedResult.count as number | undefined) ?? 0)
: 0;
return (
<span className="truncate text-[13px]">
{count === 0
? "Listing templates…"
: count === 1
? "Listed 1 template"
: `Listed ${count} templates`}
</span>
);
}
case "read_template": {
const templateRec = parsedResult
? asRecord(parsedResult.template)
: undefined;
const tmplName = templateRec
? asString(templateRec.display_name) || asString(templateRec.name)
: "";
return (
<span className="truncate text-[13px]">
{tmplName ? `Read template ${tmplName}` : "Reading template…"}
</span>
);
}
case "chat_summarized":
return <span className="truncate text-[13px]">Summarized</span>;
case "attach_file": {
const attachedName =
(parsedResult ? asString(parsedResult.name) : "") ||
@@ -139,52 +61,12 @@ export const ToolLabel: React.FC<{
<span className="truncate text-[13px]">{`Attached ${attachedName}`}</span>
);
}
case "computer":
return <span className="truncate text-[13px]">Screenshot</span>;
case "propose_plan": {
const path = parsed ? asString(parsed.path) || "PLAN.md" : "PLAN.md";
const filename = getPathBasename(path) || "PLAN.md";
return <span className="truncate text-[13px]">{filename}</span>;
}
case "advisor":
return (
<span className="truncate text-[13px] leading-4 text-content-secondary">
Advisor
</span>
);
case "read_skill": {
const skillName = parsed ? asString(parsed.name) : "";
return (
<span className="truncate text-[13px]">
{skillName
? parsedResult
? `Read skill ${skillName}`
: `Reading skill ${skillName}…`
: "Reading skill…"}
</span>
);
}
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 (
<span className="truncate text-[13px]">
{parsedResult ? `Read ${label}` : `Reading ${label}…`}
</span>
);
}
case "start_workspace": {
const wsName = parsedResult ? asString(parsedResult.workspace_name) : "";
return (
<span className="truncate text-[13px]">
{wsName ? `Started ${wsName}` : "Starting workspace…"}
</span>
);
}
default: {
const displayName = mcpSlug ? humanizeMCPToolName(mcpSlug, name) : name;