@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,864 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types, type guards, and utility functions for the Grackle WebSocket hooks.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ─── Domain hook lifecycle ────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/** Lifecycle contract that every domain hook must implement. */
|
|
10
|
+
export interface DomainHook {
|
|
11
|
+
/** Reload data when the ConnectRPC stream connects or reconnects. */
|
|
12
|
+
onConnect(): Promise<void>;
|
|
13
|
+
/** Reset transient state when the stream disconnects. */
|
|
14
|
+
onDisconnect(): void;
|
|
15
|
+
/** Handle a domain event. Return `true` if the event was consumed. */
|
|
16
|
+
handleEvent(event: GrackleEvent): boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Connection state of the event stream. */
|
|
20
|
+
export type ConnectionStatus = "connected" | "connecting" | "disconnected";
|
|
21
|
+
|
|
22
|
+
// ─── Data interfaces ──────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A provisioned environment with its current status.
|
|
26
|
+
* After normalization, `adapterConfig` is always a JSON string.
|
|
27
|
+
*/
|
|
28
|
+
export interface Environment {
|
|
29
|
+
id: string;
|
|
30
|
+
displayName: string;
|
|
31
|
+
adapterType: string;
|
|
32
|
+
adapterConfig: string;
|
|
33
|
+
status: string;
|
|
34
|
+
bootstrapped: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/** An agent session running inside an environment. */
|
|
39
|
+
export interface Session {
|
|
40
|
+
id: string;
|
|
41
|
+
environmentId: string;
|
|
42
|
+
runtime: string;
|
|
43
|
+
status: string;
|
|
44
|
+
prompt: string;
|
|
45
|
+
startedAt: string;
|
|
46
|
+
endedAt?: string;
|
|
47
|
+
error?: string;
|
|
48
|
+
endReason?: string;
|
|
49
|
+
personaId?: string;
|
|
50
|
+
inputTokens?: number;
|
|
51
|
+
outputTokens?: number;
|
|
52
|
+
costMillicents?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Aggregated usage statistics for a scope (session, task, workspace, environment). */
|
|
56
|
+
export interface UsageStats {
|
|
57
|
+
inputTokens: number;
|
|
58
|
+
outputTokens: number;
|
|
59
|
+
costMillicents: number;
|
|
60
|
+
sessionCount: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A single event emitted by an agent session. */
|
|
64
|
+
export interface SessionEvent {
|
|
65
|
+
sessionId: string;
|
|
66
|
+
eventType: string;
|
|
67
|
+
timestamp: string;
|
|
68
|
+
content: string;
|
|
69
|
+
/** Raw JSON payload from the agent runtime (e.g. tool block with is_error flag). Optional. */
|
|
70
|
+
raw?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A workspace that groups tasks and findings. */
|
|
74
|
+
export interface Workspace {
|
|
75
|
+
id: string;
|
|
76
|
+
name: string;
|
|
77
|
+
description: string;
|
|
78
|
+
repoUrl: string;
|
|
79
|
+
/** IDs of all environments linked to this workspace's pool. */
|
|
80
|
+
linkedEnvironmentIds: string[];
|
|
81
|
+
status: string;
|
|
82
|
+
workingDirectory: string;
|
|
83
|
+
useWorktrees: boolean;
|
|
84
|
+
defaultPersonaId: string;
|
|
85
|
+
/** Total token cap across all tasks; 0 = unlimited. */
|
|
86
|
+
tokenBudget: number;
|
|
87
|
+
/** Cost cap in millicents across all tasks; 0 = unlimited. */
|
|
88
|
+
costBudgetMillicents: number;
|
|
89
|
+
createdAt: string;
|
|
90
|
+
updatedAt: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A task within a workspace (or workspace-less for the root task). */
|
|
94
|
+
export interface TaskData {
|
|
95
|
+
id: string;
|
|
96
|
+
/** Workspace this task belongs to, or empty/undefined for workspace-less tasks (e.g. root task). */
|
|
97
|
+
workspaceId: string | undefined;
|
|
98
|
+
title: string;
|
|
99
|
+
description: string;
|
|
100
|
+
status: string;
|
|
101
|
+
branch: string;
|
|
102
|
+
latestSessionId: string;
|
|
103
|
+
dependsOn: string[];
|
|
104
|
+
/** @deprecated Removed — notes are now passed via StartTask. */
|
|
105
|
+
reviewNotes?: string;
|
|
106
|
+
sortOrder: number;
|
|
107
|
+
createdAt: string;
|
|
108
|
+
/** @deprecated Removed. */
|
|
109
|
+
assignedAt?: string;
|
|
110
|
+
startedAt?: string;
|
|
111
|
+
completedAt?: string;
|
|
112
|
+
parentTaskId: string;
|
|
113
|
+
depth: number;
|
|
114
|
+
childTaskIds: string[];
|
|
115
|
+
canDecompose: boolean;
|
|
116
|
+
defaultPersonaId: string;
|
|
117
|
+
workpad: string;
|
|
118
|
+
/** Total token cap (input + output); 0 = unlimited. */
|
|
119
|
+
tokenBudget: number;
|
|
120
|
+
/** Cost cap in millicents ($0.00001 units); 0 = unlimited. */
|
|
121
|
+
costBudgetMillicents: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A finding posted by an agent or user. */
|
|
125
|
+
export interface FindingData {
|
|
126
|
+
id: string;
|
|
127
|
+
workspaceId: string;
|
|
128
|
+
taskId: string;
|
|
129
|
+
sessionId: string;
|
|
130
|
+
category: string;
|
|
131
|
+
title: string;
|
|
132
|
+
content: string;
|
|
133
|
+
tags: string[];
|
|
134
|
+
createdAt: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Metadata about a stored token. */
|
|
138
|
+
export interface TokenInfo {
|
|
139
|
+
name: string;
|
|
140
|
+
tokenType: string;
|
|
141
|
+
envVar: string;
|
|
142
|
+
filePath: string;
|
|
143
|
+
expiresAt: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Configuration for which credential providers are enabled. */
|
|
147
|
+
export interface CredentialProviderConfig {
|
|
148
|
+
claude: "off" | "subscription" | "api_key";
|
|
149
|
+
github: "off" | "on";
|
|
150
|
+
copilot: "off" | "on";
|
|
151
|
+
codex: "off" | "on";
|
|
152
|
+
goose: "off" | "on";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** A GitHub Codespace returned from `gh codespace list`. */
|
|
156
|
+
export interface Codespace {
|
|
157
|
+
name: string;
|
|
158
|
+
repository: string;
|
|
159
|
+
state: string;
|
|
160
|
+
gitStatus: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** An agent or script persona configuration. */
|
|
164
|
+
export interface PersonaData {
|
|
165
|
+
id: string;
|
|
166
|
+
name: string;
|
|
167
|
+
description: string;
|
|
168
|
+
systemPrompt: string;
|
|
169
|
+
toolConfig: string;
|
|
170
|
+
runtime: string;
|
|
171
|
+
model: string;
|
|
172
|
+
maxTurns: number;
|
|
173
|
+
mcpServers: string;
|
|
174
|
+
createdAt: string;
|
|
175
|
+
updatedAt: string;
|
|
176
|
+
type: string;
|
|
177
|
+
script: string;
|
|
178
|
+
allowedMcpTools: string[];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Provisioning progress state for a single environment. */
|
|
182
|
+
export interface ProvisionStatus {
|
|
183
|
+
stage: string;
|
|
184
|
+
message: string;
|
|
185
|
+
progress: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** A domain event emitted by the server event bus and forwarded over WebSocket. */
|
|
189
|
+
export interface GrackleEvent {
|
|
190
|
+
/** ULID — chronologically sortable unique identifier. */
|
|
191
|
+
id: string;
|
|
192
|
+
/** Dot-notation event type (e.g. "task.created"). */
|
|
193
|
+
type: string;
|
|
194
|
+
/** ISO 8601 timestamp. */
|
|
195
|
+
timestamp: string;
|
|
196
|
+
/** Domain-specific payload. */
|
|
197
|
+
payload: Record<string, unknown>;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** A parsed WebSocket message with a string type and optional payload. */
|
|
201
|
+
export interface WsMessage {
|
|
202
|
+
type: string;
|
|
203
|
+
payload?: Record<string, unknown>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Function signature for sending a WebSocket message. */
|
|
207
|
+
export type SendFunction = (msg: WsMessage) => void;
|
|
208
|
+
|
|
209
|
+
// ─── Domain hook result types ─────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
/** Values returned by the environments domain hook. */
|
|
212
|
+
export interface UseEnvironmentsResult {
|
|
213
|
+
/** All known environments. */
|
|
214
|
+
environments: Environment[];
|
|
215
|
+
/** Whether the environment list is currently being loaded. */
|
|
216
|
+
environmentsLoading: boolean;
|
|
217
|
+
/** Per-environment provisioning progress. */
|
|
218
|
+
provisionStatus: Record<string, ProvisionStatus>;
|
|
219
|
+
/** Request the current environment list from the server. */
|
|
220
|
+
loadEnvironments: () => Promise<void>;
|
|
221
|
+
/** Add a new environment. */
|
|
222
|
+
addEnvironment: (
|
|
223
|
+
displayName: string,
|
|
224
|
+
adapterType: string,
|
|
225
|
+
adapterConfig?: Record<string, unknown>,
|
|
226
|
+
) => Promise<void>;
|
|
227
|
+
/** Update an existing environment's mutable fields. */
|
|
228
|
+
updateEnvironment: (
|
|
229
|
+
environmentId: string,
|
|
230
|
+
fields: { displayName?: string; adapterConfig?: Record<string, unknown> },
|
|
231
|
+
) => Promise<void>;
|
|
232
|
+
/** Provision an environment by ID. When force is true, kills active sessions and forces full provision. */
|
|
233
|
+
provisionEnvironment: (environmentId: string, force?: boolean) => Promise<void>;
|
|
234
|
+
/** Stop an environment by ID. */
|
|
235
|
+
stopEnvironment: (environmentId: string) => Promise<void>;
|
|
236
|
+
/** Remove an environment by ID. */
|
|
237
|
+
removeEnvironment: (environmentId: string) => Promise<void>;
|
|
238
|
+
/** The last operation error message, or empty string if none. */
|
|
239
|
+
operationError: string;
|
|
240
|
+
/** Clear the current operation error. */
|
|
241
|
+
clearOperationError: () => void;
|
|
242
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
243
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
244
|
+
/** Handle legacy WS messages injected by E2E tests. */
|
|
245
|
+
handleLegacyMessage?: (msg: WsMessage) => boolean;
|
|
246
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
247
|
+
domainHook: DomainHook;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Values returned by the sessions domain hook. */
|
|
251
|
+
export interface UseSessionsResult {
|
|
252
|
+
/** All known sessions. */
|
|
253
|
+
sessions: Session[];
|
|
254
|
+
/** Whether the session list is currently being loaded. */
|
|
255
|
+
sessionsLoading: boolean;
|
|
256
|
+
/** Session events currently loaded in memory. */
|
|
257
|
+
events: SessionEvent[];
|
|
258
|
+
/** The total number of events dropped due to the MAX_EVENTS cap. */
|
|
259
|
+
eventsDropped: number;
|
|
260
|
+
/** The ID of the most recently spawned session, or `undefined`. */
|
|
261
|
+
lastSpawnedId: string | undefined;
|
|
262
|
+
/** Sessions grouped by task ID. */
|
|
263
|
+
taskSessions: Record<string, Session[]>;
|
|
264
|
+
/** Refresh the session list from the server. */
|
|
265
|
+
loadSessions: () => Promise<void>;
|
|
266
|
+
/** Spawn a new session in an environment. */
|
|
267
|
+
spawn: (
|
|
268
|
+
environmentId: string,
|
|
269
|
+
prompt: string,
|
|
270
|
+
personaId?: string,
|
|
271
|
+
workingDirectory?: string,
|
|
272
|
+
) => Promise<void>;
|
|
273
|
+
/** Send text input to a running session. */
|
|
274
|
+
sendInput: (sessionId: string, text: string) => Promise<void>;
|
|
275
|
+
/** Kill a running session (hard kill / SIGKILL). */
|
|
276
|
+
kill: (sessionId: string) => Promise<void>;
|
|
277
|
+
/** Gracefully stop a running session (SIGTERM). */
|
|
278
|
+
stopGraceful: (sessionId: string) => Promise<void>;
|
|
279
|
+
/** Load stored events for a session from the server. */
|
|
280
|
+
loadSessionEvents: (sessionId: string) => Promise<void>;
|
|
281
|
+
/** Clear all in-memory events and reset the drop counter. */
|
|
282
|
+
clearEvents: () => void;
|
|
283
|
+
/** Load sessions associated with a task. */
|
|
284
|
+
loadTaskSessions: (taskId: string) => Promise<void>;
|
|
285
|
+
/**
|
|
286
|
+
* Handle an incoming WebSocket message. Returns `true` if handled.
|
|
287
|
+
* @deprecated Use handleSessionEvent for ConnectRPC streaming.
|
|
288
|
+
*/
|
|
289
|
+
handleMessage: (msg: WsMessage) => boolean;
|
|
290
|
+
/** Handle a session event from the ConnectRPC StreamEvents RPC. */
|
|
291
|
+
handleSessionEvent: (event: SessionEvent) => void;
|
|
292
|
+
/** Handle legacy WS messages injected by E2E tests. */
|
|
293
|
+
handleLegacyMessage?: (msg: WsMessage) => boolean;
|
|
294
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
295
|
+
domainHook: DomainHook;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Values returned by the workspaces domain hook. */
|
|
299
|
+
export interface UseWorkspacesResult {
|
|
300
|
+
/** All known workspaces. */
|
|
301
|
+
workspaces: Workspace[];
|
|
302
|
+
/** Whether the workspace list is currently being loaded. */
|
|
303
|
+
workspacesLoading: boolean;
|
|
304
|
+
/** Whether a workspace creation is currently in progress. */
|
|
305
|
+
workspaceCreating: boolean;
|
|
306
|
+
/** Request the current workspace list from the server. */
|
|
307
|
+
loadWorkspaces: () => Promise<void>;
|
|
308
|
+
/** Create a new workspace. */
|
|
309
|
+
createWorkspace: (
|
|
310
|
+
name: string,
|
|
311
|
+
description?: string,
|
|
312
|
+
repoUrl?: string,
|
|
313
|
+
environmentId?: string,
|
|
314
|
+
defaultPersonaId?: string,
|
|
315
|
+
useWorktrees?: boolean,
|
|
316
|
+
workingDirectory?: string,
|
|
317
|
+
onSuccess?: () => void,
|
|
318
|
+
onError?: (message: string) => void,
|
|
319
|
+
) => Promise<void>;
|
|
320
|
+
/** Archive a workspace by ID. */
|
|
321
|
+
archiveWorkspace: (workspaceId: string) => Promise<void>;
|
|
322
|
+
/** Update fields on an existing workspace. */
|
|
323
|
+
updateWorkspace: (
|
|
324
|
+
workspaceId: string,
|
|
325
|
+
fields: {
|
|
326
|
+
name?: string;
|
|
327
|
+
description?: string;
|
|
328
|
+
repoUrl?: string;
|
|
329
|
+
workingDirectory?: string;
|
|
330
|
+
useWorktrees?: boolean;
|
|
331
|
+
defaultPersonaId?: string;
|
|
332
|
+
},
|
|
333
|
+
) => Promise<void>;
|
|
334
|
+
/** Link an additional environment to a workspace's pool. */
|
|
335
|
+
linkEnvironment: (workspaceId: string, environmentId: string) => Promise<void>;
|
|
336
|
+
/** Remove a linked environment from a workspace's pool. */
|
|
337
|
+
unlinkEnvironment: (workspaceId: string, environmentId: string) => Promise<void>;
|
|
338
|
+
/** The last link/unlink operation error message, or empty string if none. */
|
|
339
|
+
linkOperationError: string;
|
|
340
|
+
/** Clear the current link/unlink operation error. */
|
|
341
|
+
clearLinkOperationError: () => void;
|
|
342
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
343
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
344
|
+
/** Reset transient state on disconnect. */
|
|
345
|
+
onDisconnect: () => void;
|
|
346
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
347
|
+
domainHook: DomainHook;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Values returned by the tasks domain hook. */
|
|
351
|
+
export interface UseTasksResult {
|
|
352
|
+
/** All known tasks (may span multiple workspaces). */
|
|
353
|
+
tasks: TaskData[];
|
|
354
|
+
/** Whether the task list is currently being loaded. */
|
|
355
|
+
tasksLoading: boolean;
|
|
356
|
+
/** The ID of the task currently being started, or `undefined`. */
|
|
357
|
+
taskStartingId: string | undefined;
|
|
358
|
+
/** Load tasks for a given workspace. */
|
|
359
|
+
loadTasks: (workspaceId: string) => Promise<void>;
|
|
360
|
+
/** Load all tasks across all workspaces. */
|
|
361
|
+
loadAllTasks: () => Promise<void>;
|
|
362
|
+
/** Create a new task in a workspace. */
|
|
363
|
+
createTask: (
|
|
364
|
+
workspaceId: string,
|
|
365
|
+
title: string,
|
|
366
|
+
description?: string,
|
|
367
|
+
dependsOn?: string[],
|
|
368
|
+
parentTaskId?: string,
|
|
369
|
+
defaultPersonaId?: string,
|
|
370
|
+
canDecompose?: boolean,
|
|
371
|
+
onSuccess?: () => void,
|
|
372
|
+
onError?: (message: string) => void,
|
|
373
|
+
) => Promise<void>;
|
|
374
|
+
/** Start a task, optionally specifying runtime parameters. */
|
|
375
|
+
startTask: (
|
|
376
|
+
taskId: string,
|
|
377
|
+
personaId?: string,
|
|
378
|
+
environmentId?: string,
|
|
379
|
+
notes?: string,
|
|
380
|
+
) => Promise<void>;
|
|
381
|
+
/** Stop a task: kill active sessions + mark complete. */
|
|
382
|
+
stopTask: (taskId: string) => Promise<void>;
|
|
383
|
+
/** Mark a task as completed. */
|
|
384
|
+
completeTask: (taskId: string) => Promise<void>;
|
|
385
|
+
/** Resume a paused/waiting task. */
|
|
386
|
+
resumeTask: (taskId: string) => Promise<void>;
|
|
387
|
+
/** Update a task's title, description, dependencies, and default persona. */
|
|
388
|
+
updateTask: (
|
|
389
|
+
taskId: string,
|
|
390
|
+
title: string,
|
|
391
|
+
description: string,
|
|
392
|
+
dependsOn: string[],
|
|
393
|
+
defaultPersonaId?: string,
|
|
394
|
+
) => Promise<void>;
|
|
395
|
+
/** Delete a task by ID. */
|
|
396
|
+
deleteTask: (taskId: string) => Promise<void>;
|
|
397
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
398
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
399
|
+
/** Reset transient state on disconnect. */
|
|
400
|
+
onDisconnect: () => void;
|
|
401
|
+
/** Handle legacy WS messages injected by E2E tests. */
|
|
402
|
+
handleLegacyMessage?: (msg: WsMessage) => boolean;
|
|
403
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
404
|
+
domainHook: DomainHook;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Values returned by the findings domain hook. */
|
|
408
|
+
export interface UseFindingsResult {
|
|
409
|
+
/** All loaded findings. */
|
|
410
|
+
findings: FindingData[];
|
|
411
|
+
/** The currently selected finding (loaded by ID). */
|
|
412
|
+
selectedFinding: FindingData | undefined;
|
|
413
|
+
/** Whether a single finding is being loaded. */
|
|
414
|
+
findingLoading: boolean;
|
|
415
|
+
/** Whether a findings list fetch is in-flight. */
|
|
416
|
+
findingsLoading: boolean;
|
|
417
|
+
/** Load findings for a given workspace. */
|
|
418
|
+
loadFindings: (workspaceId: string) => Promise<void>;
|
|
419
|
+
/** Load findings across all workspaces. */
|
|
420
|
+
loadAllFindings: () => Promise<void>;
|
|
421
|
+
/** Load a single finding by ID. */
|
|
422
|
+
loadFinding: (findingId: string) => Promise<void>;
|
|
423
|
+
/** Post a new finding to a workspace. */
|
|
424
|
+
postFinding: (
|
|
425
|
+
workspaceId: string,
|
|
426
|
+
title: string,
|
|
427
|
+
content: string,
|
|
428
|
+
category?: string,
|
|
429
|
+
tags?: string[],
|
|
430
|
+
) => Promise<void>;
|
|
431
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
432
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
433
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
434
|
+
domainHook: DomainHook;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Values returned by the tokens domain hook. */
|
|
438
|
+
export interface UseTokensResult {
|
|
439
|
+
/** All known tokens. */
|
|
440
|
+
tokens: TokenInfo[];
|
|
441
|
+
/** Whether the token list is currently being loaded. */
|
|
442
|
+
tokensLoading: boolean;
|
|
443
|
+
/** Request the current token list from the server. */
|
|
444
|
+
loadTokens: () => Promise<void>;
|
|
445
|
+
/** Create or update a token on the server. */
|
|
446
|
+
setToken: (
|
|
447
|
+
name: string,
|
|
448
|
+
value: string,
|
|
449
|
+
tokenType: string,
|
|
450
|
+
envVar: string,
|
|
451
|
+
filePath: string,
|
|
452
|
+
) => Promise<void>;
|
|
453
|
+
/** Delete a token by name. */
|
|
454
|
+
deleteToken: (name: string) => Promise<void>;
|
|
455
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
456
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
457
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
458
|
+
domainHook: DomainHook;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Values returned by the credentials domain hook. */
|
|
462
|
+
export interface UseCredentialsResult {
|
|
463
|
+
/** Current credential provider configuration. */
|
|
464
|
+
credentialProviders: CredentialProviderConfig;
|
|
465
|
+
/** Whether the credential configuration is currently being loaded. */
|
|
466
|
+
credentialsLoading: boolean;
|
|
467
|
+
/** Request the current credential provider configuration from the server. */
|
|
468
|
+
loadCredentials: () => Promise<void>;
|
|
469
|
+
/** Update the credential provider configuration on the server. */
|
|
470
|
+
updateCredentialProviders: (config: CredentialProviderConfig) => Promise<void>;
|
|
471
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
472
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
473
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
474
|
+
domainHook: DomainHook;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Values returned by the codespaces domain hook. */
|
|
478
|
+
export interface UseCodespacesResult {
|
|
479
|
+
/** All known codespaces. */
|
|
480
|
+
codespaces: Codespace[];
|
|
481
|
+
/** Error message from the most recent create attempt, or empty string. */
|
|
482
|
+
codespaceError: string;
|
|
483
|
+
/** Error message from the most recent list attempt, or empty string. */
|
|
484
|
+
codespaceListError: string;
|
|
485
|
+
/** Whether a codespace creation is currently in progress. */
|
|
486
|
+
codespaceCreating: boolean;
|
|
487
|
+
/** Request the current codespace list from the server. */
|
|
488
|
+
listCodespaces: () => Promise<void>;
|
|
489
|
+
/** Create a new codespace for the given repo. */
|
|
490
|
+
createCodespace: (repo: string, machine?: string) => Promise<void>;
|
|
491
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
492
|
+
domainHook: DomainHook;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Values returned by the personas domain hook. */
|
|
496
|
+
export interface UsePersonasResult {
|
|
497
|
+
/** All known personas. */
|
|
498
|
+
personas: PersonaData[];
|
|
499
|
+
/** Whether the persona list is currently being loaded. */
|
|
500
|
+
personasLoading: boolean;
|
|
501
|
+
/** Request the current persona list from the server. */
|
|
502
|
+
loadPersonas: () => Promise<void>;
|
|
503
|
+
/** Create a new persona. */
|
|
504
|
+
createPersona: (
|
|
505
|
+
name: string,
|
|
506
|
+
description: string,
|
|
507
|
+
systemPrompt: string,
|
|
508
|
+
runtime?: string,
|
|
509
|
+
model?: string,
|
|
510
|
+
maxTurns?: number,
|
|
511
|
+
type?: string,
|
|
512
|
+
script?: string,
|
|
513
|
+
allowedMcpTools?: string[],
|
|
514
|
+
) => Promise<PersonaData>;
|
|
515
|
+
/** Update an existing persona. */
|
|
516
|
+
updatePersona: (
|
|
517
|
+
personaId: string,
|
|
518
|
+
name?: string,
|
|
519
|
+
description?: string,
|
|
520
|
+
systemPrompt?: string,
|
|
521
|
+
runtime?: string,
|
|
522
|
+
model?: string,
|
|
523
|
+
maxTurns?: number,
|
|
524
|
+
type?: string,
|
|
525
|
+
script?: string,
|
|
526
|
+
allowedMcpTools?: string[],
|
|
527
|
+
) => Promise<PersonaData>;
|
|
528
|
+
/** Delete a persona by ID. */
|
|
529
|
+
deletePersona: (personaId: string) => Promise<void>;
|
|
530
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
531
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
532
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
533
|
+
domainHook: DomainHook;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** A cron schedule entry. */
|
|
537
|
+
export interface ScheduleData {
|
|
538
|
+
id: string;
|
|
539
|
+
title: string;
|
|
540
|
+
description: string;
|
|
541
|
+
scheduleExpression: string;
|
|
542
|
+
personaId: string;
|
|
543
|
+
workspaceId: string;
|
|
544
|
+
parentTaskId: string;
|
|
545
|
+
enabled: boolean;
|
|
546
|
+
lastRunAt: string;
|
|
547
|
+
nextRunAt: string;
|
|
548
|
+
runCount: number;
|
|
549
|
+
createdAt: string;
|
|
550
|
+
updatedAt: string;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** Fields that can be updated on an existing schedule. */
|
|
554
|
+
export interface ScheduleUpdate {
|
|
555
|
+
title?: string;
|
|
556
|
+
description?: string;
|
|
557
|
+
scheduleExpression?: string;
|
|
558
|
+
personaId?: string;
|
|
559
|
+
enabled?: boolean;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Values returned by the schedules domain hook. */
|
|
563
|
+
export interface UseSchedulesResult {
|
|
564
|
+
/** All known schedules. */
|
|
565
|
+
schedules: ScheduleData[];
|
|
566
|
+
/** Whether the schedule list is currently being loaded. */
|
|
567
|
+
schedulesLoading: boolean;
|
|
568
|
+
/** Request the current schedule list from the server. */
|
|
569
|
+
loadSchedules: () => Promise<void>;
|
|
570
|
+
/** Create a new schedule. */
|
|
571
|
+
createSchedule: (
|
|
572
|
+
title: string,
|
|
573
|
+
description: string,
|
|
574
|
+
scheduleExpression: string,
|
|
575
|
+
personaId: string,
|
|
576
|
+
workspaceId?: string,
|
|
577
|
+
parentTaskId?: string,
|
|
578
|
+
) => Promise<ScheduleData>;
|
|
579
|
+
/** Update an existing schedule. */
|
|
580
|
+
updateSchedule: (scheduleId: string, fields: ScheduleUpdate) => Promise<ScheduleData>;
|
|
581
|
+
/** Delete a schedule by ID. */
|
|
582
|
+
deleteSchedule: (scheduleId: string) => Promise<void>;
|
|
583
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
584
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
585
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
586
|
+
domainHook: DomainHook;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ─── Streams hook result ─────────────────────────────────────────────────────
|
|
590
|
+
|
|
591
|
+
/** A subscriber on an IPC stream. */
|
|
592
|
+
export interface StreamSubscriberData {
|
|
593
|
+
/** Unique subscription identifier. */
|
|
594
|
+
subscriptionId: string;
|
|
595
|
+
/** ID of the session holding this subscription. */
|
|
596
|
+
sessionId: string;
|
|
597
|
+
/** File descriptor number assigned to the subscriber. */
|
|
598
|
+
fd: number;
|
|
599
|
+
/** Access permission: "r", "w", or "rw". */
|
|
600
|
+
permission: string;
|
|
601
|
+
/** Delivery mode: "sync", "async", or "detach". */
|
|
602
|
+
deliveryMode: string;
|
|
603
|
+
/** True if this subscription was created via spawn (not attachStream). */
|
|
604
|
+
createdBySpawn: boolean;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** An IPC stream with subscriber details. */
|
|
608
|
+
export interface StreamData {
|
|
609
|
+
/** Opaque stream identifier. */
|
|
610
|
+
id: string;
|
|
611
|
+
/** Human-readable stream name. */
|
|
612
|
+
name: string;
|
|
613
|
+
/** Number of active subscribers. */
|
|
614
|
+
subscriberCount: number;
|
|
615
|
+
/** Number of messages currently buffered. */
|
|
616
|
+
messageBufferDepth: number;
|
|
617
|
+
/** Full subscriber details. */
|
|
618
|
+
subscribers: StreamSubscriberData[];
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** Values returned by the streams domain hook. */
|
|
622
|
+
export interface UseStreamsResult {
|
|
623
|
+
/** All known IPC streams. */
|
|
624
|
+
streams: StreamData[];
|
|
625
|
+
/** Whether the stream list is currently being loaded. */
|
|
626
|
+
streamsLoading: boolean;
|
|
627
|
+
/** True after at least one loadStreams attempt has completed (success or error). */
|
|
628
|
+
streamsLoadedOnce: boolean;
|
|
629
|
+
/** True if the most recent loadStreams call failed (e.g. RPC/network error). */
|
|
630
|
+
streamsLoadError: boolean;
|
|
631
|
+
/** Request the current stream list from the server. */
|
|
632
|
+
loadStreams: () => Promise<void>;
|
|
633
|
+
/** Handle a domain event from the event bus. Returns `true` if handled. */
|
|
634
|
+
handleEvent: (event: GrackleEvent) => boolean;
|
|
635
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
636
|
+
domainHook: DomainHook;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// ─── Knowledge hook result ────────────────────────────────────────────────────
|
|
640
|
+
|
|
641
|
+
/** Result returned by useKnowledge. */
|
|
642
|
+
export interface UseKnowledgeResult {
|
|
643
|
+
graphData: { nodes: GraphNode[]; links: GraphLink[] };
|
|
644
|
+
selectedNode: NodeDetail | undefined;
|
|
645
|
+
/** Currently selected node ID. */
|
|
646
|
+
selectedId: string | undefined;
|
|
647
|
+
loading: boolean;
|
|
648
|
+
searchQuery: string;
|
|
649
|
+
search(query: string): Promise<void>;
|
|
650
|
+
clearSearch(): void;
|
|
651
|
+
selectNode(id: string): Promise<void>;
|
|
652
|
+
clearSelection(): void;
|
|
653
|
+
expandNode(id: string): Promise<void>;
|
|
654
|
+
loadRecent(workspaceId?: string): Promise<void>;
|
|
655
|
+
/** Handle domain events from the event bus. Returns true if handled. */
|
|
656
|
+
handleEvent(event: GrackleEvent): boolean;
|
|
657
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
658
|
+
domainHook: DomainHook;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// ─── Runtime type guards ──────────────────────────────────────────────────────
|
|
662
|
+
|
|
663
|
+
/** Returns true when `v` is a non-null, non-array object. */
|
|
664
|
+
export function isObject(v: unknown): v is Record<string, unknown> {
|
|
665
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Emit a console warning and return `false` when an incoming payload does not
|
|
670
|
+
* match the expected shape. We warn rather than throw so a single bad message
|
|
671
|
+
* from the server does not crash the entire UI.
|
|
672
|
+
*/
|
|
673
|
+
export function warnBadPayload(msgType: string, reason: string): false {
|
|
674
|
+
console.warn(`[ws] Malformed "${msgType}" message: ${reason}`);
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Type guard for {@link GrackleEvent}. */
|
|
679
|
+
export function isGrackleEvent(v: unknown): v is GrackleEvent {
|
|
680
|
+
return (
|
|
681
|
+
isObject(v) &&
|
|
682
|
+
typeof v.id === "string" &&
|
|
683
|
+
typeof v.type === "string" &&
|
|
684
|
+
typeof v.timestamp === "string" &&
|
|
685
|
+
isObject(v.payload)
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** Type guard for {@link SessionEvent}. */
|
|
690
|
+
export function isSessionEvent(v: unknown): v is SessionEvent {
|
|
691
|
+
return (
|
|
692
|
+
isObject(v) &&
|
|
693
|
+
typeof v.sessionId === "string" &&
|
|
694
|
+
typeof v.eventType === "string" &&
|
|
695
|
+
typeof v.timestamp === "string" &&
|
|
696
|
+
typeof v.content === "string" &&
|
|
697
|
+
(v.raw === undefined || typeof v.raw === "string")
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/** Valid values for the `claude` credential provider mode. */
|
|
702
|
+
const VALID_CLAUDE_MODES: ReadonlySet<string> = new Set(["off", "subscription", "api_key"]);
|
|
703
|
+
/** Valid values for toggle-style credential provider modes. */
|
|
704
|
+
const VALID_TOGGLE_MODES: ReadonlySet<string> = new Set(["off", "on"]);
|
|
705
|
+
|
|
706
|
+
/** Type guard for {@link CredentialProviderConfig}. */
|
|
707
|
+
export function isCredentialProviderConfig(v: unknown): v is CredentialProviderConfig {
|
|
708
|
+
return (
|
|
709
|
+
isObject(v) &&
|
|
710
|
+
VALID_CLAUDE_MODES.has(v.claude as string) &&
|
|
711
|
+
VALID_TOGGLE_MODES.has(v.github as string) &&
|
|
712
|
+
VALID_TOGGLE_MODES.has(v.copilot as string) &&
|
|
713
|
+
VALID_TOGGLE_MODES.has(v.codex as string) &&
|
|
714
|
+
VALID_TOGGLE_MODES.has(v.goose as string)
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ─── Utility functions ────────────────────────────────────────────────────────
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Parse a raw WebSocket message string into a {@link WsMessage} or
|
|
722
|
+
* {@link GrackleEvent}. When both `id` and `timestamp` are present the
|
|
723
|
+
* result is a full `GrackleEvent`; otherwise a plain `WsMessage`.
|
|
724
|
+
* Returns `undefined` and logs a warning if parsing fails or the result is
|
|
725
|
+
* not a valid message object.
|
|
726
|
+
*/
|
|
727
|
+
export function parseWsMessage(data: string): WsMessage | GrackleEvent | undefined {
|
|
728
|
+
let parsed: unknown;
|
|
729
|
+
try {
|
|
730
|
+
parsed = JSON.parse(data) as unknown;
|
|
731
|
+
} catch {
|
|
732
|
+
console.warn("[ws] Failed to parse WebSocket message as JSON");
|
|
733
|
+
return undefined;
|
|
734
|
+
}
|
|
735
|
+
if (!isObject(parsed) || typeof parsed.type !== "string") {
|
|
736
|
+
console.warn(
|
|
737
|
+
"[ws] Received WebSocket message without a string 'type' field:",
|
|
738
|
+
parsed,
|
|
739
|
+
);
|
|
740
|
+
return undefined;
|
|
741
|
+
}
|
|
742
|
+
// When both id and timestamp are present, return a full GrackleEvent
|
|
743
|
+
if (typeof parsed.id === "string" && typeof parsed.timestamp === "string") {
|
|
744
|
+
return {
|
|
745
|
+
id: parsed.id,
|
|
746
|
+
type: parsed.type,
|
|
747
|
+
timestamp: parsed.timestamp,
|
|
748
|
+
payload: isObject(parsed.payload) ? parsed.payload : {},
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
type: parsed.type,
|
|
753
|
+
payload: isObject(parsed.payload) ? parsed.payload : undefined,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Map runtime status event content to normalized session status strings.
|
|
759
|
+
* The PowerLine runtime emits "waiting_input" as event content, but the
|
|
760
|
+
* server stores "idle". Terminal statuses ("completed", "killed", "failed",
|
|
761
|
+
* "interrupted", "terminated") all map to "stopped". The frontend needs to
|
|
762
|
+
* use the same strings as the server for consistency with list_sessions responses.
|
|
763
|
+
*/
|
|
764
|
+
export function mapSessionStatus(rawStatus: string): string {
|
|
765
|
+
switch (rawStatus) {
|
|
766
|
+
case "waiting_input": return "idle";
|
|
767
|
+
case "completed": return "stopped";
|
|
768
|
+
case "killed": return "stopped";
|
|
769
|
+
case "failed": return "stopped";
|
|
770
|
+
case "interrupted": return "stopped";
|
|
771
|
+
case "terminated": return "stopped";
|
|
772
|
+
default: return rawStatus;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Map a raw PowerLine event content string to an endReason value,
|
|
778
|
+
* or undefined for non-terminal events.
|
|
779
|
+
*/
|
|
780
|
+
export function mapEndReason(rawContent: string): string | undefined {
|
|
781
|
+
switch (rawContent) {
|
|
782
|
+
case "completed": return "completed";
|
|
783
|
+
case "killed": return "killed";
|
|
784
|
+
case "failed": return "interrupted";
|
|
785
|
+
case "interrupted": return "interrupted";
|
|
786
|
+
case "terminated": return "terminated";
|
|
787
|
+
default: return undefined;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
792
|
+
|
|
793
|
+
// ─── Knowledge graph types ───────────────────────────────────────────────────
|
|
794
|
+
|
|
795
|
+
/** A node in the force graph. */
|
|
796
|
+
export interface GraphNode {
|
|
797
|
+
id: string;
|
|
798
|
+
label: string;
|
|
799
|
+
kind: string;
|
|
800
|
+
category?: string;
|
|
801
|
+
sourceType?: string;
|
|
802
|
+
sourceId?: string;
|
|
803
|
+
content?: string;
|
|
804
|
+
tags?: string[];
|
|
805
|
+
workspaceId?: string;
|
|
806
|
+
createdAt?: string;
|
|
807
|
+
updatedAt?: string;
|
|
808
|
+
/** Node size (edge count). */
|
|
809
|
+
val: number;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** A link in the force graph. */
|
|
813
|
+
export interface GraphLink {
|
|
814
|
+
source: string;
|
|
815
|
+
target: string;
|
|
816
|
+
type: string;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/** Full detail for a selected node. */
|
|
820
|
+
export interface NodeDetail {
|
|
821
|
+
node: GraphNode;
|
|
822
|
+
edges: Array<{ fromId: string; toId: string; type: string; metadata?: Record<string, unknown> }>;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
826
|
+
|
|
827
|
+
// ─── Plugins ─────────────────────────────────────────────────────────────────
|
|
828
|
+
|
|
829
|
+
/** Metadata for a Grackle plugin. */
|
|
830
|
+
export interface PluginData {
|
|
831
|
+
/** Plugin identifier (e.g. "core", "orchestration"). */
|
|
832
|
+
name: string;
|
|
833
|
+
/** Human-readable description. */
|
|
834
|
+
description: string;
|
|
835
|
+
/** DB-persisted desired enabled state. */
|
|
836
|
+
enabled: boolean;
|
|
837
|
+
/** True for the core plugin — cannot be disabled. */
|
|
838
|
+
required: boolean;
|
|
839
|
+
/** Whether the plugin is currently running in this server instance. */
|
|
840
|
+
loaded: boolean;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/** Values returned by the plugins domain hook. */
|
|
844
|
+
export interface UsePluginsResult {
|
|
845
|
+
/** All known plugins. */
|
|
846
|
+
plugins: PluginData[];
|
|
847
|
+
/** Whether the plugin list is currently loading. */
|
|
848
|
+
pluginsLoading: boolean;
|
|
849
|
+
/** Request the current plugin list from the server. */
|
|
850
|
+
loadPlugins: () => Promise<void>;
|
|
851
|
+
/** Enable or disable a plugin by name. */
|
|
852
|
+
setPluginEnabled: (name: string, enabled: boolean) => Promise<void>;
|
|
853
|
+
/** Lifecycle hook for connect/disconnect/event routing. */
|
|
854
|
+
domainHook: DomainHook;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** Delay in milliseconds before attempting a WebSocket reconnect. */
|
|
858
|
+
export const WS_RECONNECT_DELAY_MS: number = 3_000;
|
|
859
|
+
|
|
860
|
+
/** Maximum number of events kept in memory per hook instance. Older events are dropped. */
|
|
861
|
+
export const MAX_EVENTS: number = 5_000;
|
|
862
|
+
|
|
863
|
+
/** WebSocket close code indicating an unauthorized connection. */
|
|
864
|
+
export const WS_CLOSE_UNAUTHORIZED: number = 4001;
|