@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.
- package/.rush/temp/{49e5384757b767ffca6c218faf139f6813911f4a.untar.log → 8d9be4152bfcbf796b578ac87621b34484202bd0.untar.log} +2 -2
- package/.rush/temp/{b32d9c7748f6c2c43df816a4bdd427ae0c7f1e32.untar.log → be1751e9cb123b206e39fdb59b24fd82523d77e2.untar.log} +2 -2
- package/.rush/temp/operation/_phase_build/all.log +13 -6
- package/.rush/temp/operation/_phase_build/log-chunks.jsonl +13 -6
- package/.rush/temp/operation/_phase_build/state.json +1 -1
- package/.rush/temp/operation/_phase_test/all.log +29 -28
- package/.rush/temp/operation/_phase_test/log-chunks.jsonl +29 -28
- package/.rush/temp/operation/_phase_test/state.json +1 -1
- package/.rush/temp/shrinkwrap-deps.json +13 -1
- package/README.md +3 -3
- package/config/rush-project.json +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +9068 -9498
- package/package.json +4 -2
- package/rush-logs/web-components._phase_build.cache.log +2 -2
- package/rush-logs/web-components._phase_test.cache.log +1 -1
- package/src/components/display/EventRenderer.stories.tsx +22 -0
- package/src/components/display/EventRenderer.tsx +28 -4
- package/src/components/index.ts +0 -3
- package/src/components/knowledge/KnowledgeDetailPanel.tsx +1 -4
- package/src/components/layout/AppNav.stories.tsx +3 -6
- package/src/components/layout/AppNav.tsx +3 -7
- package/src/components/layout/BottomStatusBar.tsx +4 -6
- package/src/components/lists/index.ts +0 -1
- package/src/components/panels/KeyboardShortcutsPanel.tsx +0 -1
- package/src/components/panels/index.ts +0 -1
- package/src/components/personas/McpToolSelector.stories.tsx +12 -12
- package/src/components/tools/ToolCard.stories.tsx +0 -26
- package/src/components/tools/ToolCard.tsx +0 -3
- package/src/components/tools/ToolSearchCard.stories.tsx +8 -8
- package/src/components/tools/WorkpadCard.stories.tsx +5 -5
- package/src/components/tools/classifyTool.test.ts +0 -1
- package/src/components/tools/classifyTool.ts +2 -7
- package/src/context/GrackleContextTypes.ts +1 -3
- package/src/hooks/types.ts +1 -44
- package/src/index.ts +4 -8
- package/src/mcp-runtime/index.tsx +99 -0
- package/src/mocks/MockGrackleProvider.tsx +0 -75
- package/src/mocks/mockData.ts +8 -99
- package/src/test-utils/storybook-helpers.ts +0 -19
- package/src/utils/breadcrumbs.test.ts +0 -43
- package/src/utils/breadcrumbs.ts +1 -37
- package/src/utils/navigation.ts +1 -20
- package/src/utils/route-config.test.ts +0 -31
- package/vite.config.ts +46 -2
- package/.rush/temp/49e5384757b767ffca6c218faf139f6813911f4a.tar.log +0 -12
- package/.rush/temp/b32d9c7748f6c2c43df816a4bdd427ae0c7f1e32.tar.log +0 -236
- package/.rush/temp/chunked-rush-logs/web-components._phase_build.chunks.jsonl +0 -19
- package/.rush/temp/chunked-rush-logs/web-components._phase_test.chunks.jsonl +0 -126
- package/rush-logs/web-components._phase_build.log +0 -19
- package/rush-logs/web-components._phase_test.log +0 -126
- package/src/components/lists/FindingsNav.module.scss +0 -126
- package/src/components/lists/FindingsNav.tsx +0 -146
- package/src/components/panels/FindingsPanel.module.scss +0 -94
- package/src/components/panels/FindingsPanel.stories.tsx +0 -109
- package/src/components/panels/FindingsPanel.tsx +0 -76
- package/src/components/tools/FindingCard.stories.tsx +0 -124
- package/src/components/tools/FindingCard.tsx +0 -178
- package/src/utils/findingCategory.ts +0 -33
- 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.
|
|
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.
|
|
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:
|
|
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.
|
|
@@ -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`
|
|
230
|
-
// (agent-authored widgets
|
|
231
|
-
|
|
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
|
|
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
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
4
|
-
import { CHAT_URL, COORDINATION_URL, ENVIRONMENTS_URL,
|
|
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" | "
|
|
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
|
|
49
|
-
?? wsTaskMatch?.params.taskId ?? wsTaskStreamMatch?.params.taskId ??
|
|
50
|
-
const wsMatch = wsTaskMatch ?? wsTaskStreamMatch ??
|
|
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 && !
|
|
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;
|
|
@@ -58,13 +58,13 @@ export const WithPresetWorker: Story = {
|
|
|
58
58
|
|
|
59
59
|
export const CustomSelection: Story = {
|
|
60
60
|
args: {
|
|
61
|
-
selectedTools: ["
|
|
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
|
|
67
|
-
await expect(
|
|
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-
|
|
82
|
-
await expect(args.onChange).toHaveBeenCalledWith(["
|
|
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, "
|
|
91
|
-
//
|
|
92
|
-
await expect(canvas.getByTestId("tool-group-
|
|
93
|
-
await expect(canvas.getByTestId("tool-
|
|
94
|
-
await expect(canvas.getByTestId("tool-
|
|
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: ["
|
|
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-
|
|
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:
|
|
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:
|
|
28
|
+
args: { query: "select:mcp__grackle__task_create", max_results: 3 },
|
|
29
29
|
result: [
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"mcp__grackle__task_create:",
|
|
31
|
+
" Create a new task in the workspace.",
|
|
32
32
|
" Parameters:",
|
|
33
|
-
" title (string, required):
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
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:
|
|
40
|
+
summary: "Tested Grackle MCP tools: created a task, wrote to workpad, and searched knowledge.",
|
|
41
41
|
extra: {
|
|
42
|
-
tools_tested: ["
|
|
43
|
-
|
|
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: "
|
|
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: "
|
|
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: `
|
|
33
|
-
* - Copilot: `grackle-
|
|
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,
|
|
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. */
|
package/src/hooks/types.ts
CHANGED
|
@@ -74,7 +74,7 @@ export interface SessionEvent {
|
|
|
74
74
|
raw?: string;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/** A workspace that groups tasks
|
|
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. */
|