@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.
- package/.rush/temp/3ae72563f781afd72723475938136f113846603e.untar.log +10 -0
- package/.rush/temp/bc1d5bf9201ce71abeaeaddd096deb9b0805d703.untar.log +10 -0
- package/.rush/temp/operation/_phase_build/all.log +18 -0
- package/.rush/temp/operation/_phase_build/log-chunks.jsonl +18 -0
- package/.rush/temp/operation/_phase_build/state.json +3 -0
- package/.rush/temp/operation/_phase_test/all.log +121 -0
- package/.rush/temp/operation/_phase_test/log-chunks.jsonl +121 -0
- package/.rush/temp/operation/_phase_test/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +938 -0
- package/.storybook/main.ts +22 -0
- package/.storybook/preview.tsx +30 -0
- package/config/rig.json +4 -0
- package/config/rush-project.json +12 -0
- package/dist/index.css +1 -0
- package/dist/index.js +39221 -0
- package/eslint.config.cjs +5 -0
- package/package.json +83 -0
- package/rush-logs/web-components._phase_build.cache.log +4 -0
- package/rush-logs/web-components._phase_test.cache.log +4 -0
- package/src/components/chat/ChatInput.module.scss +81 -0
- package/src/components/chat/ChatInput.stories.tsx +91 -0
- package/src/components/chat/ChatInput.tsx +168 -0
- package/src/components/chat/index.ts +6 -0
- package/src/components/dag/DagView.module.scss +149 -0
- package/src/components/dag/DagView.stories.tsx +125 -0
- package/src/components/dag/DagView.tsx +109 -0
- package/src/components/dag/TaskNode.stories.tsx +133 -0
- package/src/components/dag/TaskNode.tsx +40 -0
- package/src/components/dag/useDagLayout.ts +139 -0
- package/src/components/display/Breadcrumbs.module.scss +71 -0
- package/src/components/display/Breadcrumbs.stories.tsx +80 -0
- package/src/components/display/Breadcrumbs.tsx +46 -0
- package/src/components/display/Button.module.scss +110 -0
- package/src/components/display/Button.stories.tsx +88 -0
- package/src/components/display/Button.tsx +40 -0
- package/src/components/display/ConfirmDialog.module.scss +67 -0
- package/src/components/display/ConfirmDialog.stories.tsx +81 -0
- package/src/components/display/ConfirmDialog.tsx +88 -0
- package/src/components/display/CopyButton.module.scss +41 -0
- package/src/components/display/CopyButton.stories.tsx +78 -0
- package/src/components/display/CopyButton.tsx +64 -0
- package/src/components/display/DemoBanner.module.scss +37 -0
- package/src/components/display/DemoBanner.stories.tsx +40 -0
- package/src/components/display/DemoBanner.tsx +23 -0
- package/src/components/display/EventHoverRow.module.scss +102 -0
- package/src/components/display/EventHoverRow.stories.tsx +99 -0
- package/src/components/display/EventHoverRow.tsx +154 -0
- package/src/components/display/EventRenderer.module.scss +272 -0
- package/src/components/display/EventRenderer.stories.tsx +186 -0
- package/src/components/display/EventRenderer.tsx +271 -0
- package/src/components/display/EventStream.module.scss +93 -0
- package/src/components/display/EventStream.stories.tsx +249 -0
- package/src/components/display/EventStream.tsx +369 -0
- package/src/components/display/FloatingActionBar.module.scss +107 -0
- package/src/components/display/FloatingActionBar.stories.tsx +122 -0
- package/src/components/display/FloatingActionBar.tsx +119 -0
- package/src/components/display/SessionAttemptSelector.module.scss +50 -0
- package/src/components/display/SessionAttemptSelector.stories.tsx +78 -0
- package/src/components/display/SessionAttemptSelector.tsx +49 -0
- package/src/components/display/SessionPicker.module.scss +200 -0
- package/src/components/display/SessionPicker.stories.tsx +169 -0
- package/src/components/display/SessionPicker.tsx +214 -0
- package/src/components/display/Skeleton.module.scss +58 -0
- package/src/components/display/Skeleton.stories.tsx +94 -0
- package/src/components/display/Skeleton.tsx +127 -0
- package/src/components/display/Spinner.module.scss +41 -0
- package/src/components/display/Spinner.stories.tsx +66 -0
- package/src/components/display/Spinner.tsx +32 -0
- package/src/components/display/SplashScreen.module.scss +20 -0
- package/src/components/display/SplashScreen.stories.tsx +26 -0
- package/src/components/display/SplashScreen.tsx +16 -0
- package/src/components/display/SplitButton.module.scss +166 -0
- package/src/components/display/SplitButton.stories.tsx +95 -0
- package/src/components/display/SplitButton.tsx +128 -0
- package/src/components/display/Tooltip.module.scss +84 -0
- package/src/components/display/Tooltip.stories.tsx +240 -0
- package/src/components/display/Tooltip.tsx +184 -0
- package/src/components/display/extractText.test.tsx +48 -0
- package/src/components/display/index.ts +20 -0
- package/src/components/editable/EditableCheckbox.stories.tsx +54 -0
- package/src/components/editable/EditableCheckbox.tsx +39 -0
- package/src/components/editable/EditableField.module.scss +135 -0
- package/src/components/editable/EditableSelect.tsx +164 -0
- package/src/components/editable/EditableTextArea.stories.tsx +50 -0
- package/src/components/editable/EditableTextArea.tsx +148 -0
- package/src/components/editable/EditableTextField.stories.tsx +62 -0
- package/src/components/editable/EditableTextField.tsx +153 -0
- package/src/components/editable/EnvironmentSelect.module.scss +17 -0
- package/src/components/editable/EnvironmentSelect.stories.tsx +61 -0
- package/src/components/editable/EnvironmentSelect.tsx +87 -0
- package/src/components/editable/index.ts +13 -0
- package/src/components/editable/useEditableField.test.tsx +233 -0
- package/src/components/editable/useEditableField.ts +173 -0
- package/src/components/index.ts +20 -0
- package/src/components/knowledge/KnowledgeDetailPanel.module.scss +162 -0
- package/src/components/knowledge/KnowledgeDetailPanel.stories.tsx +208 -0
- package/src/components/knowledge/KnowledgeDetailPanel.tsx +122 -0
- package/src/components/knowledge/KnowledgeGraph.module.scss +110 -0
- package/src/components/knowledge/KnowledgeGraph.stories.tsx +180 -0
- package/src/components/knowledge/KnowledgeGraph.tsx +455 -0
- package/src/components/knowledge/KnowledgeNav.module.scss +130 -0
- package/src/components/knowledge/KnowledgeNav.stories.tsx +108 -0
- package/src/components/knowledge/KnowledgeNav.tsx +138 -0
- package/src/components/knowledge/index.ts +3 -0
- package/src/components/layout/AppNav.module.scss +82 -0
- package/src/components/layout/AppNav.stories.tsx +115 -0
- package/src/components/layout/AppNav.tsx +133 -0
- package/src/components/layout/BottomStatusBar.module.scss +58 -0
- package/src/components/layout/BottomStatusBar.stories.tsx +35 -0
- package/src/components/layout/BottomStatusBar.tsx +206 -0
- package/src/components/layout/Sidebar.module.scss +60 -0
- package/src/components/layout/Sidebar.stories.tsx +46 -0
- package/src/components/layout/Sidebar.tsx +84 -0
- package/src/components/layout/StatusBar.module.scss +108 -0
- package/src/components/layout/StatusBar.stories.tsx +119 -0
- package/src/components/layout/StatusBar.tsx +70 -0
- package/src/components/layout/index.ts +9 -0
- package/src/components/lists/EnvironmentNav.module.scss +118 -0
- package/src/components/lists/EnvironmentNav.stories.tsx +121 -0
- package/src/components/lists/EnvironmentNav.tsx +133 -0
- package/src/components/lists/FindingsNav.module.scss +126 -0
- package/src/components/lists/FindingsNav.tsx +146 -0
- package/src/components/lists/TaskList.module.scss +206 -0
- package/src/components/lists/TaskList.stories.tsx +401 -0
- package/src/components/lists/TaskList.tsx +509 -0
- package/src/components/lists/index.ts +6 -0
- package/src/components/lists/listHelpers.tsx +130 -0
- package/src/components/notifications/Callout.module.scss +83 -0
- package/src/components/notifications/Callout.stories.tsx +81 -0
- package/src/components/notifications/Callout.tsx +64 -0
- package/src/components/notifications/Toast.module.scss +86 -0
- package/src/components/notifications/Toast.stories.tsx +71 -0
- package/src/components/notifications/Toast.tsx +51 -0
- package/src/components/notifications/ToastContainer.module.scss +23 -0
- package/src/components/notifications/ToastContainer.stories.tsx +66 -0
- package/src/components/notifications/ToastContainer.tsx +29 -0
- package/src/components/notifications/UpdateBanner.stories.tsx +77 -0
- package/src/components/notifications/UpdateBanner.test.tsx +64 -0
- package/src/components/notifications/UpdateBanner.tsx +44 -0
- package/src/components/notifications/index.ts +8 -0
- package/src/components/panels/AboutPanel.stories.tsx +70 -0
- package/src/components/panels/AboutPanel.tsx +66 -0
- package/src/components/panels/AppearancePanel.stories.tsx +45 -0
- package/src/components/panels/AppearancePanel.tsx +97 -0
- package/src/components/panels/CredentialProvidersPanel.stories.tsx +62 -0
- package/src/components/panels/CredentialProvidersPanel.tsx +111 -0
- package/src/components/panels/EnvironmentEditPanel.module.scss +170 -0
- package/src/components/panels/EnvironmentEditPanel.stories.tsx +206 -0
- package/src/components/panels/EnvironmentEditPanel.tsx +785 -0
- package/src/components/panels/FindingsPanel.module.scss +94 -0
- package/src/components/panels/FindingsPanel.stories.tsx +109 -0
- package/src/components/panels/FindingsPanel.tsx +76 -0
- package/src/components/panels/KeyboardShortcutsPanel.module.scss +65 -0
- package/src/components/panels/KeyboardShortcutsPanel.stories.tsx +40 -0
- package/src/components/panels/KeyboardShortcutsPanel.tsx +104 -0
- package/src/components/panels/PluginsPanel.tsx +77 -0
- package/src/components/panels/SettingsPanel.module.scss +336 -0
- package/src/components/panels/TaskActionButtons.module.scss +22 -0
- package/src/components/panels/TaskActionButtons.stories.tsx +125 -0
- package/src/components/panels/TaskActionButtons.tsx +87 -0
- package/src/components/panels/TaskEditPanel.module.scss +202 -0
- package/src/components/panels/TaskEditPanel.stories.tsx +75 -0
- package/src/components/panels/TaskEditPanel.tsx +328 -0
- package/src/components/panels/TaskOverviewPanel.module.scss +236 -0
- package/src/components/panels/TaskOverviewPanel.stories.tsx +219 -0
- package/src/components/panels/TaskOverviewPanel.tsx +270 -0
- package/src/components/panels/TokensPanel.stories.tsx +131 -0
- package/src/components/panels/TokensPanel.tsx +143 -0
- package/src/components/panels/WorkpadPanel.module.scss +39 -0
- package/src/components/panels/WorkpadPanel.stories.tsx +56 -0
- package/src/components/panels/WorkpadPanel.tsx +63 -0
- package/src/components/panels/index.ts +13 -0
- package/src/components/personas/McpToolSelector.module.scss +109 -0
- package/src/components/personas/McpToolSelector.stories.tsx +129 -0
- package/src/components/personas/McpToolSelector.tsx +180 -0
- package/src/components/personas/PersonaManager.module.scss +233 -0
- package/src/components/personas/PersonaManager.stories.tsx +139 -0
- package/src/components/personas/PersonaManager.tsx +122 -0
- package/src/components/schedules/ScheduleManager.module.scss +98 -0
- package/src/components/schedules/ScheduleManager.stories.tsx +78 -0
- package/src/components/schedules/ScheduleManager.tsx +160 -0
- package/src/components/settings/SettingsNav.module.scss +82 -0
- package/src/components/settings/SettingsNav.stories.tsx +83 -0
- package/src/components/settings/SettingsNav.tsx +104 -0
- package/src/components/streams/StreamDetailPanel.module.scss +206 -0
- package/src/components/streams/StreamDetailPanel.stories.tsx +132 -0
- package/src/components/streams/StreamDetailPanel.tsx +119 -0
- package/src/components/streams/StreamList.module.scss +92 -0
- package/src/components/streams/StreamList.stories.tsx +99 -0
- package/src/components/streams/StreamList.tsx +114 -0
- package/src/components/streams/index.ts +10 -0
- package/src/components/tools/AgentToolCard.module.scss +118 -0
- package/src/components/tools/AgentToolCard.stories.tsx +304 -0
- package/src/components/tools/AgentToolCard.tsx +247 -0
- package/src/components/tools/FileEditCard.stories.tsx +138 -0
- package/src/components/tools/FileEditCard.tsx +160 -0
- package/src/components/tools/FileReadCard.stories.tsx +120 -0
- package/src/components/tools/FileReadCard.tsx +106 -0
- package/src/components/tools/FindingCard.stories.tsx +124 -0
- package/src/components/tools/FindingCard.tsx +178 -0
- package/src/components/tools/GenericToolCard.stories.tsx +80 -0
- package/src/components/tools/GenericToolCard.tsx +111 -0
- package/src/components/tools/IpcCard.stories.tsx +129 -0
- package/src/components/tools/IpcCard.tsx +178 -0
- package/src/components/tools/KnowledgeCard.stories.tsx +112 -0
- package/src/components/tools/KnowledgeCard.tsx +165 -0
- package/src/components/tools/MetadataCard.stories.tsx +32 -0
- package/src/components/tools/MetadataCard.tsx +39 -0
- package/src/components/tools/SearchCard.stories.tsx +74 -0
- package/src/components/tools/SearchCard.tsx +86 -0
- package/src/components/tools/ShellCard.stories.tsx +112 -0
- package/src/components/tools/ShellCard.tsx +106 -0
- package/src/components/tools/TaskCard.stories.tsx +123 -0
- package/src/components/tools/TaskCard.tsx +203 -0
- package/src/components/tools/TodoCard.module.scss +131 -0
- package/src/components/tools/TodoCard.stories.tsx +202 -0
- package/src/components/tools/TodoCard.tsx +200 -0
- package/src/components/tools/ToolCard.stories.tsx +177 -0
- package/src/components/tools/ToolCard.tsx +60 -0
- package/src/components/tools/ToolCardProps.ts +20 -0
- package/src/components/tools/ToolSearchCard.stories.tsx +81 -0
- package/src/components/tools/ToolSearchCard.tsx +86 -0
- package/src/components/tools/WorkpadCard.stories.tsx +106 -0
- package/src/components/tools/WorkpadCard.tsx +125 -0
- package/src/components/tools/classifyTool.test.ts +44 -0
- package/src/components/tools/classifyTool.ts +134 -0
- package/src/components/tools/parseDiff.ts +95 -0
- package/src/components/tools/parseShellOutput.ts +28 -0
- package/src/components/tools/toolCardHelpers.test.ts +53 -0
- package/src/components/tools/toolCards.module.scss +234 -0
- package/src/components/workspace/WorkspaceBoard.module.scss +238 -0
- package/src/components/workspace/WorkspaceBoard.stories.tsx +240 -0
- package/src/components/workspace/WorkspaceBoard.tsx +232 -0
- package/src/components/workspace/WorkspaceFormFields.module.scss +79 -0
- package/src/components/workspace/WorkspaceFormFields.stories.tsx +133 -0
- package/src/components/workspace/WorkspaceFormFields.tsx +185 -0
- package/src/context/GrackleContext.ts +28 -0
- package/src/context/GrackleContextTypes.ts +64 -0
- package/src/context/SidebarContext.tsx +53 -0
- package/src/context/ThemeContext.tsx +21 -0
- package/src/context/ToastContext.tsx +56 -0
- package/src/hooks/types.ts +864 -0
- package/src/hooks/useEventSelection.test.ts +204 -0
- package/src/hooks/useEventSelection.ts +158 -0
- package/src/hooks/useSmartScroll.ts +151 -0
- package/src/hooks/useTheme.ts +228 -0
- package/src/index.ts +210 -0
- package/src/mocks/MockGrackleProvider.tsx +1397 -0
- package/src/mocks/mockData.ts +1966 -0
- package/src/mocks/mockKnowledgeData.ts +294 -0
- package/src/scss.d.ts +12 -0
- package/src/styles/global.scss +244 -0
- package/src/styles/mixins.scss +278 -0
- package/src/styles/prism-theme.scss +148 -0
- package/src/styles/theme.scss +1102 -0
- package/src/test-utils/storybook-decorators.tsx +50 -0
- package/src/test-utils/storybook-helpers.ts +262 -0
- package/src/themes.ts +142 -0
- package/src/utils/boardColumns.ts +141 -0
- package/src/utils/breadcrumbs.test.ts +285 -0
- package/src/utils/breadcrumbs.ts +222 -0
- package/src/utils/dashboard.test.ts +156 -0
- package/src/utils/dashboard.ts +195 -0
- package/src/utils/eventContent.test.ts +353 -0
- package/src/utils/eventContent.ts +209 -0
- package/src/utils/findingCategory.ts +33 -0
- package/src/utils/format.ts +27 -0
- package/src/utils/iconSize.ts +18 -0
- package/src/utils/navigation.ts +205 -0
- package/src/utils/route-config.test.ts +128 -0
- package/src/utils/scrollUtils.test.ts +65 -0
- package/src/utils/scrollUtils.ts +49 -0
- package/src/utils/sessionEvents.test.ts +302 -0
- package/src/utils/sessionEvents.ts +233 -0
- package/src/utils/taskStatus.tsx +137 -0
- package/src/utils/time.ts +92 -0
- package/tsconfig.json +8 -0
- package/vite.config.ts +20 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, userEvent } from "@storybook/test";
|
|
3
|
+
import { GenericToolCard } from "./GenericToolCard.js";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof GenericToolCard> = {
|
|
6
|
+
component: GenericToolCard,
|
|
7
|
+
title: "Grackle/Tools/GenericToolCard",
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
};
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const McpTool: Story = {
|
|
14
|
+
name: "MCP tool - formatted name",
|
|
15
|
+
args: {
|
|
16
|
+
tool: "mcp__github__create_pull_request",
|
|
17
|
+
args: { title: "Add auth middleware", base: "main", head: "feat/auth" },
|
|
18
|
+
result: "PR #142 created successfully",
|
|
19
|
+
},
|
|
20
|
+
play: async ({ canvas }) => {
|
|
21
|
+
await expect(canvas.getByTestId("tool-card-generic")).toBeInTheDocument();
|
|
22
|
+
// MCP name should be formatted as "server / tool"
|
|
23
|
+
await expect(canvas.getByText("github / create_pull_request")).toBeInTheDocument();
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const UnknownTool: Story = {
|
|
28
|
+
name: "Unknown tool",
|
|
29
|
+
args: {
|
|
30
|
+
tool: "custom_tool",
|
|
31
|
+
args: { query: "find all TODOs" },
|
|
32
|
+
result: "Found 12 TODO comments across 5 files",
|
|
33
|
+
},
|
|
34
|
+
play: async ({ canvas }) => {
|
|
35
|
+
await expect(canvas.getByText("custom_tool")).toBeInTheDocument();
|
|
36
|
+
await expect(canvas.getByTestId("tool-card-result")).toHaveTextContent("Found 12 TODO comments");
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const InProgress: Story = {
|
|
41
|
+
name: "In-progress - shows formatted args",
|
|
42
|
+
args: {
|
|
43
|
+
tool: "WebFetch",
|
|
44
|
+
args: { url: "https://api.example.com/data" },
|
|
45
|
+
},
|
|
46
|
+
play: async ({ canvas }) => {
|
|
47
|
+
const card = canvas.getByTestId("tool-card-generic");
|
|
48
|
+
await expect(card.className).toContain("inProgress");
|
|
49
|
+
await expect(canvas.getByTestId("tool-card-args")).toBeInTheDocument();
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const LongResult: Story = {
|
|
54
|
+
name: "Long result - expand/collapse",
|
|
55
|
+
args: {
|
|
56
|
+
tool: "WebSearch",
|
|
57
|
+
args: { query: "nodejs best practices 2026" },
|
|
58
|
+
result: "Result 1: Use ESM modules\nResult 2: Adopt Node 22\nResult 3: Use built-in test runner\nResult 4: Prefer fetch over axios\nResult 5: Use structured logging\nResult 6: Type-check with tsc\nResult 7: Use Biome for formatting\nResult 8: Pin dependency versions",
|
|
59
|
+
},
|
|
60
|
+
play: async ({ canvas }) => {
|
|
61
|
+
const toggle = canvas.getByTestId("tool-card-toggle");
|
|
62
|
+
await expect(toggle).toBeInTheDocument();
|
|
63
|
+
await userEvent.click(toggle);
|
|
64
|
+
await expect(toggle.textContent).toContain("collapse");
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const ErrorResult: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
tool: "mcp__slack__send_message",
|
|
71
|
+
args: { channel: "#deploys", message: "Deployed v2.0" },
|
|
72
|
+
result: "Error: channel not found",
|
|
73
|
+
isError: true,
|
|
74
|
+
},
|
|
75
|
+
play: async ({ canvas }) => {
|
|
76
|
+
await expect(canvas.getByTestId("tool-card-error")).toBeInTheDocument();
|
|
77
|
+
const card = canvas.getByTestId("tool-card-generic");
|
|
78
|
+
await expect(card.className).toContain("cardRed");
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useState, type JSX } from "react";
|
|
2
|
+
import { ChevronRight, Cog } from "lucide-react";
|
|
3
|
+
import type { ToolCardProps } from "./ToolCardProps.js";
|
|
4
|
+
import { ICON_SM, ICON_MD } from "../../utils/iconSize.js";
|
|
5
|
+
import styles from "./toolCards.module.scss";
|
|
6
|
+
|
|
7
|
+
/** Formats an MCP tool name for display: `mcp__server__tool` → `server / tool`. */
|
|
8
|
+
export function formatToolName(tool: string): { display: string; isMcp: boolean } {
|
|
9
|
+
const mcpMatch = /^mcp__([^_]+(?:_[^_]+)*)__(.+)$/.exec(tool);
|
|
10
|
+
if (mcpMatch) {
|
|
11
|
+
return { display: `${mcpMatch[1]} / ${mcpMatch[2]}`, isMcp: true };
|
|
12
|
+
}
|
|
13
|
+
return { display: tool, isMcp: false };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Extracts a one-line human-readable summary of tool arguments. */
|
|
17
|
+
function argsPreview(args: unknown): string {
|
|
18
|
+
if (args === null || args === undefined) {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
if (typeof args !== "object") {
|
|
22
|
+
return String(args);
|
|
23
|
+
}
|
|
24
|
+
const a = args as Record<string, unknown>;
|
|
25
|
+
// Common patterns
|
|
26
|
+
if (typeof a.command === "string") { return a.command; }
|
|
27
|
+
if (typeof a.file_path === "string") { return a.file_path; }
|
|
28
|
+
if (typeof a.path === "string") { return a.path; }
|
|
29
|
+
if (typeof a.query === "string") { return a.query; }
|
|
30
|
+
if (typeof a.url === "string") { return a.url; }
|
|
31
|
+
// Fallback
|
|
32
|
+
try {
|
|
33
|
+
const json = JSON.stringify(args);
|
|
34
|
+
return json.length > 120 ? `${json.slice(0, 120)}\u2026` : json;
|
|
35
|
+
} catch {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Number of result lines shown when collapsed. */
|
|
41
|
+
const PREVIEW_LINES: number = 5;
|
|
42
|
+
|
|
43
|
+
/** Renders a generic/unknown tool call with formatted args and result. */
|
|
44
|
+
export function GenericToolCard({ tool, args, result, isError }: ToolCardProps): JSX.Element {
|
|
45
|
+
const [expanded, setExpanded] = useState(false);
|
|
46
|
+
const { display } = formatToolName(tool);
|
|
47
|
+
const preview = argsPreview(args);
|
|
48
|
+
const inProgress = result === undefined;
|
|
49
|
+
|
|
50
|
+
const resultLines = result?.split("\n") ?? [];
|
|
51
|
+
const hasMore = resultLines.length > PREVIEW_LINES;
|
|
52
|
+
const displayResult = expanded ? result : resultLines.slice(0, PREVIEW_LINES).join("\n");
|
|
53
|
+
|
|
54
|
+
// Format args as pretty JSON for expanded view
|
|
55
|
+
let argsFormatted = "";
|
|
56
|
+
if (args !== null && args !== undefined) {
|
|
57
|
+
try {
|
|
58
|
+
argsFormatted = JSON.stringify(args, null, 2);
|
|
59
|
+
} catch {
|
|
60
|
+
argsFormatted = String(args);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
className={`${styles.card} ${isError ? styles.cardRed : styles.cardBlue} ${inProgress ? styles.inProgress : ""}`}
|
|
67
|
+
data-testid="tool-card-generic"
|
|
68
|
+
>
|
|
69
|
+
<div className={styles.header}>
|
|
70
|
+
<span className={styles.icon}><Cog size={ICON_MD} aria-hidden="true" /></span>
|
|
71
|
+
<span className={styles.toolName} style={{ color: "var(--accent-blue)" }}>{display}</span>
|
|
72
|
+
{preview && (
|
|
73
|
+
<span className={styles.fileName}>{preview}</span>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Show formatted args when no result yet */}
|
|
78
|
+
{inProgress && argsFormatted && (
|
|
79
|
+
<pre className={styles.pre} data-testid="tool-card-args">
|
|
80
|
+
{argsFormatted}
|
|
81
|
+
</pre>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{isError && result && (
|
|
85
|
+
<pre className={styles.pre} data-testid="tool-card-error">
|
|
86
|
+
{result}
|
|
87
|
+
</pre>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{!isError && !inProgress && result && (
|
|
91
|
+
<>
|
|
92
|
+
<pre className={styles.pre} data-testid="tool-card-result">
|
|
93
|
+
{displayResult}
|
|
94
|
+
</pre>
|
|
95
|
+
{hasMore && (
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
className={styles.bodyToggle}
|
|
99
|
+
onClick={() => { setExpanded((v) => !v); }}
|
|
100
|
+
aria-expanded={expanded}
|
|
101
|
+
data-testid="tool-card-toggle"
|
|
102
|
+
>
|
|
103
|
+
<span className={`${styles.chevron} ${expanded ? styles.chevronExpanded : ""}`} aria-hidden="true"><ChevronRight size={ICON_SM} /></span>
|
|
104
|
+
{expanded ? "collapse" : `${resultLines.length - PREVIEW_LINES} more lines`}
|
|
105
|
+
</button>
|
|
106
|
+
)}
|
|
107
|
+
</>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect } from "@storybook/test";
|
|
3
|
+
import { IpcCard } from "./IpcCard.js";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof IpcCard> = {
|
|
6
|
+
component: IpcCard,
|
|
7
|
+
title: "Tools/IpcCard",
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof IpcCard>;
|
|
11
|
+
|
|
12
|
+
export const SpawnInProgress: Story = {
|
|
13
|
+
name: "ipc_spawn - in progress",
|
|
14
|
+
args: {
|
|
15
|
+
tool: "mcp__grackle__ipc_spawn",
|
|
16
|
+
args: {
|
|
17
|
+
prompt: "Investigate the authentication module and report findings",
|
|
18
|
+
pipe: "async",
|
|
19
|
+
environmentId: "local",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
play: async ({ canvas }) => {
|
|
23
|
+
await expect(canvas.getByTestId("tool-card-ipc")).toBeInTheDocument();
|
|
24
|
+
await expect(canvas.getByTestId("tool-card-ipc-info")).toHaveTextContent("[async]");
|
|
25
|
+
await expect(canvas.getByTestId("tool-card-ipc-prompt")).toBeInTheDocument();
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const SpawnCompleted: Story = {
|
|
30
|
+
name: "ipc_spawn - async result",
|
|
31
|
+
args: {
|
|
32
|
+
tool: "mcp__grackle__ipc_spawn",
|
|
33
|
+
args: { prompt: "Run tests", pipe: "async", environmentId: "local" },
|
|
34
|
+
result: JSON.stringify({
|
|
35
|
+
sessionId: "abc123-def456",
|
|
36
|
+
fd: 3,
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
play: async ({ canvas }) => {
|
|
40
|
+
await expect(canvas.getByTestId("tool-card-ipc")).toBeInTheDocument();
|
|
41
|
+
await expect(canvas.getByTestId("tool-card-ipc-session")).toHaveTextContent("abc123-def456");
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const WriteToFd: Story = {
|
|
46
|
+
name: "ipc_write - success",
|
|
47
|
+
args: {
|
|
48
|
+
tool: "mcp__grackle__ipc_write",
|
|
49
|
+
args: { fd: 3, message: "Please also check the error handling paths" },
|
|
50
|
+
result: JSON.stringify({ success: true }),
|
|
51
|
+
},
|
|
52
|
+
play: async ({ canvas }) => {
|
|
53
|
+
await expect(canvas.getByTestId("tool-card-ipc")).toBeInTheDocument();
|
|
54
|
+
await expect(canvas.getByTestId("tool-card-ipc-info")).toHaveTextContent("fd:3");
|
|
55
|
+
await expect(canvas.getByTestId("tool-card-ipc-success")).toHaveTextContent("ok");
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const CloseFd: Story = {
|
|
60
|
+
name: "ipc_close - success",
|
|
61
|
+
args: {
|
|
62
|
+
tool: "mcp__grackle__ipc_close",
|
|
63
|
+
args: { fd: 3 },
|
|
64
|
+
result: JSON.stringify({ success: true, stopped: true }),
|
|
65
|
+
},
|
|
66
|
+
play: async ({ canvas }) => {
|
|
67
|
+
await expect(canvas.getByTestId("tool-card-ipc")).toBeInTheDocument();
|
|
68
|
+
await expect(canvas.getByTestId("tool-card-ipc-info")).toHaveTextContent("fd:3");
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const ListFds: Story = {
|
|
73
|
+
name: "ipc_list_fds - with descriptors",
|
|
74
|
+
args: {
|
|
75
|
+
tool: "mcp__grackle__ipc_list_fds",
|
|
76
|
+
args: {},
|
|
77
|
+
result: JSON.stringify({
|
|
78
|
+
fds: [
|
|
79
|
+
{ fd: 3, streamName: "child-1", permission: "rw", deliveryMode: "async", owned: true, targetSessionId: "sess-1" },
|
|
80
|
+
{ fd: 4, streamName: "child-2", permission: "r", deliveryMode: "detach", owned: false, targetSessionId: "sess-2" },
|
|
81
|
+
],
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
play: async ({ canvas }) => {
|
|
85
|
+
await expect(canvas.getByTestId("tool-card-ipc")).toBeInTheDocument();
|
|
86
|
+
await expect(canvas.getByTestId("tool-card-ipc-fd-count")).toHaveTextContent("2 fds");
|
|
87
|
+
await expect(canvas.getByTestId("tool-card-ipc-fds")).toBeInTheDocument();
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const Terminate: Story = {
|
|
92
|
+
name: "ipc_terminate - success",
|
|
93
|
+
args: {
|
|
94
|
+
tool: "mcp__grackle__ipc_terminate",
|
|
95
|
+
args: { fd: 3 },
|
|
96
|
+
result: JSON.stringify({ success: true, targetSessionId: "sess-1" }),
|
|
97
|
+
},
|
|
98
|
+
play: async ({ canvas }) => {
|
|
99
|
+
await expect(canvas.getByTestId("tool-card-ipc")).toBeInTheDocument();
|
|
100
|
+
await expect(canvas.getByTestId("tool-card-ipc-success")).toHaveTextContent("ok");
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const CreateStream: Story = {
|
|
105
|
+
name: "ipc_create_stream - result",
|
|
106
|
+
args: {
|
|
107
|
+
tool: "mcp__grackle__ipc_create_stream",
|
|
108
|
+
args: { name: "broadcast-channel" },
|
|
109
|
+
result: JSON.stringify({ streamId: "stream-abc", fd: 5 }),
|
|
110
|
+
},
|
|
111
|
+
play: async ({ canvas }) => {
|
|
112
|
+
await expect(canvas.getByTestId("tool-card-ipc")).toBeInTheDocument();
|
|
113
|
+
await expect(canvas.getByTestId("tool-card-ipc-info")).toHaveTextContent("broadcast-channel");
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const ErrorState: Story = {
|
|
118
|
+
name: "ipc_write - error",
|
|
119
|
+
args: {
|
|
120
|
+
tool: "mcp__grackle__ipc_write",
|
|
121
|
+
args: { fd: 99, message: "hello" },
|
|
122
|
+
result: "gRPC error [NotFound]: fd 99 not found",
|
|
123
|
+
isError: true,
|
|
124
|
+
},
|
|
125
|
+
play: async ({ canvas }) => {
|
|
126
|
+
await expect(canvas.getByTestId("tool-card-ipc")).toBeInTheDocument();
|
|
127
|
+
await expect(canvas.getByTestId("tool-card-error")).toBeInTheDocument();
|
|
128
|
+
},
|
|
129
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useState, type JSX } from "react";
|
|
2
|
+
import type { ToolCardProps } from "./ToolCardProps.js";
|
|
3
|
+
import { extractBareName } from "./classifyTool.js";
|
|
4
|
+
import styles from "./toolCards.module.scss";
|
|
5
|
+
|
|
6
|
+
/** Shape of a file descriptor entry from ipc_list_fds. */
|
|
7
|
+
interface FdEntry {
|
|
8
|
+
fd?: number;
|
|
9
|
+
streamName?: string;
|
|
10
|
+
permission?: string;
|
|
11
|
+
deliveryMode?: string;
|
|
12
|
+
owned?: boolean;
|
|
13
|
+
targetSessionId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Extracts IPC-relevant fields from tool args. */
|
|
17
|
+
function getArgs(args: unknown): { fd?: number; pipe?: string; prompt?: string; name?: string; message?: string } {
|
|
18
|
+
if (args === null || args === undefined || typeof args !== "object") {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
const a = args as Record<string, unknown>;
|
|
22
|
+
return {
|
|
23
|
+
fd: typeof a.fd === "number" ? a.fd : undefined,
|
|
24
|
+
pipe: typeof a.pipe === "string" ? a.pipe : undefined,
|
|
25
|
+
prompt: typeof a.prompt === "string" ? a.prompt : undefined,
|
|
26
|
+
name: typeof a.name === "string" ? a.name : undefined,
|
|
27
|
+
message: typeof a.message === "string" ? a.message : undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Parses IPC result JSON. */
|
|
32
|
+
function parseResult(result: string | undefined): { sessionId?: string; fd?: number; fds?: FdEntry[]; success?: boolean; output?: string } {
|
|
33
|
+
if (!result) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const parsed: unknown = JSON.parse(result);
|
|
38
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
const obj = parsed as Record<string, unknown>;
|
|
42
|
+
return {
|
|
43
|
+
sessionId: typeof obj.sessionId === "string" ? obj.sessionId : undefined,
|
|
44
|
+
fd: typeof obj.fd === "number" ? obj.fd : undefined,
|
|
45
|
+
fds: Array.isArray(obj.fds) ? (obj.fds as unknown[]).filter((v): v is FdEntry => v !== null && typeof v === "object") : undefined,
|
|
46
|
+
success: typeof obj.success === "boolean" ? obj.success : undefined,
|
|
47
|
+
output: typeof obj.output === "string" ? obj.output : undefined,
|
|
48
|
+
};
|
|
49
|
+
} catch { /* fall through */ }
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Renders an IPC tool call with structured display. */
|
|
54
|
+
export function IpcCard({ tool, args, result, isError }: ToolCardProps): JSX.Element {
|
|
55
|
+
const [expanded, setExpanded] = useState(false);
|
|
56
|
+
const bareName = extractBareName(tool);
|
|
57
|
+
const argData = getArgs(args);
|
|
58
|
+
const inProgress = result === undefined;
|
|
59
|
+
const resultData = parseResult(result);
|
|
60
|
+
|
|
61
|
+
// Build a descriptor for the header
|
|
62
|
+
let headerInfo = "";
|
|
63
|
+
if (argData.pipe) {
|
|
64
|
+
headerInfo = `[${argData.pipe}]`;
|
|
65
|
+
} else if (argData.fd !== undefined) {
|
|
66
|
+
headerInfo = `fd:${argData.fd}`;
|
|
67
|
+
} else if (argData.name) {
|
|
68
|
+
headerInfo = `"${argData.name}"`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Prompt snippet for ipc_spawn
|
|
72
|
+
const promptSnippet = argData.prompt
|
|
73
|
+
? (argData.prompt.length > 60 ? `${argData.prompt.slice(0, 60)}...` : argData.prompt)
|
|
74
|
+
: undefined;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
className={`${styles.card} ${isError ? styles.cardRed : styles.cardOrange} ${inProgress ? styles.inProgress : ""}`}
|
|
79
|
+
data-testid="tool-card-ipc"
|
|
80
|
+
>
|
|
81
|
+
<div className={styles.header}>
|
|
82
|
+
<span className={styles.icon} aria-hidden="true">🔀</span>
|
|
83
|
+
<span className={styles.toolName} style={{ color: "var(--accent-yellow, #fbbf24)" }}>
|
|
84
|
+
{bareName}
|
|
85
|
+
</span>
|
|
86
|
+
{headerInfo && (
|
|
87
|
+
<span className={styles.fileName} data-testid="tool-card-ipc-info">
|
|
88
|
+
{headerInfo}
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
{promptSnippet && (
|
|
92
|
+
<span className={styles.fileName} title={argData.prompt} data-testid="tool-card-ipc-prompt">
|
|
93
|
+
{promptSnippet}
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
{resultData.fds && (
|
|
97
|
+
<>
|
|
98
|
+
<span className={styles.spacer} />
|
|
99
|
+
<span className={styles.badge} data-testid="tool-card-ipc-fd-count">
|
|
100
|
+
{resultData.fds.length} {resultData.fds.length === 1 ? "fd" : "fds"}
|
|
101
|
+
</span>
|
|
102
|
+
</>
|
|
103
|
+
)}
|
|
104
|
+
{resultData.success !== undefined && !resultData.fds && (
|
|
105
|
+
<>
|
|
106
|
+
<span className={styles.spacer} />
|
|
107
|
+
<span
|
|
108
|
+
className={styles.badge}
|
|
109
|
+
style={{ color: resultData.success ? "var(--accent-green, #4ade80)" : "var(--accent-red, #f87171)" }}
|
|
110
|
+
data-testid="tool-card-ipc-success"
|
|
111
|
+
>
|
|
112
|
+
{resultData.success ? "\u2713 ok" : "\u2717 failed"}
|
|
113
|
+
</span>
|
|
114
|
+
</>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
{/* In-progress: show args if nothing else to show */}
|
|
119
|
+
{inProgress && !promptSnippet && !headerInfo && args !== null && args !== undefined && (
|
|
120
|
+
<pre className={styles.pre} data-testid="tool-card-args">
|
|
121
|
+
{JSON.stringify(args, null, 2)}
|
|
122
|
+
</pre>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{/* Error */}
|
|
126
|
+
{isError && result && (
|
|
127
|
+
<pre className={styles.pre} data-testid="tool-card-error">
|
|
128
|
+
{result}
|
|
129
|
+
</pre>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
{/* Session ID from ipc_spawn */}
|
|
133
|
+
{!isError && resultData.sessionId && (
|
|
134
|
+
<div className={styles.pre} style={{ padding: "4px 8px", fontSize: "0.85em" }} data-testid="tool-card-ipc-session">
|
|
135
|
+
session: {resultData.sessionId}
|
|
136
|
+
{resultData.fd !== undefined && ` | fd: ${resultData.fd}`}
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{/* Output from sync ipc_spawn */}
|
|
141
|
+
{!isError && resultData.output && (
|
|
142
|
+
<pre className={styles.pre} data-testid="tool-card-ipc-output">
|
|
143
|
+
{resultData.output}
|
|
144
|
+
</pre>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{/* FD listing from ipc_list_fds */}
|
|
148
|
+
{!isError && resultData.fds && resultData.fds.length > 0 && (
|
|
149
|
+
<>
|
|
150
|
+
<pre className={styles.pre} data-testid="tool-card-ipc-fds">
|
|
151
|
+
{(expanded ? resultData.fds : resultData.fds.slice(0, 5)).map((f) => {
|
|
152
|
+
const parts = [`fd:${f.fd ?? "?"}`, f.permission ?? "", f.deliveryMode ?? ""];
|
|
153
|
+
if (f.streamName) {
|
|
154
|
+
parts.push(f.streamName);
|
|
155
|
+
}
|
|
156
|
+
if (f.owned) {
|
|
157
|
+
parts.push("(owned)");
|
|
158
|
+
}
|
|
159
|
+
return parts.filter(Boolean).join(" ");
|
|
160
|
+
}).join("\n")}
|
|
161
|
+
</pre>
|
|
162
|
+
{resultData.fds.length > 5 && (
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
className={styles.bodyToggle}
|
|
166
|
+
onClick={() => { setExpanded((v) => !v); }}
|
|
167
|
+
aria-expanded={expanded}
|
|
168
|
+
data-testid="tool-card-toggle"
|
|
169
|
+
>
|
|
170
|
+
<span className={`${styles.chevron} ${expanded ? styles.chevronExpanded : ""}`}>▸</span>
|
|
171
|
+
{expanded ? "collapse" : `${resultData.fds.length - 5} more fds`}
|
|
172
|
+
</button>
|
|
173
|
+
)}
|
|
174
|
+
</>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect } from "@storybook/test";
|
|
3
|
+
import { KnowledgeCard } from "./KnowledgeCard.js";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof KnowledgeCard> = {
|
|
6
|
+
component: KnowledgeCard,
|
|
7
|
+
title: "Tools/KnowledgeCard",
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof KnowledgeCard>;
|
|
11
|
+
|
|
12
|
+
export const SearchInProgress: Story = {
|
|
13
|
+
name: "knowledge_search - in progress",
|
|
14
|
+
args: {
|
|
15
|
+
tool: "mcp__grackle__knowledge_search",
|
|
16
|
+
args: { query: "authentication", limit: 5 },
|
|
17
|
+
},
|
|
18
|
+
play: async ({ canvas }) => {
|
|
19
|
+
await expect(canvas.getByTestId("tool-card-knowledge")).toBeInTheDocument();
|
|
20
|
+
await expect(canvas.getByTestId("tool-card-knowledge-query")).toHaveTextContent("authentication");
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const SearchWithResults: Story = {
|
|
25
|
+
name: "knowledge_search - with results",
|
|
26
|
+
args: {
|
|
27
|
+
tool: "mcp__grackle__knowledge_search",
|
|
28
|
+
args: { query: "authentication" },
|
|
29
|
+
result: JSON.stringify({
|
|
30
|
+
results: [
|
|
31
|
+
{ score: 0.92, node: { id: "n1", title: "Auth middleware design", category: "decision" } },
|
|
32
|
+
{ score: 0.85, node: { id: "n2", title: "Session token rotation", category: "insight" } },
|
|
33
|
+
{ score: 0.78, node: { id: "n3", title: "Pairing code auth flow", category: "reference" } },
|
|
34
|
+
],
|
|
35
|
+
neighbors: [],
|
|
36
|
+
neighborEdges: [],
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
play: async ({ canvas }) => {
|
|
40
|
+
await expect(canvas.getByTestId("tool-card-knowledge")).toBeInTheDocument();
|
|
41
|
+
await expect(canvas.getByTestId("tool-card-knowledge-count")).toHaveTextContent("3 results");
|
|
42
|
+
await expect(canvas.getByTestId("tool-card-knowledge-results")).toBeInTheDocument();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const SearchEmpty: Story = {
|
|
47
|
+
name: "knowledge_search - no results",
|
|
48
|
+
args: {
|
|
49
|
+
tool: "mcp__grackle__knowledge_search",
|
|
50
|
+
args: { query: "nonexistent topic" },
|
|
51
|
+
result: JSON.stringify({ results: [], neighbors: [], neighborEdges: [] }),
|
|
52
|
+
},
|
|
53
|
+
play: async ({ canvas }) => {
|
|
54
|
+
await expect(canvas.getByTestId("tool-card-knowledge")).toBeInTheDocument();
|
|
55
|
+
await expect(canvas.getByTestId("tool-card-knowledge-count")).toHaveTextContent("0 results");
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const GetNode: Story = {
|
|
60
|
+
name: "knowledge_get_node - with edges",
|
|
61
|
+
args: {
|
|
62
|
+
tool: "mcp__grackle__knowledge_get_node",
|
|
63
|
+
args: { id: "n1" },
|
|
64
|
+
result: JSON.stringify({
|
|
65
|
+
node: {
|
|
66
|
+
id: "n1",
|
|
67
|
+
title: "Auth middleware design",
|
|
68
|
+
category: "decision",
|
|
69
|
+
kind: "content",
|
|
70
|
+
content: "The auth middleware uses pairing-code flow for web UI and Bearer token for gRPC.",
|
|
71
|
+
},
|
|
72
|
+
edges: [
|
|
73
|
+
{ fromId: "n1", toId: "n2", type: "RELATES_TO" },
|
|
74
|
+
{ fromId: "n1", toId: "n3", type: "DEPENDS_ON" },
|
|
75
|
+
],
|
|
76
|
+
neighbors: [],
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
play: async ({ canvas }) => {
|
|
80
|
+
await expect(canvas.getByTestId("tool-card-knowledge")).toBeInTheDocument();
|
|
81
|
+
await expect(canvas.getByTestId("tool-card-knowledge-id")).toHaveTextContent("n1");
|
|
82
|
+
await expect(canvas.getByTestId("tool-card-knowledge-edges")).toHaveTextContent("2 edges");
|
|
83
|
+
await expect(canvas.getByTestId("tool-card-knowledge-node")).toBeInTheDocument();
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const CopilotFormat: Story = {
|
|
88
|
+
name: "knowledge_search - Copilot tool name",
|
|
89
|
+
args: {
|
|
90
|
+
tool: "grackle-knowledge_search",
|
|
91
|
+
args: { query: "authentication" },
|
|
92
|
+
result: JSON.stringify({ results: [], neighbors: [], neighborEdges: [] }),
|
|
93
|
+
},
|
|
94
|
+
play: async ({ canvas }) => {
|
|
95
|
+
await expect(canvas.getByTestId("tool-card-knowledge")).toBeInTheDocument();
|
|
96
|
+
await expect(canvas.getByText("knowledge_search")).toBeInTheDocument();
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const ErrorState: Story = {
|
|
101
|
+
name: "knowledge_search - error",
|
|
102
|
+
args: {
|
|
103
|
+
tool: "mcp__grackle__knowledge_search",
|
|
104
|
+
args: { query: "test" },
|
|
105
|
+
result: "UNAVAILABLE: knowledge service not running",
|
|
106
|
+
isError: true,
|
|
107
|
+
},
|
|
108
|
+
play: async ({ canvas }) => {
|
|
109
|
+
await expect(canvas.getByTestId("tool-card-knowledge")).toBeInTheDocument();
|
|
110
|
+
await expect(canvas.getByTestId("tool-card-error")).toBeInTheDocument();
|
|
111
|
+
},
|
|
112
|
+
};
|