@grackle-ai/web-components 0.107.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/.rush/temp/3ae72563f781afd72723475938136f113846603e.untar.log +10 -0
  2. package/.rush/temp/bc1d5bf9201ce71abeaeaddd096deb9b0805d703.untar.log +10 -0
  3. package/.rush/temp/operation/_phase_build/all.log +18 -0
  4. package/.rush/temp/operation/_phase_build/log-chunks.jsonl +18 -0
  5. package/.rush/temp/operation/_phase_build/state.json +3 -0
  6. package/.rush/temp/operation/_phase_test/all.log +121 -0
  7. package/.rush/temp/operation/_phase_test/log-chunks.jsonl +121 -0
  8. package/.rush/temp/operation/_phase_test/state.json +3 -0
  9. package/.rush/temp/shrinkwrap-deps.json +938 -0
  10. package/.storybook/main.ts +22 -0
  11. package/.storybook/preview.tsx +30 -0
  12. package/config/rig.json +4 -0
  13. package/config/rush-project.json +12 -0
  14. package/dist/index.css +1 -0
  15. package/dist/index.js +39221 -0
  16. package/eslint.config.cjs +5 -0
  17. package/package.json +83 -0
  18. package/rush-logs/web-components._phase_build.cache.log +4 -0
  19. package/rush-logs/web-components._phase_test.cache.log +4 -0
  20. package/src/components/chat/ChatInput.module.scss +81 -0
  21. package/src/components/chat/ChatInput.stories.tsx +91 -0
  22. package/src/components/chat/ChatInput.tsx +168 -0
  23. package/src/components/chat/index.ts +6 -0
  24. package/src/components/dag/DagView.module.scss +149 -0
  25. package/src/components/dag/DagView.stories.tsx +125 -0
  26. package/src/components/dag/DagView.tsx +109 -0
  27. package/src/components/dag/TaskNode.stories.tsx +133 -0
  28. package/src/components/dag/TaskNode.tsx +40 -0
  29. package/src/components/dag/useDagLayout.ts +139 -0
  30. package/src/components/display/Breadcrumbs.module.scss +71 -0
  31. package/src/components/display/Breadcrumbs.stories.tsx +80 -0
  32. package/src/components/display/Breadcrumbs.tsx +46 -0
  33. package/src/components/display/Button.module.scss +110 -0
  34. package/src/components/display/Button.stories.tsx +88 -0
  35. package/src/components/display/Button.tsx +40 -0
  36. package/src/components/display/ConfirmDialog.module.scss +67 -0
  37. package/src/components/display/ConfirmDialog.stories.tsx +81 -0
  38. package/src/components/display/ConfirmDialog.tsx +88 -0
  39. package/src/components/display/CopyButton.module.scss +41 -0
  40. package/src/components/display/CopyButton.stories.tsx +78 -0
  41. package/src/components/display/CopyButton.tsx +64 -0
  42. package/src/components/display/DemoBanner.module.scss +37 -0
  43. package/src/components/display/DemoBanner.stories.tsx +40 -0
  44. package/src/components/display/DemoBanner.tsx +23 -0
  45. package/src/components/display/EventHoverRow.module.scss +102 -0
  46. package/src/components/display/EventHoverRow.stories.tsx +99 -0
  47. package/src/components/display/EventHoverRow.tsx +154 -0
  48. package/src/components/display/EventRenderer.module.scss +272 -0
  49. package/src/components/display/EventRenderer.stories.tsx +186 -0
  50. package/src/components/display/EventRenderer.tsx +271 -0
  51. package/src/components/display/EventStream.module.scss +93 -0
  52. package/src/components/display/EventStream.stories.tsx +249 -0
  53. package/src/components/display/EventStream.tsx +369 -0
  54. package/src/components/display/FloatingActionBar.module.scss +107 -0
  55. package/src/components/display/FloatingActionBar.stories.tsx +122 -0
  56. package/src/components/display/FloatingActionBar.tsx +119 -0
  57. package/src/components/display/SessionAttemptSelector.module.scss +50 -0
  58. package/src/components/display/SessionAttemptSelector.stories.tsx +78 -0
  59. package/src/components/display/SessionAttemptSelector.tsx +49 -0
  60. package/src/components/display/SessionPicker.module.scss +200 -0
  61. package/src/components/display/SessionPicker.stories.tsx +169 -0
  62. package/src/components/display/SessionPicker.tsx +214 -0
  63. package/src/components/display/Skeleton.module.scss +58 -0
  64. package/src/components/display/Skeleton.stories.tsx +94 -0
  65. package/src/components/display/Skeleton.tsx +127 -0
  66. package/src/components/display/Spinner.module.scss +41 -0
  67. package/src/components/display/Spinner.stories.tsx +66 -0
  68. package/src/components/display/Spinner.tsx +32 -0
  69. package/src/components/display/SplashScreen.module.scss +20 -0
  70. package/src/components/display/SplashScreen.stories.tsx +26 -0
  71. package/src/components/display/SplashScreen.tsx +16 -0
  72. package/src/components/display/SplitButton.module.scss +166 -0
  73. package/src/components/display/SplitButton.stories.tsx +95 -0
  74. package/src/components/display/SplitButton.tsx +128 -0
  75. package/src/components/display/Tooltip.module.scss +84 -0
  76. package/src/components/display/Tooltip.stories.tsx +240 -0
  77. package/src/components/display/Tooltip.tsx +184 -0
  78. package/src/components/display/extractText.test.tsx +48 -0
  79. package/src/components/display/index.ts +20 -0
  80. package/src/components/editable/EditableCheckbox.stories.tsx +54 -0
  81. package/src/components/editable/EditableCheckbox.tsx +39 -0
  82. package/src/components/editable/EditableField.module.scss +135 -0
  83. package/src/components/editable/EditableSelect.tsx +164 -0
  84. package/src/components/editable/EditableTextArea.stories.tsx +50 -0
  85. package/src/components/editable/EditableTextArea.tsx +148 -0
  86. package/src/components/editable/EditableTextField.stories.tsx +62 -0
  87. package/src/components/editable/EditableTextField.tsx +153 -0
  88. package/src/components/editable/EnvironmentSelect.module.scss +17 -0
  89. package/src/components/editable/EnvironmentSelect.stories.tsx +61 -0
  90. package/src/components/editable/EnvironmentSelect.tsx +87 -0
  91. package/src/components/editable/index.ts +13 -0
  92. package/src/components/editable/useEditableField.test.tsx +233 -0
  93. package/src/components/editable/useEditableField.ts +173 -0
  94. package/src/components/index.ts +20 -0
  95. package/src/components/knowledge/KnowledgeDetailPanel.module.scss +162 -0
  96. package/src/components/knowledge/KnowledgeDetailPanel.stories.tsx +208 -0
  97. package/src/components/knowledge/KnowledgeDetailPanel.tsx +122 -0
  98. package/src/components/knowledge/KnowledgeGraph.module.scss +110 -0
  99. package/src/components/knowledge/KnowledgeGraph.stories.tsx +180 -0
  100. package/src/components/knowledge/KnowledgeGraph.tsx +455 -0
  101. package/src/components/knowledge/KnowledgeNav.module.scss +130 -0
  102. package/src/components/knowledge/KnowledgeNav.stories.tsx +108 -0
  103. package/src/components/knowledge/KnowledgeNav.tsx +138 -0
  104. package/src/components/knowledge/index.ts +3 -0
  105. package/src/components/layout/AppNav.module.scss +82 -0
  106. package/src/components/layout/AppNav.stories.tsx +115 -0
  107. package/src/components/layout/AppNav.tsx +133 -0
  108. package/src/components/layout/BottomStatusBar.module.scss +58 -0
  109. package/src/components/layout/BottomStatusBar.stories.tsx +35 -0
  110. package/src/components/layout/BottomStatusBar.tsx +206 -0
  111. package/src/components/layout/Sidebar.module.scss +60 -0
  112. package/src/components/layout/Sidebar.stories.tsx +46 -0
  113. package/src/components/layout/Sidebar.tsx +84 -0
  114. package/src/components/layout/StatusBar.module.scss +108 -0
  115. package/src/components/layout/StatusBar.stories.tsx +119 -0
  116. package/src/components/layout/StatusBar.tsx +70 -0
  117. package/src/components/layout/index.ts +9 -0
  118. package/src/components/lists/EnvironmentNav.module.scss +118 -0
  119. package/src/components/lists/EnvironmentNav.stories.tsx +121 -0
  120. package/src/components/lists/EnvironmentNav.tsx +133 -0
  121. package/src/components/lists/FindingsNav.module.scss +126 -0
  122. package/src/components/lists/FindingsNav.tsx +146 -0
  123. package/src/components/lists/TaskList.module.scss +206 -0
  124. package/src/components/lists/TaskList.stories.tsx +401 -0
  125. package/src/components/lists/TaskList.tsx +509 -0
  126. package/src/components/lists/index.ts +6 -0
  127. package/src/components/lists/listHelpers.tsx +130 -0
  128. package/src/components/notifications/Callout.module.scss +83 -0
  129. package/src/components/notifications/Callout.stories.tsx +81 -0
  130. package/src/components/notifications/Callout.tsx +64 -0
  131. package/src/components/notifications/Toast.module.scss +86 -0
  132. package/src/components/notifications/Toast.stories.tsx +71 -0
  133. package/src/components/notifications/Toast.tsx +51 -0
  134. package/src/components/notifications/ToastContainer.module.scss +23 -0
  135. package/src/components/notifications/ToastContainer.stories.tsx +66 -0
  136. package/src/components/notifications/ToastContainer.tsx +29 -0
  137. package/src/components/notifications/UpdateBanner.stories.tsx +77 -0
  138. package/src/components/notifications/UpdateBanner.test.tsx +64 -0
  139. package/src/components/notifications/UpdateBanner.tsx +44 -0
  140. package/src/components/notifications/index.ts +8 -0
  141. package/src/components/panels/AboutPanel.stories.tsx +70 -0
  142. package/src/components/panels/AboutPanel.tsx +66 -0
  143. package/src/components/panels/AppearancePanel.stories.tsx +45 -0
  144. package/src/components/panels/AppearancePanel.tsx +97 -0
  145. package/src/components/panels/CredentialProvidersPanel.stories.tsx +62 -0
  146. package/src/components/panels/CredentialProvidersPanel.tsx +111 -0
  147. package/src/components/panels/EnvironmentEditPanel.module.scss +170 -0
  148. package/src/components/panels/EnvironmentEditPanel.stories.tsx +206 -0
  149. package/src/components/panels/EnvironmentEditPanel.tsx +785 -0
  150. package/src/components/panels/FindingsPanel.module.scss +94 -0
  151. package/src/components/panels/FindingsPanel.stories.tsx +109 -0
  152. package/src/components/panels/FindingsPanel.tsx +76 -0
  153. package/src/components/panels/KeyboardShortcutsPanel.module.scss +65 -0
  154. package/src/components/panels/KeyboardShortcutsPanel.stories.tsx +40 -0
  155. package/src/components/panels/KeyboardShortcutsPanel.tsx +104 -0
  156. package/src/components/panels/PluginsPanel.tsx +77 -0
  157. package/src/components/panels/SettingsPanel.module.scss +336 -0
  158. package/src/components/panels/TaskActionButtons.module.scss +22 -0
  159. package/src/components/panels/TaskActionButtons.stories.tsx +125 -0
  160. package/src/components/panels/TaskActionButtons.tsx +87 -0
  161. package/src/components/panels/TaskEditPanel.module.scss +202 -0
  162. package/src/components/panels/TaskEditPanel.stories.tsx +75 -0
  163. package/src/components/panels/TaskEditPanel.tsx +328 -0
  164. package/src/components/panels/TaskOverviewPanel.module.scss +236 -0
  165. package/src/components/panels/TaskOverviewPanel.stories.tsx +219 -0
  166. package/src/components/panels/TaskOverviewPanel.tsx +270 -0
  167. package/src/components/panels/TokensPanel.stories.tsx +131 -0
  168. package/src/components/panels/TokensPanel.tsx +143 -0
  169. package/src/components/panels/WorkpadPanel.module.scss +39 -0
  170. package/src/components/panels/WorkpadPanel.stories.tsx +56 -0
  171. package/src/components/panels/WorkpadPanel.tsx +63 -0
  172. package/src/components/panels/index.ts +13 -0
  173. package/src/components/personas/McpToolSelector.module.scss +109 -0
  174. package/src/components/personas/McpToolSelector.stories.tsx +129 -0
  175. package/src/components/personas/McpToolSelector.tsx +180 -0
  176. package/src/components/personas/PersonaManager.module.scss +233 -0
  177. package/src/components/personas/PersonaManager.stories.tsx +139 -0
  178. package/src/components/personas/PersonaManager.tsx +122 -0
  179. package/src/components/schedules/ScheduleManager.module.scss +98 -0
  180. package/src/components/schedules/ScheduleManager.stories.tsx +78 -0
  181. package/src/components/schedules/ScheduleManager.tsx +160 -0
  182. package/src/components/settings/SettingsNav.module.scss +82 -0
  183. package/src/components/settings/SettingsNav.stories.tsx +83 -0
  184. package/src/components/settings/SettingsNav.tsx +104 -0
  185. package/src/components/streams/StreamDetailPanel.module.scss +206 -0
  186. package/src/components/streams/StreamDetailPanel.stories.tsx +132 -0
  187. package/src/components/streams/StreamDetailPanel.tsx +119 -0
  188. package/src/components/streams/StreamList.module.scss +92 -0
  189. package/src/components/streams/StreamList.stories.tsx +99 -0
  190. package/src/components/streams/StreamList.tsx +114 -0
  191. package/src/components/streams/index.ts +10 -0
  192. package/src/components/tools/AgentToolCard.module.scss +118 -0
  193. package/src/components/tools/AgentToolCard.stories.tsx +304 -0
  194. package/src/components/tools/AgentToolCard.tsx +247 -0
  195. package/src/components/tools/FileEditCard.stories.tsx +138 -0
  196. package/src/components/tools/FileEditCard.tsx +160 -0
  197. package/src/components/tools/FileReadCard.stories.tsx +120 -0
  198. package/src/components/tools/FileReadCard.tsx +106 -0
  199. package/src/components/tools/FindingCard.stories.tsx +124 -0
  200. package/src/components/tools/FindingCard.tsx +178 -0
  201. package/src/components/tools/GenericToolCard.stories.tsx +80 -0
  202. package/src/components/tools/GenericToolCard.tsx +111 -0
  203. package/src/components/tools/IpcCard.stories.tsx +129 -0
  204. package/src/components/tools/IpcCard.tsx +178 -0
  205. package/src/components/tools/KnowledgeCard.stories.tsx +112 -0
  206. package/src/components/tools/KnowledgeCard.tsx +165 -0
  207. package/src/components/tools/MetadataCard.stories.tsx +32 -0
  208. package/src/components/tools/MetadataCard.tsx +39 -0
  209. package/src/components/tools/SearchCard.stories.tsx +74 -0
  210. package/src/components/tools/SearchCard.tsx +86 -0
  211. package/src/components/tools/ShellCard.stories.tsx +112 -0
  212. package/src/components/tools/ShellCard.tsx +106 -0
  213. package/src/components/tools/TaskCard.stories.tsx +123 -0
  214. package/src/components/tools/TaskCard.tsx +203 -0
  215. package/src/components/tools/TodoCard.module.scss +131 -0
  216. package/src/components/tools/TodoCard.stories.tsx +202 -0
  217. package/src/components/tools/TodoCard.tsx +200 -0
  218. package/src/components/tools/ToolCard.stories.tsx +177 -0
  219. package/src/components/tools/ToolCard.tsx +60 -0
  220. package/src/components/tools/ToolCardProps.ts +20 -0
  221. package/src/components/tools/ToolSearchCard.stories.tsx +81 -0
  222. package/src/components/tools/ToolSearchCard.tsx +86 -0
  223. package/src/components/tools/WorkpadCard.stories.tsx +106 -0
  224. package/src/components/tools/WorkpadCard.tsx +125 -0
  225. package/src/components/tools/classifyTool.test.ts +44 -0
  226. package/src/components/tools/classifyTool.ts +134 -0
  227. package/src/components/tools/parseDiff.ts +95 -0
  228. package/src/components/tools/parseShellOutput.ts +28 -0
  229. package/src/components/tools/toolCardHelpers.test.ts +53 -0
  230. package/src/components/tools/toolCards.module.scss +234 -0
  231. package/src/components/workspace/WorkspaceBoard.module.scss +238 -0
  232. package/src/components/workspace/WorkspaceBoard.stories.tsx +240 -0
  233. package/src/components/workspace/WorkspaceBoard.tsx +232 -0
  234. package/src/components/workspace/WorkspaceFormFields.module.scss +79 -0
  235. package/src/components/workspace/WorkspaceFormFields.stories.tsx +133 -0
  236. package/src/components/workspace/WorkspaceFormFields.tsx +185 -0
  237. package/src/context/GrackleContext.ts +28 -0
  238. package/src/context/GrackleContextTypes.ts +64 -0
  239. package/src/context/SidebarContext.tsx +53 -0
  240. package/src/context/ThemeContext.tsx +21 -0
  241. package/src/context/ToastContext.tsx +56 -0
  242. package/src/hooks/types.ts +864 -0
  243. package/src/hooks/useEventSelection.test.ts +204 -0
  244. package/src/hooks/useEventSelection.ts +158 -0
  245. package/src/hooks/useSmartScroll.ts +151 -0
  246. package/src/hooks/useTheme.ts +228 -0
  247. package/src/index.ts +210 -0
  248. package/src/mocks/MockGrackleProvider.tsx +1397 -0
  249. package/src/mocks/mockData.ts +1966 -0
  250. package/src/mocks/mockKnowledgeData.ts +294 -0
  251. package/src/scss.d.ts +12 -0
  252. package/src/styles/global.scss +244 -0
  253. package/src/styles/mixins.scss +278 -0
  254. package/src/styles/prism-theme.scss +148 -0
  255. package/src/styles/theme.scss +1102 -0
  256. package/src/test-utils/storybook-decorators.tsx +50 -0
  257. package/src/test-utils/storybook-helpers.ts +262 -0
  258. package/src/themes.ts +142 -0
  259. package/src/utils/boardColumns.ts +141 -0
  260. package/src/utils/breadcrumbs.test.ts +285 -0
  261. package/src/utils/breadcrumbs.ts +222 -0
  262. package/src/utils/dashboard.test.ts +156 -0
  263. package/src/utils/dashboard.ts +195 -0
  264. package/src/utils/eventContent.test.ts +353 -0
  265. package/src/utils/eventContent.ts +209 -0
  266. package/src/utils/findingCategory.ts +33 -0
  267. package/src/utils/format.ts +27 -0
  268. package/src/utils/iconSize.ts +18 -0
  269. package/src/utils/navigation.ts +205 -0
  270. package/src/utils/route-config.test.ts +128 -0
  271. package/src/utils/scrollUtils.test.ts +65 -0
  272. package/src/utils/scrollUtils.ts +49 -0
  273. package/src/utils/sessionEvents.test.ts +302 -0
  274. package/src/utils/sessionEvents.ts +233 -0
  275. package/src/utils/taskStatus.tsx +137 -0
  276. package/src/utils/time.ts +92 -0
  277. package/tsconfig.json +8 -0
  278. package/vite.config.ts +20 -0
  279. package/vitest.config.ts +10 -0
@@ -0,0 +1,304 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { expect, userEvent } from "@storybook/test";
3
+ import { AgentToolCard } from "./AgentToolCard.js";
4
+
5
+ const meta: Meta<typeof AgentToolCard> = {
6
+ component: AgentToolCard,
7
+ title: "Tools/AgentToolCard",
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof meta>;
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Claude Code - Agent tool
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export const ClaudeCodeForeground: Story = {
17
+ name: "Claude Code - foreground Explore agent",
18
+ args: {
19
+ tool: "Agent",
20
+ args: {
21
+ subagent_type: "Explore",
22
+ description: "Find all TypeScript files with runtime",
23
+ prompt: "Search for all TypeScript (.ts and .tsx) files under any src directory that contain the word runtime. List the matching file paths.",
24
+ model: "sonnet",
25
+ },
26
+ result: "Found 23 matching files:\n- packages/runtime-sdk/src/runtime.ts\n- packages/runtime-claude-code/src/claude-code.ts\n- packages/runtime-copilot/src/copilot.ts\n- packages/server/src/runtime-manager.ts",
27
+ },
28
+ play: async ({ canvas }) => {
29
+ await expect(canvas.getByTestId("tool-card-agent")).toBeInTheDocument();
30
+ await expect(canvas.getByTestId("tool-card-agent-type")).toHaveTextContent("Explore");
31
+ await expect(canvas.getByTestId("tool-card-agent-model")).toHaveTextContent("sonnet");
32
+ await expect(canvas.getByTestId("tool-card-agent-description")).toHaveTextContent("Find all TypeScript files with runtime");
33
+ await expect(canvas.getByTestId("tool-card-result")).toBeInTheDocument();
34
+ // Should NOT have background indicator
35
+ await expect(canvas.queryByTestId("tool-card-agent-background")).not.toBeInTheDocument();
36
+ },
37
+ };
38
+
39
+ export const ClaudeCodeBackground: Story = {
40
+ name: "Claude Code - background agent completed",
41
+ args: {
42
+ tool: "Agent",
43
+ args: {
44
+ subagent_type: "general-purpose",
45
+ description: "Research API options",
46
+ prompt: "Research the available authentication methods for the GitHub API.",
47
+ run_in_background: true,
48
+ model: "opus",
49
+ },
50
+ result: "GitHub API supports three auth methods:\n1. Personal access tokens (PAT)\n2. GitHub App installation tokens\n3. OAuth app tokens",
51
+ },
52
+ play: async ({ canvas }) => {
53
+ await expect(canvas.getByTestId("tool-card-agent-type")).toHaveTextContent("general-purpose");
54
+ await expect(canvas.getByTestId("tool-card-agent-model")).toHaveTextContent("opus");
55
+ await expect(canvas.getByTestId("tool-card-agent-background")).toBeInTheDocument();
56
+ await expect(canvas.getByTestId("tool-card-agent-background")).toHaveTextContent("BG");
57
+ },
58
+ };
59
+
60
+ export const ClaudeCodeInProgress: Story = {
61
+ name: "Claude Code - in progress (foreground)",
62
+ args: {
63
+ tool: "Agent",
64
+ args: {
65
+ subagent_type: "Explore",
66
+ description: "Count .ts files in the project",
67
+ prompt: "Count the total number of .ts files (not .tsx) in this project. Return just the count.",
68
+ },
69
+ // No result -- still running
70
+ },
71
+ play: async ({ canvas }) => {
72
+ await expect(canvas.getByTestId("tool-card-pending")).toBeInTheDocument();
73
+ await expect(canvas.queryByTestId("tool-card-result")).not.toBeInTheDocument();
74
+ // Card should be dimmed
75
+ const card = canvas.getByTestId("tool-card-agent");
76
+ await expect(card.className).toContain("inProgress");
77
+ },
78
+ };
79
+
80
+ export const ClaudeCodeBackgroundInProgress: Story = {
81
+ name: "Claude Code - in progress (background)",
82
+ args: {
83
+ tool: "Agent",
84
+ args: {
85
+ subagent_type: "general-purpose",
86
+ description: "Long running analysis",
87
+ prompt: "Analyze the entire codebase for security vulnerabilities.",
88
+ run_in_background: true,
89
+ },
90
+ // No result -- still running
91
+ },
92
+ play: async ({ canvas }) => {
93
+ // Background agents show BG badge with pulsing dot, NOT the generic pending dot
94
+ await expect(canvas.getByTestId("tool-card-agent-background")).toBeInTheDocument();
95
+ await expect(canvas.queryByTestId("tool-card-pending")).not.toBeInTheDocument();
96
+ },
97
+ };
98
+
99
+ export const ClaudeCodeError: Story = {
100
+ name: "Claude Code - error result",
101
+ args: {
102
+ tool: "Agent",
103
+ args: {
104
+ subagent_type: "general-purpose",
105
+ description: "Fix the auth bug",
106
+ prompt: "Find and fix the authentication bug in the login flow.",
107
+ },
108
+ result: "Error: Agent exceeded maximum turns (10). The task was not completed.",
109
+ isError: true,
110
+ },
111
+ play: async ({ canvas }) => {
112
+ const card = canvas.getByTestId("tool-card-agent");
113
+ await expect(card.className).toContain("cardRed");
114
+ await expect(canvas.getByTestId("tool-card-error")).toBeInTheDocument();
115
+ },
116
+ };
117
+
118
+ export const ClaudeCodeLongResult: Story = {
119
+ name: "Claude Code - long result with expand/collapse",
120
+ args: {
121
+ tool: "Agent",
122
+ args: {
123
+ subagent_type: "Explore",
124
+ description: "List all package.json files",
125
+ prompt: "Find every package.json in the repo.",
126
+ },
127
+ result: "packages/cli/package.json\npackages/common/package.json\npackages/core/package.json\npackages/mcp/package.json\npackages/server/package.json\npackages/web/package.json\npackages/web-components/package.json\npackages/runtime-sdk/package.json\npackages/runtime-claude-code/package.json\npackages/runtime-copilot/package.json",
128
+ },
129
+ play: async ({ canvas }) => {
130
+ const toggle = canvas.getByTestId("tool-card-toggle");
131
+ await expect(toggle).toBeInTheDocument();
132
+ await expect(toggle.textContent).toContain("5 more lines");
133
+
134
+ await userEvent.click(toggle);
135
+ await expect(toggle.textContent).toContain("collapse");
136
+
137
+ await userEvent.click(toggle);
138
+ await expect(toggle.textContent).toContain("more lines");
139
+ },
140
+ };
141
+
142
+ export const ClaudeCodeResume: Story = {
143
+ name: "Claude Code - resuming a prior agent",
144
+ args: {
145
+ tool: "Agent",
146
+ args: {
147
+ subagent_type: "general-purpose",
148
+ description: "Continue the auth investigation",
149
+ prompt: "Pick up where you left off analyzing the auth flow.",
150
+ resume: "a40b974b6929b2f4a",
151
+ },
152
+ result: "Resumed analysis. The root cause is a missing token refresh in the middleware.",
153
+ },
154
+ play: async ({ canvas }) => {
155
+ await expect(canvas.getByTestId("tool-card-agent-description")).toHaveTextContent("Resuming: Continue the auth investigation");
156
+ },
157
+ };
158
+
159
+ export const ClaudeCodePromptToggle: Story = {
160
+ name: "Claude Code - prompt expand/collapse",
161
+ args: {
162
+ tool: "Agent",
163
+ args: {
164
+ subagent_type: "Explore",
165
+ description: "Search for patterns",
166
+ prompt: "This is a detailed prompt that describes exactly what the subagent should do. It includes multiple sentences and specific instructions.",
167
+ },
168
+ result: "Done.",
169
+ },
170
+ play: async ({ canvas }) => {
171
+ // Prompt should be collapsed by default
172
+ await expect(canvas.queryByTestId("tool-card-prompt")).not.toBeInTheDocument();
173
+
174
+ // Click to expand
175
+ const toggle = canvas.getByTestId("tool-card-prompt-toggle");
176
+ await userEvent.click(toggle);
177
+ await expect(canvas.getByTestId("tool-card-prompt")).toBeInTheDocument();
178
+
179
+ // Click to collapse
180
+ await userEvent.click(toggle);
181
+ await expect(canvas.queryByTestId("tool-card-prompt")).not.toBeInTheDocument();
182
+ },
183
+ };
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // Copilot - task tool
187
+ // ---------------------------------------------------------------------------
188
+
189
+ export const CopilotTask: Story = {
190
+ name: "Copilot - background task agent",
191
+ args: {
192
+ tool: "task",
193
+ args: {
194
+ agent_type: "explore",
195
+ description: "Searching for grackle in files",
196
+ mode: "background",
197
+ name: "find-grackle-files",
198
+ prompt: "Search all files in the current directory and subdirectories for the word grackle. List the file paths containing matches.",
199
+ },
200
+ result: "Agent started in background with agent_id: find-grackle-files. You can use read_agent tool with this agent_id to check status and retrieve results.",
201
+ },
202
+ play: async ({ canvas }) => {
203
+ await expect(canvas.getByTestId("tool-card-agent")).toBeInTheDocument();
204
+ await expect(canvas.getByTestId("tool-card-agent-type")).toHaveTextContent("explore");
205
+ await expect(canvas.getByTestId("tool-card-agent-background")).toBeInTheDocument();
206
+ await expect(canvas.getByTestId("tool-card-agent-name")).toHaveTextContent("find-grackle-files");
207
+ },
208
+ };
209
+
210
+ export const CopilotTaskInProgress: Story = {
211
+ name: "Copilot - task in progress",
212
+ args: {
213
+ tool: "task",
214
+ args: {
215
+ agent_type: "worker",
216
+ description: "Building the project",
217
+ mode: "background",
218
+ name: "build-task",
219
+ prompt: "Run rush build and report any errors.",
220
+ },
221
+ // No result -- still running
222
+ },
223
+ play: async ({ canvas }) => {
224
+ const card = canvas.getByTestId("tool-card-agent");
225
+ await expect(card.className).toContain("inProgress");
226
+ await expect(canvas.getByTestId("tool-card-agent-background")).toBeInTheDocument();
227
+ },
228
+ };
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Copilot - read_agent tool
232
+ // ---------------------------------------------------------------------------
233
+
234
+ export const CopilotReadAgentCompleted: Story = {
235
+ name: "Copilot - read_agent completed",
236
+ args: {
237
+ tool: "read_agent",
238
+ args: { agent_id: "find-grackle-files" },
239
+ result: "Agent completed. agent_id: find-grackle-files, agent_type: explore, status: completed, elapsed: 6s, total_turns: 0, duration: 4s\n\nFound 12 matching files:\npackages/cli/src/index.ts\npackages/server/src/index.ts\npackages/common/src/types.ts",
240
+ },
241
+ play: async ({ canvas }) => {
242
+ await expect(canvas.getByTestId("tool-card-agent")).toBeInTheDocument();
243
+ await expect(canvas.getByTestId("tool-card-agent-status")).toBeInTheDocument();
244
+ // Status should show "completed"
245
+ const status = canvas.getByTestId("tool-card-agent-status");
246
+ await expect(status.textContent).toContain("completed");
247
+ // Agent ID should be displayed in header
248
+ await expect(canvas.getByTestId("tool-card-agent-id")).toHaveTextContent("find-grackle-files");
249
+ // Result content should be the part after the prefix
250
+ await expect(canvas.getByTestId("tool-card-result")).toHaveTextContent(/Found 12 matching files/);
251
+ },
252
+ };
253
+
254
+ export const CopilotReadAgentError: Story = {
255
+ name: "Copilot - read_agent error",
256
+ args: {
257
+ tool: "read_agent",
258
+ args: { agent_id: "failed-task" },
259
+ result: "Agent failed. agent_id: failed-task, agent_type: worker, status: failed, elapsed: 30s\n\nThe agent encountered an unrecoverable error during execution.",
260
+ isError: true,
261
+ },
262
+ play: async ({ canvas }) => {
263
+ const card = canvas.getByTestId("tool-card-agent");
264
+ await expect(card.className).toContain("cardRed");
265
+ await expect(canvas.getByTestId("tool-card-error")).toBeInTheDocument();
266
+ },
267
+ };
268
+
269
+ export const CopilotReadAgentUnparseable: Story = {
270
+ name: "Copilot - read_agent with unstructured result",
271
+ args: {
272
+ tool: "read_agent",
273
+ args: { agent_id: "some-agent" },
274
+ result: "The agent returned some plain text result without the structured prefix.",
275
+ },
276
+ play: async ({ canvas }) => {
277
+ // Should gracefully fall back to showing raw result
278
+ await expect(canvas.getByTestId("tool-card-result")).toHaveTextContent("The agent returned some plain text result");
279
+ // No status line since it couldn't be parsed
280
+ await expect(canvas.queryByTestId("tool-card-agent-status")).not.toBeInTheDocument();
281
+ },
282
+ };
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Legacy - Claude Code Task (old name)
286
+ // ---------------------------------------------------------------------------
287
+
288
+ export const LegacyTask: Story = {
289
+ name: "Claude Code - legacy Task tool name",
290
+ args: {
291
+ tool: "Task",
292
+ args: {
293
+ subagent_type: "Explore",
294
+ description: "Find test files",
295
+ prompt: "List all test files in the project.",
296
+ },
297
+ result: "Found 42 test files across 8 packages.",
298
+ },
299
+ play: async ({ canvas }) => {
300
+ // Should render as AgentToolCard, not GenericToolCard
301
+ await expect(canvas.getByTestId("tool-card-agent")).toBeInTheDocument();
302
+ await expect(canvas.getByTestId("tool-card-agent-type")).toHaveTextContent("Explore");
303
+ },
304
+ };
@@ -0,0 +1,247 @@
1
+ import { useState, type JSX } from "react";
2
+ import type { ToolCardProps } from "./ToolCardProps.js";
3
+ import styles from "./toolCards.module.scss";
4
+ import agentStyles from "./AgentToolCard.module.scss";
5
+
6
+ /** Normalized info extracted from agent tool args across runtimes. */
7
+ interface AgentInfo {
8
+ /** Agent type: "Explore", "Plan", "general-purpose", "explore", "worker", etc. */
9
+ agentType?: string;
10
+ /** Short description of the subagent task. */
11
+ description?: string;
12
+ /** Full prompt sent to the subagent. */
13
+ prompt?: string;
14
+ /** Whether the subagent runs in the background. */
15
+ isBackground?: boolean;
16
+ /** Model override (e.g. "sonnet", "opus", "claude-sonnet-4-20250514"). */
17
+ model?: string;
18
+ /** Copilot: human-readable agent name (e.g. "find-tests"). */
19
+ agentName?: string;
20
+ /** Copilot read_agent: the agent_id being polled. */
21
+ agentId?: string;
22
+ /** Whether this is a resume of a prior subagent. */
23
+ isResume?: boolean;
24
+ /** Whether this is a read_agent poll (not a spawn). */
25
+ isPoll?: boolean;
26
+ }
27
+
28
+ /**
29
+ * Parses agent tool args from all supported runtimes into a normalized shape.
30
+ *
31
+ * Handles:
32
+ * - Claude Code `Agent` / `Task`: `{ subagent_type, description, prompt, run_in_background, model, resume }`
33
+ * - Copilot `task`: `{ agent_type, description, prompt, mode, name }`
34
+ * - Copilot `read_agent`: `{ agent_id }`
35
+ */
36
+ function parseAgentArgs(tool: string, args: unknown): AgentInfo {
37
+ if (args === null || args === undefined || typeof args !== "object") {
38
+ return {};
39
+ }
40
+ const a = args as Record<string, unknown>;
41
+ const toolLower = tool.toLowerCase();
42
+
43
+ // Copilot read_agent — polling a background agent
44
+ if (toolLower === "read_agent") {
45
+ return {
46
+ agentId: typeof a.agent_id === "string" ? a.agent_id : undefined,
47
+ isPoll: true,
48
+ };
49
+ }
50
+
51
+ // Copilot task — has `agent_type` and `name` fields
52
+ if (typeof a.agent_type === "string" || typeof a.name === "string") {
53
+ return {
54
+ agentType: typeof a.agent_type === "string" ? a.agent_type : undefined,
55
+ description: typeof a.description === "string" ? a.description : undefined,
56
+ prompt: typeof a.prompt === "string" ? a.prompt : undefined,
57
+ isBackground: a.mode === "background",
58
+ agentName: typeof a.name === "string" ? a.name : undefined,
59
+ };
60
+ }
61
+
62
+ // Claude Code Agent / Task — has `subagent_type` field
63
+ return {
64
+ agentType: typeof a.subagent_type === "string" ? a.subagent_type : undefined,
65
+ description: typeof a.description === "string" ? a.description : undefined,
66
+ prompt: typeof a.prompt === "string" ? a.prompt : undefined,
67
+ isBackground: a.run_in_background === true,
68
+ model: typeof a.model === "string" ? a.model : undefined,
69
+ isResume: typeof a.resume === "string" && a.resume.length > 0,
70
+ };
71
+ }
72
+
73
+ /** Regex to parse Copilot read_agent structured result prefix. */
74
+ const READ_AGENT_STATUS_PATTERN: RegExp =
75
+ /^Agent\s+(completed|running|failed|error)\.\s*agent_id:\s*(\S+),?\s*([^\n]*)(?:\n\n([\s\S]*))?$/i;
76
+
77
+ /** Parsed result from a Copilot read_agent poll. */
78
+ interface ReadAgentResult {
79
+ /** Agent lifecycle status. */
80
+ status: string;
81
+ /** The agent_id that was polled. */
82
+ agentId: string;
83
+ /** Metadata line (e.g. "elapsed: 6s, total_turns: 0, duration: 4s"). */
84
+ metadata: string;
85
+ /** The actual content after the status prefix. */
86
+ content?: string;
87
+ }
88
+
89
+ /** Attempts to parse the structured prefix from a read_agent result. */
90
+ function parseReadAgentResult(result: string): ReadAgentResult | undefined {
91
+ const match = READ_AGENT_STATUS_PATTERN.exec(result);
92
+ if (!match) {
93
+ return undefined;
94
+ }
95
+ const rawContent: string | undefined = match[4] as string | undefined;
96
+ return {
97
+ status: match[1].toLowerCase(),
98
+ agentId: match[2].replace(/,$/, ""),
99
+ metadata: match[3].trim(),
100
+ content: rawContent ? rawContent.trim() : undefined,
101
+ };
102
+ }
103
+
104
+ /** Number of result lines shown when collapsed. */
105
+ const PREVIEW_LINES: number = 5;
106
+
107
+ /** Renders a subagent tool call (Claude Code Agent, Copilot task/read_agent). */
108
+ export function AgentToolCard({ tool, args, result, isError }: ToolCardProps): JSX.Element {
109
+ const [expanded, setExpanded] = useState(false);
110
+ const [promptExpanded, setPromptExpanded] = useState(false);
111
+ const info = parseAgentArgs(tool, args);
112
+ const inProgress = result === undefined;
113
+
114
+ // For read_agent, try to parse the structured result
115
+ const parsedPoll = info.isPoll && result ? parseReadAgentResult(result) : undefined;
116
+ const displayResult = parsedPoll?.content ?? result;
117
+
118
+ const resultLines = displayResult?.split("\n") ?? [];
119
+ const hasMore = resultLines.length > PREVIEW_LINES;
120
+ const visibleResult = expanded ? displayResult : resultLines.slice(0, PREVIEW_LINES).join("\n");
121
+
122
+ // Header label
123
+ const headerLabel = info.isPoll ? "Subagent" : "Agent";
124
+
125
+ return (
126
+ <div
127
+ className={`${styles.card} ${isError ? styles.cardRed : styles.cardTeal} ${inProgress ? styles.inProgress : ""}`}
128
+ data-testid="tool-card-agent"
129
+ >
130
+ {/* Header row */}
131
+ <div className={styles.header}>
132
+ <span className={styles.icon} style={{ color: "var(--accent-teal, #2dd4bf)" }}>&#9654;</span>
133
+ <span className={styles.toolName} style={{ color: "var(--accent-teal, #2dd4bf)" }}>
134
+ {headerLabel}
135
+ </span>
136
+
137
+ {info.agentType && (
138
+ <span className={agentStyles.badgePill} data-testid="tool-card-agent-type">
139
+ {info.agentType}
140
+ </span>
141
+ )}
142
+
143
+ {info.model && (
144
+ <span className={agentStyles.modelBadge} data-testid="tool-card-agent-model">
145
+ {info.model}
146
+ </span>
147
+ )}
148
+
149
+ {info.isBackground && (
150
+ <span className={agentStyles.backgroundBadge} data-testid="tool-card-agent-background">
151
+ <span className={inProgress ? agentStyles.backgroundDotPulsing : agentStyles.backgroundDot}>&#9679;</span>
152
+ BG
153
+ </span>
154
+ )}
155
+
156
+ {info.agentName && (
157
+ <span className={styles.fileName} data-testid="tool-card-agent-name">
158
+ {info.agentName}
159
+ </span>
160
+ )}
161
+
162
+ {info.agentId && (
163
+ <span className={styles.fileName} data-testid="tool-card-agent-id">
164
+ {info.agentId}
165
+ </span>
166
+ )}
167
+
168
+ <span className={styles.spacer} />
169
+
170
+ {inProgress && !info.isBackground && (
171
+ <span className={styles.exitPending} data-testid="tool-card-pending">&#9679;</span>
172
+ )}
173
+
174
+ </div>
175
+
176
+ {/* Description */}
177
+ {info.description && (
178
+ <div className={agentStyles.description} data-testid="tool-card-agent-description">
179
+ {info.isResume ? `Resuming: ${info.description}` : info.description}
180
+ </div>
181
+ )}
182
+
183
+ {/* read_agent status line */}
184
+ {parsedPoll && (
185
+ <div className={agentStyles.statusLine} data-testid="tool-card-agent-status">
186
+ <span className={
187
+ parsedPoll.status === "completed" ? agentStyles.statusCompleted
188
+ : parsedPoll.status === "running" ? agentStyles.statusRunning
189
+ : agentStyles.statusError
190
+ }>
191
+ {parsedPoll.status}
192
+ </span>
193
+ {parsedPoll.metadata && <span>{parsedPoll.metadata}</span>}
194
+ </div>
195
+ )}
196
+
197
+ {/* Collapsible prompt */}
198
+ {info.prompt && (
199
+ <>
200
+ <button
201
+ type="button"
202
+ className={agentStyles.promptToggle}
203
+ onClick={() => { setPromptExpanded((v) => !v); }}
204
+ aria-expanded={promptExpanded}
205
+ data-testid="tool-card-prompt-toggle"
206
+ >
207
+ <span className={`${styles.chevron} ${promptExpanded ? styles.chevronExpanded : ""}`}>&#9656;</span>
208
+ prompt
209
+ </button>
210
+ {promptExpanded && (
211
+ <pre className={styles.pre} data-testid="tool-card-prompt">
212
+ {info.prompt}
213
+ </pre>
214
+ )}
215
+ </>
216
+ )}
217
+
218
+ {/* Error result */}
219
+ {isError && result && (
220
+ <pre className={styles.pre} data-testid="tool-card-error">
221
+ {result}
222
+ </pre>
223
+ )}
224
+
225
+ {/* Normal result */}
226
+ {!isError && !inProgress && displayResult && (
227
+ <>
228
+ <pre className={styles.pre} data-testid="tool-card-result">
229
+ {visibleResult}
230
+ </pre>
231
+ {hasMore && (
232
+ <button
233
+ type="button"
234
+ className={styles.bodyToggle}
235
+ onClick={() => { setExpanded((v) => !v); }}
236
+ aria-expanded={expanded}
237
+ data-testid="tool-card-toggle"
238
+ >
239
+ <span className={`${styles.chevron} ${expanded ? styles.chevronExpanded : ""}`}>&#9656;</span>
240
+ {expanded ? "collapse" : `${resultLines.length - PREVIEW_LINES} more lines`}
241
+ </button>
242
+ )}
243
+ </>
244
+ )}
245
+ </div>
246
+ );
247
+ }
@@ -0,0 +1,138 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { expect, userEvent } from "@storybook/test";
3
+ import { FileEditCard } from "./FileEditCard.js";
4
+
5
+ const meta: Meta<typeof FileEditCard> = {
6
+ component: FileEditCard,
7
+ title: "Grackle/Tools/FileEditCard",
8
+ tags: ["autodocs"],
9
+ };
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ export const FromOldNewStrings: Story = {
14
+ name: "Claude Code - old/new strings",
15
+ args: {
16
+ tool: "Edit",
17
+ args: {
18
+ file_path: "/src/middleware/auth.ts",
19
+ old_string: 'const secret = "hardcoded";\nconst expiry = 3600;',
20
+ new_string: 'const secret = process.env.JWT_SECRET;\nif (!secret) {\n throw new Error("JWT_SECRET is required");\n}\nconst expiry = 86400;',
21
+ },
22
+ result: "File updated successfully",
23
+ },
24
+ play: async ({ canvas }) => {
25
+ await expect(canvas.getByTestId("tool-card-file-edit")).toBeInTheDocument();
26
+ await expect(canvas.getByText("auth.ts")).toBeInTheDocument();
27
+ await expect(canvas.getByTestId("tool-card-diff-stats")).toBeInTheDocument();
28
+ await expect(canvas.getByTestId("tool-card-diff")).toBeInTheDocument();
29
+ },
30
+ };
31
+
32
+ export const FromUnifiedDiff: Story = {
33
+ name: "Copilot - unified diff in detailedResult",
34
+ args: {
35
+ tool: "edit",
36
+ args: {
37
+ path: "C:\\Users\\nickp\\src\\grackle4\\README.md",
38
+ old_str: "## Requirements",
39
+ new_str: "## Getting Started\n\n```bash\nnpm install\nrush build\nrush serve\n```\n\n## Requirements",
40
+ },
41
+ result: "File updated with changes.",
42
+ detailedResult: `diff --git a/README.md b/README.md
43
+ index 0000000..0000001 100644
44
+ --- a/README.md
45
+ +++ b/README.md
46
+ @@ -280,6 +280,15 @@
47
+ \`\`\`
48
+ </details>
49
+
50
+ +## Getting Started
51
+ +
52
+ +\`\`\`bash
53
+ +npm install
54
+ +rush build
55
+ +rush serve
56
+ +\`\`\`
57
+ +
58
+ ## Requirements
59
+
60
+ - Docker (recommended)`,
61
+ },
62
+ play: async ({ canvas }) => {
63
+ await expect(canvas.getByTestId("tool-card-diff")).toBeInTheDocument();
64
+ // Should show the parsed unified diff, not the old/new fallback
65
+ const diff = canvas.getByTestId("tool-card-diff");
66
+ await expect(diff.textContent).toContain("Getting Started");
67
+ },
68
+ };
69
+
70
+ export const SmallEdit: Story = {
71
+ name: "Single-line change",
72
+ args: {
73
+ tool: "Edit",
74
+ args: {
75
+ file_path: "/src/config.ts",
76
+ old_string: "const PORT = 3000;",
77
+ new_string: "const PORT = 8080;",
78
+ },
79
+ result: "File updated",
80
+ },
81
+ play: async ({ canvas }) => {
82
+ await expect(canvas.getByText("config.ts")).toBeInTheDocument();
83
+ // No toggle for <=5 diff lines
84
+ await expect(canvas.queryByTestId("tool-card-toggle")).not.toBeInTheDocument();
85
+ },
86
+ };
87
+
88
+ export const LargeDiffExpandCollapse: Story = {
89
+ name: "Large diff - expand/collapse",
90
+ args: {
91
+ tool: "Edit",
92
+ args: {
93
+ file_path: "/src/routes/api.ts",
94
+ old_string: "line 1\nline 2\nline 3\nline 4\nline 5",
95
+ new_string: "new line 1\nnew line 2\nnew line 3\nnew line 4\nnew line 5\nnew line 6\nnew line 7\nnew line 8",
96
+ },
97
+ result: "File updated",
98
+ },
99
+ play: async ({ canvas }) => {
100
+ // Should have toggle (5 removes + 8 adds = 13 lines > 5)
101
+ const toggle = canvas.getByTestId("tool-card-toggle");
102
+ await expect(toggle).toBeInTheDocument();
103
+
104
+ // Expand
105
+ await userEvent.click(toggle);
106
+ await expect(toggle.textContent).toContain("collapse");
107
+
108
+ // Collapse
109
+ await userEvent.click(toggle);
110
+ await expect(toggle.textContent).toContain("more lines");
111
+ },
112
+ };
113
+
114
+ export const InProgress: Story = {
115
+ args: {
116
+ tool: "Edit",
117
+ args: { file_path: "/src/index.ts", old_string: "foo", new_string: "bar" },
118
+ // No result yet
119
+ },
120
+ play: async ({ canvas }) => {
121
+ const card = canvas.getByTestId("tool-card-file-edit");
122
+ await expect(card.className).toContain("inProgress");
123
+ },
124
+ };
125
+
126
+ export const ErrorResult: Story = {
127
+ args: {
128
+ tool: "edit",
129
+ args: { path: "/src/missing.ts", old_str: "x", new_str: "y" },
130
+ result: "Error: old_str not found in file",
131
+ isError: true,
132
+ },
133
+ play: async ({ canvas }) => {
134
+ const card = canvas.getByTestId("tool-card-file-edit");
135
+ await expect(card.className).toContain("cardRed");
136
+ await expect(canvas.getByTestId("tool-card-error")).toBeInTheDocument();
137
+ },
138
+ };