diff --git a/site/src/components/ai-elements/tool.stories.tsx b/site/src/components/ai-elements/tool.stories.tsx
index 4f7aead699..43c66be6d8 100644
--- a/site/src/components/ai-elements/tool.stories.tsx
+++ b/site/src/components/ai-elements/tool.stories.tsx
@@ -1257,3 +1257,139 @@ export const WaitAgentComputerUseRunning: Story = {
});
},
};
+
+// ---------------------------------------------------------------------------
+// read_skill stories
+// ---------------------------------------------------------------------------
+
+export const ReadSkillRunning: Story = {
+ args: {
+ name: "read_skill",
+ status: "running",
+ args: { name: "deep-review" },
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/Reading skill deep-review/)).toBeInTheDocument();
+ },
+};
+
+export const ReadSkillCompleted: Story = {
+ args: {
+ name: "read_skill",
+ status: "completed",
+ args: { name: "deep-review" },
+ result: {
+ name: "deep-review",
+ body: "## Deep Review Skill\n\nReview the code changes thoroughly.\n\n1. Check for correctness\n2. Verify tests\n3. Ensure style consistency",
+ files: ["roles/security-reviewer.md", "templates/review-checklist.md"],
+ },
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/Read skill deep-review/)).toBeInTheDocument();
+ },
+};
+
+export const ReadSkillError: Story = {
+ args: {
+ name: "read_skill",
+ status: "error",
+ isError: true,
+ args: { name: "nonexistent-skill" },
+ result: { error: 'skill "nonexistent-skill" not found' },
+ },
+ play: async ({ canvasElement }) => {
+ expect(
+ canvasElement.querySelector(".lucide-triangle-alert"),
+ ).not.toBeNull();
+ },
+};
+
+// ---------------------------------------------------------------------------
+// read_skill_file stories
+// ---------------------------------------------------------------------------
+
+export const ReadSkillFileRunning: Story = {
+ args: {
+ name: "read_skill_file",
+ status: "running",
+ args: { name: "deep-review", path: "roles/security-reviewer.md" },
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(
+ canvas.getByText(/Reading deep-review\/roles\/security-reviewer\.md/),
+ ).toBeInTheDocument();
+ },
+};
+
+export const ReadSkillFileCompleted: Story = {
+ args: {
+ name: "read_skill_file",
+ status: "completed",
+ args: { name: "deep-review", path: "roles/security-reviewer.md" },
+ result: {
+ content:
+ "# Security Reviewer Role\n\nFocus on authentication, authorization, and input validation.\n\n## Checklist\n- [ ] Verify auth middleware\n- [ ] Check for SQL injection\n- [ ] Validate user inputs",
+ },
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(
+ canvas.getByText(/Read deep-review\/roles\/security-reviewer\.md/),
+ ).toBeInTheDocument();
+ },
+};
+
+export const ReadSkillFileError: Story = {
+ args: {
+ name: "read_skill_file",
+ status: "error",
+ isError: true,
+ args: { name: "deep-review", path: "missing-file.md" },
+ result: { error: "file not found" },
+ },
+};
+
+// ---------------------------------------------------------------------------
+// start_workspace stories
+// ---------------------------------------------------------------------------
+
+export const StartWorkspaceRunning: Story = {
+ args: {
+ name: "start_workspace",
+ status: "running",
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText("Starting workspace…")).toBeInTheDocument();
+ },
+};
+
+export const StartWorkspaceCompleted: Story = {
+ args: {
+ name: "start_workspace",
+ status: "completed",
+ result: {
+ started: true,
+ workspace_name: "my-project",
+ agent_status: "ready",
+ },
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText("Started my-project")).toBeInTheDocument();
+ },
+};
+
+export const StartWorkspaceError: Story = {
+ args: {
+ name: "start_workspace",
+ status: "error",
+ isError: true,
+ result: {
+ error: "workspace was deleted; use create_workspace to make a new one",
+ },
+ },
+};
diff --git a/site/src/components/ai-elements/tool/ToolIcon.tsx b/site/src/components/ai-elements/tool/ToolIcon.tsx
index 5af37b8e97..61402984e0 100644
--- a/site/src/components/ai-elements/tool/ToolIcon.tsx
+++ b/site/src/components/ai-elements/tool/ToolIcon.tsx
@@ -1,9 +1,11 @@
import {
+ BookOpenIcon,
BotIcon,
ClipboardListIcon,
FileIcon,
FilePenIcon,
MonitorIcon,
+ PlayIcon,
PlusCircleIcon,
TerminalIcon,
WrenchIcon,
@@ -85,6 +87,12 @@ export const ToolIcon: React.FC<{
case "computer":
case "spawn_computer_use_agent":
return ;
+ case "read_skill":
+ case "read_skill_file":
+ return ;
+ case "start_workspace":
+ return ;
+
default:
return ;
}
diff --git a/site/src/components/ai-elements/tool/ToolLabel.tsx b/site/src/components/ai-elements/tool/ToolLabel.tsx
index a6ee73908a..4f9d4396d9 100644
--- a/site/src/components/ai-elements/tool/ToolLabel.tsx
+++ b/site/src/components/ai-elements/tool/ToolLabel.tsx
@@ -170,6 +170,40 @@ export const ToolLabel: React.FC<{
);
}
+ case "read_skill": {
+ const skillName = parsed ? asString(parsed.name) : "";
+ return (
+
+ {skillName
+ ? parsedResult
+ ? `Read skill ${skillName}`
+ : `Reading skill ${skillName}…`
+ : "Reading skill…"}
+
+ );
+ }
+ 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 (
+
+ {parsedResult ? `Read ${label}` : `Reading ${label}…`}
+
+ );
+ }
+ case "start_workspace": {
+ const wsName = parsedResult ? asString(parsedResult.workspace_name) : "";
+ return (
+
+ {wsName ? `Started ${wsName}` : "Starting workspace…"}
+
+ );
+ }
+
default: {
const displayName = mcpSlug ? humanizeMCPToolName(mcpSlug, name) : name;
return (