@grackle-ai/web-components 0.115.2 → 0.117.0

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 (60) hide show
  1. package/.rush/temp/{49e5384757b767ffca6c218faf139f6813911f4a.untar.log → 8d9be4152bfcbf796b578ac87621b34484202bd0.untar.log} +2 -2
  2. package/.rush/temp/{b32d9c7748f6c2c43df816a4bdd427ae0c7f1e32.untar.log → be1751e9cb123b206e39fdb59b24fd82523d77e2.untar.log} +2 -2
  3. package/.rush/temp/operation/_phase_build/all.log +13 -6
  4. package/.rush/temp/operation/_phase_build/log-chunks.jsonl +13 -6
  5. package/.rush/temp/operation/_phase_build/state.json +1 -1
  6. package/.rush/temp/operation/_phase_test/all.log +29 -28
  7. package/.rush/temp/operation/_phase_test/log-chunks.jsonl +29 -28
  8. package/.rush/temp/operation/_phase_test/state.json +1 -1
  9. package/.rush/temp/shrinkwrap-deps.json +13 -1
  10. package/README.md +3 -3
  11. package/config/rush-project.json +1 -1
  12. package/dist/index.css +1 -1
  13. package/dist/index.js +9068 -9498
  14. package/package.json +4 -2
  15. package/rush-logs/web-components._phase_build.cache.log +2 -2
  16. package/rush-logs/web-components._phase_test.cache.log +1 -1
  17. package/src/components/display/EventRenderer.stories.tsx +22 -0
  18. package/src/components/display/EventRenderer.tsx +28 -4
  19. package/src/components/index.ts +0 -3
  20. package/src/components/knowledge/KnowledgeDetailPanel.tsx +1 -4
  21. package/src/components/layout/AppNav.stories.tsx +3 -6
  22. package/src/components/layout/AppNav.tsx +3 -7
  23. package/src/components/layout/BottomStatusBar.tsx +4 -6
  24. package/src/components/lists/index.ts +0 -1
  25. package/src/components/panels/KeyboardShortcutsPanel.tsx +0 -1
  26. package/src/components/panels/index.ts +0 -1
  27. package/src/components/personas/McpToolSelector.stories.tsx +12 -12
  28. package/src/components/tools/ToolCard.stories.tsx +0 -26
  29. package/src/components/tools/ToolCard.tsx +0 -3
  30. package/src/components/tools/ToolSearchCard.stories.tsx +8 -8
  31. package/src/components/tools/WorkpadCard.stories.tsx +5 -5
  32. package/src/components/tools/classifyTool.test.ts +0 -1
  33. package/src/components/tools/classifyTool.ts +2 -7
  34. package/src/context/GrackleContextTypes.ts +1 -3
  35. package/src/hooks/types.ts +1 -44
  36. package/src/index.ts +4 -8
  37. package/src/mcp-runtime/index.tsx +99 -0
  38. package/src/mocks/MockGrackleProvider.tsx +0 -75
  39. package/src/mocks/mockData.ts +8 -99
  40. package/src/test-utils/storybook-helpers.ts +0 -19
  41. package/src/utils/breadcrumbs.test.ts +0 -43
  42. package/src/utils/breadcrumbs.ts +1 -37
  43. package/src/utils/navigation.ts +1 -20
  44. package/src/utils/route-config.test.ts +0 -31
  45. package/vite.config.ts +46 -2
  46. package/.rush/temp/49e5384757b767ffca6c218faf139f6813911f4a.tar.log +0 -12
  47. package/.rush/temp/b32d9c7748f6c2c43df816a4bdd427ae0c7f1e32.tar.log +0 -236
  48. package/.rush/temp/chunked-rush-logs/web-components._phase_build.chunks.jsonl +0 -19
  49. package/.rush/temp/chunked-rush-logs/web-components._phase_test.chunks.jsonl +0 -126
  50. package/rush-logs/web-components._phase_build.log +0 -19
  51. package/rush-logs/web-components._phase_test.log +0 -126
  52. package/src/components/lists/FindingsNav.module.scss +0 -126
  53. package/src/components/lists/FindingsNav.tsx +0 -146
  54. package/src/components/panels/FindingsPanel.module.scss +0 -94
  55. package/src/components/panels/FindingsPanel.stories.tsx +0 -109
  56. package/src/components/panels/FindingsPanel.tsx +0 -76
  57. package/src/components/tools/FindingCard.stories.tsx +0 -124
  58. package/src/components/tools/FindingCard.tsx +0 -178
  59. package/src/utils/findingCategory.ts +0 -33
  60. package/temp/build/lint/_eslint-5eVG3S6w.json +0 -850
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grackle-ai/web-components",
3
- "version": "0.115.2",
3
+ "version": "0.117.0",
4
4
  "description": "Presentational React component library for the Grackle web UI",
5
5
  "license": "MIT",
6
6
  "sideEffects": [
@@ -41,12 +41,13 @@
41
41
  "d3-zoom": "^3.0.0",
42
42
  "react": "^19.0.0",
43
43
  "react-dom": "^19.0.0",
44
+ "react-live": "^4.1.8",
44
45
  "react-markdown": "^9.0.3",
45
46
  "rehype-prism-plus": "^2.0.0",
46
47
  "remark-gfm": "^4.0.0",
47
48
  "lucide-react": "~0.474.0",
48
49
  "react-router": "^7.0.0",
49
- "@grackle-ai/common": "0.115.2"
50
+ "@grackle-ai/common": "0.117.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@rushstack/heft": "1.2.7",
@@ -60,6 +61,7 @@
60
61
  "@types/react-dom": "^19.0.0",
61
62
  "sass": "^1.86.0",
62
63
  "vite": "^6.4.2",
64
+ "vite-plugin-css-injected-by-js": "^4.0.1",
63
65
  "vitest": "^3.1.1",
64
66
  "@testing-library/react": "^16.3.2",
65
67
  "jsdom": "^29.0.0",
@@ -1,4 +1,4 @@
1
1
  Build cache hit.
2
- Cache key: b32d9c7748f6c2c43df816a4bdd427ae0c7f1e32
3
- Clearing cached folders: dist, storybook-static, .rush/temp/operation/_phase_build
2
+ Cache key: 8d9be4152bfcbf796b578ac87621b34484202bd0
3
+ Clearing cached folders: dist, storybook-static, mcp-app-runtime, .rush/temp/operation/_phase_build
4
4
  Successfully restored output from the build cache.
@@ -1,4 +1,4 @@
1
1
  Build cache hit.
2
- Cache key: 49e5384757b767ffca6c218faf139f6813911f4a
2
+ Cache key: be1751e9cb123b206e39fdb59b24fd82523d77e2
3
3
  Clearing cached folders: .rush/temp/operation/_phase_test
4
4
  Successfully restored output from the build cache.
@@ -216,6 +216,28 @@ export const AgentWidgetEvent: Story = {
216
216
  },
217
217
  };
218
218
 
219
+ /** GenUX React runtime (#1268): rendererKind "grackle-react" — `html` is JSX source. */
220
+ export const ReactRuntimeWidgetEvent: Story = {
221
+ args: {
222
+ event: makeEvent({
223
+ eventType: "widget",
224
+ content: JSON.stringify({
225
+ resourceUri: "",
226
+ toolName: "component_show",
227
+ rendererKind: "grackle-react",
228
+ html: "render(<Button>{props.label}</Button>)",
229
+ csp: { resourceDomains: ["http://localhost:6007"], connectDomains: ["http://localhost:6007"], allowUnsafeEval: true },
230
+ toolInput: { label: "Hi" },
231
+ }),
232
+ }),
233
+ sandboxProxyUrl: "http://localhost:6007/sandbox.html",
234
+ },
235
+ play: async ({ canvas }) => {
236
+ // The grackle-react branch builds a runtime bootstrap and mounts the host iframe.
237
+ await expect(await canvas.findByTestId("mcp-app-widget")).toBeInTheDocument();
238
+ },
239
+ };
240
+
219
241
  /** An unknown rendererKind falls back to the default event card (no crash). */
220
242
  export const UnknownRendererKindWidget: Story = {
221
243
  args: {
@@ -226,9 +226,10 @@ export function EventRenderer({ event, toolUseCtx, settled, sandboxProxyUrl }: P
226
226
  let payload: {
227
227
  html?: string;
228
228
  rendererKind?: string;
229
- // `allowInlineScripts` is a Grackle extension to the upstream CSP type
230
- // (agent-authored widgets, #1239); it is forwarded verbatim to the sandbox.
231
- csp?: McpUiResourceCsp & { allowInlineScripts?: boolean };
229
+ // `allowInlineScripts`/`allowUnsafeEval` are Grackle extensions to the
230
+ // upstream CSP type (agent-authored widgets #1239; React runtime #1268);
231
+ // forwarded verbatim to the sandbox.
232
+ csp?: McpUiResourceCsp & { allowInlineScripts?: boolean; allowUnsafeEval?: boolean };
232
233
  toolInput?: Record<string, unknown>;
233
234
  toolResult?: CallToolResult;
234
235
  } = {};
@@ -236,8 +237,31 @@ export function EventRenderer({ event, toolUseCtx, settled, sandboxProxyUrl }: P
236
237
  payload = JSON.parse(event.content) as typeof payload;
237
238
  } catch { /* malformed widget payload — fall back */ }
238
239
  // Dispatch on rendererKind (default "mcp-app-html" for back-compat). This
239
- // switch is the seam for future declarative renderers (e.g. adaptive-card).
240
+ // switch is the seam for declarative/runtime renderers.
240
241
  const rendererKind: string = payload.rendererKind ?? "mcp-app-html";
242
+ // GenUX React runtime (#1268): payload.html is JSX *source* (not a full
243
+ // document). Render it via a bootstrap that loads the runtime bundle from the
244
+ // sandbox origin; the source + props are delivered as tool input. The runtime
245
+ // transpiles + renders the component against the Grackle component library.
246
+ // An absolute runtime.js URL is required — the inner iframe is written via
247
+ // doc.write (about:blank base), so a relative "/runtime.js" would not resolve.
248
+ if (rendererKind === "grackle-react" && payload.html) {
249
+ const sandboxOrigin: string = new URL(sandboxProxyUrl, window.location.href).origin;
250
+ const bootstrap: string =
251
+ `<!doctype html><html><head><meta charset="utf-8"></head>` +
252
+ `<body><div id="grackle-root"></div>` +
253
+ `<script type="module" src="${sandboxOrigin}/runtime.js"></script></body></html>`;
254
+ return (
255
+ <Suspense fallback={<DefaultEvent content="Loading widget..." />}>
256
+ <McpAppWidget
257
+ widgetHtml={bootstrap}
258
+ sandboxProxyUrl={sandboxProxyUrl}
259
+ csp={payload.csp}
260
+ toolInput={{ source: payload.html, props: payload.toolInput ?? {} }}
261
+ />
262
+ </Suspense>
263
+ );
264
+ }
241
265
  if (rendererKind !== "mcp-app-html" || !payload.html) {
242
266
  return <DefaultEvent content={event.content} />;
243
267
  }
@@ -6,9 +6,6 @@
6
6
  // Layout components - application shell structure
7
7
  export { StatusBar, Sidebar, BottomStatusBar } from "./layout/index.js";
8
8
 
9
- // Panel components - main content areas
10
- export { FindingsPanel } from "./panels/index.js";
11
-
12
9
  // List components - sidebar navigation
13
10
  export { EnvironmentNav } from "./lists/index.js";
14
11
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { useMemo, type JSX } from "react";
8
8
  import type { GraphNode, NodeDetail } from "../../hooks/types.js";
9
- import { taskUrl, sessionUrl, findingUrl } from "../../utils/navigation.js";
9
+ import { taskUrl, sessionUrl } from "../../utils/navigation.js";
10
10
  import { useAppNavigate } from "../../utils/navigation.js";
11
11
  import styles from "./KnowledgeDetailPanel.module.scss";
12
12
 
@@ -40,9 +40,6 @@ export function KnowledgeDetailPanel({
40
40
  case "session":
41
41
  navigate(sessionUrl(node.sourceId));
42
42
  break;
43
- case "finding":
44
- navigate(findingUrl(node.sourceId));
45
- break;
46
43
  default:
47
44
  break;
48
45
  }
@@ -1,9 +1,9 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { expect, userEvent } from "@storybook/test";
3
- import { Brain, ClipboardList, Home, MessageSquare, Monitor, Search, Settings } from "lucide-react";
3
+ import { Brain, ClipboardList, Home, MessageSquare, Monitor, Settings } from "lucide-react";
4
4
  import { AppNav } from "./AppNav.js";
5
5
  import { ICON_LG } from "../../utils/iconSize.js";
6
- import { HOME_URL, CHAT_URL, ENVIRONMENTS_URL, SETTINGS_CREDENTIALS_URL, TASKS_URL, FINDINGS_URL, KNOWLEDGE_URL } from "../../utils/navigation.js";
6
+ import { HOME_URL, CHAT_URL, ENVIRONMENTS_URL, SETTINGS_CREDENTIALS_URL, TASKS_URL, KNOWLEDGE_URL } from "../../utils/navigation.js";
7
7
 
8
8
  const meta: Meta<typeof AppNav> = {
9
9
  title: "Grackle/Layout/AppNav",
@@ -25,7 +25,7 @@ export const AllTabsRendered: Story = {
25
25
  },
26
26
  };
27
27
 
28
- /** Core-only tabs: orchestration (Tasks, Findings) and knowledge tabs are absent. */
28
+ /** Core-only tabs: orchestration (Tasks) and knowledge tabs are absent. */
29
29
  export const CoreOnlyTabs: Story = {
30
30
  args: {
31
31
  tabs: [
@@ -55,13 +55,11 @@ export const AllTabsExplicit: Story = {
55
55
  { view: "tasks", label: "Tasks", icon: <ClipboardList size={ICON_LG} />, route: TASKS_URL, testId: "sidebar-tab-tasks" },
56
56
  { view: "environments", label: "Environments", icon: <Monitor size={ICON_LG} />, route: ENVIRONMENTS_URL, testId: "sidebar-tab-environments" },
57
57
  { view: "knowledge", label: "Knowledge", icon: <Brain size={ICON_LG} />, route: KNOWLEDGE_URL, testId: "sidebar-tab-knowledge" },
58
- { view: "findings", label: "Findings", icon: <Search size={ICON_LG} />, route: FINDINGS_URL, testId: "sidebar-tab-findings" },
59
58
  { view: "settings", label: "Settings", icon: <Settings size={ICON_LG} />, route: SETTINGS_CREDENTIALS_URL, testId: "sidebar-tab-settings" },
60
59
  ],
61
60
  },
62
61
  play: async ({ canvas }) => {
63
62
  await expect(canvas.getByRole("tab", { name: /Tasks/ })).toBeInTheDocument();
64
- await expect(canvas.getByRole("tab", { name: /Findings/ })).toBeInTheDocument();
65
63
  await expect(canvas.getByRole("tab", { name: /Knowledge/ })).toBeInTheDocument();
66
64
  },
67
65
  };
@@ -82,7 +80,6 @@ export const SettingsPinnedRight: Story = {
82
80
  { view: "environments", label: "Environments", icon: <Monitor size={ICON_LG} />, route: ENVIRONMENTS_URL, testId: "sidebar-tab-environments" },
83
81
  { view: "settings", label: "Settings", icon: <Settings size={ICON_LG} />, route: SETTINGS_CREDENTIALS_URL, testId: "sidebar-tab-settings", align: "end" },
84
82
  { view: "tasks", label: "Tasks", icon: <ClipboardList size={ICON_LG} />, route: TASKS_URL, testId: "sidebar-tab-tasks" },
85
- { view: "findings", label: "Findings", icon: <Search size={ICON_LG} />, route: FINDINGS_URL, testId: "sidebar-tab-findings" },
86
83
  { view: "knowledge", label: "Knowledge", icon: <Brain size={ICON_LG} />, route: KNOWLEDGE_URL, testId: "sidebar-tab-knowledge" },
87
84
  ],
88
85
  },
@@ -1,13 +1,13 @@
1
1
  import { useCallback, useMemo, useRef, type JSX, type KeyboardEvent, type ReactNode } from "react";
2
2
  import { useLocation } from "react-router";
3
- import { Brain, ClipboardList, Home, MessageSquare, Monitor, Network, Search, Settings } from "lucide-react";
4
- import { CHAT_URL, COORDINATION_URL, ENVIRONMENTS_URL, FINDINGS_URL, HOME_URL, KNOWLEDGE_URL, SETTINGS_URL, SETTINGS_CREDENTIALS_URL, TASKS_URL, useAppNavigate } from "../../utils/navigation.js";
3
+ import { Brain, ClipboardList, Home, MessageSquare, Monitor, Network, Settings } from "lucide-react";
4
+ import { CHAT_URL, COORDINATION_URL, ENVIRONMENTS_URL, HOME_URL, KNOWLEDGE_URL, SETTINGS_URL, SETTINGS_CREDENTIALS_URL, TASKS_URL, useAppNavigate } from "../../utils/navigation.js";
5
5
  import { ICON_LG } from "../../utils/iconSize.js";
6
6
  import { Tooltip } from "../display/Tooltip.js";
7
7
  import styles from "./AppNav.module.scss";
8
8
 
9
9
  /** Application view identifiers. */
10
- export type AppView = "dashboard" | "chat" | "tasks" | "environments" | "knowledge" | "findings" | "coordination" | "settings";
10
+ export type AppView = "dashboard" | "chat" | "tasks" | "environments" | "knowledge" | "coordination" | "settings";
11
11
 
12
12
  /** Tab definition for the application navigation bar. */
13
13
  export interface AppTab {
@@ -37,7 +37,6 @@ export const TABS: AppTab[] = [
37
37
  { view: "tasks", label: "Tasks", icon: <ClipboardList size={ICON_LG} />, route: TASKS_URL, testId: "sidebar-tab-tasks", order: 1 },
38
38
  { view: "environments", label: "Environments", icon: <Monitor size={ICON_LG} />, route: ENVIRONMENTS_URL, testId: "sidebar-tab-environments", order: 2 },
39
39
  { view: "chat", label: "Root", icon: <MessageSquare size={ICON_LG} />, route: CHAT_URL, testId: "sidebar-tab-chat", order: 3 },
40
- { view: "findings", label: "Findings", icon: <Search size={ICON_LG} />, route: FINDINGS_URL, testId: "sidebar-tab-findings", order: 4 },
41
40
  { view: "knowledge", label: "Knowledge", icon: <Brain size={ICON_LG} />, route: KNOWLEDGE_URL, testId: "sidebar-tab-knowledge", order: 5 },
42
41
  { view: "coordination", label: "Coordination", icon: <Network size={ICON_LG} />, route: COORDINATION_URL, testId: "sidebar-tab-coordination", order: 6 },
43
42
  { view: "settings", label: "Settings", icon: <Settings size={ICON_LG} />, route: SETTINGS_CREDENTIALS_URL, testId: "sidebar-tab-settings", align: "end" },
@@ -60,9 +59,6 @@ export function getActiveView(pathname: string): AppView {
60
59
  if (pathname.startsWith(KNOWLEDGE_URL)) {
61
60
  return "knowledge";
62
61
  }
63
- if (pathname.startsWith(FINDINGS_URL)) {
64
- return "findings";
65
- }
66
62
  if (pathname.startsWith(SETTINGS_URL)) {
67
63
  return "settings";
68
64
  }
@@ -30,11 +30,9 @@ export function BottomStatusBar({ sessions, tasks, environments }: BottomStatusB
30
30
  const sessionMatch = useMatch("/sessions/:sessionId");
31
31
  const taskMatch = useMatch("/tasks/:taskId");
32
32
  const taskStreamMatch = useMatch("/tasks/:taskId/stream");
33
- const taskFindingsMatch = useMatch("/tasks/:taskId/findings");
34
33
  const taskEditMatch = useMatch("/tasks/:taskId/edit");
35
34
  const wsTaskMatch = useMatch("/environments/:environmentId/workspaces/:workspaceId/tasks/:taskId");
36
35
  const wsTaskStreamMatch = useMatch("/environments/:environmentId/workspaces/:workspaceId/tasks/:taskId/stream");
37
- const wsTaskFindingsMatch = useMatch("/environments/:environmentId/workspaces/:workspaceId/tasks/:taskId/findings");
38
36
  const wsTaskEditMatch = useMatch("/environments/:environmentId/workspaces/:workspaceId/tasks/:taskId/edit");
39
37
  const newChatMatch = useMatch("/sessions/new");
40
38
  const workspaceMatch = useMatch("/environments/:environmentId/workspaces/:workspaceId");
@@ -45,14 +43,14 @@ export function BottomStatusBar({ sessions, tasks, environments }: BottomStatusB
45
43
 
46
44
  // Derive current page context
47
45
  const sessionId = sessionMatch?.params.sessionId;
48
- const taskId = taskMatch?.params.taskId ?? taskStreamMatch?.params.taskId ?? taskFindingsMatch?.params.taskId
49
- ?? wsTaskMatch?.params.taskId ?? wsTaskStreamMatch?.params.taskId ?? wsTaskFindingsMatch?.params.taskId ?? wsTaskEditMatch?.params.taskId;
50
- const wsMatch = wsTaskMatch ?? wsTaskStreamMatch ?? wsTaskFindingsMatch ?? wsTaskEditMatch;
46
+ const taskId = taskMatch?.params.taskId ?? taskStreamMatch?.params.taskId
47
+ ?? wsTaskMatch?.params.taskId ?? wsTaskStreamMatch?.params.taskId ?? wsTaskEditMatch?.params.taskId;
48
+ const wsMatch = wsTaskMatch ?? wsTaskStreamMatch ?? wsTaskEditMatch;
51
49
  const routeEnvironmentId = wsMatch?.params.environmentId ?? workspaceMatch?.params.environmentId;
52
50
  const isEnvironments = location.pathname.startsWith("/environments") && !workspaceMatch && !wsMatch;
53
51
  const isChat = !!chatMatch;
54
52
  const isNewChat = !!newChatMatch;
55
- const isWorkspace = !!workspaceMatch && !wsTaskMatch && !wsTaskStreamMatch && !wsTaskFindingsMatch && !wsTaskEditMatch;
53
+ const isWorkspace = !!workspaceMatch && !wsTaskMatch && !wsTaskStreamMatch && !wsTaskEditMatch;
56
54
  const isNewTask = !!newTaskMatch;
57
55
  const isTaskEdit = !!taskEditMatch || !!wsTaskEditMatch;
58
56
  const isEmpty = !!emptyMatch && !isNewChat && !isWorkspace && !isNewTask;
@@ -3,4 +3,3 @@
3
3
  * @module lists
4
4
  */
5
5
  export { EnvironmentNav } from "./EnvironmentNav.js";
6
- export { FindingsNav } from "./FindingsNav.js";
@@ -33,7 +33,6 @@ const SHORTCUT_GROUPS: ShortcutGroup[] = [
33
33
  shortcuts: [
34
34
  { keys: ["1"], description: "Switch to Overview tab" },
35
35
  { keys: ["2"], description: "Switch to Stream tab" },
36
- { keys: ["3"], description: "Switch to Findings tab" },
37
36
  ],
38
37
  },
39
38
  {
@@ -2,7 +2,6 @@
2
2
  * Main content panel components.
3
3
  * @module panels
4
4
  */
5
- export { FindingsPanel } from "./FindingsPanel.js";
6
5
  export { TokensPanel } from "./TokensPanel.js";
7
6
  export { AppearancePanel } from "./AppearancePanel.js";
8
7
  export { AboutPanel } from "./AboutPanel.js";
@@ -58,13 +58,13 @@ export const WithPresetWorker: Story = {
58
58
 
59
59
  export const CustomSelection: Story = {
60
60
  args: {
61
- selectedTools: ["finding_post", "task_list", "workpad_read"],
61
+ selectedTools: ["task_show", "task_list", "workpad_read"],
62
62
  },
63
63
  play: async ({ canvasElement }) => {
64
64
  const canvas = within(canvasElement);
65
65
  // Verify selected tools are checked
66
- const findingPost = canvas.getByTestId("tool-finding_post") as HTMLInputElement;
67
- await expect(findingPost.checked).toBe(true);
66
+ const taskShow = canvas.getByTestId("tool-task_show") as HTMLInputElement;
67
+ await expect(taskShow.checked).toBe(true);
68
68
  const taskList = canvas.getByTestId("tool-task_list") as HTMLInputElement;
69
69
  await expect(taskList.checked).toBe(true);
70
70
  // Verify unselected tool is not checked
@@ -78,8 +78,8 @@ export const CustomSelection: Story = {
78
78
  export const ToggleIndividualTool: Story = {
79
79
  play: async ({ canvasElement, args }) => {
80
80
  const canvas = within(canvasElement);
81
- await userEvent.click(canvas.getByTestId("tool-finding_post"));
82
- await expect(args.onChange).toHaveBeenCalledWith(["finding_post"]);
81
+ await userEvent.click(canvas.getByTestId("tool-task_show"));
82
+ await expect(args.onChange).toHaveBeenCalledWith(["task_show"]);
83
83
  },
84
84
  };
85
85
 
@@ -87,11 +87,11 @@ export const SearchFilter: Story = {
87
87
  play: async ({ canvasElement }) => {
88
88
  const canvas = within(canvasElement);
89
89
  const filterInput = canvas.getByTestId("mcp-tool-filter");
90
- await userEvent.type(filterInput, "finding");
91
- // finding group should be visible with its tools
92
- await expect(canvas.getByTestId("tool-group-finding")).toBeInTheDocument();
93
- await expect(canvas.getByTestId("tool-finding_post")).toBeInTheDocument();
94
- await expect(canvas.getByTestId("tool-finding_list")).toBeInTheDocument();
90
+ await userEvent.type(filterInput, "workpad");
91
+ // workpad group should be visible with its tools
92
+ await expect(canvas.getByTestId("tool-group-workpad")).toBeInTheDocument();
93
+ await expect(canvas.getByTestId("tool-workpad_write")).toBeInTheDocument();
94
+ await expect(canvas.getByTestId("tool-workpad_read")).toBeInTheDocument();
95
95
  // env group should be hidden (no match)
96
96
  await expect(canvas.queryByTestId("tool-group-env")).not.toBeInTheDocument();
97
97
  },
@@ -115,7 +115,7 @@ export const GroupSelectAll: Story = {
115
115
  export const DisabledState: Story = {
116
116
  args: {
117
117
  disabled: true,
118
- selectedTools: ["finding_post"],
118
+ selectedTools: ["task_show"],
119
119
  },
120
120
  play: async ({ canvasElement }) => {
121
121
  const canvas = within(canvasElement);
@@ -124,6 +124,6 @@ export const DisabledState: Story = {
124
124
  // Filter input should be disabled
125
125
  await expect(canvas.getByTestId("mcp-tool-filter")).toBeDisabled();
126
126
  // Tool checkboxes should be disabled
127
- await expect(canvas.getByTestId("tool-finding_post")).toBeDisabled();
127
+ await expect(canvas.getByTestId("tool-task_show")).toBeDisabled();
128
128
  },
129
129
  };
@@ -49,32 +49,6 @@ export const GenericTool: Story = {
49
49
  },
50
50
  };
51
51
 
52
- /** MCP finding tool (Claude Code format) routes to FindingCard. */
53
- export const McpFinding: Story = {
54
- name: "MCP finding_post (Claude Code)",
55
- args: {
56
- tool: "mcp__grackle__finding_post",
57
- args: { title: "Test finding", category: "insight" },
58
- result: JSON.stringify({ id: "f1", title: "Test finding", category: "insight", tags: [] }),
59
- },
60
- play: async ({ canvas }) => {
61
- await expect(canvas.getByTestId("tool-card-finding")).toBeInTheDocument();
62
- },
63
- };
64
-
65
- /** MCP finding tool (Copilot format) routes to FindingCard. */
66
- export const McpFindingCopilot: Story = {
67
- name: "MCP finding_post (Copilot)",
68
- args: {
69
- tool: "grackle-finding_post",
70
- args: { title: "Copilot finding", category: "bug" },
71
- result: JSON.stringify({ id: "f2", title: "Copilot finding", category: "bug", tags: [] }),
72
- },
73
- play: async ({ canvas }) => {
74
- await expect(canvas.getByTestId("tool-card-finding")).toBeInTheDocument();
75
- },
76
- };
77
-
78
52
  /** MCP task tool routes to TaskCard. */
79
53
  export const McpTask: Story = {
80
54
  name: "MCP task_list",
@@ -7,7 +7,6 @@ import { ShellCard } from "./ShellCard.js";
7
7
  import { SearchCard } from "./SearchCard.js";
8
8
  import { TodoCard } from "./TodoCard.js";
9
9
  import { MetadataCard } from "./MetadataCard.js";
10
- import { FindingCard } from "./FindingCard.js";
11
10
  import { TaskCard } from "./TaskCard.js";
12
11
  import { WorkpadCard } from "./WorkpadCard.js";
13
12
  import { KnowledgeCard } from "./KnowledgeCard.js";
@@ -40,8 +39,6 @@ export function ToolCard(props: ToolCardProps): JSX.Element {
40
39
  return <TodoCard {...props} />;
41
40
  case "metadata":
42
41
  return <MetadataCard {...props} />;
43
- case "finding":
44
- return <FindingCard {...props} />;
45
42
  case "task":
46
43
  return <TaskCard {...props} />;
47
44
  case "workpad":
@@ -13,7 +13,7 @@ export const InProgress: Story = {
13
13
  name: "ToolSearch - in progress",
14
14
  args: {
15
15
  tool: "ToolSearch",
16
- args: { query: "select:mcp__grackle__finding_post,mcp__grackle__workpad_write", max_results: 3 },
16
+ args: { query: "select:mcp__grackle__task_create,mcp__grackle__workpad_write", max_results: 3 },
17
17
  },
18
18
  play: async ({ canvas }) => {
19
19
  await expect(canvas.getByTestId("tool-card-tool-search")).toBeInTheDocument();
@@ -25,15 +25,15 @@ export const WithResults: Story = {
25
25
  name: "ToolSearch - with results",
26
26
  args: {
27
27
  tool: "ToolSearch",
28
- args: { query: "select:mcp__grackle__finding_post", max_results: 3 },
28
+ args: { query: "select:mcp__grackle__task_create", max_results: 3 },
29
29
  result: [
30
- "mcp__grackle__finding_post:",
31
- " Post a new finding to the workspace.",
30
+ "mcp__grackle__task_create:",
31
+ " Create a new task in the workspace.",
32
32
  " Parameters:",
33
- " title (string, required): Finding title",
34
- " category (string, optional): Category (bug, insight, decision)",
35
- " content (string, optional): Detailed content",
36
- " tags (array, optional): Tags for categorization",
33
+ " title (string, required): Task title",
34
+ " description (string, optional): Task description",
35
+ " parentTaskId (string, optional): Parent task to nest under",
36
+ " canDecompose (boolean, optional): Allow the task to create subtasks",
37
37
  "",
38
38
  "mcp__grackle__workpad_write:",
39
39
  " Write to the task workpad.",
@@ -37,10 +37,10 @@ export const WriteCompleted: Story = {
37
37
  taskId: "74f5b716",
38
38
  workpad: {
39
39
  status: "completed",
40
- summary: "Tested Grackle MCP tools: posted a finding, wrote to workpad, and searched knowledge.",
40
+ summary: "Tested Grackle MCP tools: created a task, wrote to workpad, and searched knowledge.",
41
41
  extra: {
42
- tools_tested: ["finding_post", "workpad_write", "knowledge_search"],
43
- finding_topic: "qdrant catalog",
42
+ tools_tested: ["task_create", "workpad_write", "knowledge_search"],
43
+ search_topic: "qdrant catalog",
44
44
  },
45
45
  },
46
46
  }),
@@ -79,10 +79,10 @@ export const CopilotFormat: Story = {
79
79
  name: "workpad_write - Copilot tool name",
80
80
  args: {
81
81
  tool: "grackle-workpad_write",
82
- args: { status: "in progress", summary: "Posted a finding about Rush worktrees." },
82
+ args: { status: "in progress", summary: "Wrote a progress note about Rush worktrees." },
83
83
  result: JSON.stringify({
84
84
  taskId: "e4366a55",
85
- workpad: { status: "in progress", summary: "Posted a finding about Rush worktrees." },
85
+ workpad: { status: "in progress", summary: "Wrote a progress note about Rush worktrees." },
86
86
  }),
87
87
  },
88
88
  play: async ({ canvas }) => {
@@ -33,7 +33,6 @@ describe("classifyTool", () => {
33
33
 
34
34
  it("classifies Grackle MCP tools", () => {
35
35
  expect(classifyTool("mcp__grackle__workpad_write")).toBe("workpad");
36
- expect(classifyTool("mcp__grackle__finding_post")).toBe("finding");
37
36
  expect(classifyTool("mcp__grackle__task_create")).toBe("task");
38
37
  });
39
38
 
@@ -14,7 +14,6 @@ export type ToolCategory =
14
14
  | "search"
15
15
  | "todo"
16
16
  | "metadata"
17
- | "finding"
18
17
  | "task"
19
18
  | "workpad"
20
19
  | "knowledge"
@@ -29,8 +28,8 @@ const KNOWN_MCP_SERVERS: Set<string> = new Set(["grackle"]);
29
28
  /**
30
29
  * Extracts the bare tool name from runtime-specific naming conventions.
31
30
  *
32
- * - Claude Code / Codex: `mcp__grackle__finding_post` -> `finding_post`
33
- * - Copilot: `grackle-finding_post` -> `finding_post`
31
+ * - Claude Code / Codex: `mcp__grackle__task_create` -> `task_create`
32
+ * - Copilot: `grackle-task_create` -> `task_create`
34
33
  * - Built-in: `Read` -> `read` (unchanged, lowered later)
35
34
  */
36
35
  export function extractBareName(toolName: string): string {
@@ -84,10 +83,6 @@ const TOOL_MAP: Record<string, ToolCategory> = {
84
83
  // Metadata — Copilot: report_intent
85
84
  report_intent: "metadata",
86
85
 
87
- // Finding — Grackle MCP
88
- finding_post: "finding",
89
- finding_list: "finding",
90
-
91
86
  // Task — Grackle MCP
92
87
  task_list: "task",
93
88
  task_create: "task",
@@ -9,7 +9,7 @@
9
9
  import type {
10
10
  UsageStats, UseKnowledgeResult,
11
11
  UseEnvironmentsResult, UseSessionsResult, UseWorkspacesResult,
12
- UseTasksResult, UseFindingsResult, UseTokensResult,
12
+ UseTasksResult, UseTokensResult,
13
13
  UseCredentialsResult, UseCodespacesResult, UseDockerContainersResult, UsePersonasResult,
14
14
  UsePluginsResult,
15
15
  UseSchedulesResult,
@@ -30,8 +30,6 @@ export interface UseGrackleSocketResult {
30
30
  workspaces: Omit<UseWorkspacesResult, "handleEvent" | "onDisconnect">;
31
31
  /** Task state and actions. */
32
32
  tasks: Omit<UseTasksResult, "handleEvent" | "onDisconnect" | "handleLegacyMessage">;
33
- /** Finding state and actions. */
34
- findings: Omit<UseFindingsResult, "handleEvent">;
35
33
  /** Token state and actions. */
36
34
  tokens: Omit<UseTokensResult, "handleEvent">;
37
35
  /** Credential provider state and actions. */
@@ -74,7 +74,7 @@ export interface SessionEvent {
74
74
  raw?: string;
75
75
  }
76
76
 
77
- /** A workspace that groups tasks and findings. */
77
+ /** A workspace that groups tasks. */
78
78
  export interface Workspace {
79
79
  id: string;
80
80
  name: string;
@@ -125,19 +125,6 @@ export interface TaskData {
125
125
  costBudgetMillicents: number;
126
126
  }
127
127
 
128
- /** A finding posted by an agent or user. */
129
- export interface FindingData {
130
- id: string;
131
- workspaceId: string;
132
- taskId: string;
133
- sessionId: string;
134
- category: string;
135
- title: string;
136
- content: string;
137
- tags: string[];
138
- createdAt: string;
139
- }
140
-
141
128
  /** Metadata about a stored token. */
142
129
  export interface TokenInfo {
143
130
  name: string;
@@ -419,36 +406,6 @@ export interface UseTasksResult {
419
406
  domainHook: DomainHook;
420
407
  }
421
408
 
422
- /** Values returned by the findings domain hook. */
423
- export interface UseFindingsResult {
424
- /** All loaded findings. */
425
- findings: FindingData[];
426
- /** The currently selected finding (loaded by ID). */
427
- selectedFinding: FindingData | undefined;
428
- /** Whether a single finding is being loaded. */
429
- findingLoading: boolean;
430
- /** Whether a findings list fetch is in-flight. */
431
- findingsLoading: boolean;
432
- /** Load findings for a given workspace. */
433
- loadFindings: (workspaceId: string) => Promise<void>;
434
- /** Load findings across all workspaces. */
435
- loadAllFindings: () => Promise<void>;
436
- /** Load a single finding by ID. */
437
- loadFinding: (findingId: string) => Promise<void>;
438
- /** Post a new finding to a workspace. */
439
- postFinding: (
440
- workspaceId: string,
441
- title: string,
442
- content: string,
443
- category?: string,
444
- tags?: string[],
445
- ) => Promise<void>;
446
- /** Handle a domain event from the event bus. Returns `true` if handled. */
447
- handleEvent: (event: GrackleEvent) => boolean;
448
- /** Lifecycle hook for connect/disconnect/event routing. */
449
- domainHook: DomainHook;
450
- }
451
-
452
409
  /** Values returned by the tokens domain hook. */
453
410
  export interface UseTokensResult {
454
411
  /** All known tokens. */