@kiberon-labs/behave-graph-flow 1.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.fallowrc.json +16 -0
- package/.storybook/main.ts +32 -0
- package/.storybook/manager.ts +6 -0
- package/.storybook/preview.ts +64 -0
- package/.storybook/styles.css +16 -0
- package/.turbo/turbo-build.log +7 -0
- package/CHANGELOG.md +368 -0
- package/LICENSE +6 -0
- package/README.md +2 -2
- package/data/Polynomial.json +510 -0
- package/data/sequence.json +337 -0
- package/data/trigger-event.json +241 -0
- package/data/variable-change.json +210 -0
- package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
- package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
- package/dist/entry.css +4 -0
- package/dist/index.css +42 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +3597 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18009 -0
- package/dist/index.js.map +1 -0
- package/dist/noteImpl-KkrrWgJd.js +242 -0
- package/dist/noteImpl-KkrrWgJd.js.map +1 -0
- package/dist/styles.module-CvmpDkZj.css +3 -0
- package/dist/styles.module-CvmpDkZj.css.map +1 -0
- package/dist/styles.module-DZxg8aW9.js +271 -0
- package/dist/styles.module-DZxg8aW9.js.map +1 -0
- package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
- package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
- package/docs/notifications.md +246 -0
- package/docs/protocol.md +702 -0
- package/docs/specifics.md +191 -0
- package/package.json +82 -22
- package/postcss.config.ts +3 -4
- package/src/annotations/index.ts +32 -0
- package/src/components/FloatingToolbar/index.module.css +37 -0
- package/src/components/FloatingToolbar/index.tsx +256 -0
- package/src/components/Flow.tsx +287 -75
- package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
- package/src/components/contextMenus/NodePicker.module.css +274 -0
- package/src/components/contextMenus/NodePicker.tsx +481 -0
- package/src/components/contextMenus/edge.tsx +22 -0
- package/src/components/contextMenus/node.tsx +15 -0
- package/src/components/contextMenus/selection.tsx +11 -0
- package/src/components/controls/any/AnyControlImpl.tsx +14 -0
- package/src/components/controls/any/index.tsx +19 -0
- package/src/components/controls/boolean/index.tsx +13 -0
- package/src/components/controls/colorPicker/InputPopover.module.css +100 -0
- package/src/components/controls/colorPicker/InputPopover.tsx +31 -0
- package/src/components/controls/colorPicker/index.module.css +18 -0
- package/src/components/controls/colorPicker/index.tsx +61 -0
- package/src/components/controls/number/index.tsx +35 -0
- package/src/components/controls/string/index.tsx +16 -0
- package/src/components/edges/index.tsx +475 -0
- package/src/components/edges/offsetBezier.ts +134 -0
- package/src/components/hotKeys.tsx +20 -0
- package/src/components/layoutController/index.module.css +13 -0
- package/src/components/layoutController/index.tsx +140 -0
- package/src/components/layoutController/utils.ts +248 -0
- package/src/components/menubar/defaults.tsx +516 -0
- package/src/components/menubar/index.tsx +49 -0
- package/src/components/menubar/menuItem.module.css +31 -0
- package/src/components/menubar/menuItem.tsx +65 -0
- package/src/components/nodes/behave/Node.module.css +23 -0
- package/src/components/nodes/behave/Node.tsx +176 -0
- package/src/components/nodes/behave/NodeContainer.module.css +88 -0
- package/src/components/nodes/behave/NodeContainer.tsx +46 -0
- package/src/components/nodes/behave/index.tsx +14 -0
- package/src/components/nodes/group/index.tsx +109 -0
- package/src/components/nodes/wrapper/index.tsx +73 -0
- package/src/components/nodes/wrapper/styles.module.css +87 -0
- package/src/components/notifications/NotificationProvider.tsx +81 -0
- package/src/components/notifications/index.ts +2 -0
- package/src/components/notifications/utils.ts +71 -0
- package/src/components/panels/alignment/index.module.css +10 -0
- package/src/components/panels/alignment/index.tsx +244 -0
- package/src/components/panels/base/index.tsx +5 -0
- package/src/components/panels/base/styles.module.css +12 -0
- package/src/components/panels/common/PanelHeader.module.css +24 -0
- package/src/components/panels/common/PanelHeader.tsx +22 -0
- package/src/components/panels/common/SectionTitle.module.css +13 -0
- package/src/components/panels/common/SectionTitle.tsx +10 -0
- package/src/components/panels/events/EditEventPanel.tsx +324 -0
- package/src/components/panels/events/ManageEventsPanel.tsx +101 -0
- package/src/components/panels/events/index.tsx +23 -0
- package/src/components/panels/events/styles.module.css +178 -0
- package/src/components/panels/graphProperties/index.tsx +125 -0
- package/src/components/panels/history/index.tsx +92 -0
- package/src/components/panels/history/styles.module.css +97 -0
- package/src/components/panels/keymaps/index.module.css +68 -0
- package/src/components/panels/keymaps/index.tsx +166 -0
- package/src/components/panels/layers/index.tsx +245 -0
- package/src/components/panels/layers/styles.module.css +107 -0
- package/src/components/panels/legend/index.module.css +6 -0
- package/src/components/panels/legend/index.tsx +76 -0
- package/src/components/panels/logs/index.module.css +218 -0
- package/src/components/panels/logs/index.tsx +288 -0
- package/src/components/panels/nodeInputs/InputControl.tsx +63 -0
- package/src/components/panels/nodeInputs/InputsGroup.tsx +65 -0
- package/src/components/panels/nodeInputs/MultipleNodesView.tsx +37 -0
- package/src/components/panels/nodeInputs/NodeSettings.tsx +92 -0
- package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +125 -0
- package/src/components/panels/nodeInputs/OutputsGroup.tsx +55 -0
- package/src/components/panels/nodeInputs/SocketGenerators.tsx +32 -0
- package/src/components/panels/nodeInputs/index.module.css +308 -0
- package/src/components/panels/nodeInputs/index.tsx +349 -0
- package/src/components/panels/nodeInputs/useNodeHandlers.ts +76 -0
- package/src/components/panels/nodeInputs/useNodeInputsData.ts +153 -0
- package/src/components/panels/nodePicker/index.tsx +115 -0
- package/src/components/panels/panel/index.module.css +66 -0
- package/src/components/panels/panel/index.tsx +88 -0
- package/src/components/panels/search/index.module.css +16 -0
- package/src/components/panels/search/index.tsx +215 -0
- package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
- package/src/components/panels/systemSettings/index.tsx +251 -0
- package/src/components/panels/systemSettings/styles.module.css +138 -0
- package/src/components/panels/traces/GridLines.tsx +38 -0
- package/src/components/panels/traces/TimeGrid.tsx +48 -0
- package/src/components/panels/traces/TraceLane.tsx +62 -0
- package/src/components/panels/traces/TraceTooltip.tsx +22 -0
- package/src/components/panels/traces/TracesHeader.tsx +56 -0
- package/src/components/panels/traces/index.module.css +159 -0
- package/src/components/panels/traces/index.tsx +298 -0
- package/src/components/panels/traces/types.ts +48 -0
- package/src/components/panels/traces/useDerivedSpans.ts +307 -0
- package/src/components/panels/traces/utils.ts +33 -0
- package/src/components/panels/variables/CreateVariableScreen.tsx +162 -0
- package/src/components/panels/variables/ManageVariablesScreen.tsx +147 -0
- package/src/components/panels/variables/index.tsx +125 -0
- package/src/components/panels/variables/styles.module.css +149 -0
- package/src/components/primitives/icon.module.css +45 -0
- package/src/components/primitives/icon.tsx +38 -0
- package/src/components/sockets/input/index.tsx +83 -0
- package/src/components/sockets/input/styles.module.css +26 -0
- package/src/components/sockets/output/index.tsx +68 -0
- package/src/components/sockets/output/styles.module.css +22 -0
- package/src/css/notes.css +135 -0
- package/src/css/prosemirror.css +57 -0
- package/src/css/rc-dock.css +212 -0
- package/src/css/rc-menu.css +101 -0
- package/src/css/themes/kiberon.css +127 -0
- package/src/css/vars.css +198 -0
- package/src/css/vscode-elements.css +124 -0
- package/src/entry.css +4 -0
- package/src/generators/CallSubgraphGenerator.tsx +136 -0
- package/src/generators/CustomEventOnTriggeredGenerator.tsx +85 -0
- package/src/generators/GraphBoundaryGenerator.module.css +32 -0
- package/src/generators/GraphBoundaryGenerator.tsx +193 -0
- package/src/generators/SequenceGenerator.tsx +104 -0
- package/src/generators/SwitchOnIntegerGenerator.tsx +256 -0
- package/src/generators/SwitchOnStringGenerator.tsx +263 -0
- package/src/generators/callSubgraphSync.ts +126 -0
- package/src/generators/registerDefaultGenerators.ts +55 -0
- package/src/generators/registerDefaults.ts +26 -0
- package/src/hooks/useBehaveGraphFlow.ts +17 -16
- package/src/hooks/useFlowHandlers.ts +154 -30
- package/src/hooks/useWasdPan.ts +210 -0
- package/src/index.css +134 -0
- package/src/index.ts +53 -18
- package/src/manifest/contributionRegistry.ts +93 -0
- package/src/manifest/index.ts +4 -0
- package/src/manifest/loadManifest.ts +82 -0
- package/src/manifest/manifestPlugin.ts +29 -0
- package/src/manifest/passthroughValueType.ts +40 -0
- package/src/plugin/alignment/index.ts +91 -0
- package/src/plugin/autosave/controller.ts +366 -0
- package/src/plugin/autosave/index.tsx +114 -0
- package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
- package/src/plugin/autosave/panel/index.tsx +1 -0
- package/src/plugin/autosave/panel/styles.module.css +56 -0
- package/src/plugin/autosave/settings.ts +65 -0
- package/src/plugin/autosave/storage.ts +147 -0
- package/src/plugin/docs/index.tsx +297 -0
- package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
- package/src/plugin/docs/panel/index.tsx +21 -0
- package/src/plugin/docs/panel/styles.module.css +174 -0
- package/src/plugin/graphrunner/actions.ts +326 -0
- package/src/plugin/graphrunner/buttons.tsx +95 -0
- package/src/plugin/graphrunner/client.ts +707 -0
- package/src/plugin/graphrunner/index.tsx +184 -0
- package/src/plugin/graphrunner/panel.tsx +386 -0
- package/src/plugin/graphrunner/runController.ts +283 -0
- package/src/plugin/graphrunner/runner.ts +187 -0
- package/src/plugin/graphrunner/session.ts +243 -0
- package/src/plugin/graphrunner/store.ts +196 -0
- package/src/plugin/graphrunner/styles.module.css +171 -0
- package/src/plugin/graphrunner/transport.ts +250 -0
- package/src/plugin/graphrunner/types.ts +693 -0
- package/src/plugin/graphrunner-local/execution-utils.ts +637 -0
- package/src/plugin/graphrunner-local/index.tsx +172 -0
- package/src/plugin/graphrunner-local/panel.tsx +187 -0
- package/src/plugin/graphrunner-local/store.ts +41 -0
- package/src/plugin/graphrunner-local/styles.module.css +82 -0
- package/src/plugin/graphrunner-local/transport.ts +1339 -0
- package/src/plugin/graphrunner-local/types.ts +10 -0
- package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +635 -0
- package/src/plugin/graphrunner-webworker/index.tsx +140 -0
- package/src/plugin/graphrunner-webworker/panel.tsx +173 -0
- package/src/plugin/graphrunner-webworker/store.ts +98 -0
- package/src/plugin/graphrunner-webworker/worker-transport.ts +123 -0
- package/src/plugin/kitchen-sink/index.ts +38 -0
- package/src/plugin/layout/dagre.ts +131 -0
- package/src/plugin/layout/elk.ts +216 -0
- package/src/plugin/layout/index.ts +80 -0
- package/src/plugin/notes/FormatToolbar.tsx +200 -0
- package/src/plugin/notes/index.tsx +191 -0
- package/src/plugin/notes/nodeActions.ts +100 -0
- package/src/plugin/notes/note.tsx +20 -0
- package/src/plugin/notes/noteImpl.tsx +89 -0
- package/src/plugin/realtime/realtimeRunner.ts +624 -0
- package/src/specifics/CustomEventOnTriggeredSpecific.tsx +92 -0
- package/src/specifics/CustomEventTriggerSpecific.tsx +141 -0
- package/src/specifics/VariableGetSpecific.tsx +110 -0
- package/src/specifics/VariableSetSpecific.tsx +110 -0
- package/src/store/actions.tsx +698 -0
- package/src/store/commands.ts +278 -0
- package/src/store/contextMenu.ts +192 -0
- package/src/store/controls.tsx +62 -0
- package/src/store/conversions.ts +47 -0
- package/src/store/documentation.tsx +69 -0
- package/src/store/events.tsx +116 -0
- package/src/store/flow.tsx +230 -0
- package/src/store/graphMeta.ts +39 -0
- package/src/store/hotKeys.tsx +364 -0
- package/src/store/layers.ts +259 -0
- package/src/store/legend.tsx +76 -0
- package/src/store/logs.ts +28 -0
- package/src/store/menubar.ts +41 -0
- package/src/store/refs.ts +84 -0
- package/src/store/registry.ts +51 -0
- package/src/store/selection.ts +22 -0
- package/src/store/settings.ts +99 -0
- package/src/store/settingsSchema.ts +210 -0
- package/src/store/socketGenerator.tsx +54 -0
- package/src/store/specific.tsx +75 -0
- package/src/store/specs.tsx +35 -0
- package/src/store/tabs.ts +282 -0
- package/src/store/toolbar.tsx +45 -0
- package/src/store/traces.ts +240 -0
- package/src/store/variables.ts +37 -0
- package/src/system/graph.ts +131 -0
- package/src/system/graphSession.ts +172 -0
- package/src/system/index.ts +6 -0
- package/src/system/notifications.ts +111 -0
- package/src/system/persistence.ts +82 -0
- package/src/system/plugin.ts +55 -0
- package/src/system/provider.tsx +86 -0
- package/src/system/pubsub.ts +323 -0
- package/src/system/system.ts +653 -0
- package/src/system/tabLoader.tsx +303 -0
- package/src/system/undoRedo.ts +103 -0
- package/src/transformers/Uigraph.ts +61 -0
- package/src/transformers/behaveToFlow.ts +16 -4
- package/src/transformers/contract.ts +87 -0
- package/src/transformers/flowToBehave.ts +40 -12
- package/src/types/NodeMetadata.ts +27 -0
- package/src/types/graph.ts +49 -0
- package/src/types/nodes.ts +50 -0
- package/src/types.ts +18 -0
- package/src/util/autoConvert.ts +200 -0
- package/src/util/colors.ts +1 -29
- package/src/util/downloadJson.ts +18 -0
- package/src/util/extractNodeMetadata.ts +16 -0
- package/src/util/getPickerFilters.ts +1 -1
- package/src/util/isBehaveNode.ts +6 -0
- package/src/util/isValidConnection.ts +51 -17
- package/src/util/mergeSockets.ts +29 -0
- package/src/util/serializeVariables.ts +66 -0
- package/src/util/sockets.ts +43 -0
- package/stories/apex/layoutController/example-graph.worker.ts +39 -0
- package/stories/apex/layoutController/index.stories.tsx +48 -0
- package/stories/apex/layoutController/webworker.stories.tsx +103 -0
- package/stories/apex/menubar/menubar.stories.tsx +19 -0
- package/stories/components/colorpicker/index.stories.tsx +20 -0
- package/stories/components/contextMenus/edge.stories.tsx +32 -0
- package/stories/components/contextMenus/node.stories.tsx +26 -0
- package/stories/components/contextMenus/nodePicker.stories.tsx +115 -0
- package/stories/components/controls/any/index.stories.tsx +19 -0
- package/stories/components/controls/boolean/index.stories.tsx +19 -0
- package/stories/components/controls/colorPicker/index.stories.tsx +49 -0
- package/stories/components/controls/number/index.stories.tsx +19 -0
- package/stories/components/controls/string/index.stories.tsx +19 -0
- package/stories/components/nodes/behaveNode.stories.tsx +108 -0
- package/stories/components/panels/alignment.stories.tsx +24 -0
- package/stories/components/panels/events.stories.tsx +38 -0
- package/stories/components/panels/graphRunner.stories.tsx +317 -0
- package/stories/components/panels/history.stories.tsx +37 -0
- package/stories/components/panels/keymaps.stories.tsx +21 -0
- package/stories/components/panels/legend.stories.tsx +37 -0
- package/stories/components/panels/logs.stories.tsx +24 -0
- package/stories/components/panels/nodeInputs.stories.tsx +21 -0
- package/stories/components/panels/nodePicker.stories.tsx +37 -0
- package/stories/components/panels/panel.stories.tsx +39 -0
- package/stories/components/panels/search.stories.tsx +24 -0
- package/stories/components/panels/systemSettings.stories.tsx +26 -0
- package/stories/components/panels/traces.stories.tsx +225 -0
- package/stories/components/panels/variables.stories.tsx +24 -0
- package/stories/defaults/defaultStoryProvider.tsx +170 -0
- package/stories/defaults/systemGenerator.ts +43 -0
- package/stories/plugins/notes.stories.tsx +100 -0
- package/tests/autoConvert.test.ts +329 -0
- package/tests/autosavePlugin.test.ts +204 -0
- package/tests/callSubgraphSync.test.ts +148 -0
- package/tests/commandRegistry.test.ts +137 -0
- package/tests/components/edges/offsetBezier.test.ts +51 -0
- package/tests/components/layoutController/utils.test.ts +68 -0
- package/tests/components/panels/traces/utils.test.ts +52 -0
- package/tests/contract.test.ts +51 -0
- package/tests/contractSerialize.test.ts +62 -0
- package/tests/deriveSpans.test.ts +71 -0
- package/tests/flowToBehave.test.ts +27 -4
- package/tests/hotkeys.test.ts +79 -0
- package/tests/keepAliveLifecycle.test.ts +167 -0
- package/tests/loadManifest.test.ts +113 -0
- package/tests/noteMarkdown.test.ts +65 -0
- package/tests/notesPlugin.test.ts +162 -0
- package/tests/notifications.test.ts +87 -0
- package/tests/persistence.test.ts +51 -0
- package/tests/saveLoad.test.ts +373 -0
- package/tests/settings.test.ts +178 -0
- package/tests/traceStore.test.ts +46 -0
- package/tests/util/calculateNewEdge.test.ts +98 -0
- package/tests/util/getSocketsByNodeTypeAndHandleType.test.ts +31 -0
- package/tests/util/hasPositionMetaData.test.ts +33 -0
- package/tests/util/isBehaveNode.test.ts +22 -0
- package/tests/util/isHandleConnected.test.ts +37 -0
- package/tests/util/mergeSockets.test.ts +43 -0
- package/tests/visual/README.md +64 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-alignment-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-traces-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
- package/tests/visual/panels.visual.test.tsx +76 -0
- package/tests/wasdPan.test.ts +71 -0
- package/tsconfig.base.json +39 -0
- package/tsconfig.json +18 -59
- package/tsconfig.prod.json +23 -0
- package/tsdown.config.ts +15 -3
- package/typedoc.json +7 -7
- package/vite.config.js +7 -0
- package/vitest.config.ts +5 -2
- package/vitest.visual.config.ts +55 -0
- package/src/components/AutoSizeInput.tsx +0 -65
- package/src/components/Controls.tsx +0 -87
- package/src/components/InputSocket.tsx +0 -142
- package/src/components/Node.tsx +0 -68
- package/src/components/NodeContainer.tsx +0 -46
- package/src/components/NodePicker.tsx +0 -77
- package/src/components/OutputSocket.tsx +0 -58
- package/src/components/modals/ClearModal.tsx +0 -40
- package/src/components/modals/HelpModal.tsx +0 -36
- package/src/components/modals/LoadModal.tsx +0 -96
- package/src/components/modals/Modal.tsx +0 -64
- package/src/components/modals/SaveModal.tsx +0 -60
- package/src/hooks/useCustomNodeTypes.tsx +0 -31
- package/src/hooks/useGraphRunner.ts +0 -104
- package/src/hooks/useMergeMap.ts +0 -14
- package/src/hooks/useNodeSpecJson.ts +0 -20
- package/src/hooks/useQueriableDefinitions.ts +0 -22
- package/src/styles.css +0 -8
- package/tailwind.config.ts +0 -19
- package/tests/tsconfig.json +0 -10
- /package/src/{types.d.ts → types-declarations.d.ts} +0 -0
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local (in-browser) transport implementation for graph execution
|
|
3
|
+
* Executes graphs directly using the local Engine instead of a remote server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
GraphRunnerMessage,
|
|
8
|
+
RunStatus,
|
|
9
|
+
GraphRunnerCapabilities,
|
|
10
|
+
ServerVariable,
|
|
11
|
+
ServerEvent,
|
|
12
|
+
ServerGraphRunnerMessage,
|
|
13
|
+
RunGraphMessage,
|
|
14
|
+
HelloMessage,
|
|
15
|
+
CreateSessionMessage,
|
|
16
|
+
GetNodeTypesMessage,
|
|
17
|
+
GetStatusMessage,
|
|
18
|
+
CloseSessionMessage,
|
|
19
|
+
StopGraphMessage,
|
|
20
|
+
GetSocketConstraintsMessage,
|
|
21
|
+
AddNodeMessage,
|
|
22
|
+
RemoveNodeMessage,
|
|
23
|
+
UpdateSocketValueMessage,
|
|
24
|
+
UpdateNodeParamMessage,
|
|
25
|
+
CreateLinkMessage,
|
|
26
|
+
RemoveLinkMessage,
|
|
27
|
+
DirectExecuteNodeMessage
|
|
28
|
+
} from '../graphrunner/types.js';
|
|
29
|
+
import type {
|
|
30
|
+
ITransport,
|
|
31
|
+
IExecutionControl,
|
|
32
|
+
TransportState
|
|
33
|
+
} from '../graphrunner/transport.js';
|
|
34
|
+
import {
|
|
35
|
+
Engine,
|
|
36
|
+
type GraphInstance,
|
|
37
|
+
type ILifecycleEventEmitter,
|
|
38
|
+
readGraphFromJSON,
|
|
39
|
+
validateGraph,
|
|
40
|
+
DefaultLogger,
|
|
41
|
+
type ILogger,
|
|
42
|
+
Link,
|
|
43
|
+
makeGraphApi,
|
|
44
|
+
runSubgraph,
|
|
45
|
+
DEFAULT_SUBGRAPH_MAX_DEPTH
|
|
46
|
+
} from '@kiberon-labs/behave-graph';
|
|
47
|
+
import type {
|
|
48
|
+
IRegistry,
|
|
49
|
+
IGraphApi,
|
|
50
|
+
GraphJSON
|
|
51
|
+
} from '@kiberon-labs/behave-graph';
|
|
52
|
+
import type { StoreApi } from 'zustand';
|
|
53
|
+
import type { LocalGraphRunnerStore } from './store.js';
|
|
54
|
+
import { sleep } from '@kiberon-labs/behave-graph';
|
|
55
|
+
import {
|
|
56
|
+
setupTracing,
|
|
57
|
+
setupVariableChangeTracking,
|
|
58
|
+
prepareRegistryWithDependencies,
|
|
59
|
+
handleGetServerVariables,
|
|
60
|
+
handleGetServerEvents,
|
|
61
|
+
handleGetSocketConstraints,
|
|
62
|
+
handleGetNodeTypes,
|
|
63
|
+
executeGraphLifecycle,
|
|
64
|
+
type ActiveRun as BaseActiveRun,
|
|
65
|
+
type MessageContext
|
|
66
|
+
} from './execution-utils.js';
|
|
67
|
+
import {
|
|
68
|
+
SessionManager,
|
|
69
|
+
type Session,
|
|
70
|
+
type SessionConfig,
|
|
71
|
+
type SessionFactory
|
|
72
|
+
} from '../graphrunner/session.js';
|
|
73
|
+
import { createNode } from '@kiberon-labs/behave-graph';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Local run record. Extends the shared {@link BaseActiveRun} (used by both the
|
|
77
|
+
* local and worker runners) with the session + tick bookkeeping that only the
|
|
78
|
+
* local, interactively-controllable transport needs.
|
|
79
|
+
*/
|
|
80
|
+
interface ActiveRun extends BaseActiveRun {
|
|
81
|
+
sessionId: string;
|
|
82
|
+
maxTicks: number;
|
|
83
|
+
/** Whether this run finalizes when its flows drain (default: stay alive). */
|
|
84
|
+
autoEnd: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Local transport that executes graphs in the browser using the Engine
|
|
89
|
+
*/
|
|
90
|
+
export class LocalTransport implements ITransport, IExecutionControl {
|
|
91
|
+
private state: TransportState = 'disconnected';
|
|
92
|
+
private messageHandlers: Array<(message: ServerGraphRunnerMessage) => void> =
|
|
93
|
+
[];
|
|
94
|
+
private stateChangeHandlers: Array<(state: TransportState) => void> = [];
|
|
95
|
+
private errorHandlers: Array<(error: Error) => void> = [];
|
|
96
|
+
private registry: IRegistry;
|
|
97
|
+
private sessionManager: SessionManager;
|
|
98
|
+
private activeRuns = new Map<string, ActiveRun>();
|
|
99
|
+
private store: StoreApi<LocalGraphRunnerStore> | null = null;
|
|
100
|
+
private variables: ServerVariable[];
|
|
101
|
+
private serverEvents: ServerEvent[];
|
|
102
|
+
private resolveGraph?: (id: string) => GraphJSON | undefined;
|
|
103
|
+
|
|
104
|
+
constructor(
|
|
105
|
+
registry: IRegistry,
|
|
106
|
+
options?: {
|
|
107
|
+
store?: StoreApi<LocalGraphRunnerStore>;
|
|
108
|
+
variables?: ServerVariable[];
|
|
109
|
+
serverEvents?: ServerEvent[];
|
|
110
|
+
sessionFactory?: SessionFactory;
|
|
111
|
+
/**
|
|
112
|
+
* Resolve a referenced graph's JSON by id, enabling Call Subgraph nodes.
|
|
113
|
+
*/
|
|
114
|
+
resolveGraph?: (id: string) => GraphJSON | undefined;
|
|
115
|
+
}
|
|
116
|
+
) {
|
|
117
|
+
this.registry = registry;
|
|
118
|
+
this.store = options?.store ?? null;
|
|
119
|
+
this.variables = options?.variables ?? [];
|
|
120
|
+
this.serverEvents = options?.serverEvents ?? [];
|
|
121
|
+
this.sessionManager = new SessionManager(options?.sessionFactory);
|
|
122
|
+
this.resolveGraph = options?.resolveGraph;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create a logger that forwards log messages to the client
|
|
127
|
+
*/
|
|
128
|
+
private createTransportLogger(runId: string, graphId: string): ILogger {
|
|
129
|
+
const baseLogger =
|
|
130
|
+
(this.registry.dependencies?.ILogger as ILogger) || new DefaultLogger();
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
log: (severity: string, text: string) => {
|
|
134
|
+
baseLogger.log(severity as any, text);
|
|
135
|
+
this.notifyMessage({
|
|
136
|
+
type: 'log',
|
|
137
|
+
runId,
|
|
138
|
+
graphId,
|
|
139
|
+
level: severity,
|
|
140
|
+
message: text
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getState(): TransportState {
|
|
147
|
+
return this.state;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async connect(): Promise<void> {
|
|
151
|
+
this.setState('connected');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
disconnect(): void {
|
|
155
|
+
// Clean up all active runs
|
|
156
|
+
for (const run of this.activeRuns.values()) {
|
|
157
|
+
run.engine.dispose();
|
|
158
|
+
}
|
|
159
|
+
this.activeRuns.clear();
|
|
160
|
+
|
|
161
|
+
// Close all sessions
|
|
162
|
+
for (const session of this.sessionManager.getActiveSessions()) {
|
|
163
|
+
void this.sessionManager.closeSession(session.sessionId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.setState('disconnected');
|
|
167
|
+
this.updateStoreActiveRuns();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
send(message: GraphRunnerMessage): void {
|
|
171
|
+
// Handle messages synchronously in the browser
|
|
172
|
+
try {
|
|
173
|
+
this.handleMessage(message);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
this.notifyError(
|
|
176
|
+
error instanceof Error ? error : new Error(String(error))
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
onMessage(handler: (message: ServerGraphRunnerMessage) => void): void {
|
|
182
|
+
this.messageHandlers.push(handler);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onStateChange(handler: (state: TransportState) => void): void {
|
|
186
|
+
this.stateChangeHandlers.push(handler);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
onError(handler: (error: Error) => void): void {
|
|
190
|
+
this.errorHandlers.push(handler);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
removeAllHandlers(): void {
|
|
194
|
+
this.messageHandlers = [];
|
|
195
|
+
this.stateChangeHandlers = [];
|
|
196
|
+
this.errorHandlers = [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private setState(newState: TransportState): void {
|
|
200
|
+
this.state = newState;
|
|
201
|
+
this.stateChangeHandlers.forEach((handler) => handler(newState));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private notifyError(error: Error): void {
|
|
205
|
+
this.errorHandlers.forEach((handler) => handler(error));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private notifyMessage(message: ServerGraphRunnerMessage): void {
|
|
209
|
+
this.messageHandlers.forEach((handler) => handler(message));
|
|
210
|
+
}
|
|
211
|
+
updateStoreActiveRuns(): void {
|
|
212
|
+
if (this.store) {
|
|
213
|
+
this.store.getState().setActiveRuns(this.activeRuns.size);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private updateStoreExecutionState(
|
|
218
|
+
isExecuting: boolean,
|
|
219
|
+
isPaused: boolean
|
|
220
|
+
): void {
|
|
221
|
+
if (this.store) {
|
|
222
|
+
this.store.getState().setIsExecuting(isExecuting);
|
|
223
|
+
this.store.getState().setIsPaused(isPaused);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private getExecutionDelay(): number {
|
|
228
|
+
if (this.store) {
|
|
229
|
+
const { stepDelay, executionSpeed } = this.store.getState();
|
|
230
|
+
// Apply speed multiplier and step delay
|
|
231
|
+
return (
|
|
232
|
+
stepDelay + (executionSpeed < 1.0 ? (1.0 - executionSpeed) * 100 : 0)
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private getExecutionStepLimit(): number {
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private getTickInterval(): number {
|
|
243
|
+
if (this.store) {
|
|
244
|
+
return this.store.getState().tickInterval;
|
|
245
|
+
}
|
|
246
|
+
return 50; // Default 50ms
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get the default sleep-based tick strategy
|
|
251
|
+
*/
|
|
252
|
+
private createSleepTickStrategy(tickInterval: number): () => Promise<void> {
|
|
253
|
+
return async () => {
|
|
254
|
+
await sleep(tickInterval / 1000); // Convert ms to seconds
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private generateId(prefix: string): string {
|
|
259
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private handleMessage(message: GraphRunnerMessage): void {
|
|
263
|
+
switch (message.type) {
|
|
264
|
+
case 'hello':
|
|
265
|
+
this.handleHello(message);
|
|
266
|
+
break;
|
|
267
|
+
case 'createSession':
|
|
268
|
+
this.handleCreateSession(message);
|
|
269
|
+
break;
|
|
270
|
+
case 'getCapabilities':
|
|
271
|
+
this.handleGetCapabilities();
|
|
272
|
+
break;
|
|
273
|
+
case 'getServerVariables':
|
|
274
|
+
this.handleGetServerVariables(message);
|
|
275
|
+
break;
|
|
276
|
+
case 'getServerEvents':
|
|
277
|
+
this.handleGetServerEvents(message);
|
|
278
|
+
break;
|
|
279
|
+
case 'getSocketConstraints':
|
|
280
|
+
this.handleGetSocketConstraints(message);
|
|
281
|
+
break;
|
|
282
|
+
case 'getNodeTypes':
|
|
283
|
+
this.handleGetNodeTypes(message);
|
|
284
|
+
break;
|
|
285
|
+
case 'runGraph':
|
|
286
|
+
this.handleRunGraph(message);
|
|
287
|
+
break;
|
|
288
|
+
case 'stopGraph':
|
|
289
|
+
this.handleStopGraph(message);
|
|
290
|
+
break;
|
|
291
|
+
case 'getStatus':
|
|
292
|
+
this.handleGetStatus(message);
|
|
293
|
+
break;
|
|
294
|
+
case 'closeSession':
|
|
295
|
+
this.handleCloseSession(message);
|
|
296
|
+
break;
|
|
297
|
+
case 'addNode':
|
|
298
|
+
this.handleAddNode(message);
|
|
299
|
+
break;
|
|
300
|
+
case 'removeNode':
|
|
301
|
+
this.handleRemoveNode(message);
|
|
302
|
+
break;
|
|
303
|
+
case 'updateSocketValue':
|
|
304
|
+
this.handleUpdateSocketValue(message);
|
|
305
|
+
break;
|
|
306
|
+
case 'updateNodeParam':
|
|
307
|
+
this.handleUpdateNodeParam(message);
|
|
308
|
+
break;
|
|
309
|
+
case 'createLink':
|
|
310
|
+
this.handleCreateLink(message);
|
|
311
|
+
break;
|
|
312
|
+
case 'removeLink':
|
|
313
|
+
this.handleRemoveLink(message);
|
|
314
|
+
break;
|
|
315
|
+
case 'directExecuteNode':
|
|
316
|
+
this.handleDirectExecuteNode(message);
|
|
317
|
+
break;
|
|
318
|
+
default:
|
|
319
|
+
this.sendError(
|
|
320
|
+
'PROTOCOL_VIOLATION',
|
|
321
|
+
`Unsupported message type: ${(message as GraphRunnerMessage).type}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private handleHello(message: HelloMessage): void {
|
|
327
|
+
this.notifyMessage({
|
|
328
|
+
type: 'welcome',
|
|
329
|
+
protocolVersion: message.protocolVersion,
|
|
330
|
+
serverId: 'local-runner',
|
|
331
|
+
authenticated: true,
|
|
332
|
+
userId: 'local-user'
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private handleCreateSession(message: CreateSessionMessage): void {
|
|
337
|
+
const sessionId = this.generateId('session');
|
|
338
|
+
const sessionConfig: SessionConfig = {
|
|
339
|
+
metadata: message.metadata
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const session = this.sessionManager.createSession(sessionId, sessionConfig);
|
|
343
|
+
|
|
344
|
+
this.notifyMessage({
|
|
345
|
+
type: 'sessionCreated',
|
|
346
|
+
sessionId: session.sessionId,
|
|
347
|
+
expiresAt: session.expiresAt
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private handleGetCapabilities(): void {
|
|
352
|
+
// Get default capabilities, can be overridden by session
|
|
353
|
+
const capabilities: GraphRunnerCapabilities = {
|
|
354
|
+
trace: true,
|
|
355
|
+
validation: true,
|
|
356
|
+
graphRegistry: false,
|
|
357
|
+
eventFiltering: false,
|
|
358
|
+
batchOperations: false,
|
|
359
|
+
runHistory: false,
|
|
360
|
+
runtimeMetadata: true,
|
|
361
|
+
maxConcurrentRuns: 10,
|
|
362
|
+
realtime: true,
|
|
363
|
+
maxConcurrentDynamicRuns: 10,
|
|
364
|
+
updateGranularity: 'socket'
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
this.notifyMessage({
|
|
368
|
+
type: 'capabilities',
|
|
369
|
+
capabilities
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private handleGetServerVariables(_message: {
|
|
374
|
+
type: 'getServerVariables';
|
|
375
|
+
sessionId: string;
|
|
376
|
+
}): void {
|
|
377
|
+
handleGetServerVariables(this.variables, {
|
|
378
|
+
sendMessage: this.notifyMessage.bind(this),
|
|
379
|
+
sendError: (code, msg, details) => this.sendError(code, msg, details)
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private handleGetServerEvents(_message: {
|
|
384
|
+
type: 'getServerEvents';
|
|
385
|
+
sessionId: string;
|
|
386
|
+
}): void {
|
|
387
|
+
handleGetServerEvents(this.serverEvents, {
|
|
388
|
+
sendMessage: this.notifyMessage.bind(this),
|
|
389
|
+
sendError: (code, msg, details) => this.sendError(code, msg, details)
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private handleGetSocketConstraints(
|
|
394
|
+
message: GetSocketConstraintsMessage
|
|
395
|
+
): void {
|
|
396
|
+
handleGetSocketConstraints(
|
|
397
|
+
{ nodeType: message.nodeType, socketName: message.socketName },
|
|
398
|
+
this.registry,
|
|
399
|
+
{
|
|
400
|
+
sendMessage: this.notifyMessage.bind(this),
|
|
401
|
+
sendError: (code, msg, details) => this.sendError(code, msg, details)
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private handleGetNodeTypes(_message: GetNodeTypesMessage): void {
|
|
407
|
+
handleGetNodeTypes(this.registry, {
|
|
408
|
+
sendMessage: this.notifyMessage.bind(this),
|
|
409
|
+
sendError: (code, msg, details) => this.sendError(code, msg, details)
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private async handleRunGraph(message: RunGraphMessage): Promise<void> {
|
|
414
|
+
const runId = this.generateId('run');
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
// Get session for this run
|
|
418
|
+
const session = this.sessionManager.getSession(message.sessionId);
|
|
419
|
+
if (!session) {
|
|
420
|
+
this.sendError('SESSION_NOT_FOUND', 'Session not found', {
|
|
421
|
+
runId,
|
|
422
|
+
graphId: message.graphId
|
|
423
|
+
});
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!message.graph) {
|
|
428
|
+
this.sendError('INVALID_GRAPH', 'Graph not provided', {
|
|
429
|
+
runId,
|
|
430
|
+
graphId: message.graphId
|
|
431
|
+
});
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Create transport logger that forwards log messages to the client
|
|
436
|
+
const transportLogger = this.createTransportLogger(
|
|
437
|
+
runId,
|
|
438
|
+
message.graphId
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
// Ensure lifecycle event emitter and logger are available in registry
|
|
442
|
+
let registryToUse = this.registry;
|
|
443
|
+
|
|
444
|
+
// Apply session registry overrides if provided
|
|
445
|
+
if (session.config.registryOverrides) {
|
|
446
|
+
registryToUse = {
|
|
447
|
+
...registryToUse,
|
|
448
|
+
...session.config.registryOverrides
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Inject the lifecycle event emitter (if absent) and the forwarding logger
|
|
453
|
+
// , shared with the worker runner.
|
|
454
|
+
registryToUse = prepareRegistryWithDependencies(
|
|
455
|
+
registryToUse,
|
|
456
|
+
transportLogger
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
// Inject the subgraph resolver so Call Subgraph nodes can run referenced
|
|
460
|
+
// graphs. runSubgraph builds a cycle/depth-guarded IGraphApi for nested
|
|
461
|
+
// calls; the active graph id seeds the call stack so a graph calling back
|
|
462
|
+
// to the active graph (or itself) is detected as a cycle and refused.
|
|
463
|
+
if (this.resolveGraph) {
|
|
464
|
+
const resolveGraph = this.resolveGraph;
|
|
465
|
+
const activeGraphId = message.graphId;
|
|
466
|
+
const graphApi: IGraphApi = {
|
|
467
|
+
getGraph: (id) => resolveGraph(id),
|
|
468
|
+
runGraph: (id, inputs) => {
|
|
469
|
+
const childGraph = resolveGraph(id);
|
|
470
|
+
return childGraph
|
|
471
|
+
? runSubgraph({
|
|
472
|
+
graphJson: childGraph,
|
|
473
|
+
registry: registryToUse,
|
|
474
|
+
inputs,
|
|
475
|
+
resolveGraph,
|
|
476
|
+
graphId: id,
|
|
477
|
+
stack: [activeGraphId],
|
|
478
|
+
maxDepth: DEFAULT_SUBGRAPH_MAX_DEPTH
|
|
479
|
+
})
|
|
480
|
+
: Promise.resolve({});
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
registryToUse = {
|
|
484
|
+
...registryToUse,
|
|
485
|
+
dependencies: { ...registryToUse.dependencies, IGraphApi: graphApi }
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Parse graph with registry that has lifecycle event emitter
|
|
490
|
+
const graphInstance = readGraphFromJSON({
|
|
491
|
+
graphJson: message.graph,
|
|
492
|
+
registry: registryToUse
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Validate graph
|
|
496
|
+
const errors = validateGraph(graphInstance);
|
|
497
|
+
if (errors.length > 0) {
|
|
498
|
+
this.sendError('VALIDATION_FAILED', errors.join('; '), {
|
|
499
|
+
runId,
|
|
500
|
+
graphId: message.graphId
|
|
501
|
+
});
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Create engine - it will now have access to lifecycle event emitter through graph instance
|
|
506
|
+
const engine = new Engine(graphInstance, registryToUse);
|
|
507
|
+
|
|
508
|
+
// Merge execution options: message options override session defaults
|
|
509
|
+
const executionOptions = {
|
|
510
|
+
...session.config.defaultExecutionOptions,
|
|
511
|
+
...message.options
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// Create run record
|
|
515
|
+
const run: ActiveRun = {
|
|
516
|
+
runId,
|
|
517
|
+
sessionId: message.sessionId,
|
|
518
|
+
graphId: message.graphId,
|
|
519
|
+
|
|
520
|
+
engine,
|
|
521
|
+
graphInstance,
|
|
522
|
+
registry: registryToUse,
|
|
523
|
+
status: 'running',
|
|
524
|
+
startedAt: Date.now(),
|
|
525
|
+
performance: {
|
|
526
|
+
nodesExecuted: 0,
|
|
527
|
+
eventsEmitted: 0,
|
|
528
|
+
variableChanges: 0
|
|
529
|
+
},
|
|
530
|
+
isPaused: false,
|
|
531
|
+
executionPhase: 'start',
|
|
532
|
+
currentTick: 0,
|
|
533
|
+
maxTicks: Infinity, // No limit - tick events run until stopped
|
|
534
|
+
// Runs stay alive by default so event subscriptions (ai/onToolCall
|
|
535
|
+
// etc.) keep firing after the start flow drains; opting into autoEnd
|
|
536
|
+
// restores fire-and-forget completion.
|
|
537
|
+
autoEnd: executionOptions.autoEnd ?? false
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
this.activeRuns.set(runId, run);
|
|
541
|
+
this.sessionManager.addRunToSession(message.sessionId, runId);
|
|
542
|
+
|
|
543
|
+
// Call session hook for run started
|
|
544
|
+
if (session.config.hooks?.onRunStarted) {
|
|
545
|
+
await session.config.hooks.onRunStarted(
|
|
546
|
+
session,
|
|
547
|
+
runId,
|
|
548
|
+
message.graphId
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Send run started
|
|
553
|
+
this.notifyMessage({
|
|
554
|
+
type: 'runStarted',
|
|
555
|
+
runId,
|
|
556
|
+
graphId: message.graphId,
|
|
557
|
+
startedAt: run.startedAt
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Update store state
|
|
561
|
+
this.updateStoreActiveRuns();
|
|
562
|
+
this.updateStoreExecutionState(true, false);
|
|
563
|
+
|
|
564
|
+
// Set up variable change tracking
|
|
565
|
+
setupVariableChangeTracking(run, message.graphId, {
|
|
566
|
+
sendMessage: this.notifyMessage.bind(this),
|
|
567
|
+
sendError: (code, msg, details) => this.sendError(code, msg, details)
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Set up tracing , shared with the worker runner so the trace event shape
|
|
571
|
+
// and timestamps stay consistent across runners.
|
|
572
|
+
if (executionOptions.trace) {
|
|
573
|
+
setupTracing(run, message.graphId, {
|
|
574
|
+
sendMessage: this.notifyMessage.bind(this),
|
|
575
|
+
sendError: (code, msg, details) => this.sendError(code, msg, details)
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Execute graph asynchronously
|
|
580
|
+
this.executeGraph(run, message.graphId, run.autoEnd, session);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
const errorMessage =
|
|
583
|
+
error instanceof Error ? error.message : String(error);
|
|
584
|
+
|
|
585
|
+
// Get session for error hook
|
|
586
|
+
const session = this.sessionManager.getSession(message.sessionId);
|
|
587
|
+
if (session?.config.hooks?.onRunError) {
|
|
588
|
+
await session.config.hooks.onRunError(
|
|
589
|
+
session,
|
|
590
|
+
runId,
|
|
591
|
+
message.graphId,
|
|
592
|
+
error instanceof Error ? error : new Error(errorMessage)
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
this.sendError('NODE_EXECUTION_ERROR', errorMessage, {
|
|
597
|
+
runId,
|
|
598
|
+
graphId: message.graphId
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
private async executeGraph(
|
|
604
|
+
run: ActiveRun,
|
|
605
|
+
graphId: string,
|
|
606
|
+
autoEnd: boolean,
|
|
607
|
+
session: Session
|
|
608
|
+
): Promise<void> {
|
|
609
|
+
const ctx: MessageContext = {
|
|
610
|
+
sendMessage: this.notifyMessage.bind(this),
|
|
611
|
+
sendError: (code, message, details) =>
|
|
612
|
+
this.sendError(code, message, details)
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// Tick timing: the session's custom strategy if provided, else a sleep based
|
|
616
|
+
// on the configured tick interval.
|
|
617
|
+
const tickStrategy =
|
|
618
|
+
session.config.tickStrategy ||
|
|
619
|
+
this.createSleepTickStrategy(
|
|
620
|
+
session.config.executionSettings?.tickInterval ?? this.getTickInterval()
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
// Tear the run down and re-sync the panel's running / active-runs state.
|
|
624
|
+
const cleanup = (): void => {
|
|
625
|
+
this.activeRuns.delete(run.runId);
|
|
626
|
+
this.sessionManager.removeRunFromSession(run.sessionId, run.runId);
|
|
627
|
+
this.updateStoreActiveRuns();
|
|
628
|
+
this.updateStoreExecutionState(false, false);
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
await executeGraphLifecycle(run, graphId, ctx, {
|
|
633
|
+
autoEnd,
|
|
634
|
+
// Pause-aware executor that also honours the local step-delay / speed.
|
|
635
|
+
executeStep: () => this.executeWithPauseSupport(run),
|
|
636
|
+
tickStrategy,
|
|
637
|
+
onComplete: async () => {
|
|
638
|
+
if (session.config.hooks?.onRunCompleted) {
|
|
639
|
+
await session.config.hooks.onRunCompleted(
|
|
640
|
+
session,
|
|
641
|
+
run.runId,
|
|
642
|
+
graphId,
|
|
643
|
+
null
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
cleanup();
|
|
647
|
+
},
|
|
648
|
+
onError: async (error) => {
|
|
649
|
+
if (session.config.hooks?.onRunError) {
|
|
650
|
+
await session.config.hooks.onRunError(
|
|
651
|
+
session,
|
|
652
|
+
run.runId,
|
|
653
|
+
graphId,
|
|
654
|
+
error
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
this.sendError('NODE_EXECUTION_ERROR', error.message, {
|
|
658
|
+
runId: run.runId,
|
|
659
|
+
graphId
|
|
660
|
+
});
|
|
661
|
+
cleanup();
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
} catch {
|
|
665
|
+
// The error was already reported + cleaned up by the onError hook; this
|
|
666
|
+
// method is fire-and-forget, so swallow the lifecycle's rethrow.
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Execute engine with pause support - executes one step at a time with configurable delay
|
|
672
|
+
*/
|
|
673
|
+
private async executeWithPauseSupport(run: ActiveRun): Promise<void> {
|
|
674
|
+
const session = this.sessionManager.getSession(run.sessionId);
|
|
675
|
+
|
|
676
|
+
// Get settings from session, fallback to store
|
|
677
|
+
const stepDelay =
|
|
678
|
+
session?.config.executionSettings?.stepDelay ??
|
|
679
|
+
this.store?.getState().stepDelay ??
|
|
680
|
+
0;
|
|
681
|
+
const executionSpeed =
|
|
682
|
+
session?.config.executionSettings?.executionSpeed ??
|
|
683
|
+
this.store?.getState().executionSpeed ??
|
|
684
|
+
1.0;
|
|
685
|
+
|
|
686
|
+
const delay =
|
|
687
|
+
stepDelay + (executionSpeed < 1.0 ? (1.0 - executionSpeed) * 100 : 0);
|
|
688
|
+
|
|
689
|
+
// Full-speed path: no artificial delay requested, so drain in large chunks.
|
|
690
|
+
// The single-step limit below exists only to interleave the step delay; at
|
|
691
|
+
// full speed it forced an executeAllAsync call (time checks, promise churn)
|
|
692
|
+
// per node, which dominated the per-tick cost. Chunking keeps pause
|
|
693
|
+
// responsive (checked between chunks) while the engine's sync fast path
|
|
694
|
+
// runs unhindered within a chunk.
|
|
695
|
+
if (delay <= 0) {
|
|
696
|
+
const CHUNK_STEPS = 8192;
|
|
697
|
+
while (run.engine.hasPending() && !run.isPaused) {
|
|
698
|
+
await run.engine.executeAllAsync(5, CHUNK_STEPS);
|
|
699
|
+
}
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const stepLimit = this.getExecutionStepLimit();
|
|
704
|
+
|
|
705
|
+
// Slow-motion path: execute step by step, sleeping between steps.
|
|
706
|
+
while (run.engine.hasPending() && !run.isPaused) {
|
|
707
|
+
// Execute limited number of steps
|
|
708
|
+
await run.engine.executeAllAsync(5, stepLimit);
|
|
709
|
+
|
|
710
|
+
// Apply delay between successive calls
|
|
711
|
+
if (delay > 0 && run.engine.hasPending() && !run.isPaused) {
|
|
712
|
+
await sleep(delay / 1000);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Pause execution of a running graph
|
|
719
|
+
*/
|
|
720
|
+
public pauseExecution(runId: string): void {
|
|
721
|
+
const run = this.activeRuns.get(runId);
|
|
722
|
+
if (!run) {
|
|
723
|
+
this.updateStoreExecutionState(true, true);
|
|
724
|
+
throw new Error(`Run not found: ${runId}`);
|
|
725
|
+
}
|
|
726
|
+
run.isPaused = true;
|
|
727
|
+
run.status = 'running'; // Keep as running but paused
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Resume execution of a paused graph
|
|
732
|
+
*/
|
|
733
|
+
public async resumeExecution(runId: string): Promise<void> {
|
|
734
|
+
this.updateStoreExecutionState(true, false);
|
|
735
|
+
const run = this.activeRuns.get(runId);
|
|
736
|
+
if (!run) {
|
|
737
|
+
throw new Error(`Run not found: ${runId}`);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const session = this.sessionManager.getSession(run.sessionId);
|
|
741
|
+
if (!session) {
|
|
742
|
+
throw new Error(`Session not found: ${run.sessionId}`);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
run.isPaused = false;
|
|
746
|
+
// Continue execution from where we left off
|
|
747
|
+
await this.executeGraph(run, run.graphId, run.autoEnd, session);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Step forward one execution step
|
|
752
|
+
*/
|
|
753
|
+
public async stepExecution(runId: string): Promise<void> {
|
|
754
|
+
const run = this.activeRuns.get(runId);
|
|
755
|
+
if (!run) {
|
|
756
|
+
throw new Error(`Run not found: ${runId}`);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const eventEmitter = run.registry.dependencies?.ILifecycleEventEmitter as
|
|
760
|
+
| ILifecycleEventEmitter
|
|
761
|
+
| undefined;
|
|
762
|
+
|
|
763
|
+
// Execute one step based on current phase
|
|
764
|
+
if (run.executionPhase === 'start') {
|
|
765
|
+
if (
|
|
766
|
+
eventEmitter?.startEvent &&
|
|
767
|
+
eventEmitter.startEvent.listenerCount > 0
|
|
768
|
+
) {
|
|
769
|
+
eventEmitter.startEvent.emit();
|
|
770
|
+
}
|
|
771
|
+
// Execute one fiber step
|
|
772
|
+
await run.engine.executeAllSync(5, 1);
|
|
773
|
+
|
|
774
|
+
// Check if we should move to next phase
|
|
775
|
+
if (!run.engine.hasPending()) {
|
|
776
|
+
run.executionPhase = 'tick';
|
|
777
|
+
}
|
|
778
|
+
} else if (run.executionPhase === 'tick') {
|
|
779
|
+
if (run.currentTick < run.maxTicks) {
|
|
780
|
+
if (
|
|
781
|
+
eventEmitter?.tickEvent &&
|
|
782
|
+
eventEmitter.tickEvent.listenerCount > 0 &&
|
|
783
|
+
!run.engine.hasPending()
|
|
784
|
+
) {
|
|
785
|
+
eventEmitter.tickEvent.emit();
|
|
786
|
+
}
|
|
787
|
+
// Execute one fiber step
|
|
788
|
+
await run.engine.executeAllSync(5, 1);
|
|
789
|
+
|
|
790
|
+
// Check if current tick is done
|
|
791
|
+
if (!run.engine.hasPending()) {
|
|
792
|
+
run.currentTick++;
|
|
793
|
+
if (run.currentTick >= run.maxTicks) {
|
|
794
|
+
run.executionPhase = 'end';
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
} else {
|
|
798
|
+
run.executionPhase = 'end';
|
|
799
|
+
}
|
|
800
|
+
} else if (run.executionPhase === 'end') {
|
|
801
|
+
if (
|
|
802
|
+
eventEmitter?.endEvent &&
|
|
803
|
+
eventEmitter.endEvent.listenerCount > 0 &&
|
|
804
|
+
!run.engine.hasPending()
|
|
805
|
+
) {
|
|
806
|
+
eventEmitter.endEvent.emit();
|
|
807
|
+
}
|
|
808
|
+
// Execute one fiber step
|
|
809
|
+
await run.engine.executeAllSync(5, 1);
|
|
810
|
+
|
|
811
|
+
// Check if we're done
|
|
812
|
+
if (!run.engine.hasPending()) {
|
|
813
|
+
run.executionPhase = 'completed';
|
|
814
|
+
|
|
815
|
+
// Run completed successfully
|
|
816
|
+
run.status = 'completed';
|
|
817
|
+
run.flushTracing?.();
|
|
818
|
+
const elapsedMs = Date.now() - run.startedAt;
|
|
819
|
+
const result = null;
|
|
820
|
+
|
|
821
|
+
// Call session hook for run completed
|
|
822
|
+
const session = this.sessionManager.getSession(run.sessionId);
|
|
823
|
+
if (session?.config.hooks?.onRunCompleted) {
|
|
824
|
+
await session.config.hooks.onRunCompleted(
|
|
825
|
+
session,
|
|
826
|
+
run.runId,
|
|
827
|
+
run.graphId,
|
|
828
|
+
result
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
this.notifyMessage({
|
|
833
|
+
type: 'completed',
|
|
834
|
+
runId: run.runId,
|
|
835
|
+
graphId: run.graphId,
|
|
836
|
+
completedAt: Date.now(),
|
|
837
|
+
elapsedMs,
|
|
838
|
+
result,
|
|
839
|
+
performance: run.performance
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
// Cleanup
|
|
843
|
+
run.engine.dispose();
|
|
844
|
+
this.activeRuns.delete(run.runId);
|
|
845
|
+
this.sessionManager.removeRunFromSession(run.sessionId, run.runId);
|
|
846
|
+
// Keep the panel's running/active-runs state in sync when stepping
|
|
847
|
+
// reaches the end of the graph.
|
|
848
|
+
this.updateStoreActiveRuns();
|
|
849
|
+
this.updateStoreExecutionState(false, false);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Check if a run is currently paused
|
|
856
|
+
*/
|
|
857
|
+
public isPaused(runId: string): boolean {
|
|
858
|
+
const run = this.activeRuns.get(runId);
|
|
859
|
+
return run?.isPaused ?? false;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private handleStopGraph(message: StopGraphMessage): void {
|
|
863
|
+
const run = this.activeRuns.get(message.runId);
|
|
864
|
+
if (!run) {
|
|
865
|
+
this.sendError('RUN_NOT_FOUND', 'Run not found', {
|
|
866
|
+
runId: message.runId
|
|
867
|
+
});
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
run.status = 'stopped';
|
|
872
|
+
run.flushTracing?.();
|
|
873
|
+
this.updateStoreActiveRuns();
|
|
874
|
+
this.updateStoreExecutionState(false, false);
|
|
875
|
+
run.engine.dispose();
|
|
876
|
+
this.activeRuns.delete(message.runId);
|
|
877
|
+
this.sessionManager.removeRunFromSession(run.sessionId, message.runId);
|
|
878
|
+
|
|
879
|
+
this.notifyMessage({
|
|
880
|
+
type: 'stopped',
|
|
881
|
+
runId: message.runId,
|
|
882
|
+
graphId: run.graphId,
|
|
883
|
+
reason: 'User requested stop'
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private handleGetStatus(message: GetStatusMessage): void {
|
|
888
|
+
const run = this.activeRuns.get(message.runId);
|
|
889
|
+
if (!run) {
|
|
890
|
+
this.sendError('RUN_NOT_FOUND', 'Run not found', {
|
|
891
|
+
runId: message.runId
|
|
892
|
+
});
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const elapsedMs = Date.now() - run.startedAt;
|
|
897
|
+
|
|
898
|
+
this.notifyMessage({
|
|
899
|
+
type: 'status',
|
|
900
|
+
runId: run.runId,
|
|
901
|
+
graphId: run.graphId,
|
|
902
|
+
status: run.status,
|
|
903
|
+
startedAt: run.startedAt,
|
|
904
|
+
elapsedMs,
|
|
905
|
+
performance: run.performance,
|
|
906
|
+
startedGraphs: []
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
private async handleCloseSession(
|
|
911
|
+
message: CloseSessionMessage
|
|
912
|
+
): Promise<void> {
|
|
913
|
+
const session = this.sessionManager.getSession(message.sessionId);
|
|
914
|
+
|
|
915
|
+
if (session) {
|
|
916
|
+
// Clean up all runs in this session
|
|
917
|
+
for (const run of this.activeRuns.values()) {
|
|
918
|
+
if (run.sessionId === message.sessionId) {
|
|
919
|
+
run.engine.dispose();
|
|
920
|
+
this.activeRuns.delete(run.runId);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
this.updateStoreActiveRuns();
|
|
925
|
+
this.updateStoreExecutionState(false, false);
|
|
926
|
+
|
|
927
|
+
// Close the session (calls session hooks)
|
|
928
|
+
await this.sessionManager.closeSession(message.sessionId);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
this.notifyMessage({
|
|
932
|
+
type: 'sessionClosed',
|
|
933
|
+
sessionId: message.sessionId
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
private sendError(
|
|
938
|
+
code: string,
|
|
939
|
+
message: string,
|
|
940
|
+
details?: Record<string, unknown>
|
|
941
|
+
): void {
|
|
942
|
+
this.notifyMessage({
|
|
943
|
+
type: 'error',
|
|
944
|
+
code: code as any,
|
|
945
|
+
message,
|
|
946
|
+
...details
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Realtime modification handlers
|
|
951
|
+
|
|
952
|
+
private handleAddNode(message: AddNodeMessage): void {
|
|
953
|
+
const run = this.activeRuns.get(message.runId);
|
|
954
|
+
if (!run) {
|
|
955
|
+
this.sendError('RUN_NOT_FOUND', `Run ${message.runId} not found`);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
try {
|
|
960
|
+
run.graphInstance.nodes[message.nodeId] = createNode({
|
|
961
|
+
id: message.nodeId,
|
|
962
|
+
nodeTypeName: message.nodeType,
|
|
963
|
+
nodeConfiguration: message.nodeData?.configuration || {},
|
|
964
|
+
registry: run.registry,
|
|
965
|
+
graph: makeGraphApi({
|
|
966
|
+
...run.registry,
|
|
967
|
+
variables: run.graphInstance.variables,
|
|
968
|
+
customEvents: run.graphInstance.customEvents
|
|
969
|
+
})
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
this.notifyMessage({
|
|
973
|
+
type: 'nodeAdded',
|
|
974
|
+
runId: message.runId,
|
|
975
|
+
graphId: run.graphId,
|
|
976
|
+
nodeId: message.nodeId,
|
|
977
|
+
nodeType: message.nodeType,
|
|
978
|
+
nodeData: message.nodeData
|
|
979
|
+
});
|
|
980
|
+
} catch (error) {
|
|
981
|
+
this.sendError(
|
|
982
|
+
'NODE_EXECUTION_ERROR',
|
|
983
|
+
`Failed to add node: ${error instanceof Error ? error.message : String(error)}`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
private handleRemoveNode(message: RemoveNodeMessage): void {
|
|
989
|
+
const run = this.activeRuns.get(message.runId);
|
|
990
|
+
if (!run) {
|
|
991
|
+
this.sendError('RUN_NOT_FOUND', `Run ${message.runId} not found`);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const node = run.graphInstance.nodes?.[message.nodeId];
|
|
996
|
+
if (!node) {
|
|
997
|
+
this.sendError(
|
|
998
|
+
'NODE_EXECUTION_ERROR',
|
|
999
|
+
`Node ${message.nodeId} not found in graph`
|
|
1000
|
+
);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
try {
|
|
1005
|
+
// Clean up links connected to this node's sockets
|
|
1006
|
+
for (const socket of [...(node.inputs || []), ...(node.outputs || [])]) {
|
|
1007
|
+
// Clear all links by removing them one by one
|
|
1008
|
+
while (socket.links.length > 0) {
|
|
1009
|
+
socket.links.pop();
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// Also clean up links pointing TO this node from other nodes
|
|
1014
|
+
for (const otherNode of Object.values(run.graphInstance.nodes || {})) {
|
|
1015
|
+
if (otherNode && otherNode !== node) {
|
|
1016
|
+
for (const inputSocket of otherNode.inputs || []) {
|
|
1017
|
+
for (let i = inputSocket.links.length - 1; i >= 0; i--) {
|
|
1018
|
+
const link = inputSocket.links[i];
|
|
1019
|
+
if (link && link.nodeId === message.nodeId) {
|
|
1020
|
+
inputSocket.links.splice(i, 1);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Remove the node from the graph
|
|
1028
|
+
delete run.graphInstance.nodes[message.nodeId];
|
|
1029
|
+
|
|
1030
|
+
// Notify client
|
|
1031
|
+
this.notifyMessage({
|
|
1032
|
+
type: 'nodeRemoved',
|
|
1033
|
+
runId: message.runId,
|
|
1034
|
+
graphId: run.graphId,
|
|
1035
|
+
nodeId: message.nodeId
|
|
1036
|
+
});
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
this.sendError(
|
|
1039
|
+
'NODE_EXECUTION_ERROR',
|
|
1040
|
+
`Failed to remove node: ${error instanceof Error ? error.message : String(error)}`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private handleUpdateSocketValue(message: UpdateSocketValueMessage): void {
|
|
1046
|
+
const run = this.activeRuns.get(message.runId);
|
|
1047
|
+
if (!run) {
|
|
1048
|
+
this.sendError('RUN_NOT_FOUND', `Run ${message.runId} not found`);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const node = run.graphInstance.nodes?.[message.nodeId];
|
|
1053
|
+
if (!node) {
|
|
1054
|
+
this.sendError(
|
|
1055
|
+
'NODE_EXECUTION_ERROR',
|
|
1056
|
+
`Node ${message.nodeId} not found in graph`
|
|
1057
|
+
);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
try {
|
|
1062
|
+
// Find the socket by name
|
|
1063
|
+
const socket = node.inputs.find((s) => s.name === message.socketName);
|
|
1064
|
+
if (!socket) {
|
|
1065
|
+
this.sendError(
|
|
1066
|
+
'NODE_EXECUTION_ERROR',
|
|
1067
|
+
`Socket ${message.socketName} not found on node`
|
|
1068
|
+
);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Update the socket value
|
|
1073
|
+
socket.value = message.value;
|
|
1074
|
+
|
|
1075
|
+
// Notify about the change
|
|
1076
|
+
this.notifyMessage({
|
|
1077
|
+
type: 'trace',
|
|
1078
|
+
runId: message.runId,
|
|
1079
|
+
graphId: run.graphId,
|
|
1080
|
+
nodeId: message.nodeId,
|
|
1081
|
+
event: 'socketUpdated',
|
|
1082
|
+
data: { socketName: message.socketName, value: message.value },
|
|
1083
|
+
timestamp: Date.now() - run.startedAt
|
|
1084
|
+
});
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
this.sendError(
|
|
1087
|
+
'NODE_EXECUTION_ERROR',
|
|
1088
|
+
`Failed to update socket: ${error instanceof Error ? error.message : String(error)}`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
private handleUpdateNodeParam(message: UpdateNodeParamMessage): void {
|
|
1094
|
+
const run = this.activeRuns.get(message.runId);
|
|
1095
|
+
if (!run) {
|
|
1096
|
+
this.sendError('RUN_NOT_FOUND', `Run ${message.runId} not found`);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const node = run.graphInstance.nodes?.[message.nodeId];
|
|
1101
|
+
if (!node) {
|
|
1102
|
+
this.sendError(
|
|
1103
|
+
'NODE_EXECUTION_ERROR',
|
|
1104
|
+
`Node ${message.nodeId} not found in graph`
|
|
1105
|
+
);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
try {
|
|
1110
|
+
// Store old value for delta
|
|
1111
|
+
const oldValue = node.configuration[message.paramName];
|
|
1112
|
+
|
|
1113
|
+
// Update the parameter
|
|
1114
|
+
node.configuration[message.paramName] = message.value;
|
|
1115
|
+
|
|
1116
|
+
// Notify about the change
|
|
1117
|
+
this.notifyMessage({
|
|
1118
|
+
type: 'nodeParamUpdated',
|
|
1119
|
+
runId: message.runId,
|
|
1120
|
+
graphId: run.graphId,
|
|
1121
|
+
nodeId: message.nodeId,
|
|
1122
|
+
paramName: message.paramName,
|
|
1123
|
+
oldValue,
|
|
1124
|
+
newValue: message.value
|
|
1125
|
+
});
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
this.sendError(
|
|
1128
|
+
'NODE_EXECUTION_ERROR',
|
|
1129
|
+
`Failed to update parameter: ${error instanceof Error ? error.message : String(error)}`
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
private handleCreateLink(message: CreateLinkMessage): void {
|
|
1135
|
+
const run = this.activeRuns.get(message.runId);
|
|
1136
|
+
if (!run) {
|
|
1137
|
+
this.sendError('RUN_NOT_FOUND', `Run ${message.runId} not found`);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
try {
|
|
1142
|
+
const fromNode = run.graphInstance.nodes?.[message.fromNodeId];
|
|
1143
|
+
const toNode = run.graphInstance.nodes?.[message.toNodeId];
|
|
1144
|
+
|
|
1145
|
+
if (!fromNode) {
|
|
1146
|
+
this.sendError('NODE_EXECUTION_ERROR', `Src node not found in graph`);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (!toNode) {
|
|
1150
|
+
this.sendError('NODE_EXECUTION_ERROR', `Dest node not found in graph`);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Find the output socket on the source node
|
|
1155
|
+
const fromSocket = fromNode.outputs.find(
|
|
1156
|
+
(s) => s.name === message.fromSocket
|
|
1157
|
+
);
|
|
1158
|
+
if (!fromSocket) {
|
|
1159
|
+
this.sendError(
|
|
1160
|
+
'NODE_EXECUTION_ERROR',
|
|
1161
|
+
`Output socket ${message.fromSocket} not found on source node`
|
|
1162
|
+
);
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// Find the input socket on the target node
|
|
1167
|
+
const toSocket = toNode.inputs.find((s) => s.name === message.toSocket);
|
|
1168
|
+
if (!toSocket) {
|
|
1169
|
+
this.sendError(
|
|
1170
|
+
'NODE_EXECUTION_ERROR',
|
|
1171
|
+
`Input socket ${message.toSocket} not found on target node`
|
|
1172
|
+
);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Create the link
|
|
1177
|
+
const link = new Link(message.fromNodeId, message.fromSocket);
|
|
1178
|
+
toSocket.links.push(link);
|
|
1179
|
+
|
|
1180
|
+
// Notify about the change
|
|
1181
|
+
this.notifyMessage({
|
|
1182
|
+
type: 'linkCreated',
|
|
1183
|
+
runId: message.runId,
|
|
1184
|
+
graphId: run.graphId,
|
|
1185
|
+
fromNodeId: message.fromNodeId,
|
|
1186
|
+
fromSocket: message.fromSocket,
|
|
1187
|
+
toNodeId: message.toNodeId,
|
|
1188
|
+
toSocket: message.toSocket
|
|
1189
|
+
});
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
this.sendError(
|
|
1192
|
+
'NODE_EXECUTION_ERROR',
|
|
1193
|
+
`Failed to create link: ${error instanceof Error ? error.message : String(error)}`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
private handleRemoveLink(message: RemoveLinkMessage): void {
|
|
1199
|
+
const run = this.activeRuns.get(message.runId);
|
|
1200
|
+
if (!run) {
|
|
1201
|
+
this.sendError('RUN_NOT_FOUND', `Run ${message.runId} not found`);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
try {
|
|
1206
|
+
const toNode = run.graphInstance.nodes?.[message.toNodeId];
|
|
1207
|
+
if (!toNode) {
|
|
1208
|
+
this.sendError(
|
|
1209
|
+
'NODE_EXECUTION_ERROR',
|
|
1210
|
+
`Target node ${message.toNodeId} not found in graph`
|
|
1211
|
+
);
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Find the input socket on the target node
|
|
1216
|
+
const toSocket = toNode.inputs.find((s) => s.name === message.toSocket);
|
|
1217
|
+
if (!toSocket) {
|
|
1218
|
+
this.sendError(
|
|
1219
|
+
'NODE_EXECUTION_ERROR',
|
|
1220
|
+
`Input socket ${message.toSocket} not found on target node`
|
|
1221
|
+
);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Remove the matching link
|
|
1226
|
+
for (let i = toSocket.links.length - 1; i >= 0; i--) {
|
|
1227
|
+
const link = toSocket.links[i];
|
|
1228
|
+
if (
|
|
1229
|
+
link &&
|
|
1230
|
+
link.nodeId === message.fromNodeId &&
|
|
1231
|
+
link.socketName === message.fromSocket
|
|
1232
|
+
) {
|
|
1233
|
+
toSocket.links.splice(i, 1);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Notify about the change
|
|
1238
|
+
this.notifyMessage({
|
|
1239
|
+
type: 'linkRemoved',
|
|
1240
|
+
runId: message.runId,
|
|
1241
|
+
graphId: run.graphId,
|
|
1242
|
+
fromNodeId: message.fromNodeId,
|
|
1243
|
+
fromSocket: message.fromSocket,
|
|
1244
|
+
toNodeId: message.toNodeId,
|
|
1245
|
+
toSocket: message.toSocket
|
|
1246
|
+
});
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
this.sendError(
|
|
1249
|
+
'NODE_EXECUTION_ERROR',
|
|
1250
|
+
`Failed to remove link: ${error instanceof Error ? error.message : String(error)}`
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
private handleDirectExecuteNode(message: DirectExecuteNodeMessage): void {
|
|
1256
|
+
const run = this.activeRuns.get(message.runId);
|
|
1257
|
+
if (!run) {
|
|
1258
|
+
this.sendError('RUN_NOT_FOUND', `Run ${message.runId} not found`);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
try {
|
|
1263
|
+
const node = run.graphInstance.nodes?.[message.nodeId];
|
|
1264
|
+
if (!node) {
|
|
1265
|
+
this.sendError(
|
|
1266
|
+
'NODE_EXECUTION_ERROR',
|
|
1267
|
+
`Node ${message.nodeId} not found in graph`
|
|
1268
|
+
);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// Find and update the input socket
|
|
1273
|
+
const inputSocket = node.inputs.find(
|
|
1274
|
+
(s) => s.name === message.inputSocketName
|
|
1275
|
+
);
|
|
1276
|
+
if (!inputSocket) {
|
|
1277
|
+
this.sendError(
|
|
1278
|
+
'NODE_EXECUTION_ERROR',
|
|
1279
|
+
`Input socket ${message.inputSocketName} not found on node`
|
|
1280
|
+
);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
inputSocket.value = message.inputValue;
|
|
1285
|
+
|
|
1286
|
+
// Get downstream nodes
|
|
1287
|
+
const downstreamNodes = this.getDownstreamNodes(
|
|
1288
|
+
message.nodeId,
|
|
1289
|
+
run.graphInstance
|
|
1290
|
+
);
|
|
1291
|
+
const nodesToExecute = [message.nodeId, ...downstreamNodes];
|
|
1292
|
+
|
|
1293
|
+
// Execute the node and downstream
|
|
1294
|
+
this.notifyMessage({
|
|
1295
|
+
type: 'affectedNodes',
|
|
1296
|
+
runId: message.runId,
|
|
1297
|
+
graphId: run.graphId,
|
|
1298
|
+
nodeIds: nodesToExecute,
|
|
1299
|
+
reason: 'direct-execution'
|
|
1300
|
+
});
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
this.sendError(
|
|
1303
|
+
'NODE_EXECUTION_ERROR',
|
|
1304
|
+
`Failed to execute node: ${error instanceof Error ? error.message : String(error)}`
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
private getDownstreamNodes(nodeId: string, graph: GraphInstance): string[] {
|
|
1310
|
+
const downstream = new Set<string>();
|
|
1311
|
+
const visited = new Set<string>();
|
|
1312
|
+
const queue = [nodeId];
|
|
1313
|
+
|
|
1314
|
+
while (queue.length > 0) {
|
|
1315
|
+
const currentId = queue.shift()!;
|
|
1316
|
+
if (visited.has(currentId)) {
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
visited.add(currentId);
|
|
1320
|
+
|
|
1321
|
+
const currentNode = graph.nodes?.[currentId];
|
|
1322
|
+
if (!currentNode) {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Find all nodes that depend on this one via output socket links
|
|
1327
|
+
for (const outputSocket of currentNode.outputs) {
|
|
1328
|
+
for (const link of outputSocket.links) {
|
|
1329
|
+
if (!visited.has(link.nodeId)) {
|
|
1330
|
+
downstream.add(link.nodeId);
|
|
1331
|
+
queue.push(link.nodeId);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
return Array.from(downstream);
|
|
1338
|
+
}
|
|
1339
|
+
}
|