mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site/src/pages): delete unreachable tool render paths (#27527)
This commit is contained in:
@@ -23,7 +23,7 @@ const LiveActivitySlot: FC = () => (
|
||||
data-testid="live-activity-slot"
|
||||
className="flex h-6 items-center gap-2 text-content-secondary"
|
||||
>
|
||||
<ToolIcon name="thinking" isError={false} />
|
||||
<ToolIcon name="thinking" />
|
||||
<Shimmer as="span" className="text-[13px] leading-6">
|
||||
Thinking
|
||||
</Shimmer>
|
||||
|
||||
@@ -1151,30 +1151,4 @@ describe("getSubagentDescriptor", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("renders list_agents with a fixed generic affordance", () => {
|
||||
const descriptor = getSubagentDescriptor({
|
||||
name: "list_agents",
|
||||
args: {},
|
||||
result: {
|
||||
agents: [
|
||||
{ chat_id: "agent-1", type: "explore", status: "completed" },
|
||||
{ chat_id: "agent-2", type: "computer_use", status: "running" },
|
||||
],
|
||||
total: 2,
|
||||
returned: 2,
|
||||
offset: 0,
|
||||
has_more: false,
|
||||
},
|
||||
});
|
||||
|
||||
// The list result has no single top-level type, so the descriptor
|
||||
// must not derive a variant from per-agent types.
|
||||
expect(descriptor).toMatchObject({
|
||||
action: "list",
|
||||
variant: "general",
|
||||
iconKind: "bot",
|
||||
supportsDesktopAffordance: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ExternalLinkIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { Link } from "react-router";
|
||||
import { ToolCall } from "./ToolCall";
|
||||
import { asRecord, asString, type ToolStatus } from "./utils";
|
||||
import { asString, parseArgs, type ToolStatus } from "./utils";
|
||||
import { WorkspaceBuildLogSection } from "./WorkspaceBuildLogSection";
|
||||
|
||||
/**
|
||||
@@ -32,15 +32,7 @@ export const CreateWorkspaceTool: React.FC<{
|
||||
labelOverride,
|
||||
}) => {
|
||||
const isRunning = status === "running";
|
||||
let rec: Record<string, unknown> | null = null;
|
||||
if (resultJson) {
|
||||
try {
|
||||
const parsed = JSON.parse(resultJson);
|
||||
rec = asRecord(parsed);
|
||||
} catch {
|
||||
rec = asRecord(resultJson);
|
||||
}
|
||||
}
|
||||
const rec = parseArgs(resultJson);
|
||||
const ownerName = rec ? asString(rec.owner_name) : "";
|
||||
const wsName = rec ? asString(rec.workspace_name) : workspaceName;
|
||||
const workspaceLink = ownerName && wsName ? `/@${ownerName}/${wsName}` : null;
|
||||
|
||||
@@ -35,14 +35,6 @@ export const ShortCommand: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const RunningWithoutCommand: Story = {
|
||||
args: {
|
||||
command: "",
|
||||
status: "running",
|
||||
transcriptBlocks: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const LongCommand: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
|
||||
@@ -56,7 +56,6 @@ export const ExecuteTool: React.FC<ExecuteToolProps> = ({
|
||||
parsedCommands,
|
||||
shellToolDisplayMode,
|
||||
}) => {
|
||||
const hasCommand = command.trim().length > 0;
|
||||
const hasTranscriptBlocks = transcriptBlocks.length > 0;
|
||||
const autoDisplayState: AgentDisplayState =
|
||||
hasTranscriptBlocks ||
|
||||
@@ -78,10 +77,6 @@ export const ExecuteTool: React.FC<ExecuteToolProps> = ({
|
||||
autoDisplayState,
|
||||
);
|
||||
|
||||
if (!hasCommand) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolCall.Root
|
||||
key={`${shellToolDisplayMode ?? "auto"}:${autoDisplayState}`}
|
||||
|
||||
@@ -53,12 +53,6 @@ const SUBAGENT_VERBS: Record<
|
||||
error: "Failed to interrupt ",
|
||||
timeout: "Timed out interrupting ",
|
||||
},
|
||||
list: {
|
||||
completed: "Listed ",
|
||||
running: "Listing ",
|
||||
error: "Failed to list ",
|
||||
timeout: "Timed out listing ",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1122,7 +1122,6 @@ export const WaitAgentExploreStreamingFromHistory: Story = {
|
||||
expect(
|
||||
canvas.getByRole("button", { name: /Waiting for Explore agent/ }),
|
||||
).toBeInTheDocument();
|
||||
expect(canvas.queryByText("Waiting for sub-agent…")).toBeNull();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1142,7 +1141,6 @@ export const MessageAgentExploreStreamingFromResult: Story = {
|
||||
expect(
|
||||
canvas.getByRole("button", { name: /Messaging Explore agent/ }),
|
||||
).toBeInTheDocument();
|
||||
expect(canvas.queryByText("Messaging sub-agent…")).toBeNull();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1114,10 +1114,9 @@ export const Tool = memo(
|
||||
ref,
|
||||
...props
|
||||
}: ToolProps) => {
|
||||
const Renderer =
|
||||
isSubagentToolName(name) && name !== "list_agents"
|
||||
? SubagentRenderer
|
||||
: (toolRenderers[name] ?? GenericToolRenderer);
|
||||
const Renderer = isSubagentToolName(name)
|
||||
? SubagentRenderer
|
||||
: (toolRenderers[name] ?? GenericToolRenderer);
|
||||
const isShellTool = name === "execute" || name === "process_output";
|
||||
if (!shouldRenderTool({ name, status, args, result })) {
|
||||
return null;
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
import { cn } from "#/utils/cn";
|
||||
import { Shimmer } from "../Shimmer";
|
||||
import { TranscriptRow } from "../TranscriptRow";
|
||||
import type { SubagentIconKind } from "./subagentDescriptor";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
@@ -208,7 +207,6 @@ type ToolCallLeadingIconProps = {
|
||||
children?: ReactNode;
|
||||
iconUrl?: string;
|
||||
serverName?: string;
|
||||
subagentIconKind?: SubagentIconKind;
|
||||
};
|
||||
|
||||
const LeadingIcon: FC<ToolCallLeadingIconProps> = ({
|
||||
@@ -216,9 +214,8 @@ const LeadingIcon: FC<ToolCallLeadingIconProps> = ({
|
||||
children,
|
||||
iconUrl,
|
||||
serverName,
|
||||
subagentIconKind,
|
||||
}) => {
|
||||
const { active, failed } = useToolCallContext();
|
||||
const { active } = useToolCallContext();
|
||||
if (children) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -229,11 +226,9 @@ const LeadingIcon: FC<ToolCallLeadingIconProps> = ({
|
||||
return (
|
||||
<ToolIcon
|
||||
name={name}
|
||||
isError={failed}
|
||||
isRunning={active}
|
||||
iconUrl={iconUrl}
|
||||
serverName={serverName}
|
||||
subagentIconKind={subagentIconKind}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -267,16 +262,11 @@ const Label: FC<ToolCallLabelProps> = ({
|
||||
|
||||
type ToolCallStatusProps = {
|
||||
className?: string;
|
||||
errorMessage?: string;
|
||||
};
|
||||
|
||||
const Status: FC<ToolCallStatusProps> = ({ className, errorMessage }) => {
|
||||
const {
|
||||
active,
|
||||
errorMessage: contextErrorMessage,
|
||||
failed,
|
||||
} = useToolCallContext();
|
||||
const message = errorMessage || contextErrorMessage || "Tool call failed";
|
||||
const Status: FC<ToolCallStatusProps> = ({ className }) => {
|
||||
const { active, errorMessage, failed } = useToolCallContext();
|
||||
const message = errorMessage || "Tool call failed";
|
||||
return (
|
||||
<>
|
||||
{active && (
|
||||
@@ -367,7 +357,6 @@ type ToolCallHeaderProps = {
|
||||
label: ReactNode;
|
||||
iconUrl?: string;
|
||||
serverName?: string;
|
||||
subagentIconKind?: SubagentIconKind;
|
||||
secondaryLabel?: ReactNode;
|
||||
trailing?: ReactNode;
|
||||
showStatus?: boolean;
|
||||
@@ -388,7 +377,6 @@ const Header: FC<ToolCallHeaderProps> = ({
|
||||
label,
|
||||
iconUrl,
|
||||
serverName,
|
||||
subagentIconKind,
|
||||
secondaryLabel,
|
||||
trailing,
|
||||
showStatus = true,
|
||||
@@ -396,12 +384,7 @@ const Header: FC<ToolCallHeaderProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<HeaderButton className={headerClassName}>
|
||||
<LeadingIcon
|
||||
name={iconName}
|
||||
iconUrl={iconUrl}
|
||||
serverName={serverName}
|
||||
subagentIconKind={subagentIconKind}
|
||||
/>
|
||||
<LeadingIcon name={iconName} iconUrl={iconUrl} serverName={serverName} />
|
||||
<Label>{label}</Label>
|
||||
{secondaryLabel}
|
||||
{showStatus && <Status />}
|
||||
|
||||
@@ -22,19 +22,13 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import {
|
||||
isSubagentToolName,
|
||||
type SubagentIconKind,
|
||||
} from "./subagentDescriptor";
|
||||
|
||||
export const ToolIcon: React.FC<{
|
||||
name: string;
|
||||
isError: boolean;
|
||||
iconUrl?: string;
|
||||
isRunning?: boolean;
|
||||
serverName?: string;
|
||||
subagentIconKind?: SubagentIconKind;
|
||||
}> = ({ name, iconUrl, isRunning, serverName, subagentIconKind }) => {
|
||||
}> = ({ name, iconUrl, isRunning, serverName }) => {
|
||||
const [imgError, setImgError] = useState(false);
|
||||
const color = "text-current";
|
||||
const base = cn(
|
||||
@@ -80,20 +74,6 @@ export const ToolIcon: React.FC<{
|
||||
return img;
|
||||
}
|
||||
|
||||
if (isSubagentToolName(name)) {
|
||||
// This name-based fallback only exists for legacy callers that do
|
||||
// not pass a descriptor. The descriptor path should provide
|
||||
// subagentIconKind for new subagent types instead of extending it.
|
||||
const iconKind =
|
||||
subagentIconKind ||
|
||||
(name === "spawn_computer_use_agent" ? "monitor" : "bot");
|
||||
return iconKind === "monitor" ? (
|
||||
<MonitorIcon className={base} />
|
||||
) : (
|
||||
<BotIcon className={base} />
|
||||
);
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "execute":
|
||||
case "process_output":
|
||||
@@ -116,6 +96,7 @@ export const ToolIcon: React.FC<{
|
||||
case "start_workspace":
|
||||
return <PowerIcon className={base} />;
|
||||
case "chat_summarized":
|
||||
case "list_agents":
|
||||
return <BotIcon className={base} />;
|
||||
case "thinking":
|
||||
return <LightbulbIcon className={base} />;
|
||||
|
||||
@@ -1,58 +1,7 @@
|
||||
import type React from "react";
|
||||
import { getPathBasename } from "../../../utils/path";
|
||||
import {
|
||||
getProvidedSubagentTitle,
|
||||
getSubagentDescriptor,
|
||||
} from "./subagentDescriptor";
|
||||
import { asRecord, asString, humanizeMCPToolName, parseArgs } from "./utils";
|
||||
|
||||
const renderSubagentLabel = (
|
||||
name: string,
|
||||
args: unknown,
|
||||
result: unknown,
|
||||
): React.ReactNode | null => {
|
||||
const descriptor = getSubagentDescriptor({ name, args, result });
|
||||
if (!descriptor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const providedTitle = getProvidedSubagentTitle({ args, result });
|
||||
const fallbackTitle = descriptor.fallbackTitle;
|
||||
const text = (() => {
|
||||
switch (descriptor.action) {
|
||||
case "spawn":
|
||||
if (providedTitle) {
|
||||
return `Spawning ${providedTitle}`;
|
||||
}
|
||||
if (descriptor.variant === "explore") {
|
||||
return "Spawning Explore agent…";
|
||||
}
|
||||
if (descriptor.variant === "computer_use") {
|
||||
return "Spawning computer use sub-agent…";
|
||||
}
|
||||
return `Spawning ${fallbackTitle}…`;
|
||||
case "wait":
|
||||
return providedTitle
|
||||
? `Waiting for ${providedTitle}`
|
||||
: `Waiting for ${fallbackTitle}…`;
|
||||
case "message":
|
||||
return providedTitle
|
||||
? `Messaging ${providedTitle}`
|
||||
: `Messaging ${fallbackTitle}…`;
|
||||
case "interrupt":
|
||||
return providedTitle
|
||||
? `Interrupting ${providedTitle}`
|
||||
: `Interrupting ${fallbackTitle}`;
|
||||
case "list":
|
||||
return providedTitle
|
||||
? `Listing ${providedTitle}`
|
||||
: `Listing ${fallbackTitle}`;
|
||||
}
|
||||
})();
|
||||
|
||||
return <span className="truncate text-[13px]">{text}</span>;
|
||||
};
|
||||
|
||||
export const ToolLabel: React.FC<{
|
||||
name: string;
|
||||
args: unknown;
|
||||
@@ -61,14 +10,6 @@ export const ToolLabel: React.FC<{
|
||||
}> = ({ name, args, result, mcpSlug }) => {
|
||||
const parsed = parseArgs(args);
|
||||
const parsedResult = asRecord(result);
|
||||
const subagentLabel = renderSubagentLabel(
|
||||
name,
|
||||
parsed ?? args,
|
||||
parsedResult ?? result,
|
||||
);
|
||||
if (subagentLabel) {
|
||||
return subagentLabel;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "execute": {
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { asString } from "../runtimeTypeUtils";
|
||||
import { parseArgs } from "./utils";
|
||||
|
||||
export type SubagentAction =
|
||||
| "spawn"
|
||||
| "wait"
|
||||
| "message"
|
||||
| "interrupt"
|
||||
| "list";
|
||||
export type SubagentAction = "spawn" | "wait" | "message" | "interrupt";
|
||||
export type SubagentVariant = "general" | "explore" | "computer_use";
|
||||
export type SubagentIconKind = "bot" | "monitor";
|
||||
type SubagentIconKind = "bot" | "monitor";
|
||||
|
||||
export type SubagentDescriptor = {
|
||||
action: SubagentAction;
|
||||
@@ -59,11 +54,6 @@ const actionByToolName: Record<string, SubagentAction> = {
|
||||
// Legacy persisted tool name kept so old chat histories still render.
|
||||
close_agent: "interrupt",
|
||||
interrupt_agent: "interrupt",
|
||||
// list_agents is a subagent tool but renders through
|
||||
// ListAgentsRenderer, not SubagentRenderer. The "list" action
|
||||
// exists for isSubagentToolName classification and ToolIcon
|
||||
// dispatch, not for the SubagentRenderer label machinery.
|
||||
list_agents: "list",
|
||||
};
|
||||
|
||||
const variantBySpawnToolName: Record<string, SubagentVariant> = {
|
||||
|
||||
@@ -136,7 +136,7 @@ describe("toolVisibility", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("renders list_agents rows even without a chat_id", () => {
|
||||
it("renders list_agents rows regardless of chat_id", () => {
|
||||
expect(
|
||||
shouldRenderTool({
|
||||
name: "list_agents",
|
||||
|
||||
Reference in New Issue
Block a user