@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.
Files changed (378) hide show
  1. package/.fallowrc.json +16 -0
  2. package/.storybook/main.ts +32 -0
  3. package/.storybook/manager.ts +6 -0
  4. package/.storybook/preview.ts +64 -0
  5. package/.storybook/styles.css +16 -0
  6. package/.turbo/turbo-build.log +7 -0
  7. package/CHANGELOG.md +368 -0
  8. package/LICENSE +6 -0
  9. package/README.md +2 -2
  10. package/data/Polynomial.json +510 -0
  11. package/data/sequence.json +337 -0
  12. package/data/trigger-event.json +241 -0
  13. package/data/variable-change.json +210 -0
  14. package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
  15. package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
  16. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
  17. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
  18. package/dist/entry.css +4 -0
  19. package/dist/index.css +42 -0
  20. package/dist/index.css.map +1 -0
  21. package/dist/index.d.ts +3597 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +18009 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/noteImpl-KkrrWgJd.js +242 -0
  26. package/dist/noteImpl-KkrrWgJd.js.map +1 -0
  27. package/dist/styles.module-CvmpDkZj.css +3 -0
  28. package/dist/styles.module-CvmpDkZj.css.map +1 -0
  29. package/dist/styles.module-DZxg8aW9.js +271 -0
  30. package/dist/styles.module-DZxg8aW9.js.map +1 -0
  31. package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
  32. package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
  33. package/docs/notifications.md +246 -0
  34. package/docs/protocol.md +702 -0
  35. package/docs/specifics.md +191 -0
  36. package/package.json +82 -22
  37. package/postcss.config.ts +3 -4
  38. package/src/annotations/index.ts +32 -0
  39. package/src/components/FloatingToolbar/index.module.css +37 -0
  40. package/src/components/FloatingToolbar/index.tsx +256 -0
  41. package/src/components/Flow.tsx +287 -75
  42. package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
  43. package/src/components/contextMenus/NodePicker.module.css +274 -0
  44. package/src/components/contextMenus/NodePicker.tsx +481 -0
  45. package/src/components/contextMenus/edge.tsx +22 -0
  46. package/src/components/contextMenus/node.tsx +15 -0
  47. package/src/components/contextMenus/selection.tsx +11 -0
  48. package/src/components/controls/any/AnyControlImpl.tsx +14 -0
  49. package/src/components/controls/any/index.tsx +19 -0
  50. package/src/components/controls/boolean/index.tsx +13 -0
  51. package/src/components/controls/colorPicker/InputPopover.module.css +100 -0
  52. package/src/components/controls/colorPicker/InputPopover.tsx +31 -0
  53. package/src/components/controls/colorPicker/index.module.css +18 -0
  54. package/src/components/controls/colorPicker/index.tsx +61 -0
  55. package/src/components/controls/number/index.tsx +35 -0
  56. package/src/components/controls/string/index.tsx +16 -0
  57. package/src/components/edges/index.tsx +475 -0
  58. package/src/components/edges/offsetBezier.ts +134 -0
  59. package/src/components/hotKeys.tsx +20 -0
  60. package/src/components/layoutController/index.module.css +13 -0
  61. package/src/components/layoutController/index.tsx +140 -0
  62. package/src/components/layoutController/utils.ts +248 -0
  63. package/src/components/menubar/defaults.tsx +516 -0
  64. package/src/components/menubar/index.tsx +49 -0
  65. package/src/components/menubar/menuItem.module.css +31 -0
  66. package/src/components/menubar/menuItem.tsx +65 -0
  67. package/src/components/nodes/behave/Node.module.css +23 -0
  68. package/src/components/nodes/behave/Node.tsx +176 -0
  69. package/src/components/nodes/behave/NodeContainer.module.css +88 -0
  70. package/src/components/nodes/behave/NodeContainer.tsx +46 -0
  71. package/src/components/nodes/behave/index.tsx +14 -0
  72. package/src/components/nodes/group/index.tsx +109 -0
  73. package/src/components/nodes/wrapper/index.tsx +73 -0
  74. package/src/components/nodes/wrapper/styles.module.css +87 -0
  75. package/src/components/notifications/NotificationProvider.tsx +81 -0
  76. package/src/components/notifications/index.ts +2 -0
  77. package/src/components/notifications/utils.ts +71 -0
  78. package/src/components/panels/alignment/index.module.css +10 -0
  79. package/src/components/panels/alignment/index.tsx +244 -0
  80. package/src/components/panels/base/index.tsx +5 -0
  81. package/src/components/panels/base/styles.module.css +12 -0
  82. package/src/components/panels/common/PanelHeader.module.css +24 -0
  83. package/src/components/panels/common/PanelHeader.tsx +22 -0
  84. package/src/components/panels/common/SectionTitle.module.css +13 -0
  85. package/src/components/panels/common/SectionTitle.tsx +10 -0
  86. package/src/components/panels/events/EditEventPanel.tsx +324 -0
  87. package/src/components/panels/events/ManageEventsPanel.tsx +101 -0
  88. package/src/components/panels/events/index.tsx +23 -0
  89. package/src/components/panels/events/styles.module.css +178 -0
  90. package/src/components/panels/graphProperties/index.tsx +125 -0
  91. package/src/components/panels/history/index.tsx +92 -0
  92. package/src/components/panels/history/styles.module.css +97 -0
  93. package/src/components/panels/keymaps/index.module.css +68 -0
  94. package/src/components/panels/keymaps/index.tsx +166 -0
  95. package/src/components/panels/layers/index.tsx +245 -0
  96. package/src/components/panels/layers/styles.module.css +107 -0
  97. package/src/components/panels/legend/index.module.css +6 -0
  98. package/src/components/panels/legend/index.tsx +76 -0
  99. package/src/components/panels/logs/index.module.css +218 -0
  100. package/src/components/panels/logs/index.tsx +288 -0
  101. package/src/components/panels/nodeInputs/InputControl.tsx +63 -0
  102. package/src/components/panels/nodeInputs/InputsGroup.tsx +65 -0
  103. package/src/components/panels/nodeInputs/MultipleNodesView.tsx +37 -0
  104. package/src/components/panels/nodeInputs/NodeSettings.tsx +92 -0
  105. package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +125 -0
  106. package/src/components/panels/nodeInputs/OutputsGroup.tsx +55 -0
  107. package/src/components/panels/nodeInputs/SocketGenerators.tsx +32 -0
  108. package/src/components/panels/nodeInputs/index.module.css +308 -0
  109. package/src/components/panels/nodeInputs/index.tsx +349 -0
  110. package/src/components/panels/nodeInputs/useNodeHandlers.ts +76 -0
  111. package/src/components/panels/nodeInputs/useNodeInputsData.ts +153 -0
  112. package/src/components/panels/nodePicker/index.tsx +115 -0
  113. package/src/components/panels/panel/index.module.css +66 -0
  114. package/src/components/panels/panel/index.tsx +88 -0
  115. package/src/components/panels/search/index.module.css +16 -0
  116. package/src/components/panels/search/index.tsx +215 -0
  117. package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
  118. package/src/components/panels/systemSettings/index.tsx +251 -0
  119. package/src/components/panels/systemSettings/styles.module.css +138 -0
  120. package/src/components/panels/traces/GridLines.tsx +38 -0
  121. package/src/components/panels/traces/TimeGrid.tsx +48 -0
  122. package/src/components/panels/traces/TraceLane.tsx +62 -0
  123. package/src/components/panels/traces/TraceTooltip.tsx +22 -0
  124. package/src/components/panels/traces/TracesHeader.tsx +56 -0
  125. package/src/components/panels/traces/index.module.css +159 -0
  126. package/src/components/panels/traces/index.tsx +298 -0
  127. package/src/components/panels/traces/types.ts +48 -0
  128. package/src/components/panels/traces/useDerivedSpans.ts +307 -0
  129. package/src/components/panels/traces/utils.ts +33 -0
  130. package/src/components/panels/variables/CreateVariableScreen.tsx +162 -0
  131. package/src/components/panels/variables/ManageVariablesScreen.tsx +147 -0
  132. package/src/components/panels/variables/index.tsx +125 -0
  133. package/src/components/panels/variables/styles.module.css +149 -0
  134. package/src/components/primitives/icon.module.css +45 -0
  135. package/src/components/primitives/icon.tsx +38 -0
  136. package/src/components/sockets/input/index.tsx +83 -0
  137. package/src/components/sockets/input/styles.module.css +26 -0
  138. package/src/components/sockets/output/index.tsx +68 -0
  139. package/src/components/sockets/output/styles.module.css +22 -0
  140. package/src/css/notes.css +135 -0
  141. package/src/css/prosemirror.css +57 -0
  142. package/src/css/rc-dock.css +212 -0
  143. package/src/css/rc-menu.css +101 -0
  144. package/src/css/themes/kiberon.css +127 -0
  145. package/src/css/vars.css +198 -0
  146. package/src/css/vscode-elements.css +124 -0
  147. package/src/entry.css +4 -0
  148. package/src/generators/CallSubgraphGenerator.tsx +136 -0
  149. package/src/generators/CustomEventOnTriggeredGenerator.tsx +85 -0
  150. package/src/generators/GraphBoundaryGenerator.module.css +32 -0
  151. package/src/generators/GraphBoundaryGenerator.tsx +193 -0
  152. package/src/generators/SequenceGenerator.tsx +104 -0
  153. package/src/generators/SwitchOnIntegerGenerator.tsx +256 -0
  154. package/src/generators/SwitchOnStringGenerator.tsx +263 -0
  155. package/src/generators/callSubgraphSync.ts +126 -0
  156. package/src/generators/registerDefaultGenerators.ts +55 -0
  157. package/src/generators/registerDefaults.ts +26 -0
  158. package/src/hooks/useBehaveGraphFlow.ts +17 -16
  159. package/src/hooks/useFlowHandlers.ts +154 -30
  160. package/src/hooks/useWasdPan.ts +210 -0
  161. package/src/index.css +134 -0
  162. package/src/index.ts +53 -18
  163. package/src/manifest/contributionRegistry.ts +93 -0
  164. package/src/manifest/index.ts +4 -0
  165. package/src/manifest/loadManifest.ts +82 -0
  166. package/src/manifest/manifestPlugin.ts +29 -0
  167. package/src/manifest/passthroughValueType.ts +40 -0
  168. package/src/plugin/alignment/index.ts +91 -0
  169. package/src/plugin/autosave/controller.ts +366 -0
  170. package/src/plugin/autosave/index.tsx +114 -0
  171. package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
  172. package/src/plugin/autosave/panel/index.tsx +1 -0
  173. package/src/plugin/autosave/panel/styles.module.css +56 -0
  174. package/src/plugin/autosave/settings.ts +65 -0
  175. package/src/plugin/autosave/storage.ts +147 -0
  176. package/src/plugin/docs/index.tsx +297 -0
  177. package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
  178. package/src/plugin/docs/panel/index.tsx +21 -0
  179. package/src/plugin/docs/panel/styles.module.css +174 -0
  180. package/src/plugin/graphrunner/actions.ts +326 -0
  181. package/src/plugin/graphrunner/buttons.tsx +95 -0
  182. package/src/plugin/graphrunner/client.ts +707 -0
  183. package/src/plugin/graphrunner/index.tsx +184 -0
  184. package/src/plugin/graphrunner/panel.tsx +386 -0
  185. package/src/plugin/graphrunner/runController.ts +283 -0
  186. package/src/plugin/graphrunner/runner.ts +187 -0
  187. package/src/plugin/graphrunner/session.ts +243 -0
  188. package/src/plugin/graphrunner/store.ts +196 -0
  189. package/src/plugin/graphrunner/styles.module.css +171 -0
  190. package/src/plugin/graphrunner/transport.ts +250 -0
  191. package/src/plugin/graphrunner/types.ts +693 -0
  192. package/src/plugin/graphrunner-local/execution-utils.ts +637 -0
  193. package/src/plugin/graphrunner-local/index.tsx +172 -0
  194. package/src/plugin/graphrunner-local/panel.tsx +187 -0
  195. package/src/plugin/graphrunner-local/store.ts +41 -0
  196. package/src/plugin/graphrunner-local/styles.module.css +82 -0
  197. package/src/plugin/graphrunner-local/transport.ts +1339 -0
  198. package/src/plugin/graphrunner-local/types.ts +10 -0
  199. package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +635 -0
  200. package/src/plugin/graphrunner-webworker/index.tsx +140 -0
  201. package/src/plugin/graphrunner-webworker/panel.tsx +173 -0
  202. package/src/plugin/graphrunner-webworker/store.ts +98 -0
  203. package/src/plugin/graphrunner-webworker/worker-transport.ts +123 -0
  204. package/src/plugin/kitchen-sink/index.ts +38 -0
  205. package/src/plugin/layout/dagre.ts +131 -0
  206. package/src/plugin/layout/elk.ts +216 -0
  207. package/src/plugin/layout/index.ts +80 -0
  208. package/src/plugin/notes/FormatToolbar.tsx +200 -0
  209. package/src/plugin/notes/index.tsx +191 -0
  210. package/src/plugin/notes/nodeActions.ts +100 -0
  211. package/src/plugin/notes/note.tsx +20 -0
  212. package/src/plugin/notes/noteImpl.tsx +89 -0
  213. package/src/plugin/realtime/realtimeRunner.ts +624 -0
  214. package/src/specifics/CustomEventOnTriggeredSpecific.tsx +92 -0
  215. package/src/specifics/CustomEventTriggerSpecific.tsx +141 -0
  216. package/src/specifics/VariableGetSpecific.tsx +110 -0
  217. package/src/specifics/VariableSetSpecific.tsx +110 -0
  218. package/src/store/actions.tsx +698 -0
  219. package/src/store/commands.ts +278 -0
  220. package/src/store/contextMenu.ts +192 -0
  221. package/src/store/controls.tsx +62 -0
  222. package/src/store/conversions.ts +47 -0
  223. package/src/store/documentation.tsx +69 -0
  224. package/src/store/events.tsx +116 -0
  225. package/src/store/flow.tsx +230 -0
  226. package/src/store/graphMeta.ts +39 -0
  227. package/src/store/hotKeys.tsx +364 -0
  228. package/src/store/layers.ts +259 -0
  229. package/src/store/legend.tsx +76 -0
  230. package/src/store/logs.ts +28 -0
  231. package/src/store/menubar.ts +41 -0
  232. package/src/store/refs.ts +84 -0
  233. package/src/store/registry.ts +51 -0
  234. package/src/store/selection.ts +22 -0
  235. package/src/store/settings.ts +99 -0
  236. package/src/store/settingsSchema.ts +210 -0
  237. package/src/store/socketGenerator.tsx +54 -0
  238. package/src/store/specific.tsx +75 -0
  239. package/src/store/specs.tsx +35 -0
  240. package/src/store/tabs.ts +282 -0
  241. package/src/store/toolbar.tsx +45 -0
  242. package/src/store/traces.ts +240 -0
  243. package/src/store/variables.ts +37 -0
  244. package/src/system/graph.ts +131 -0
  245. package/src/system/graphSession.ts +172 -0
  246. package/src/system/index.ts +6 -0
  247. package/src/system/notifications.ts +111 -0
  248. package/src/system/persistence.ts +82 -0
  249. package/src/system/plugin.ts +55 -0
  250. package/src/system/provider.tsx +86 -0
  251. package/src/system/pubsub.ts +323 -0
  252. package/src/system/system.ts +653 -0
  253. package/src/system/tabLoader.tsx +303 -0
  254. package/src/system/undoRedo.ts +103 -0
  255. package/src/transformers/Uigraph.ts +61 -0
  256. package/src/transformers/behaveToFlow.ts +16 -4
  257. package/src/transformers/contract.ts +87 -0
  258. package/src/transformers/flowToBehave.ts +40 -12
  259. package/src/types/NodeMetadata.ts +27 -0
  260. package/src/types/graph.ts +49 -0
  261. package/src/types/nodes.ts +50 -0
  262. package/src/types.ts +18 -0
  263. package/src/util/autoConvert.ts +200 -0
  264. package/src/util/colors.ts +1 -29
  265. package/src/util/downloadJson.ts +18 -0
  266. package/src/util/extractNodeMetadata.ts +16 -0
  267. package/src/util/getPickerFilters.ts +1 -1
  268. package/src/util/isBehaveNode.ts +6 -0
  269. package/src/util/isValidConnection.ts +51 -17
  270. package/src/util/mergeSockets.ts +29 -0
  271. package/src/util/serializeVariables.ts +66 -0
  272. package/src/util/sockets.ts +43 -0
  273. package/stories/apex/layoutController/example-graph.worker.ts +39 -0
  274. package/stories/apex/layoutController/index.stories.tsx +48 -0
  275. package/stories/apex/layoutController/webworker.stories.tsx +103 -0
  276. package/stories/apex/menubar/menubar.stories.tsx +19 -0
  277. package/stories/components/colorpicker/index.stories.tsx +20 -0
  278. package/stories/components/contextMenus/edge.stories.tsx +32 -0
  279. package/stories/components/contextMenus/node.stories.tsx +26 -0
  280. package/stories/components/contextMenus/nodePicker.stories.tsx +115 -0
  281. package/stories/components/controls/any/index.stories.tsx +19 -0
  282. package/stories/components/controls/boolean/index.stories.tsx +19 -0
  283. package/stories/components/controls/colorPicker/index.stories.tsx +49 -0
  284. package/stories/components/controls/number/index.stories.tsx +19 -0
  285. package/stories/components/controls/string/index.stories.tsx +19 -0
  286. package/stories/components/nodes/behaveNode.stories.tsx +108 -0
  287. package/stories/components/panels/alignment.stories.tsx +24 -0
  288. package/stories/components/panels/events.stories.tsx +38 -0
  289. package/stories/components/panels/graphRunner.stories.tsx +317 -0
  290. package/stories/components/panels/history.stories.tsx +37 -0
  291. package/stories/components/panels/keymaps.stories.tsx +21 -0
  292. package/stories/components/panels/legend.stories.tsx +37 -0
  293. package/stories/components/panels/logs.stories.tsx +24 -0
  294. package/stories/components/panels/nodeInputs.stories.tsx +21 -0
  295. package/stories/components/panels/nodePicker.stories.tsx +37 -0
  296. package/stories/components/panels/panel.stories.tsx +39 -0
  297. package/stories/components/panels/search.stories.tsx +24 -0
  298. package/stories/components/panels/systemSettings.stories.tsx +26 -0
  299. package/stories/components/panels/traces.stories.tsx +225 -0
  300. package/stories/components/panels/variables.stories.tsx +24 -0
  301. package/stories/defaults/defaultStoryProvider.tsx +170 -0
  302. package/stories/defaults/systemGenerator.ts +43 -0
  303. package/stories/plugins/notes.stories.tsx +100 -0
  304. package/tests/autoConvert.test.ts +329 -0
  305. package/tests/autosavePlugin.test.ts +204 -0
  306. package/tests/callSubgraphSync.test.ts +148 -0
  307. package/tests/commandRegistry.test.ts +137 -0
  308. package/tests/components/edges/offsetBezier.test.ts +51 -0
  309. package/tests/components/layoutController/utils.test.ts +68 -0
  310. package/tests/components/panels/traces/utils.test.ts +52 -0
  311. package/tests/contract.test.ts +51 -0
  312. package/tests/contractSerialize.test.ts +62 -0
  313. package/tests/deriveSpans.test.ts +71 -0
  314. package/tests/flowToBehave.test.ts +27 -4
  315. package/tests/hotkeys.test.ts +79 -0
  316. package/tests/keepAliveLifecycle.test.ts +167 -0
  317. package/tests/loadManifest.test.ts +113 -0
  318. package/tests/noteMarkdown.test.ts +65 -0
  319. package/tests/notesPlugin.test.ts +162 -0
  320. package/tests/notifications.test.ts +87 -0
  321. package/tests/persistence.test.ts +51 -0
  322. package/tests/saveLoad.test.ts +373 -0
  323. package/tests/settings.test.ts +178 -0
  324. package/tests/traceStore.test.ts +46 -0
  325. package/tests/util/calculateNewEdge.test.ts +98 -0
  326. package/tests/util/getSocketsByNodeTypeAndHandleType.test.ts +31 -0
  327. package/tests/util/hasPositionMetaData.test.ts +33 -0
  328. package/tests/util/isBehaveNode.test.ts +22 -0
  329. package/tests/util/isHandleConnected.test.ts +37 -0
  330. package/tests/util/mergeSockets.test.ts +43 -0
  331. package/tests/visual/README.md +64 -0
  332. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-alignment-chromium-win32.png +0 -0
  333. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
  334. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
  335. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
  336. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
  337. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
  338. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
  339. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
  340. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
  341. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
  342. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
  343. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
  344. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
  345. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
  346. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-traces-chromium-win32.png +0 -0
  347. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
  348. package/tests/visual/panels.visual.test.tsx +76 -0
  349. package/tests/wasdPan.test.ts +71 -0
  350. package/tsconfig.base.json +39 -0
  351. package/tsconfig.json +18 -59
  352. package/tsconfig.prod.json +23 -0
  353. package/tsdown.config.ts +15 -3
  354. package/typedoc.json +7 -7
  355. package/vite.config.js +7 -0
  356. package/vitest.config.ts +5 -2
  357. package/vitest.visual.config.ts +55 -0
  358. package/src/components/AutoSizeInput.tsx +0 -65
  359. package/src/components/Controls.tsx +0 -87
  360. package/src/components/InputSocket.tsx +0 -142
  361. package/src/components/Node.tsx +0 -68
  362. package/src/components/NodeContainer.tsx +0 -46
  363. package/src/components/NodePicker.tsx +0 -77
  364. package/src/components/OutputSocket.tsx +0 -58
  365. package/src/components/modals/ClearModal.tsx +0 -40
  366. package/src/components/modals/HelpModal.tsx +0 -36
  367. package/src/components/modals/LoadModal.tsx +0 -96
  368. package/src/components/modals/Modal.tsx +0 -64
  369. package/src/components/modals/SaveModal.tsx +0 -60
  370. package/src/hooks/useCustomNodeTypes.tsx +0 -31
  371. package/src/hooks/useGraphRunner.ts +0 -104
  372. package/src/hooks/useMergeMap.ts +0 -14
  373. package/src/hooks/useNodeSpecJson.ts +0 -20
  374. package/src/hooks/useQueriableDefinitions.ts +0 -22
  375. package/src/styles.css +0 -8
  376. package/tailwind.config.ts +0 -19
  377. package/tests/tsconfig.json +0 -10
  378. /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
+ }