diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ReadSkillTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadSkillTool.tsx new file mode 100644 index 0000000000..6a35d426d9 --- /dev/null +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ReadSkillTool.tsx @@ -0,0 +1,63 @@ +import { BookOpenIcon, LoaderIcon, TriangleAlertIcon } from "lucide-react"; +import type React from "react"; +import { ScrollArea } from "#/components/ScrollArea/ScrollArea"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "#/components/Tooltip/Tooltip"; +import { cn } from "#/utils/cn"; +import { Response } from "../Response"; +import { ToolCollapsible } from "./ToolCollapsible"; +import type { ToolStatus } from "./utils"; + +export const ReadSkillTool: React.FC<{ + label: string; + body: string; + status: ToolStatus; + isError: boolean; + errorMessage?: string; +}> = ({ label, body, status, isError, errorMessage }) => { + const hasContent = body.length > 0; + const isRunning = status === "running"; + + return ( + + + + {isRunning ? `Reading ${label}…` : `Read ${label}`} + + {isError && ( + + + + + + {errorMessage || "Failed to read skill"} + + + )} + {isRunning && ( + + )} + + } + > + {body && ( + +
+ {body} +
+
+ )} +
+ ); +}; 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 ca782edc12..42cd297038 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx @@ -1342,6 +1342,12 @@ export const ReadSkillCompleted: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByText(/Read skill deep-review/)).toBeInTheDocument(); + // Expand the collapsible to verify markdown body renders. + const toggle = canvas.getByRole("button"); + await userEvent.click(toggle); + await waitFor(() => { + expect(canvas.getByText("Deep Review Skill")).toBeInTheDocument(); + }); }, }; @@ -1393,6 +1399,12 @@ export const ReadSkillFileCompleted: Story = { expect( canvas.getByText(/Read deep-review\/roles\/security-reviewer\.md/), ).toBeInTheDocument(); + // Expand the collapsible to verify markdown content renders. + const toggle = canvas.getByRole("button"); + await userEvent.click(toggle); + await waitFor(() => { + expect(canvas.getByText("Security Reviewer Role")).toBeInTheDocument(); + }); }, }; diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx index 084f00e8b5..e085aa6073 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx @@ -23,6 +23,7 @@ import { ListTemplatesTool } from "./ListTemplatesTool"; import { ProcessOutputTool } from "./ProcessOutputTool"; import { ProposePlanTool } from "./ProposePlanTool"; import { ReadFileTool } from "./ReadFileTool"; +import { ReadSkillTool } from "./ReadSkillTool"; import { ReadTemplateTool } from "./ReadTemplateTool"; import { SubagentTool } from "./SubagentTool"; import { ToolCollapsible } from "./ToolCollapsible"; @@ -210,6 +211,55 @@ const ReadFileRenderer: FC = ({ ); }; +const ReadSkillRenderer: FC = ({ + status, + args, + result, + isError, +}) => { + const parsedArgs = parseArgs(args); + const skillName = parsedArgs ? asString(parsedArgs.name) : ""; + const rec = asRecord(result); + const body = rec ? asString(rec.body) : ""; + + return ( + + ); +}; + +const ReadSkillFileRenderer: FC = ({ + status, + args, + result, + isError, +}) => { + const parsedArgs = parseArgs(args); + const skillName = parsedArgs ? asString(parsedArgs.name) : ""; + const filePath = parsedArgs ? asString(parsedArgs.path) : ""; + const label = + skillName && filePath + ? `${skillName}/${filePath}` + : skillName || filePath || "skill file"; + const rec = asRecord(result); + const content = rec ? asString(rec.content) : ""; + + return ( + + ); +}; + const WriteFileRenderer: FC = ({ status, args, @@ -667,6 +717,8 @@ const toolRenderers: Record> = { create_workspace: CreateWorkspaceRenderer, list_templates: ListTemplatesRenderer, read_template: ReadTemplateRenderer, + read_skill: ReadSkillRenderer, + read_skill_file: ReadSkillFileRenderer, spawn_agent: SubagentRenderer, wait_agent: SubagentRenderer, message_agent: SubagentRenderer,