mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user