@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,106 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, userEvent } from "@storybook/test";
|
|
3
|
+
import { WorkpadCard } from "./WorkpadCard.js";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof WorkpadCard> = {
|
|
6
|
+
component: WorkpadCard,
|
|
7
|
+
title: "Tools/WorkpadCard",
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof WorkpadCard>;
|
|
11
|
+
|
|
12
|
+
export const WriteInProgress: Story = {
|
|
13
|
+
name: "workpad_write - in progress",
|
|
14
|
+
args: {
|
|
15
|
+
tool: "mcp__grackle__workpad_write",
|
|
16
|
+
args: {
|
|
17
|
+
status: "in_progress",
|
|
18
|
+
summary: "Working on authentication refactor...",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
play: async ({ canvas }) => {
|
|
22
|
+
await expect(canvas.getByTestId("tool-card-workpad")).toBeInTheDocument();
|
|
23
|
+
await expect(canvas.getByTestId("tool-card-workpad-status")).toHaveTextContent("in_progress");
|
|
24
|
+
await expect(canvas.getByTestId("tool-card-workpad-summary")).toHaveTextContent("Working on authentication");
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const WriteCompleted: Story = {
|
|
29
|
+
name: "workpad_write - completed",
|
|
30
|
+
args: {
|
|
31
|
+
tool: "mcp__grackle__workpad_write",
|
|
32
|
+
args: {
|
|
33
|
+
status: "completed",
|
|
34
|
+
summary: "Tested Grackle MCP tools",
|
|
35
|
+
},
|
|
36
|
+
result: JSON.stringify({
|
|
37
|
+
taskId: "74f5b716",
|
|
38
|
+
workpad: {
|
|
39
|
+
status: "completed",
|
|
40
|
+
summary: "Tested Grackle MCP tools: posted a finding, wrote to workpad, and searched knowledge.",
|
|
41
|
+
extra: {
|
|
42
|
+
tools_tested: ["finding_post", "workpad_write", "knowledge_search"],
|
|
43
|
+
finding_topic: "qdrant catalog",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
play: async ({ canvas }) => {
|
|
49
|
+
await expect(canvas.getByTestId("tool-card-workpad")).toBeInTheDocument();
|
|
50
|
+
await expect(canvas.getByTestId("tool-card-workpad-status")).toHaveTextContent("completed");
|
|
51
|
+
await expect(canvas.getByTestId("tool-card-workpad-summary")).toBeInTheDocument();
|
|
52
|
+
// Extra data toggle should be present
|
|
53
|
+
const toggle = canvas.getByTestId("tool-card-toggle");
|
|
54
|
+
await expect(toggle).toBeInTheDocument();
|
|
55
|
+
// Expand to see extra data
|
|
56
|
+
await userEvent.click(toggle);
|
|
57
|
+
await expect(canvas.getByTestId("tool-card-workpad-extra")).toBeInTheDocument();
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const ReadResult: Story = {
|
|
62
|
+
name: "workpad_read - with data",
|
|
63
|
+
args: {
|
|
64
|
+
tool: "mcp__grackle__workpad_read",
|
|
65
|
+
args: { taskId: "74f5b716" },
|
|
66
|
+
result: JSON.stringify({
|
|
67
|
+
status: "completed",
|
|
68
|
+
summary: "All tests passing. PR ready for review.",
|
|
69
|
+
extra: { pr_number: 142, branch: "feat/auth" },
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
play: async ({ canvas }) => {
|
|
73
|
+
await expect(canvas.getByTestId("tool-card-workpad")).toBeInTheDocument();
|
|
74
|
+
await expect(canvas.getByTestId("tool-card-workpad-status")).toHaveTextContent("completed");
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const CopilotFormat: Story = {
|
|
79
|
+
name: "workpad_write - Copilot tool name",
|
|
80
|
+
args: {
|
|
81
|
+
tool: "grackle-workpad_write",
|
|
82
|
+
args: { status: "in progress", summary: "Posted a finding about Rush worktrees." },
|
|
83
|
+
result: JSON.stringify({
|
|
84
|
+
taskId: "e4366a55",
|
|
85
|
+
workpad: { status: "in progress", summary: "Posted a finding about Rush worktrees." },
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
play: async ({ canvas }) => {
|
|
89
|
+
await expect(canvas.getByTestId("tool-card-workpad")).toBeInTheDocument();
|
|
90
|
+
await expect(canvas.getByText("workpad_write")).toBeInTheDocument();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const ErrorState: Story = {
|
|
95
|
+
name: "workpad_write - error",
|
|
96
|
+
args: {
|
|
97
|
+
tool: "mcp__grackle__workpad_write",
|
|
98
|
+
args: { status: "done" },
|
|
99
|
+
result: "gRPC error [FailedPrecondition]: no task context",
|
|
100
|
+
isError: true,
|
|
101
|
+
},
|
|
102
|
+
play: async ({ canvas }) => {
|
|
103
|
+
await expect(canvas.getByTestId("tool-card-workpad")).toBeInTheDocument();
|
|
104
|
+
await expect(canvas.getByTestId("tool-card-error")).toBeInTheDocument();
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
/** Extracts workpad-relevant fields from tool args. */
|
|
7
|
+
function getArgs(args: unknown): { status?: string; summary?: string } {
|
|
8
|
+
if (args === null || args === undefined || typeof args !== "object") {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
const a = args as Record<string, unknown>;
|
|
12
|
+
return {
|
|
13
|
+
status: typeof a.status === "string" ? a.status : undefined,
|
|
14
|
+
summary: typeof a.summary === "string" ? a.summary : undefined,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Parses workpad result. Could be { taskId, workpad: {...} } or the workpad object itself. */
|
|
19
|
+
function parseResult(result: string | undefined): { status?: string; summary?: string; extra?: Record<string, unknown> } {
|
|
20
|
+
if (!result) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const parsed: unknown = JSON.parse(result);
|
|
25
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
const obj = parsed as Record<string, unknown>;
|
|
29
|
+
// workpad_write returns { taskId, workpad: { status, summary, extra } }
|
|
30
|
+
const workpad = (typeof obj.workpad === "object" && obj.workpad !== null)
|
|
31
|
+
? obj.workpad as Record<string, unknown>
|
|
32
|
+
: obj;
|
|
33
|
+
return {
|
|
34
|
+
status: typeof workpad.status === "string" ? workpad.status : undefined,
|
|
35
|
+
summary: typeof workpad.summary === "string" ? workpad.summary : undefined,
|
|
36
|
+
extra: typeof workpad.extra === "object" && workpad.extra !== null
|
|
37
|
+
? workpad.extra as Record<string, unknown>
|
|
38
|
+
: undefined,
|
|
39
|
+
};
|
|
40
|
+
} catch { /* fall through */ }
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Renders a workpad tool call (workpad_write, workpad_read) with structured display. */
|
|
45
|
+
export function WorkpadCard({ tool, args, result, isError }: ToolCardProps): JSX.Element {
|
|
46
|
+
const [expanded, setExpanded] = useState(false);
|
|
47
|
+
const bareName = extractBareName(tool);
|
|
48
|
+
const argData = getArgs(args);
|
|
49
|
+
const inProgress = result === undefined;
|
|
50
|
+
const resultData = parseResult(result);
|
|
51
|
+
|
|
52
|
+
const displayStatus = resultData.status ?? argData.status;
|
|
53
|
+
const displaySummary = resultData.summary ?? argData.summary;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
className={`${styles.card} ${isError ? styles.cardRed : styles.cardGreen} ${inProgress ? styles.inProgress : ""}`}
|
|
58
|
+
data-testid="tool-card-workpad"
|
|
59
|
+
>
|
|
60
|
+
<div className={styles.header}>
|
|
61
|
+
<span className={styles.icon} aria-hidden="true">📓</span>
|
|
62
|
+
<span className={styles.toolName} style={{ color: "var(--accent-green, #4ade80)" }}>
|
|
63
|
+
{bareName}
|
|
64
|
+
</span>
|
|
65
|
+
{displayStatus && (
|
|
66
|
+
<>
|
|
67
|
+
<span className={styles.spacer} />
|
|
68
|
+
<span className={styles.badge} data-testid="tool-card-workpad-status">
|
|
69
|
+
{displayStatus}
|
|
70
|
+
</span>
|
|
71
|
+
</>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{/* Summary */}
|
|
76
|
+
{displaySummary && (
|
|
77
|
+
<pre className={styles.pre} style={{ whiteSpace: "pre-wrap" }} data-testid="tool-card-workpad-summary">
|
|
78
|
+
{displaySummary}
|
|
79
|
+
</pre>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
{/* In-progress: show args if no summary */}
|
|
83
|
+
{inProgress && !displaySummary && args !== null && args !== undefined && (
|
|
84
|
+
<pre className={styles.pre} data-testid="tool-card-args">
|
|
85
|
+
{JSON.stringify(args, null, 2)}
|
|
86
|
+
</pre>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{/* Error */}
|
|
90
|
+
{isError && result && (
|
|
91
|
+
<pre className={styles.pre} data-testid="tool-card-error">
|
|
92
|
+
{result}
|
|
93
|
+
</pre>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{/* Raw result fallback when parsing yields nothing structured */}
|
|
97
|
+
{!isError && !inProgress && result && !displaySummary && !displayStatus && !resultData.extra && (
|
|
98
|
+
<pre className={styles.pre} data-testid="tool-card-result">
|
|
99
|
+
{result}
|
|
100
|
+
</pre>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{/* Extra data (expandable) */}
|
|
104
|
+
{!isError && resultData.extra && (
|
|
105
|
+
<>
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
className={styles.bodyToggle}
|
|
109
|
+
onClick={() => { setExpanded((v) => !v); }}
|
|
110
|
+
aria-expanded={expanded}
|
|
111
|
+
data-testid="tool-card-toggle"
|
|
112
|
+
>
|
|
113
|
+
<span className={`${styles.chevron} ${expanded ? styles.chevronExpanded : ""}`}>▸</span>
|
|
114
|
+
{expanded ? "collapse" : "extra data"}
|
|
115
|
+
</button>
|
|
116
|
+
{expanded && (
|
|
117
|
+
<pre className={styles.pre} data-testid="tool-card-workpad-extra">
|
|
118
|
+
{JSON.stringify(resultData.extra, null, 2)}
|
|
119
|
+
</pre>
|
|
120
|
+
)}
|
|
121
|
+
</>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { classifyTool } from "./classifyTool.js";
|
|
3
|
+
|
|
4
|
+
describe("classifyTool", () => {
|
|
5
|
+
it("classifies Claude Code tools", () => {
|
|
6
|
+
expect(classifyTool("Bash")).toBe("shell");
|
|
7
|
+
expect(classifyTool("Read")).toBe("file-read");
|
|
8
|
+
expect(classifyTool("Edit")).toBe("file-edit");
|
|
9
|
+
expect(classifyTool("Write")).toBe("file-write");
|
|
10
|
+
expect(classifyTool("Grep")).toBe("search");
|
|
11
|
+
expect(classifyTool("Glob")).toBe("search");
|
|
12
|
+
expect(classifyTool("TodoWrite")).toBe("todo");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("classifies Copilot tools", () => {
|
|
16
|
+
expect(classifyTool("powershell")).toBe("shell");
|
|
17
|
+
expect(classifyTool("view")).toBe("file-read");
|
|
18
|
+
expect(classifyTool("edit")).toBe("file-edit");
|
|
19
|
+
expect(classifyTool("report_intent")).toBe("metadata");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("classifies Codex tools", () => {
|
|
23
|
+
expect(classifyTool("command_execution")).toBe("shell");
|
|
24
|
+
expect(classifyTool("file_change")).toBe("file-edit");
|
|
25
|
+
expect(classifyTool("update_plan")).toBe("todo");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("is case-insensitive", () => {
|
|
29
|
+
expect(classifyTool("BASH")).toBe("shell");
|
|
30
|
+
expect(classifyTool("PowerShell")).toBe("shell");
|
|
31
|
+
expect(classifyTool("GREP")).toBe("search");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("classifies Grackle MCP tools", () => {
|
|
35
|
+
expect(classifyTool("mcp__grackle__workpad_write")).toBe("workpad");
|
|
36
|
+
expect(classifyTool("mcp__grackle__finding_post")).toBe("finding");
|
|
37
|
+
expect(classifyTool("mcp__grackle__task_create")).toBe("task");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns generic for unknown tools", () => {
|
|
41
|
+
expect(classifyTool("unknown_tool")).toBe("generic");
|
|
42
|
+
expect(classifyTool("mcp__unknown_server__foo")).toBe("generic");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classifies a tool name into a rendering category.
|
|
3
|
+
*
|
|
4
|
+
* Maps tool names from all supported runtimes (Claude Code, Copilot, Codex)
|
|
5
|
+
* to a UI category that determines which card component renders the event.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Tool rendering categories corresponding to specialized card components. */
|
|
9
|
+
export type ToolCategory =
|
|
10
|
+
| "file-read"
|
|
11
|
+
| "file-edit"
|
|
12
|
+
| "file-write"
|
|
13
|
+
| "shell"
|
|
14
|
+
| "search"
|
|
15
|
+
| "todo"
|
|
16
|
+
| "metadata"
|
|
17
|
+
| "finding"
|
|
18
|
+
| "task"
|
|
19
|
+
| "workpad"
|
|
20
|
+
| "knowledge"
|
|
21
|
+
| "ipc"
|
|
22
|
+
| "tool-search"
|
|
23
|
+
| "agent"
|
|
24
|
+
| "generic";
|
|
25
|
+
|
|
26
|
+
/** Known MCP server names for bare-name extraction from Copilot dash format. */
|
|
27
|
+
const KNOWN_MCP_SERVERS: Set<string> = new Set(["grackle"]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extracts the bare tool name from runtime-specific naming conventions.
|
|
31
|
+
*
|
|
32
|
+
* - Claude Code / Codex: `mcp__grackle__finding_post` -> `finding_post`
|
|
33
|
+
* - Copilot: `grackle-finding_post` -> `finding_post`
|
|
34
|
+
* - Built-in: `Read` -> `read` (unchanged, lowered later)
|
|
35
|
+
*/
|
|
36
|
+
export function extractBareName(toolName: string): string {
|
|
37
|
+
// MCP double-underscore format: mcp__<server>__<tool> (only for known servers)
|
|
38
|
+
if (toolName.startsWith("mcp__")) {
|
|
39
|
+
const serverSep = toolName.indexOf("__", 5);
|
|
40
|
+
if (serverSep > 5) {
|
|
41
|
+
const server = toolName.slice(5, serverSep);
|
|
42
|
+
if (KNOWN_MCP_SERVERS.has(server)) {
|
|
43
|
+
return toolName.slice(serverSep + 2);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Copilot dash format: <server>-<tool> (only for known servers)
|
|
48
|
+
const dashIndex = toolName.indexOf("-");
|
|
49
|
+
if (dashIndex > 0) {
|
|
50
|
+
const server = toolName.slice(0, dashIndex);
|
|
51
|
+
if (KNOWN_MCP_SERVERS.has(server)) {
|
|
52
|
+
return toolName.slice(dashIndex + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return toolName;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const TOOL_MAP: Record<string, ToolCategory> = {
|
|
59
|
+
// File read — Claude Code: Read, Copilot: view
|
|
60
|
+
read: "file-read",
|
|
61
|
+
view: "file-read",
|
|
62
|
+
|
|
63
|
+
// File edit — Claude Code: Edit, Copilot: edit, Codex: file_change
|
|
64
|
+
edit: "file-edit",
|
|
65
|
+
file_change: "file-edit",
|
|
66
|
+
|
|
67
|
+
// File write — Claude Code: Write
|
|
68
|
+
write: "file-write",
|
|
69
|
+
|
|
70
|
+
// Shell — Claude Code: Bash, Copilot: powershell, Codex: command_execution
|
|
71
|
+
bash: "shell",
|
|
72
|
+
powershell: "shell",
|
|
73
|
+
command_execution: "shell",
|
|
74
|
+
|
|
75
|
+
// Search — Claude Code: Grep, Glob
|
|
76
|
+
grep: "search",
|
|
77
|
+
glob: "search",
|
|
78
|
+
|
|
79
|
+
// Todo — Claude Code: TodoWrite, Codex: update_plan, Goose: todo_write
|
|
80
|
+
todowrite: "todo",
|
|
81
|
+
update_plan: "todo",
|
|
82
|
+
todo_write: "todo",
|
|
83
|
+
|
|
84
|
+
// Metadata — Copilot: report_intent
|
|
85
|
+
report_intent: "metadata",
|
|
86
|
+
|
|
87
|
+
// Finding — Grackle MCP
|
|
88
|
+
finding_post: "finding",
|
|
89
|
+
finding_list: "finding",
|
|
90
|
+
|
|
91
|
+
// Task — Grackle MCP
|
|
92
|
+
task_list: "task",
|
|
93
|
+
task_create: "task",
|
|
94
|
+
task_show: "task",
|
|
95
|
+
task_update: "task",
|
|
96
|
+
task_start: "task",
|
|
97
|
+
task_complete: "task",
|
|
98
|
+
task_resume: "task",
|
|
99
|
+
task_delete: "task",
|
|
100
|
+
|
|
101
|
+
// Workpad — Grackle MCP
|
|
102
|
+
workpad_write: "workpad",
|
|
103
|
+
workpad_read: "workpad",
|
|
104
|
+
|
|
105
|
+
// Knowledge — Grackle MCP
|
|
106
|
+
knowledge_search: "knowledge",
|
|
107
|
+
knowledge_get_node: "knowledge",
|
|
108
|
+
knowledge_create_node: "knowledge",
|
|
109
|
+
|
|
110
|
+
// IPC — Grackle MCP
|
|
111
|
+
ipc_spawn: "ipc",
|
|
112
|
+
ipc_write: "ipc",
|
|
113
|
+
ipc_close: "ipc",
|
|
114
|
+
ipc_list_fds: "ipc",
|
|
115
|
+
ipc_terminate: "ipc",
|
|
116
|
+
ipc_create_stream: "ipc",
|
|
117
|
+
ipc_attach: "ipc",
|
|
118
|
+
ipc_share_stream: "ipc",
|
|
119
|
+
ipc_list_streams: "ipc",
|
|
120
|
+
|
|
121
|
+
// ToolSearch — Claude Code built-in
|
|
122
|
+
toolsearch: "tool-search",
|
|
123
|
+
|
|
124
|
+
// Agent — Claude Code: Agent (formerly Task), Copilot: task, read_agent
|
|
125
|
+
agent: "agent",
|
|
126
|
+
task: "agent",
|
|
127
|
+
read_agent: "agent",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** Classifies a tool name to determine which card component should render it. */
|
|
131
|
+
export function classifyTool(toolName: string): ToolCategory {
|
|
132
|
+
const bare = extractBareName(toolName);
|
|
133
|
+
return TOOL_MAP[bare.toLowerCase()] ?? "generic";
|
|
134
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight diff parsing utilities for the FileEditCard.
|
|
3
|
+
*
|
|
4
|
+
* Handles two formats:
|
|
5
|
+
* 1. Unified diff strings (from Copilot's `detailedContent` field)
|
|
6
|
+
* 2. Old/new string pairs (from Claude Code's `Edit` tool args)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** A single line in a parsed diff. */
|
|
10
|
+
export interface DiffLine {
|
|
11
|
+
/** Line classification. */
|
|
12
|
+
type: "add" | "remove" | "context" | "header";
|
|
13
|
+
/** Line content (without the leading +/- prefix). */
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Addition/removal counts for a diff. */
|
|
18
|
+
export interface DiffStats {
|
|
19
|
+
added: number;
|
|
20
|
+
removed: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parses a unified diff string into typed lines.
|
|
25
|
+
*
|
|
26
|
+
* Recognizes `@@` hunk headers, `+`/`-` prefixed lines, and context lines.
|
|
27
|
+
* Skips `---`/`+++` file header lines and `diff --git` preamble.
|
|
28
|
+
*/
|
|
29
|
+
export function parseUnifiedDiff(diff: string): DiffLine[] {
|
|
30
|
+
const lines = diff.split("\n");
|
|
31
|
+
const result: DiffLine[] = [];
|
|
32
|
+
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
if (line.startsWith("diff --git") || line.startsWith("index ")) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// Skip file header lines (--- a/file, +++ b/file) but not hunk content
|
|
38
|
+
// lines that happen to start with "---" or "+++" (which appear as
|
|
39
|
+
// "----..." or "++++..." in the diff and are handled by +/- rules below).
|
|
40
|
+
if ((line.startsWith("--- ") || line.startsWith("+++ ")) && !line.startsWith("---- ") && !line.startsWith("++++ ")) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (line.startsWith("@@")) {
|
|
44
|
+
result.push({ type: "header", content: line });
|
|
45
|
+
} else if (line.startsWith("+")) {
|
|
46
|
+
result.push({ type: "add", content: line.slice(1) });
|
|
47
|
+
} else if (line.startsWith("-")) {
|
|
48
|
+
result.push({ type: "remove", content: line.slice(1) });
|
|
49
|
+
} else if (line.startsWith(" ")) {
|
|
50
|
+
result.push({ type: "context", content: line.slice(1) });
|
|
51
|
+
} else if (line === "") {
|
|
52
|
+
// Empty lines in unified diffs are context lines
|
|
53
|
+
result.push({ type: "context", content: "" });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Constructs a minimal diff from old and new strings.
|
|
62
|
+
*
|
|
63
|
+
* This is a simple line-by-line comparison — not a true LCS diff algorithm.
|
|
64
|
+
* Shows all old lines as removals followed by all new lines as additions.
|
|
65
|
+
* Good enough for the small, targeted edits agents typically make.
|
|
66
|
+
*/
|
|
67
|
+
export function diffFromOldNew(oldStr: string, newStr: string): DiffLine[] {
|
|
68
|
+
const oldLines = oldStr.split("\n");
|
|
69
|
+
const newLines = newStr.split("\n");
|
|
70
|
+
const result: DiffLine[] = [];
|
|
71
|
+
|
|
72
|
+
for (const line of oldLines) {
|
|
73
|
+
result.push({ type: "remove", content: line });
|
|
74
|
+
}
|
|
75
|
+
for (const line of newLines) {
|
|
76
|
+
result.push({ type: "add", content: line });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Counts additions and removals in a parsed diff. */
|
|
83
|
+
export function diffStats(lines: DiffLine[]): DiffStats {
|
|
84
|
+
let added = 0;
|
|
85
|
+
let removed = 0;
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
if (line.type === "add") {
|
|
88
|
+
added++;
|
|
89
|
+
}
|
|
90
|
+
if (line.type === "remove") {
|
|
91
|
+
removed++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { added, removed };
|
|
95
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses shell command output from different runtime formats.
|
|
3
|
+
*
|
|
4
|
+
* Codex emits `[exit N] output` format. Other runtimes emit plain output.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Parsed shell command result. */
|
|
8
|
+
export interface ShellResult {
|
|
9
|
+
/** Exit code, or undefined if not available. */
|
|
10
|
+
exitCode: number | undefined;
|
|
11
|
+
/** Command output (stdout/stderr). */
|
|
12
|
+
output: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Pattern matching Codex's `[exit N] rest` format. */
|
|
16
|
+
const EXIT_CODE_PATTERN: RegExp = /^\[exit (\d+)\]\s*/;
|
|
17
|
+
|
|
18
|
+
/** Parses shell output, extracting exit code if present. */
|
|
19
|
+
export function parseShellOutput(content: string): ShellResult {
|
|
20
|
+
const match: RegExpExecArray | null = EXIT_CODE_PATTERN.exec(content);
|
|
21
|
+
if (match) {
|
|
22
|
+
return {
|
|
23
|
+
exitCode: Number(match[1]),
|
|
24
|
+
output: content.slice(match[0].length),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return { exitCode: undefined, output: content };
|
|
28
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { formatToolName } from "./GenericToolCard.js";
|
|
3
|
+
import { simplifyCommand } from "./ShellCard.js";
|
|
4
|
+
|
|
5
|
+
describe("formatToolName", () => {
|
|
6
|
+
it("parses standard MCP tool names", () => {
|
|
7
|
+
expect(formatToolName("mcp__server__tool")).toEqual({ display: "server / tool", isMcp: true });
|
|
8
|
+
expect(formatToolName("mcp__my-server__my-tool")).toEqual({ display: "my-server / my-tool", isMcp: true });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("preserves underscores within server and tool segments", () => {
|
|
12
|
+
expect(formatToolName("mcp__qdrant-search__semantic_search")).toEqual({ display: "qdrant-search / semantic_search", isMcp: true });
|
|
13
|
+
expect(formatToolName("mcp__grackle__env_add")).toEqual({ display: "grackle / env_add", isMcp: true });
|
|
14
|
+
expect(formatToolName("mcp__playwright__browser_navigate")).toEqual({ display: "playwright / browser_navigate", isMcp: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("splits only at the first double-underscore boundary", () => {
|
|
18
|
+
expect(formatToolName("mcp__server__some__deep__tool")).toEqual({ display: "server / some__deep__tool", isMcp: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns non-MCP tools unchanged", () => {
|
|
22
|
+
expect(formatToolName("Bash")).toEqual({ display: "Bash", isMcp: false });
|
|
23
|
+
expect(formatToolName("not_mcp_tool")).toEqual({ display: "not_mcp_tool", isMcp: false });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("rejects malformed MCP names", () => {
|
|
27
|
+
expect(formatToolName("mcp____tool")).toEqual({ display: "mcp____tool", isMcp: false });
|
|
28
|
+
expect(formatToolName("mcp__server__")).toEqual({ display: "mcp__server__", isMcp: false });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("simplifyCommand", () => {
|
|
33
|
+
it("strips single-quoted pwsh wrapper", () => {
|
|
34
|
+
expect(simplifyCommand("pwsh -Command 'Get-Process'")).toBe("Get-Process");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("strips double-quoted pwsh wrapper", () => {
|
|
38
|
+
expect(simplifyCommand("pwsh -Command \"Get-Process\"")).toBe("Get-Process");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("handles pwsh.exe with path prefix", () => {
|
|
42
|
+
expect(simplifyCommand("pwsh.exe -Command 'ls -la'")).toBe("ls -la");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("handles trailing whitespace", () => {
|
|
46
|
+
expect(simplifyCommand("pwsh -Command 'echo hello' ")).toBe("echo hello");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns non-pwsh commands unchanged", () => {
|
|
50
|
+
expect(simplifyCommand("git status")).toBe("git status");
|
|
51
|
+
expect(simplifyCommand("ls -la")).toBe("ls -la");
|
|
52
|
+
});
|
|
53
|
+
});
|