@bian-womp/spark-workbench 0.3.88 → 0.3.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/lib/src/adapters/cli/index.d.ts +22 -0
  2. package/lib/src/adapters/cli/index.d.ts.map +1 -0
  3. package/lib/src/adapters/cli/index.js +50 -0
  4. package/lib/src/adapters/cli/index.js.map +1 -0
  5. package/lib/src/core/AbstractWorkbench.d.ts +40 -0
  6. package/lib/src/core/AbstractWorkbench.d.ts.map +1 -0
  7. package/lib/src/core/AbstractWorkbench.js +15 -0
  8. package/lib/src/core/AbstractWorkbench.js.map +1 -0
  9. package/lib/src/core/InMemoryWorkbench.d.ts +304 -0
  10. package/lib/src/core/InMemoryWorkbench.d.ts.map +1 -0
  11. package/lib/src/core/InMemoryWorkbench.js +1016 -0
  12. package/lib/src/core/InMemoryWorkbench.js.map +1 -0
  13. package/lib/src/core/contracts.d.ts +172 -0
  14. package/lib/src/core/contracts.d.ts.map +1 -0
  15. package/lib/src/core/contracts.js +2 -0
  16. package/lib/src/core/contracts.js.map +1 -0
  17. package/lib/src/core/ui-extensions.d.ts +85 -0
  18. package/lib/src/core/ui-extensions.d.ts.map +1 -0
  19. package/lib/src/core/ui-extensions.js +111 -0
  20. package/lib/src/core/ui-extensions.js.map +1 -0
  21. package/lib/src/examples/cli.d.ts +2 -0
  22. package/lib/src/examples/cli.d.ts.map +1 -0
  23. package/lib/src/examples/cli.js +244 -0
  24. package/lib/src/examples/cli.js.map +1 -0
  25. package/lib/src/examples/reactflow/App.d.ts +2 -0
  26. package/lib/src/examples/reactflow/App.d.ts.map +1 -0
  27. package/lib/src/examples/reactflow/App.js +20 -0
  28. package/lib/src/examples/reactflow/App.js.map +1 -0
  29. package/lib/src/index.d.ts +30 -0
  30. package/lib/src/index.d.ts.map +1 -0
  31. package/lib/src/index.js +30 -0
  32. package/lib/src/index.js.map +1 -0
  33. package/lib/src/misc/DebugEvents.d.ts +7 -0
  34. package/lib/src/misc/DebugEvents.d.ts.map +1 -0
  35. package/lib/src/misc/DebugEvents.js +51 -0
  36. package/lib/src/misc/DebugEvents.js.map +1 -0
  37. package/lib/src/misc/DefaultEdge.d.ts +5 -0
  38. package/lib/src/misc/DefaultEdge.d.ts.map +1 -0
  39. package/lib/src/misc/DefaultEdge.js +15 -0
  40. package/lib/src/misc/DefaultEdge.js.map +1 -0
  41. package/lib/src/misc/DefaultNode.d.ts +5 -0
  42. package/lib/src/misc/DefaultNode.d.ts.map +1 -0
  43. package/lib/src/misc/DefaultNode.js +25 -0
  44. package/lib/src/misc/DefaultNode.js.map +1 -0
  45. package/lib/src/misc/DefaultNodeContent.d.ts +4 -0
  46. package/lib/src/misc/DefaultNodeContent.d.ts.map +1 -0
  47. package/lib/src/misc/DefaultNodeContent.js +58 -0
  48. package/lib/src/misc/DefaultNodeContent.js.map +1 -0
  49. package/lib/src/misc/DefaultNodeHeader.d.ts +13 -0
  50. package/lib/src/misc/DefaultNodeHeader.d.ts.map +1 -0
  51. package/lib/src/misc/DefaultNodeHeader.js +78 -0
  52. package/lib/src/misc/DefaultNodeHeader.js.map +1 -0
  53. package/lib/src/misc/Inspector.d.ts +12 -0
  54. package/lib/src/misc/Inspector.d.ts.map +1 -0
  55. package/lib/src/misc/Inspector.js +253 -0
  56. package/lib/src/misc/Inspector.js.map +1 -0
  57. package/lib/src/misc/IssueBadge.d.ts +7 -0
  58. package/lib/src/misc/IssueBadge.d.ts.map +1 -0
  59. package/lib/src/misc/IssueBadge.js +7 -0
  60. package/lib/src/misc/IssueBadge.js.map +1 -0
  61. package/lib/src/misc/KeyboardShortcutToast.d.ts +16 -0
  62. package/lib/src/misc/KeyboardShortcutToast.d.ts.map +1 -0
  63. package/lib/src/misc/KeyboardShortcutToast.js +40 -0
  64. package/lib/src/misc/KeyboardShortcutToast.js.map +1 -0
  65. package/lib/src/misc/NodeHandles.d.ts +18 -0
  66. package/lib/src/misc/NodeHandles.d.ts.map +1 -0
  67. package/lib/src/misc/NodeHandles.js +67 -0
  68. package/lib/src/misc/NodeHandles.js.map +1 -0
  69. package/lib/src/misc/SelectionActiveSync.d.ts +10 -0
  70. package/lib/src/misc/SelectionActiveSync.d.ts.map +1 -0
  71. package/lib/src/misc/SelectionActiveSync.js +21 -0
  72. package/lib/src/misc/SelectionActiveSync.js.map +1 -0
  73. package/lib/src/misc/WorkbenchCanvas.d.ts +23 -0
  74. package/lib/src/misc/WorkbenchCanvas.d.ts.map +1 -0
  75. package/lib/src/misc/WorkbenchCanvas.js +669 -0
  76. package/lib/src/misc/WorkbenchCanvas.js.map +1 -0
  77. package/lib/src/misc/WorkbenchStudio.d.ts +43 -0
  78. package/lib/src/misc/WorkbenchStudio.d.ts.map +1 -0
  79. package/lib/src/misc/WorkbenchStudio.js +463 -0
  80. package/lib/src/misc/WorkbenchStudio.js.map +1 -0
  81. package/lib/src/misc/constants.d.ts +4 -0
  82. package/lib/src/misc/constants.d.ts.map +1 -0
  83. package/lib/src/misc/constants.js +5 -0
  84. package/lib/src/misc/constants.js.map +1 -0
  85. package/lib/src/misc/context/WorkbenchContext.d.ts +133 -0
  86. package/lib/src/misc/context/WorkbenchContext.d.ts.map +1 -0
  87. package/lib/src/misc/context/WorkbenchContext.js +9 -0
  88. package/lib/src/misc/context/WorkbenchContext.js.map +1 -0
  89. package/lib/src/misc/context/WorkbenchContext.provider.d.ts +12 -0
  90. package/lib/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -0
  91. package/lib/src/misc/context/WorkbenchContext.provider.js +995 -0
  92. package/lib/src/misc/context/WorkbenchContext.provider.js.map +1 -0
  93. package/lib/src/misc/context-menu/ContextMenuButton.d.ts +8 -0
  94. package/lib/src/misc/context-menu/ContextMenuButton.d.ts.map +1 -0
  95. package/lib/src/misc/context-menu/ContextMenuButton.js +10 -0
  96. package/lib/src/misc/context-menu/ContextMenuButton.js.map +1 -0
  97. package/lib/src/misc/context-menu/ContextMenuHandlers.d.ts +85 -0
  98. package/lib/src/misc/context-menu/ContextMenuHandlers.d.ts.map +1 -0
  99. package/lib/src/misc/context-menu/ContextMenuHandlers.js +2 -0
  100. package/lib/src/misc/context-menu/ContextMenuHandlers.js.map +1 -0
  101. package/lib/src/misc/context-menu/ContextMenuHelpers.d.ts +45 -0
  102. package/lib/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -0
  103. package/lib/src/misc/context-menu/ContextMenuHelpers.js +182 -0
  104. package/lib/src/misc/context-menu/ContextMenuHelpers.js.map +1 -0
  105. package/lib/src/misc/context-menu/DefaultContextMenu.d.ts +3 -0
  106. package/lib/src/misc/context-menu/DefaultContextMenu.d.ts.map +1 -0
  107. package/lib/src/misc/context-menu/DefaultContextMenu.js +111 -0
  108. package/lib/src/misc/context-menu/DefaultContextMenu.js.map +1 -0
  109. package/lib/src/misc/context-menu/NodeContextMenu.d.ts +3 -0
  110. package/lib/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -0
  111. package/lib/src/misc/context-menu/NodeContextMenu.js +51 -0
  112. package/lib/src/misc/context-menu/NodeContextMenu.js.map +1 -0
  113. package/lib/src/misc/context-menu/SelectionContextMenu.d.ts +3 -0
  114. package/lib/src/misc/context-menu/SelectionContextMenu.d.ts.map +1 -0
  115. package/lib/src/misc/context-menu/SelectionContextMenu.js +47 -0
  116. package/lib/src/misc/context-menu/SelectionContextMenu.js.map +1 -0
  117. package/lib/src/misc/hooks.d.ts +18 -0
  118. package/lib/src/misc/hooks.d.ts.map +1 -0
  119. package/lib/src/misc/hooks.js +275 -0
  120. package/lib/src/misc/hooks.js.map +1 -0
  121. package/lib/src/misc/layout.d.ts +117 -0
  122. package/lib/src/misc/layout.d.ts.map +1 -0
  123. package/lib/src/misc/layout.js +205 -0
  124. package/lib/src/misc/layout.js.map +1 -0
  125. package/lib/src/misc/load.d.ts +5 -0
  126. package/lib/src/misc/load.d.ts.map +1 -0
  127. package/lib/src/misc/load.js +106 -0
  128. package/lib/src/misc/load.js.map +1 -0
  129. package/lib/src/misc/mapping.d.ts +128 -0
  130. package/lib/src/misc/mapping.d.ts.map +1 -0
  131. package/lib/src/misc/mapping.js +270 -0
  132. package/lib/src/misc/mapping.js.map +1 -0
  133. package/lib/src/misc/merge-utils.d.ts +12 -0
  134. package/lib/src/misc/merge-utils.d.ts.map +1 -0
  135. package/lib/src/misc/merge-utils.js +51 -0
  136. package/lib/src/misc/merge-utils.js.map +1 -0
  137. package/lib/src/misc/thumbnail-utils.d.ts +53 -0
  138. package/lib/src/misc/thumbnail-utils.d.ts.map +1 -0
  139. package/lib/src/misc/thumbnail-utils.js +629 -0
  140. package/lib/src/misc/thumbnail-utils.js.map +1 -0
  141. package/lib/src/misc/types.d.ts +18 -0
  142. package/lib/src/misc/types.d.ts.map +1 -0
  143. package/lib/src/misc/types.js +2 -0
  144. package/lib/src/misc/types.js.map +1 -0
  145. package/lib/src/misc/value.d.ts +16 -0
  146. package/lib/src/misc/value.d.ts.map +1 -0
  147. package/lib/src/misc/value.js +114 -0
  148. package/lib/src/misc/value.js.map +1 -0
  149. package/lib/src/misc/viewport-utils.d.ts +6 -0
  150. package/lib/src/misc/viewport-utils.d.ts.map +1 -0
  151. package/lib/src/misc/viewport-utils.js +18 -0
  152. package/lib/src/misc/viewport-utils.js.map +1 -0
  153. package/lib/src/runtime/AbstractGraphRunner.d.ts +61 -0
  154. package/lib/src/runtime/AbstractGraphRunner.d.ts.map +1 -0
  155. package/lib/src/runtime/AbstractGraphRunner.js +63 -0
  156. package/lib/src/runtime/AbstractGraphRunner.js.map +1 -0
  157. package/lib/src/runtime/IGraphRunner.d.ts +100 -0
  158. package/lib/src/runtime/IGraphRunner.d.ts.map +1 -0
  159. package/lib/src/runtime/IGraphRunner.js +2 -0
  160. package/lib/src/runtime/IGraphRunner.js.map +1 -0
  161. package/lib/src/runtime/LocalGraphRunner.d.ts +60 -0
  162. package/lib/src/runtime/LocalGraphRunner.d.ts.map +1 -0
  163. package/lib/src/runtime/LocalGraphRunner.js +294 -0
  164. package/lib/src/runtime/LocalGraphRunner.js.map +1 -0
  165. package/lib/src/runtime/RemoteGraphRunner.d.ts +109 -0
  166. package/lib/src/runtime/RemoteGraphRunner.d.ts.map +1 -0
  167. package/lib/src/runtime/RemoteGraphRunner.js +696 -0
  168. package/lib/src/runtime/RemoteGraphRunner.js.map +1 -0
  169. package/package.json +4 -4
@@ -0,0 +1,995 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick } from "../hooks";
4
+ import { WorkbenchContext } from "./WorkbenchContext";
5
+ import { estimateNodeSize, computeEffectiveHandles } from "../layout";
6
+ import { resolveOutputDisplay } from "../value";
7
+ import { excludeViewportFromUIState, isValidViewport } from "../viewport-utils";
8
+ // Helper to compute invalidated status from runtime metadata
9
+ function computeInvalidatedFromMetadata(metadata) {
10
+ if (!metadata)
11
+ return true;
12
+ const { lastSuccessAt, lastInputAt, lastRunAt } = metadata;
13
+ if (!lastSuccessAt && !lastRunAt)
14
+ return true;
15
+ if (!lastInputAt || Object.keys(lastInputAt).length === 0)
16
+ return false;
17
+ const maxInputTime = Math.max(...Object.values(lastInputAt));
18
+ return maxInputTime > (lastSuccessAt ?? lastRunAt ?? 0);
19
+ }
20
+ export function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
21
+ const [nodeStatus, setNodeStatus] = useState({});
22
+ const [edgeStatus, setEdgeStatus] = useState({});
23
+ const [runMode, setRunModeState] = useState("manual");
24
+ const [events, setEvents] = useState([]);
25
+ const clearEvents = useCallback(() => setEvents([]), []);
26
+ const [systemErrors, setSystemErrors] = useState([]);
27
+ const [registryErrors, setRegistryErrors] = useState([]);
28
+ const [inputValidationErrors, setInputValidationErrors] = useState([]);
29
+ const [registryVersion, setRegistryVersion] = useState(0);
30
+ const [registryReady, setRegistryReady] = useState(false);
31
+ const clearSystemErrors = useCallback(() => setSystemErrors([]), []);
32
+ const clearRegistryErrors = useCallback(() => setRegistryErrors([]), []);
33
+ const clearInputValidationErrors = useCallback(() => setInputValidationErrors([]), []);
34
+ const removeSystemError = useCallback((index) => {
35
+ setSystemErrors((prev) => prev.filter((_, idx) => idx !== index));
36
+ }, []);
37
+ const removeRegistryError = useCallback((index) => {
38
+ setRegistryErrors((prev) => prev.filter((_, idx) => idx !== index));
39
+ }, []);
40
+ const removeInputValidationError = useCallback((index) => {
41
+ setInputValidationErrors((prev) => prev.filter((_, idx) => idx !== index));
42
+ }, []);
43
+ // Fallback progress animation: drive progress to 100% over ~2 minutes
44
+ const FALLBACK_TOTAL_MS = 2 * 60 * 1000;
45
+ const [fallbackStarts, setFallbackStarts] = useState({});
46
+ // Track runs that emitted an error so we can keep progress on completion
47
+ const errorRunsRef = useRef({});
48
+ // Periodically advance fallback progress for running nodes without explicit progress
49
+ useEffect(() => {
50
+ const interval = setInterval(() => {
51
+ setNodeStatus((prev) => {
52
+ let changed = false;
53
+ const next = { ...prev };
54
+ const now = Date.now();
55
+ for (const id of Object.keys(prev)) {
56
+ const st = prev[id];
57
+ if (!st)
58
+ continue;
59
+ const runs = st.activeRuns ?? 0;
60
+ const startAt = fallbackStarts[id];
61
+ if (runs > 0 && startAt) {
62
+ const cur = Math.max(0, Math.min(1, Number(st.progress) || 0));
63
+ const elapsed = Math.max(0, now - startAt);
64
+ // Approach 100% over the target window, but cap below 1 until done
65
+ const target = Math.max(0, Math.min(0.99, elapsed / FALLBACK_TOTAL_MS));
66
+ const merged = Math.max(cur, target);
67
+ if (merged > cur + 0.005 && merged <= 1) {
68
+ next[id] = { ...st, progress: merged };
69
+ changed = true;
70
+ }
71
+ }
72
+ }
73
+ return changed ? next : prev;
74
+ });
75
+ }, 200);
76
+ return () => clearInterval(interval);
77
+ }, [fallbackStarts]);
78
+ // Validation
79
+ const [validation, setValidation] = useState(undefined);
80
+ // Selection (mirror workbench selectionChanged)
81
+ const [selectedNodeId, setSelectedNodeId] = useState();
82
+ const [selectedEdgeId, setSelectedEdgeId] = useState();
83
+ const setSelection = useCallback((sel) => wb.setSelection(sel), [wb]);
84
+ // Ticks
85
+ const graphTick = useWorkbenchGraphTick(wb);
86
+ const graphUiTick = useWorkbenchGraphUiTick(wb);
87
+ const versionTick = useWorkbenchVersionTick(runner);
88
+ const valuesTick = versionTick + graphTick + graphUiTick;
89
+ useEffect(() => {
90
+ const offRunnerStatus = runner.on("status", (status) => {
91
+ if (status.runMode) {
92
+ setRunModeState(status.runMode);
93
+ }
94
+ });
95
+ return () => {
96
+ offRunnerStatus();
97
+ };
98
+ }, [runner]);
99
+ const handlesMap = useMemo(() => {
100
+ const out = {};
101
+ for (const n of wb.def.nodes) {
102
+ out[n.nodeId] = computeEffectiveHandles(n, wb.registry);
103
+ }
104
+ return out;
105
+ }, [wb, wb.def, graphTick, wb.registry, registryVersion]);
106
+ // Def and IO values
107
+ const inputsMap = useMemo(() => runner.getInputs(wb.def), [runner, wb, wb.def, valuesTick]);
108
+ const inputDefaultsMap = useMemo(() => runner.getInputDefaults(wb.def), [runner, wb, wb.def, valuesTick]);
109
+ const outputsMap = useMemo(() => runner.getOutputs(wb.def), [runner, wb, wb.def, valuesTick]);
110
+ const outputTypesMap = useMemo(() => {
111
+ const out = {};
112
+ // Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
113
+ for (const n of wb.def.nodes) {
114
+ const outputsDecl = handlesMap[n.nodeId]?.outputs ?? {};
115
+ const handles = Object.keys(outputsDecl);
116
+ const cur = {};
117
+ for (const h of handles) {
118
+ const v = outputsMap[n.nodeId]?.[h];
119
+ const declared = outputsDecl[h];
120
+ const { typeId } = resolveOutputDisplay(v, declared);
121
+ cur[h] = typeId;
122
+ }
123
+ if (Object.keys(cur).length > 0)
124
+ out[n.nodeId] = cur;
125
+ }
126
+ return out;
127
+ }, [wb, wb.def, outputsMap, handlesMap]);
128
+ // Initialize nodes and derive invalidated status from persisted metadata
129
+ useEffect(() => {
130
+ const workbenchRuntimeState = wb.getRuntimeState() ?? { nodes: {} };
131
+ setNodeStatus((prev) => {
132
+ const next = { ...prev };
133
+ const metadata = workbenchRuntimeState;
134
+ for (const n of wb.def.nodes) {
135
+ const cur = next[n.nodeId] ?? (next[n.nodeId] = {});
136
+ const nodeMeta = metadata.nodes[n.nodeId];
137
+ const updates = {};
138
+ if (cur.invalidated === undefined) {
139
+ updates.invalidated = computeInvalidatedFromMetadata(nodeMeta);
140
+ }
141
+ if (cur.activeRunIds === undefined) {
142
+ updates.activeRunIds = [];
143
+ }
144
+ if (cur.activeRuns === undefined) {
145
+ updates.activeRuns = 0;
146
+ }
147
+ if (nodeMeta?.lastErrorSummary && cur.lastError === undefined) {
148
+ updates.lastError = nodeMeta.lastErrorSummary;
149
+ }
150
+ if (Object.keys(updates).length > 0) {
151
+ next[n.nodeId] = { ...cur, ...updates };
152
+ }
153
+ }
154
+ return next;
155
+ });
156
+ }, [wb.def, wb]);
157
+ // Auto layout (simple layered layout)
158
+ const runAutoLayout = useCallback(() => {
159
+ // Build DAG layers by indegree
160
+ const indegree = {};
161
+ const adj = {};
162
+ for (const n of wb.def.nodes) {
163
+ indegree[n.nodeId] = 0;
164
+ adj[n.nodeId] = [];
165
+ }
166
+ for (const e of wb.def.edges) {
167
+ indegree[e.target.nodeId] = (indegree[e.target.nodeId] ?? 0) + 1;
168
+ adj[e.source.nodeId].push(e.target.nodeId);
169
+ }
170
+ const q = Object.keys(indegree).filter((k) => indegree[k] === 0);
171
+ const layers = [];
172
+ while (q.length) {
173
+ const layer = [];
174
+ const next = [];
175
+ for (const id of q) {
176
+ layer.push(id);
177
+ for (const nb of adj[id]) {
178
+ indegree[nb] -= 1;
179
+ if (indegree[nb] === 0)
180
+ next.push(nb);
181
+ }
182
+ }
183
+ layers.push(layer);
184
+ q.splice(0, q.length, ...next);
185
+ }
186
+ // Size-aware placement: columns by layer, stacking nodes vertically in each column
187
+ // Use the same sizing heuristic as mapping via estimateNodeSize
188
+ const H_GAP = 160;
189
+ const V_GAP = 24;
190
+ const pos = {};
191
+ let curX = 0;
192
+ for (const layer of layers) {
193
+ // Compute max width in this layer and individual heights
194
+ let maxWidth = 0;
195
+ const heights = {};
196
+ for (const id of layer) {
197
+ const node = wb.def.nodes.find((n) => n.nodeId === id);
198
+ if (!node)
199
+ continue;
200
+ // Prefer showValues sizing similar to node rendering
201
+ // Consider per-type overrides when available via UI
202
+ const overrideSize = overrides?.getDefaultNodeSize?.(node.typeId) ?? undefined;
203
+ const size = estimateNodeSize({
204
+ node,
205
+ registry: wb.registry,
206
+ showValues: true,
207
+ overrides: overrideSize,
208
+ });
209
+ heights[id] = size.height;
210
+ if (size.width > maxWidth)
211
+ maxWidth = size.width;
212
+ }
213
+ // Place nodes in this column
214
+ let curY = 0;
215
+ for (const id of layer) {
216
+ const h = heights[id] ?? 0;
217
+ pos[id] = { x: curX, y: curY };
218
+ curY += h + V_GAP;
219
+ }
220
+ curX += maxWidth + H_GAP;
221
+ }
222
+ wb.setPositions(pos, { commit: true, reason: "auto-layout" });
223
+ }, [wb, wb.def, wb.registry, registryVersion, overrides?.getDefaultNodeSize]);
224
+ const updateEdgeType = useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
225
+ const triggerExternal = useCallback((nodeId, event) => runner.triggerExternal(nodeId, event), [runner]);
226
+ const getNodeDisplayName = useCallback((nodeId) => {
227
+ const customName = wb.getNodeName(nodeId);
228
+ if (customName)
229
+ return customName;
230
+ const node = wb.def.nodes.find((n) => n.nodeId === nodeId);
231
+ if (!node)
232
+ return nodeId;
233
+ const desc = wb.registry.nodes.get(node.typeId);
234
+ return desc?.displayName || node.typeId;
235
+ }, [wb, wb.registry, registryVersion]);
236
+ const setNodeName = useCallback((nodeId, name) => {
237
+ wb.setNodeName(nodeId, name, { commit: true, reason: "rename-node" });
238
+ }, [wb]);
239
+ // Helper to save runtime metadata and UI state to extData
240
+ const saveUiRuntimeMetadata = useCallback(async (workbench, graphRunner) => {
241
+ try {
242
+ const currentRuntime = workbench.getRuntimeState() ?? { nodes: {} };
243
+ const runtimeMetaData = { nodes: { ...currentRuntime.nodes } };
244
+ const customMetaData = workbench.getCustomData();
245
+ // Clean up metadata for nodes that no longer exist
246
+ const nodeIds = new Set(workbench.def.nodes.map((n) => n.nodeId));
247
+ for (const nodeId of Object.keys(runtimeMetaData.nodes)) {
248
+ if (!nodeIds.has(nodeId)) {
249
+ delete runtimeMetaData.nodes[nodeId];
250
+ }
251
+ }
252
+ // Save cleaned metadata to workbench state
253
+ workbench.setRuntimeState(runtimeMetaData);
254
+ const fullUiState = workbench.getUIState();
255
+ const uiWithoutViewport = excludeViewportFromUIState(fullUiState);
256
+ // Use updateExtData for efficient batched partial updates
257
+ const updates = [];
258
+ if (Object.keys(uiWithoutViewport || {}).length > 0) {
259
+ updates.push({ path: "ui", value: uiWithoutViewport });
260
+ }
261
+ updates.push({ path: "runtime", value: runtimeMetaData });
262
+ updates.push({ path: "custom", value: customMetaData });
263
+ await graphRunner.updateExtData(updates);
264
+ }
265
+ catch (err) {
266
+ console.warn("[WorkbenchContext] Failed to save runtime metadata:", err);
267
+ }
268
+ }, []);
269
+ // Subscribe to runner/workbench events
270
+ useEffect(() => {
271
+ const add = (source, type) => (payload) => setEvents((prev) => {
272
+ if (source === "workbench" && (type === "graphChanged" || type === "graphUiChanged")) {
273
+ const changeType = payload.change?.type;
274
+ if (changeType === "moveNode" || changeType === "moveNodes" || changeType === "resizeNodes")
275
+ return prev;
276
+ }
277
+ const nextNo = prev.length > 0 ? (prev[0]?.no ?? 0) + 1 : 1;
278
+ const next = [
279
+ {
280
+ no: nextNo,
281
+ at: Date.now(),
282
+ source,
283
+ type,
284
+ payload: structuredClone(payload),
285
+ },
286
+ ...prev,
287
+ ];
288
+ return next.length > 200 ? next.slice(0, 200) : next;
289
+ });
290
+ // Helper to apply resolved handles from event payload to workbench
291
+ const applyResolvedHandles = (resolvedHandles) => {
292
+ for (const n of wb.def.nodes) {
293
+ const updated = resolvedHandles[n.nodeId];
294
+ if (updated) {
295
+ const before = JSON.stringify(n.resolvedHandles || {});
296
+ const after = JSON.stringify(updated);
297
+ if (before !== after) {
298
+ wb.updateResolvedHandles(n.nodeId, updated, { dry: true });
299
+ }
300
+ }
301
+ }
302
+ };
303
+ const offRunnerValue = runner.on("value", (e) => {
304
+ if (e?.dry)
305
+ return;
306
+ const now = Date.now();
307
+ if (e?.io === "input") {
308
+ const nodeId = e.nodeId;
309
+ const handle = e.handle;
310
+ // Track input timestamp in workbench runtime state
311
+ wb.updateNodeRuntimeMetadata(nodeId, (nodeMeta) => ({
312
+ ...nodeMeta,
313
+ lastInputAt: {
314
+ ...(nodeMeta.lastInputAt ?? {}),
315
+ [handle]: now,
316
+ },
317
+ }));
318
+ // Clear validation errors for this input when a valid value is set
319
+ setInputValidationErrors((prev) => prev.filter((err) => !(err.nodeId === nodeId && err.handle === handle)));
320
+ }
321
+ else if (e?.io === "output") {
322
+ const nodeId = e.nodeId;
323
+ const handle = e.handle;
324
+ // Track output timestamp in workbench runtime state
325
+ wb.updateNodeRuntimeMetadata(nodeId, (nodeMeta) => ({
326
+ ...nodeMeta,
327
+ lastOutputAt: {
328
+ ...(nodeMeta.lastOutputAt ?? {}),
329
+ [handle]: now,
330
+ },
331
+ }));
332
+ }
333
+ return add("runner", "value")(e);
334
+ });
335
+ const offRunnerError = runner.on("error", (e) => {
336
+ const edgeError = e;
337
+ const nodeError = e;
338
+ const registryError = e;
339
+ const systemError = e;
340
+ const inputValidationError = e;
341
+ if (edgeError.kind === "edge-convert") {
342
+ const edgeId = edgeError.edgeId;
343
+ setEdgeStatus((s) => ({
344
+ ...s,
345
+ [edgeId]: { ...s[edgeId], lastError: edgeError.err },
346
+ }));
347
+ }
348
+ else if (nodeError.kind === "node-run" && nodeError.nodeId) {
349
+ const nodeId = nodeError.nodeId;
350
+ const runId = nodeError.runId;
351
+ const now = Date.now();
352
+ // Track error timestamp and summary in workbench runtime state
353
+ const err = nodeError.err;
354
+ let errorSummary;
355
+ if (err && typeof err === "object") {
356
+ const errAny = err;
357
+ const message = errAny.userMessage || errAny.message || String(err);
358
+ const code = errAny.statusCode || errAny.code;
359
+ errorSummary = {
360
+ message: typeof message === "string" ? message : String(message),
361
+ code: typeof code === "number" ? code : undefined,
362
+ };
363
+ }
364
+ wb.updateNodeRuntimeMetadata(nodeId, (nodeMeta) => ({
365
+ ...nodeMeta,
366
+ lastErrorAt: now,
367
+ lastRunAt: now,
368
+ ...(errorSummary ? { lastErrorSummary: errorSummary } : {}),
369
+ }));
370
+ setNodeStatus((s) => ({
371
+ ...s,
372
+ [nodeId]: {
373
+ ...s[nodeId],
374
+ lastError: errorSummary,
375
+ },
376
+ }));
377
+ // Mark this runId as errored
378
+ if (runId) {
379
+ errorRunsRef.current[nodeId] = {
380
+ ...errorRunsRef.current[nodeId],
381
+ [runId]: true,
382
+ };
383
+ }
384
+ }
385
+ else if (registryError.kind === "registry") {
386
+ // Track registry errors for UI display
387
+ setRegistryErrors((prev) => {
388
+ // Avoid duplicates by checking message
389
+ if (prev.some((err) => err.message === registryError.message)) {
390
+ return prev;
391
+ }
392
+ return [...prev, registryError];
393
+ });
394
+ }
395
+ else if (inputValidationError.kind === "input-validation") {
396
+ // Track input validation errors for UI display
397
+ setInputValidationErrors((prev) => {
398
+ // Avoid duplicates by checking nodeId, handle, and typeId
399
+ if (prev.some((err) => err.nodeId === inputValidationError.nodeId &&
400
+ err.handle === inputValidationError.handle &&
401
+ err.typeId === inputValidationError.typeId)) {
402
+ return prev;
403
+ }
404
+ return [...prev, inputValidationError];
405
+ });
406
+ }
407
+ else if (systemError.kind === "system") {
408
+ // Track custom errors for UI display
409
+ setSystemErrors((prev) => {
410
+ // Avoid duplicates by checking message and code
411
+ if (prev.some((err) => err.message === systemError.message && err.code === systemError.code)) {
412
+ return prev;
413
+ }
414
+ return [...prev, systemError];
415
+ });
416
+ }
417
+ return add("runner", "error")(e);
418
+ });
419
+ const offRunnerInvalidate = runner.on("invalidate", (e) => {
420
+ // If resolvedHandles are included in the event, use them directly (more efficient)
421
+ if (e?.resolvedHandles && Object.keys(e.resolvedHandles).length > 0) {
422
+ applyResolvedHandles(e.resolvedHandles);
423
+ }
424
+ return add("runner", "invalidate")(e);
425
+ });
426
+ const offRunnerStats = runner.on("stats", (s) => {
427
+ if (!s)
428
+ return;
429
+ if (s.kind === "node-start") {
430
+ const id = s.nodeId;
431
+ const runId = s.runId;
432
+ const now = Date.now();
433
+ // Track run timestamp in workbench runtime state
434
+ wb.updateNodeRuntimeMetadata(id, (nodeMeta) => ({
435
+ ...nodeMeta,
436
+ lastRunAt: now,
437
+ }));
438
+ // Validate runId is a non-empty string
439
+ const isValidRunId = runId && typeof runId === "string" && runId.length > 0;
440
+ if (!isValidRunId) {
441
+ console.warn(`[WorkbenchContext] node-start event missing or invalid runId for node ${id}`, {
442
+ runId,
443
+ event: s,
444
+ });
445
+ }
446
+ setNodeStatus((prev) => {
447
+ const current = prev[id]?.activeRuns ?? 0;
448
+ const currentRunIds = prev[id]?.activeRunIds ?? [];
449
+ return {
450
+ ...prev,
451
+ [id]: {
452
+ ...prev[id],
453
+ activeRuns: current + 1,
454
+ activeRunIds: isValidRunId ? [...currentRunIds, runId] : currentRunIds,
455
+ progress: 0,
456
+ },
457
+ };
458
+ });
459
+ // Start fallback animation window
460
+ setFallbackStarts((prev) => ({ ...prev, [id]: now }));
461
+ }
462
+ else if (s.kind === "node-custom-data") {
463
+ const id = s.nodeId;
464
+ wb.setCustomNodeData(id, s.data, { commit: false, merge: true });
465
+ }
466
+ else if (s.kind === "node-progress") {
467
+ const id = s.nodeId;
468
+ setNodeStatus((prev) => ({
469
+ ...prev,
470
+ [id]: {
471
+ ...prev[id],
472
+ progress: Number(s.progress) || 0,
473
+ },
474
+ }));
475
+ }
476
+ else if (s.kind === "node-done") {
477
+ const id = s.nodeId;
478
+ const runId = s.runId;
479
+ const now = Date.now();
480
+ // Validate runId is a non-empty string
481
+ const isValidRunId = runId && typeof runId === "string" && runId.length > 0;
482
+ const hadError = !!(isValidRunId && errorRunsRef.current[id]?.[runId]);
483
+ // Track success timestamp if no error in workbench runtime state
484
+ if (!hadError) {
485
+ wb.updateNodeRuntimeMetadata(id, (nodeMeta) => {
486
+ const updated = { ...nodeMeta, lastSuccessAt: now };
487
+ // Clear error summary on success
488
+ if (updated.lastErrorSummary) {
489
+ delete updated.lastErrorSummary;
490
+ }
491
+ return updated;
492
+ });
493
+ }
494
+ setNodeStatus((prev) => {
495
+ const current = prev[id]?.activeRuns ?? 0;
496
+ const currentRunIds = prev[id]?.activeRunIds ?? [];
497
+ if (isValidRunId && !currentRunIds.includes(runId)) {
498
+ console.warn(`[WorkbenchContext] node-done event for unknown runId: node=${id} runId=${runId}`, {
499
+ event: s,
500
+ currentRunIds,
501
+ });
502
+ return prev; // Ignore stale event
503
+ }
504
+ const nextActive = Math.max(0, current - 1); // Prevent negative values
505
+ const nextRunIds = isValidRunId ? currentRunIds.filter((rid) => rid !== runId) : currentRunIds;
506
+ const keepProgress = hadError || nextActive > 0;
507
+ // Clear error flag for this runId
508
+ if (isValidRunId && errorRunsRef.current[id]?.[runId]) {
509
+ delete errorRunsRef.current[id]?.[runId];
510
+ }
511
+ return {
512
+ ...prev,
513
+ [id]: {
514
+ ...prev[id],
515
+ activeRuns: nextActive,
516
+ activeRunIds: nextRunIds,
517
+ progress: keepProgress ? prev[id]?.progress : 0,
518
+ lastError: hadError ? prev[id]?.lastError : undefined,
519
+ },
520
+ };
521
+ });
522
+ // Clear fallback start timestamp if no more active runs
523
+ setFallbackStarts((prev) => {
524
+ const nextPrev = { ...prev };
525
+ // If we don't know nextActive here, conservatively clear to stop animation
526
+ delete nextPrev[id];
527
+ return nextPrev;
528
+ });
529
+ }
530
+ else if (s.kind === "edge-start") {
531
+ const id = s.edgeId;
532
+ setEdgeStatus((prev) => {
533
+ const current = prev[id]?.activeRuns ?? 0;
534
+ return {
535
+ ...prev,
536
+ [id]: { ...prev[id], activeRuns: current + 1 },
537
+ };
538
+ });
539
+ }
540
+ else if (s.kind === "edge-done") {
541
+ const id = s.edgeId;
542
+ setEdgeStatus((prev) => {
543
+ const current = prev[id]?.activeRuns ?? 0;
544
+ return {
545
+ ...prev,
546
+ [id]: { ...prev[id], activeRuns: Math.max(0, current - 1) }, // Prevent negative values
547
+ };
548
+ });
549
+ }
550
+ return add("runner", "stats")(s);
551
+ });
552
+ const offWbRegistryChanged = wb.on("registryChanged", (evt) => {
553
+ setRegistryVersion((v) => v + 1);
554
+ });
555
+ const offWbGraphChanged = wb.on("graphChanged", (event) => {
556
+ if (event.change?.type === "removeNode") {
557
+ const removedNodeId = event.change.nodeId;
558
+ setInputValidationErrors((prev) => prev.filter((err) => err.nodeId !== removedNodeId));
559
+ }
560
+ else if (event.change?.type === "batch") {
561
+ const removedIds = event.change.changes
562
+ .filter((c) => c.type === "removeNode")
563
+ .map((c) => c.nodeId);
564
+ if (removedIds.length > 0) {
565
+ setInputValidationErrors((prev) => prev.filter((err) => !removedIds.includes(err.nodeId)));
566
+ }
567
+ }
568
+ return add("workbench", "graphChanged")(event);
569
+ });
570
+ const offWbGraphUiChangedForLog = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
571
+ const offWbValidationChanged = wb.on("validationChanged", add("workbench", "validationChanged"));
572
+ // Ensure newly added nodes start as invalidated until first evaluation
573
+ const GRAPH_REASON_BY_CHANGE = {
574
+ addNode: "add-node",
575
+ removeNode: "remove-node",
576
+ connect: "connect-edge",
577
+ disconnect: "disconnect-edge",
578
+ updateParams: "update-node-params",
579
+ updateEdgeType: "update-edge-type",
580
+ setInputs: "set-inputs",
581
+ updateResolvedHandles: "update-resolved-handles",
582
+ };
583
+ const getGraphReason = (event) => {
584
+ const changeType = event.change?.type;
585
+ return (changeType && GRAPH_REASON_BY_CHANGE[changeType]) || "graph-changed";
586
+ };
587
+ const commitGraphChange = async (shouldCommit, reason, errorPrefix) => {
588
+ if (!shouldCommit)
589
+ return;
590
+ await saveUiRuntimeMetadata(wb, runner);
591
+ const history = await runner.commit(reason).catch((err) => {
592
+ console.error(errorPrefix, err);
593
+ return undefined;
594
+ });
595
+ if (history)
596
+ wb.setHistory(history);
597
+ };
598
+ const applyAddNodeEffects = async (nodeId, inputs, copyOutputsFrom, dry) => {
599
+ if (inputs) {
600
+ await runner.setInputs(nodeId, inputs, { dry });
601
+ }
602
+ if (copyOutputsFrom) {
603
+ await runner.copyOutputs(copyOutputsFrom, nodeId, { dry });
604
+ }
605
+ };
606
+ const applyBatchGraphChange = async (event) => {
607
+ if (event.change?.type !== "batch")
608
+ return;
609
+ const { changes } = event.change;
610
+ const dry = !!event.dry;
611
+ if (!runner.isRunning())
612
+ return;
613
+ const hasNodeOps = changes.some((c) => c.type === "addNode" && (c.inputs || c.copyOutputsFrom));
614
+ await runner.update(event.def, { dry: dry || hasNodeOps });
615
+ for (const change of changes) {
616
+ if (change.type === "addNode") {
617
+ await applyAddNodeEffects(change.nodeId, change.inputs, change.copyOutputsFrom, dry);
618
+ }
619
+ else if (change.type === "setInputs") {
620
+ await runner.setInputs(change.nodeId, change.inputs, { dry });
621
+ }
622
+ }
623
+ };
624
+ const applySingleGraphChange = async (event) => {
625
+ if (event.change?.type === "batch")
626
+ return;
627
+ if (event.change?.type === "setInputs") {
628
+ const { nodeId, inputs } = event.change;
629
+ await runner.setInputs(nodeId, inputs, { dry: event.dry });
630
+ }
631
+ if (!runner.isRunning())
632
+ return;
633
+ if (event.change?.type === "addNode") {
634
+ const { nodeId, inputs, copyOutputsFrom } = event.change;
635
+ if (event.dry) {
636
+ await runner.update(event.def, { dry: true });
637
+ await applyAddNodeEffects(nodeId, inputs, copyOutputsFrom, true);
638
+ return;
639
+ }
640
+ await runner.update(event.def, { dry: !!inputs });
641
+ await applyAddNodeEffects(nodeId, inputs, undefined, false);
642
+ return;
643
+ }
644
+ if (event.change?.type !== "setInputs" && event.change?.type !== "updateResolvedHandles") {
645
+ await runner.update(event.def, { dry: event.dry });
646
+ }
647
+ };
648
+ const offWbGraphChangedForUpdate = wb.on("graphChanged", async (event) => {
649
+ try {
650
+ if (event.change?.type === "batch") {
651
+ await applyBatchGraphChange(event);
652
+ await commitGraphChange(event.commit, event.reason ?? "batch", "[WorkbenchContext] Error committing batch:");
653
+ return;
654
+ }
655
+ const reason = getGraphReason(event);
656
+ await applySingleGraphChange(event);
657
+ await commitGraphChange(event.commit, event.reason ?? reason, "[WorkbenchContext] Error committing:");
658
+ }
659
+ catch (err) {
660
+ console.error("[WorkbenchContext] Error processing graph change:", err);
661
+ }
662
+ });
663
+ const offWbSetValidation = wb.on("validationChanged", (r) => setValidation(r));
664
+ const offWbSelectionChanged = wb.on("selectionChanged", async (sel) => {
665
+ setSelectedNodeId(sel.nodes?.[0]);
666
+ setSelectedEdgeId(sel.edges?.[0]);
667
+ if (sel.commit) {
668
+ await saveUiRuntimeMetadata(wb, runner);
669
+ const history = await runner.commit(sel.reason ?? "selection").catch((err) => {
670
+ console.error("[WorkbenchContext] Error committing selection change:", err);
671
+ return undefined;
672
+ });
673
+ if (history) {
674
+ wb.setHistory(history);
675
+ }
676
+ }
677
+ });
678
+ const offWbGraphUiChanged = wb.on("graphUiChanged", async (event) => {
679
+ // Handle viewport changes separately (send via SetViewport command, not commit)
680
+ if (event.change?.type === "viewport") {
681
+ const viewport = wb.getViewport();
682
+ if (viewport && runner.setViewport) {
683
+ runner.setViewport(viewport).catch((err) => {
684
+ console.warn("[WorkbenchContext] Failed to send viewport update:", err);
685
+ });
686
+ }
687
+ return;
688
+ }
689
+ // Only commit if commit flag is true (e.g., drag end, not during dragging)
690
+ if (event.commit) {
691
+ // Build detailed reason from change type
692
+ let reason = "ui-changed";
693
+ if (event.change) {
694
+ const changeType = event.change.type;
695
+ if (changeType === "moveNode") {
696
+ reason = "move-node";
697
+ }
698
+ else if (changeType === "moveNodes") {
699
+ reason = "move-nodes";
700
+ }
701
+ else if (changeType === "resizeNodes") {
702
+ reason = "resize-nodes";
703
+ }
704
+ else if (changeType === "selection") {
705
+ reason = "selection";
706
+ }
707
+ else if (changeType === "customNodeData") {
708
+ reason = "custom-node-data";
709
+ }
710
+ else if (changeType === "customEdgeData") {
711
+ reason = "custom-edge-data";
712
+ }
713
+ else if (changeType === "customMetaData") {
714
+ reason = "custom-meta";
715
+ }
716
+ else if (changeType === "customData") {
717
+ reason = "custom";
718
+ }
719
+ }
720
+ await saveUiRuntimeMetadata(wb, runner);
721
+ const history = await runner.commit(event.reason ?? reason).catch((err) => {
722
+ console.error("[WorkbenchContext] Error committing UI changes:", err);
723
+ return undefined;
724
+ });
725
+ if (history) {
726
+ wb.setHistory(history);
727
+ }
728
+ }
729
+ });
730
+ const offWbError = wb.on("error", add("workbench", "error"));
731
+ // Registry updates: swap registry and refresh graph validation/UI
732
+ const offRunnerRegistry = runner.on("registry", async (registry) => {
733
+ wb.setRegistry(registry);
734
+ setRegistryReady(true);
735
+ });
736
+ const offFlowViewport = runner.on("viewport", (event) => {
737
+ const viewport = event.viewport;
738
+ if (isValidViewport(viewport)) {
739
+ wb.setViewport(viewport);
740
+ }
741
+ });
742
+ const offRunnerTransport = runner.on("transport", (t) => {
743
+ // Remote registry is only considered ready once we've received at least one "registry" event.
744
+ // Reset readiness whenever transport is not stable.
745
+ if (t.state === "local") {
746
+ setRegistryReady(true);
747
+ }
748
+ else if (t.state === "connecting" || t.state === "retrying") {
749
+ setRegistryReady(false);
750
+ }
751
+ else if (t.state === "disconnected") {
752
+ setRegistryReady(false);
753
+ }
754
+ if (t.state === "disconnected") {
755
+ console.info("[WorkbenchContext] Transport disconnected, resetting node status");
756
+ // Reinitialize node status with invalidated=true for all nodes
757
+ setNodeStatus(() => {
758
+ const next = {};
759
+ const metadata = wb.getRuntimeState() ?? { nodes: {} };
760
+ for (const n of wb.def.nodes) {
761
+ const nodeMeta = metadata.nodes[n.nodeId];
762
+ next[n.nodeId] = {
763
+ activeRuns: 0,
764
+ activeRunIds: [],
765
+ invalidated: computeInvalidatedFromMetadata(nodeMeta),
766
+ };
767
+ }
768
+ return next;
769
+ });
770
+ setEdgeStatus({});
771
+ setFallbackStarts({});
772
+ errorRunsRef.current = {};
773
+ }
774
+ });
775
+ const offWbRuntimeMetadataChanged = wb.on("runtimeMetadataChanged", (event) => {
776
+ const workbenchRuntimeState = wb.getRuntimeState() ?? { nodes: {} };
777
+ setNodeStatus((prev) => {
778
+ const next = { ...prev };
779
+ const metadata = workbenchRuntimeState;
780
+ let changed = false;
781
+ // If nodeId is specified, only update that node; otherwise update all nodes
782
+ const nodesToUpdate = event.nodeId ? wb.def.nodes.filter((n) => n.nodeId === event.nodeId) : wb.def.nodes;
783
+ for (const n of nodesToUpdate) {
784
+ const cur = next[n.nodeId];
785
+ if (!cur)
786
+ continue;
787
+ const nodeMeta = metadata.nodes[n.nodeId];
788
+ const newInvalidated = computeInvalidatedFromMetadata(nodeMeta);
789
+ if (cur.invalidated !== newInvalidated) {
790
+ next[n.nodeId] = { ...cur, invalidated: newInvalidated };
791
+ changed = true;
792
+ }
793
+ }
794
+ return changed ? next : prev;
795
+ });
796
+ });
797
+ wb.refreshValidation();
798
+ return () => {
799
+ offRunnerValue();
800
+ offRunnerError();
801
+ offRunnerInvalidate();
802
+ offRunnerStats();
803
+ offWbRegistryChanged();
804
+ offWbGraphChanged();
805
+ offWbGraphUiChangedForLog();
806
+ offWbGraphUiChanged();
807
+ offWbValidationChanged();
808
+ offWbError();
809
+ offWbGraphChangedForUpdate();
810
+ offWbSetValidation();
811
+ offWbSelectionChanged();
812
+ offRunnerRegistry();
813
+ offRunnerTransport();
814
+ offFlowViewport();
815
+ offWbRuntimeMetadataChanged();
816
+ };
817
+ }, [runner, wb]);
818
+ const isRunning = useCallback(() => runner.isRunning(), [runner]);
819
+ const setRunMode = useCallback((mode) => {
820
+ if (mode === runMode)
821
+ return;
822
+ runner.setRunMode(mode);
823
+ setRunModeState(mode);
824
+ }, [runMode, runner]);
825
+ const runNodeAction = useCallback(async (nodeId) => {
826
+ // Check if we're in auto mode or if node has autoRun policy
827
+ const nodeHasAutoRun = wb.shouldNodeAutoRun(nodeId);
828
+ const shouldUseTriggerExternal = runMode === "auto" || nodeHasAutoRun;
829
+ if (shouldUseTriggerExternal) {
830
+ // In auto mode or when node has autoRun, use triggerExternal with invalidate event
831
+ await runner.triggerExternal(nodeId, { action: "invalidate" });
832
+ }
833
+ else {
834
+ // In manual mode without autoRun, use computeNode
835
+ await runner.computeNode(nodeId);
836
+ }
837
+ }, [runner, wb, runMode]);
838
+ const runFromHereAction = useCallback(async (nodeId) => {
839
+ await runner.runFromHere(nodeId);
840
+ }, [runner]);
841
+ const abortNodeAction = useCallback((nodeId) => {
842
+ runner.cancelNodeRuns([nodeId]);
843
+ }, [runner]);
844
+ const validationByNode = useMemo(() => {
845
+ const inputs = {};
846
+ const outputs = {};
847
+ const issues = {};
848
+ if (!validation)
849
+ return { inputs, outputs, issues };
850
+ for (const is of validation.issues ?? []) {
851
+ const d = is.data;
852
+ const level = is.level;
853
+ const code = String(is.code ?? "");
854
+ const message = String(is.message ?? code);
855
+ if (!d)
856
+ continue;
857
+ if (d.nodeId) {
858
+ if (d.input) {
859
+ const arr = inputs[d.nodeId] ?? (inputs[d.nodeId] = []);
860
+ arr.push({ handle: String(d.input), level, message, code });
861
+ const nodeArr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
862
+ nodeArr.push(is);
863
+ }
864
+ if (d.output) {
865
+ const arr = outputs[d.nodeId] ?? (outputs[d.nodeId] = []);
866
+ arr.push({ handle: String(d.output), level, message, code });
867
+ const nodeArr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
868
+ nodeArr.push(is);
869
+ }
870
+ if (!d.input && !d.output) {
871
+ const arr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
872
+ arr.push(is);
873
+ }
874
+ }
875
+ }
876
+ return { inputs, outputs, issues };
877
+ }, [validation]);
878
+ const validationGlobal = useMemo(() => {
879
+ const list = [];
880
+ if (!validation)
881
+ return list;
882
+ for (const is of validation.issues ?? []) {
883
+ const d = is.data;
884
+ if (!d || (!d.nodeId && !d.edgeId)) {
885
+ list.push(is);
886
+ }
887
+ }
888
+ return list;
889
+ }, [validation]);
890
+ const validationByEdge = useMemo(() => {
891
+ const errors = {};
892
+ const issues = {};
893
+ if (!validation)
894
+ return { errors, issues };
895
+ for (const is of validation.issues ?? []) {
896
+ const d = is.data;
897
+ const level = is.level;
898
+ if (d?.edgeId) {
899
+ if (level === "error")
900
+ errors[d.edgeId] = true;
901
+ const arr = issues[d.edgeId] ?? (issues[d.edgeId] = []);
902
+ arr.push(is);
903
+ }
904
+ }
905
+ return { errors, issues };
906
+ }, [validation]);
907
+ const value = useMemo(() => ({
908
+ wb,
909
+ runner,
910
+ selectedNodeId,
911
+ selectedEdgeId,
912
+ setSelection,
913
+ nodeStatus,
914
+ edgeStatus,
915
+ valuesTick,
916
+ handlesMap,
917
+ inputsMap,
918
+ inputDefaultsMap,
919
+ outputsMap,
920
+ outputTypesMap,
921
+ validationByNode,
922
+ validationByEdge,
923
+ validationGlobal,
924
+ events,
925
+ clearEvents,
926
+ systemErrors,
927
+ registryErrors,
928
+ inputValidationErrors,
929
+ clearSystemErrors,
930
+ clearRegistryErrors,
931
+ clearInputValidationErrors,
932
+ removeSystemError,
933
+ removeRegistryError,
934
+ removeInputValidationError,
935
+ isRunning,
936
+ runMode,
937
+ setRunMode,
938
+ runNode: runNodeAction,
939
+ runFromHere: runFromHereAction,
940
+ abortNode: abortNodeAction,
941
+ runAutoLayout,
942
+ updateEdgeType,
943
+ triggerExternal,
944
+ uiVersion,
945
+ registryVersion,
946
+ registryReady,
947
+ overrides,
948
+ getNodeDisplayName,
949
+ setNodeName,
950
+ }), [
951
+ wb,
952
+ runner,
953
+ selectedNodeId,
954
+ selectedEdgeId,
955
+ setSelection,
956
+ nodeStatus,
957
+ edgeStatus,
958
+ valuesTick,
959
+ systemErrors,
960
+ registryErrors,
961
+ inputValidationErrors,
962
+ clearSystemErrors,
963
+ clearRegistryErrors,
964
+ clearInputValidationErrors,
965
+ removeSystemError,
966
+ removeRegistryError,
967
+ removeInputValidationError,
968
+ handlesMap,
969
+ inputsMap,
970
+ inputDefaultsMap,
971
+ outputsMap,
972
+ validationByNode,
973
+ validationByEdge,
974
+ validationGlobal,
975
+ events,
976
+ clearEvents,
977
+ isRunning,
978
+ runMode,
979
+ setRunMode,
980
+ runNodeAction,
981
+ runFromHereAction,
982
+ abortNodeAction,
983
+ runAutoLayout,
984
+ wb,
985
+ runner,
986
+ uiVersion,
987
+ registryVersion,
988
+ registryReady,
989
+ overrides,
990
+ getNodeDisplayName,
991
+ setNodeName,
992
+ ]);
993
+ return _jsx(WorkbenchContext.Provider, { value: value, children: children });
994
+ }
995
+ //# sourceMappingURL=WorkbenchContext.provider.js.map