diff --git a/site/src/pages/AgentsPage/components/ChatConversation/ChatStatusCallout.tsx b/site/src/pages/AgentsPage/components/ChatConversation/ChatStatusCallout.tsx index c030cba85f..e44ba842fc 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/ChatStatusCallout.tsx +++ b/site/src/pages/AgentsPage/components/ChatConversation/ChatStatusCallout.tsx @@ -1,3 +1,4 @@ +import { BrainIcon } from "lucide-react"; import { type FC, useEffect, useState } from "react"; import { Alert, AlertDescription, AlertTitle } from "#/components/Alert/Alert"; import { Link } from "#/components/Link/Link"; @@ -18,14 +19,18 @@ type ReconnectingStatus = Extract; const StatusPlaceholder: FC<{ text: string; shimmer?: boolean; -}> = ({ text, shimmer = false }) => { + showThinkingIcon?: boolean; +}> = ({ text, shimmer = false, showThinkingIcon = false }) => { return (
{/* Reserve the final response height without exposing a selectable copy. */} {text} -
+
+ {showThinkingIcon && ( + + )} {shimmer ? ( {text} @@ -54,6 +59,7 @@ const StartingPlaceholder: FC = () => { ); }; diff --git a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx index daf1b4c007..37e8a013fe 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx +++ b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx @@ -1,4 +1,9 @@ -import { ChevronLeftIcon, ChevronRightIcon, PencilIcon } from "lucide-react"; +import { + BrainIcon, + ChevronLeftIcon, + ChevronRightIcon, + PencilIcon, +} from "lucide-react"; import { type FC, Fragment, @@ -160,13 +165,16 @@ const ReasoningDisclosure = memo<{ expanded={expanded} onExpandedChange={(open) => setManualToggle(open)} header={ - isStreaming ? ( - - {title} - - ) : ( - {title} - ) + <> + + {isStreaming ? ( + + {title} + + ) : ( + {title} + )} + } > {hasText && ( diff --git a/site/src/pages/AgentsPage/components/ChatConversation/StreamingOutput.tsx b/site/src/pages/AgentsPage/components/ChatConversation/StreamingOutput.tsx index d3ce729db2..e95dadffea 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/StreamingOutput.tsx +++ b/site/src/pages/AgentsPage/components/ChatConversation/StreamingOutput.tsx @@ -1,3 +1,4 @@ +import { BrainIcon } from "lucide-react"; import type { FC } from "react"; import type { UrlTransform } from "streamdown"; import type * as TypesGen from "#/api/typesGenerated"; @@ -36,7 +37,8 @@ const hasTextOrReasoningBlock = (blocks: readonly RenderBlock[]): boolean => const StreamingThinkingPlaceholder: FC = () => (
- + + Thinking diff --git a/site/src/pages/AgentsPage/components/ChatElements/Thinking.tsx b/site/src/pages/AgentsPage/components/ChatElements/Thinking.tsx index f049c02183..1bf736a8e8 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/Thinking.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/Thinking.tsx @@ -1,3 +1,4 @@ +import { BrainIcon } from "lucide-react"; import type { ComponentPropsWithRef } from "react"; import { cn } from "#/utils/cn"; @@ -8,10 +9,13 @@ export const Thinking = ({ className, ref, ...props }: ThinkingProps) => {
+ > + +
{props.children}
+
); }; diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/AdvisorTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/AdvisorTool.tsx index 1dac5ba647..eaaca11788 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/AdvisorTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/AdvisorTool.tsx @@ -53,55 +53,55 @@ export const AdvisorTool: React.FC = ({ defaultExpanded headerClassName="items-start" header={(expanded) => ( - <> -
-
- - - {isRunning && ( - - {RUNNING_MESSAGE} - - )} - {advisorModelText && ( - - {advisorModelText} - - )} - {remainingUses !== undefined && ( - - {remainingUses.toLocaleString("en-US")} uses left - - )} -
- - {questionText} - +
+
+ + + {isRunning && ( + + {RUNNING_MESSAGE} + + )} + {advisorModelText && ( + + {advisorModelText} + + )} + {remainingUses !== undefined && ( + + {remainingUses.toLocaleString("en-US")} uses left + + )}
- {showLimitReached ? ( - - ) : showError ? ( - - ) : isRunning ? ( - - ) : null} - + + {questionText} + +
)} + headerStatus={ + showLimitReached ? ( + + ) : showError ? ( + + ) : isRunning ? ( + + ) : null + } > = ({
+ = ({ return (
{isRunning ? ( - + + Asking for clarification... @@ -677,7 +688,16 @@ export const AskUserQuestionTool: FC = ({ return (
{isRunning && ( - + + Asking for clarification... diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ChatSummarizedTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ChatSummarizedTool.tsx index 3319ddfd46..61a041d890 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ChatSummarizedTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ChatSummarizedTool.tsx @@ -8,6 +8,7 @@ import { } from "#/components/Tooltip/Tooltip"; import { Response } from "../Response"; import { ToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import type { ToolStatus } from "./utils"; /** @@ -29,9 +30,18 @@ export const ChatSummarizedTool: React.FC<{ hasContent={hasSummary} header={ <> - + + {isRunning ? "Summarizing…" : "Summarized"} + + } + headerStatus={ + <> {isError && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ComputerTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ComputerTool.tsx index ae70532609..513ee35175 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ComputerTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ComputerTool.tsx @@ -8,6 +8,7 @@ import { } from "#/components/Tooltip/Tooltip"; import { ImageLightbox } from "../../ImageLightbox"; import { ToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import type { ToolStatus } from "./utils"; /** @@ -39,9 +40,14 @@ export const ComputerTool: React.FC<{ defaultExpanded={hasImage} header={ <> - + + {isRunning ? "Taking screenshot…" : "Screenshot"} + + } + headerStatus={ + <> {isError && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/CreateWorkspaceTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/CreateWorkspaceTool.tsx index 85a762705d..256717ef0d 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/CreateWorkspaceTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/CreateWorkspaceTool.tsx @@ -1,9 +1,4 @@ -import { - ExternalLinkIcon, - LoaderIcon, - MonitorIcon, - TriangleAlertIcon, -} from "lucide-react"; +import { ExternalLinkIcon, LoaderIcon, TriangleAlertIcon } from "lucide-react"; import type React from "react"; import { Link } from "react-router"; import { @@ -12,6 +7,7 @@ import { TooltipTrigger, } from "#/components/Tooltip/Tooltip"; import { ToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import { asRecord, asString, type ToolStatus } from "./utils"; import { WorkspaceBuildLogSection } from "./WorkspaceBuildLogSection"; @@ -72,8 +68,26 @@ export const CreateWorkspaceTool: React.FC<{ const header = ( <> - - {label} + + {label} + {workspaceLink && !isRunning && ( + e.stopPropagation()} + className="ml-1 inline-flex align-middle text-content-secondary opacity-50 transition-opacity hover:opacity-100" + aria-label="View workspace" + > + + + )} + + ); + const headerStatus = ( + <> {isError && ( @@ -87,16 +101,6 @@ export const CreateWorkspaceTool: React.FC<{ {isRunning && ( )} - {workspaceLink && !isRunning && ( - e.stopPropagation()} - className="ml-1 inline-flex align-middle text-content-secondary opacity-50 transition-opacity hover:opacity-100" - aria-label="View workspace" - > - - - )} ); @@ -104,6 +108,7 @@ export const CreateWorkspaceTool: React.FC<{
diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/EditFilesTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/EditFilesTool.tsx index 4f9f69cced..b7f9cf6a64 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/EditFilesTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/EditFilesTool.tsx @@ -16,6 +16,7 @@ import { resolveAgentDisplayState, } from "./displayMode"; import { AgentDisplayModeToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import { DIFFS_FONT_STYLE, type EditFilesFileEntry, @@ -69,7 +70,12 @@ export const EditFilesTool: React.FC<{ autoDisplayState={EDIT_FILES_AUTO_DISPLAY_STATE} header={ <> - {label} + + {label} + + } + headerStatus={ + <> {isError && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.tsx index 7a38ce4be7..0a7c54d252 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.tsx @@ -26,6 +26,7 @@ import { isAgentDisplayOpen, resolveAgentDisplayState, } from "./displayMode"; +import { ToolIcon } from "./ToolIcon"; import { formatShellDurationMs, sanitizeExecuteModelIntent, @@ -190,7 +191,8 @@ const ShellCommandLine: React.FC<{ const commandDisplay = summary || command; return ( <> - + + {intentLabel ? ( <> {intentLabel} using {commandDisplay} diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ListTemplatesTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ListTemplatesTool.tsx index 5c40aadad5..b8ec34a15f 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ListTemplatesTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ListTemplatesTool.tsx @@ -7,6 +7,7 @@ import { TooltipTrigger, } from "#/components/Tooltip/Tooltip"; import { ToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import { asRecord, asString, type ToolStatus } from "./utils"; /** @@ -36,7 +37,16 @@ export const ListTemplatesTool: React.FC<{ hasContent={hasContent} header={ <> - {label} + + {label} + + } + headerStatus={ + <> {isError && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ProcessOutputTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ProcessOutputTool.tsx index ecf7eb15cc..2ab190b020 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ProcessOutputTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ProcessOutputTool.tsx @@ -16,6 +16,7 @@ import { resolveAgentDisplayState, } from "./displayMode"; import { AgentDisplayModeToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import { COLLAPSED_OUTPUT_HEIGHT, signalTooltipLabel } from "./utils"; type ProcessOutputToolProps = { @@ -77,8 +78,7 @@ const ProcessOutputToolInner: React.FC = ({ const toggleOutputExpansion = () => { setOutputFullyExpanded((expanded) => !expanded); }; - const hasHeaderActions = - isRunning || Boolean(killedBySignal) || showExitCode || hasOutput; + const hasHeaderActions = Boolean(killedBySignal) || showExitCode || hasOutput; return ( = ({ ariaLabel={(expanded) => expanded ? "Collapse process output" : "Expand process output" } - header={Process output} + header={ + <> + + Process output + + } + headerStatus={ + isRunning ? ( + + ) : undefined + } headerActions={ hasHeaderActions ? ( <> - {isRunning && ( - - )} {killedBySignal && !isRunning && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ProposePlanTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ProposePlanTool.tsx index d4a92d4f46..a19a0426b9 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ProposePlanTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ProposePlanTool.tsx @@ -11,6 +11,7 @@ import { } from "#/components/Tooltip/Tooltip"; import { Response } from "../Response"; import { TranscriptRow } from "../TranscriptRow"; +import { ToolIcon } from "./ToolIcon"; import type { ToolStatus } from "./utils"; export const ProposePlanTool: React.FC<{ @@ -73,8 +74,13 @@ export const ProposePlanTool: React.FC<{ return (
- - + + + {isRunning ? `Proposing ${filename}…` : `Proposed ${filename}`} {effectiveError && ( @@ -138,7 +144,7 @@ export const ProposePlanTool: React.FC<{ ) )} {fetchLoading && ( - + Loading plan… diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadFileTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadFileTool.tsx index 2459a846b3..7e4391fba2 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadFileTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadFileTool.tsx @@ -10,6 +10,7 @@ import { } from "#/components/Tooltip/Tooltip"; import { asRecord, asString } from "../runtimeTypeUtils"; import { ToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import { DIFFS_FONT_STYLE, getFileViewerOptionsMinimal, @@ -96,7 +97,12 @@ export const ReadFileTool: React.FC<{ onExpandedChange={onExpandedChange} header={ <> - {label} + + {label} + + } + headerStatus={ + <> {isError && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadFilesTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadFilesTool.tsx index 93473bdb73..d157e9592d 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadFilesTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadFilesTool.tsx @@ -8,6 +8,7 @@ import { import type { MergedTool } from "../../ChatConversation/types"; import { getReadFileToolData, ReadFileTool } from "./ReadFileTool"; import { ToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; type ReadFileItem = { id: string; @@ -50,7 +51,12 @@ export const ReadFilesTool: FC<{ onExpandedChange={onExpandedChange} header={ <> - {label} + + {label} {isError && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadSkillTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadSkillTool.tsx index 59df11ab67..33f210d7e2 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadSkillTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadSkillTool.tsx @@ -1,4 +1,4 @@ -import { BookOpenIcon, LoaderIcon, TriangleAlertIcon } from "lucide-react"; +import { LoaderIcon, TriangleAlertIcon } from "lucide-react"; import type React from "react"; import { ScrollArea } from "#/components/ScrollArea/ScrollArea"; import { @@ -8,6 +8,7 @@ import { } from "#/components/Tooltip/Tooltip"; import { Response } from "../Response"; import { ToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import type { ToolStatus } from "./utils"; export const ReadSkillTool: React.FC<{ @@ -26,10 +27,14 @@ export const ReadSkillTool: React.FC<{ hasContent={hasContent} header={ <> - - + + {isRunning ? `Reading ${label}…` : `Read ${label}`} + + } + headerStatus={ + <> {isError && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadTemplateTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadTemplateTool.tsx index f8c24bd996..70b428cc41 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadTemplateTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadTemplateTool.tsx @@ -6,6 +6,7 @@ import { TooltipTrigger, } from "#/components/Tooltip/Tooltip"; import { TranscriptRow } from "../TranscriptRow"; +import { ToolIcon } from "./ToolIcon"; import type { ToolStatus } from "./utils"; /** @@ -27,8 +28,9 @@ export const ReadTemplateTool: React.FC<{ : "Read template"; return ( - - {label} + + + {label} {isError && ( diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/StartWorkspaceTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/StartWorkspaceTool.tsx index 7cb14e41e1..98b1262efc 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/StartWorkspaceTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/StartWorkspaceTool.tsx @@ -1,4 +1,4 @@ -import { LoaderIcon, MonitorPlayIcon, TriangleAlertIcon } from "lucide-react"; +import { LoaderIcon, TriangleAlertIcon } from "lucide-react"; import type { FC } from "react"; import { Tooltip, @@ -6,6 +6,7 @@ import { TooltipTrigger, } from "#/components/Tooltip/Tooltip"; import { ToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import type { ToolStatus } from "./utils"; import { WorkspaceBuildLogSection } from "./WorkspaceBuildLogSection"; @@ -42,8 +43,16 @@ export const StartWorkspaceTool: FC = ({ const header = ( <> - - {label} + + {label} + + ); + const headerStatus = ( + <> {isError && ( @@ -67,6 +76,7 @@ export const StartWorkspaceTool: FC = ({
diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/SubagentTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/SubagentTool.tsx index a6a558a60f..b3f34bbd8e 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/SubagentTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/SubagentTool.tsx @@ -69,7 +69,7 @@ function getSubagentLabel( ): React.ReactNode { if (showDesktopPreview && toolStatus === "running") { return ( - + Using the computer... ); @@ -125,20 +125,22 @@ const SubagentStatusIcon: React.FC<{ const subagentCompleted = isSubagentSuccessStatus(subagentStatus); const DefaultIcon = iconKind === "monitor" ? MonitorIcon : BotIcon; if (isTimeout && !subagentCompleted) { - return ; + return ; } if ((isError && !subagentCompleted) || toolStatus === "error") { return ; } if (toolStatus === "running") { if (showDesktopPreview) { - return ; + return ( + + ); } return ( ); } - return ; + return ; }; /** diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx index 218be52f2a..65965d07d0 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx @@ -10,6 +10,7 @@ import { } from "storybook/test"; import { reactRouterParameters } from "storybook-addon-remix-react-router"; import { ChatWorkspaceContext } from "../../../context/ChatWorkspaceContext"; +import { BlockList } from "../../ChatConversation/ConversationTimeline"; import { DesktopPanelContext } from "./DesktopPanelContext"; import { Tool } from "./Tool"; @@ -18,6 +19,10 @@ const executeIntentCommand = "npm test"; const longExecuteCommand = "docker build --no-cache --build-arg NODE_ENV=production --build-arg API_URL=https://coder.example.com/api --build-arg SENTRY_DSN=https://example.com/sentry --build-arg FEATURE_FLAGS=agents,shell-tools --tag coder-agent:latest ."; +// 1x1 solid coral (#FF6B6B) PNG encoded as base64. +const TEST_PNG_B64 = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4n539HwAHFwLVF8kc1wAAAABJRU5ErkJggg=="; + const getDiffsText = (element: HTMLElement) => Array.from(element.querySelectorAll("diffs-container")) .map((container) => container.shadowRoot?.textContent ?? "") @@ -48,6 +53,212 @@ const meta: Meta = { export default meta; type Story = StoryObj; +type ToolShowcaseItem = { + name: string; + status?: React.ComponentProps["status"]; + args?: unknown; + result?: unknown; + isError?: boolean; + killedBySignal?: "kill" | "terminate"; + modelIntent?: string; + parsedCommands?: readonly string[][]; + subagentVariants?: Map; +}; + +const allToolShowcaseItems: ToolShowcaseItem[] = [ + { + name: "execute", + args: { command: "pnpm check", model_intent: "Checking frontend" }, + modelIntent: "Checking frontend", + parsedCommands: [["pnpm", "check"]], + result: { + output: "Checked 1799 files.", + wall_duration_ms: 2400, + exit_code: 0, + }, + }, + { + name: "process_output", + args: { process_id: "storybook-process" }, + result: { output: "dev server ready on :6006" }, + }, + { + name: "process_list", + args: {}, + result: { + processes: [ + { + id: "storybook-process", + command: "pnpm storybook", + status: "running", + }, + ], + }, + }, + { + name: "process_signal", + args: { process_id: "storybook-process", signal: "terminate" }, + result: { success: true }, + }, + { + name: "wait_for_external_auth", + args: { provider: "github" }, + result: { provider_display_name: "GitHub", authenticated: true }, + }, + { + name: "read_file", + args: { path: "site/src/pages/AgentsPage/AgentChatPage.tsx" }, + result: { content: "export const AgentChatPage = () => null;" }, + }, + { + name: "write_file", + args: { path: "docs/example.md", content: "# Example\n" }, + result: { path: "docs/example.md" }, + }, + { + name: "edit_files", + args: { + files: [ + { + path: "site/src/example.ts", + edits: [{ old_text: "foo", new_text: "bar" }], + }, + ], + }, + result: { files: [{ path: "site/src/example.ts", status: "edited" }] }, + }, + { + name: "list_templates", + result: { + templates: [ + { + id: "template-1", + name: "go-template", + display_name: "Go Development", + }, + ], + count: 1, + }, + }, + { + name: "read_template", + args: { template_id: "template-1" }, + result: { + template: { name: "go-template", display_name: "Go Development" }, + }, + }, + { + name: "create_workspace", + result: { + created: true, + workspace_name: "agent-icons", + build_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + }, + }, + { + name: "start_workspace", + result: { + started: true, + workspace_name: "agent-icons", + agent_status: "ready", + build_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + }, + }, + { + name: "chat_summarized", + result: { summary: "Earlier transcript content was compacted." }, + }, + { + name: "propose_plan", + args: { path: "/home/coder/.coder/plans/PLAN-example.md" }, + result: { path: "/home/coder/.coder/plans/PLAN-example.md" }, + }, + { + name: "ask_user_question", + args: { questions: [] }, + status: "running", + }, + { + name: "advisor", + args: { question: "Which icon family should represent transcript tools?" }, + result: { answer: "Use category-level icons for better scanning." }, + }, + { + name: "computer", + args: { action: "screenshot" }, + result: { output: { type: "image", data: TEST_PNG_B64 } }, + }, + { + name: "read_skill", + args: { name: "deep-review" }, + result: { + name: "deep-review", + content: "# Deep Review\nReview code carefully.", + }, + }, + { + name: "read_skill_file", + args: { name: "deep-review", path: "roles/security-reviewer.md" }, + result: { content: "# Security Reviewer Role\nCheck auth boundaries." }, + }, + { + name: "spawn_agent", + args: { title: "Repository review", prompt: "Review the code." }, + result: { + chat_id: "bot-child", + title: "Repository review", + status: "completed", + }, + }, + { + name: "wait_agent", + args: { chat_id: "bot-child" }, + result: { + chat_id: "bot-child", + title: "Repository review", + status: "completed", + report: "No issues found.", + }, + }, + { + name: "message_agent", + args: { chat_id: "bot-child", message: "Check icon consistency." }, + result: { chat_id: "bot-child", status: "completed" }, + }, + { + name: "close_agent", + args: { chat_id: "bot-child" }, + result: { chat_id: "bot-child", status: "completed" }, + }, + { + name: "spawn_computer_use_agent", + args: { prompt: "Inspect the UI." }, + result: { chat_id: "desktop-child", status: "completed" }, + subagentVariants: new Map([["desktop-child", "computer_use"]]), + }, + { + name: "read_file", + args: { path: "site/src/pages/AgentsPage/Missing.tsx" }, + status: "error", + isError: true, + result: { error: "File not found" }, + }, + { + name: "create_workspace", + status: "running", + args: { workspace_name: "agent-icons" }, + result: { + workspace_name: "agent-icons", + build_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + }, + }, + { + name: "unknown_tool", + args: { example: true }, + result: { ok: true }, + }, +]; + // --------------------------------------------------------------------------- // Execute stories // --------------------------------------------------------------------------- @@ -2578,3 +2789,65 @@ export const CreateWorkspaceBuildFailed: Story = { expect(canvas.getByText("Failed to create workspace")).toBeInTheDocument(); }, }; + +export const AllToolIconsTranscript: Story = { + render: () => ( + + +
+ + {allToolShowcaseItems.map((tool, index) => ( + + ))} +
+
+
+ ), + parameters: { + queries: [ + { + key: ["workspace", "test-workspace-id"], + data: { + id: "test-workspace-id", + latest_build: { + id: "test-build-id", + status: "running", + }, + }, + }, + { + key: [ + "workspaceBuilds", + "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "logs", + ], + data: [], + }, + ], + }, +}; diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx index 10ea071eda..82c18f6fe7 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx @@ -938,6 +938,10 @@ const GenericToolRenderer: FC = ({ mcpSlug={mcpServer?.slug} /> )} + + ); + const toolHeaderStatus = ( + <> {isError && ( @@ -963,7 +967,11 @@ const GenericToolRenderer: FC = ({ ); return ( - + {toolContent} ); diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolCollapsible.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolCollapsible.tsx index 8b8f236341..d894f33dec 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolCollapsible.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolCollapsible.tsx @@ -17,6 +17,7 @@ interface ToolCollapsibleProps { children: ReactNode; header: ToolCollapsibleHeader; headerActions?: ReactNode; + headerStatus?: ReactNode; hasContent?: boolean; defaultExpanded?: boolean; expanded?: boolean; @@ -50,6 +51,7 @@ export const ToolCollapsible: FC = ({ children, header, headerActions, + headerStatus, hasContent = true, defaultExpanded = false, expanded: expandedProp, @@ -88,6 +90,7 @@ export const ToolCollapsible: FC = ({ onClick={toggleExpanded} > {renderedHeader} + {headerStatus} = ({ )} > {renderedHeader} + {headerStatus} ); diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx index ea3ce5e009..4e79adae93 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx @@ -1,13 +1,14 @@ import { - BookOpenIcon, + BadgeQuestionMarkIcon, BotIcon, - ClipboardListIcon, - FileIcon, - FilePenIcon, - LightbulbIcon, + CompassIcon, + FilePenLineIcon, + FileTextIcon, + LogInIcon, MonitorIcon, - PlayIcon, - PlusCircleIcon, + PowerIcon, + RouteIcon, + ServerIcon, TerminalIcon, WrenchIcon, } from "lucide-react"; @@ -35,7 +36,12 @@ export const ToolIcon: React.FC<{ }> = ({ name, iconUrl, isRunning, serverName, subagentIconKind }) => { const [imgError, setImgError] = useState(false); const color = "text-current"; - const base = cn("size-4 shrink-0", color, isRunning && "grayscale"); + const base = cn( + "size-4 shrink-0", + color, + "stroke-[1.5]", + isRunning && "grayscale", + ); // If an MCP icon URL is provided and hasn't failed, render it. // Strip colour so external icons match the monochrome lucide @@ -93,28 +99,31 @@ export const ToolIcon: React.FC<{ case "process_list": case "process_signal": return ; + case "wait_for_external_auth": + return ; case "read_file": - case "list_templates": - case "read_template": - return ; + case "read_skill": + case "read_skill_file": + return ; case "write_file": case "edit_files": - return ; + return ; + case "list_templates": + case "read_template": case "create_workspace": - return ; + return ; + case "start_workspace": + return ; case "chat_summarized": return ; case "propose_plan": - return ; + return ; + case "ask_user_question": + return ; case "advisor": - return ; + return ; case "computer": return ; - case "read_skill": - case "read_skill_file": - return ; - case "start_workspace": - return ; default: return ; diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/WebSearchSources.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/WebSearchSources.tsx index b6822b2ef0..0ede427fd2 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/WebSearchSources.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/WebSearchSources.tsx @@ -36,8 +36,8 @@ const WebSearchSources: FC = ({ sources }) => { hasContent={unique.length > 0} header={ <> - - + + Searched {detail} diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/WriteFileTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/WriteFileTool.tsx index 11751bf4d7..e70cf930bf 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/WriteFileTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/WriteFileTool.tsx @@ -16,6 +16,7 @@ import { resolveAgentDisplayState, } from "./displayMode"; import { AgentDisplayModeToolCollapsible } from "./ToolCollapsible"; +import { ToolIcon } from "./ToolIcon"; import { DIFFS_FONT_STYLE, getDiffViewerOptions, @@ -53,7 +54,12 @@ export const WriteFileTool: React.FC<{ autoDisplayState={WRITE_FILE_AUTO_DISPLAY_STATE} header={ <> - {label} + + {label} + + } + headerStatus={ + <> {isError && (