@kiberon-labs/behave-graph-flow 2.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 (251) hide show
  1. package/.storybook/manager.ts +6 -0
  2. package/.storybook/preview.ts +49 -1
  3. package/.storybook/styles.css +9 -3
  4. package/.turbo/turbo-build.log +1 -1
  5. package/CHANGELOG.md +368 -0
  6. package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
  7. package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
  8. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
  9. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
  10. package/dist/index.css +36 -33
  11. package/dist/index.css.map +1 -1
  12. package/dist/index.d.ts +1865 -550
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +14357 -11221
  15. package/dist/index.js.map +1 -1
  16. package/dist/noteImpl-KkrrWgJd.js +242 -0
  17. package/dist/noteImpl-KkrrWgJd.js.map +1 -0
  18. package/dist/styles.module-CvmpDkZj.css +3 -0
  19. package/dist/styles.module-CvmpDkZj.css.map +1 -0
  20. package/dist/styles.module-DZxg8aW9.js +271 -0
  21. package/dist/styles.module-DZxg8aW9.js.map +1 -0
  22. package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
  23. package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
  24. package/docs/protocol.md +43 -20
  25. package/package.json +5 -9
  26. package/src/components/FloatingToolbar/index.module.css +5 -13
  27. package/src/components/FloatingToolbar/index.tsx +9 -9
  28. package/src/components/Flow.tsx +34 -23
  29. package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
  30. package/src/components/contextMenus/NodePicker.module.css +13 -13
  31. package/src/components/contextMenus/edge.tsx +9 -95
  32. package/src/components/contextMenus/node.tsx +9 -149
  33. package/src/components/contextMenus/selection.tsx +5 -71
  34. package/src/components/controls/any/AnyControlImpl.tsx +14 -0
  35. package/src/components/controls/any/index.tsx +13 -2
  36. package/src/components/edges/index.tsx +75 -69
  37. package/src/components/layoutController/index.module.css +3 -0
  38. package/src/components/layoutController/index.tsx +24 -1
  39. package/src/components/layoutController/utils.ts +46 -3
  40. package/src/components/menubar/defaults.tsx +55 -19
  41. package/src/components/menubar/menuItem.module.css +18 -3
  42. package/src/components/menubar/menuItem.tsx +34 -1
  43. package/src/components/nodes/behave/NodeContainer.module.css +26 -25
  44. package/src/components/nodes/group/index.tsx +3 -3
  45. package/src/components/nodes/wrapper/styles.module.css +6 -32
  46. package/src/components/panels/alignment/index.module.css +0 -10
  47. package/src/components/panels/alignment/index.tsx +4 -4
  48. package/src/components/panels/base/styles.module.css +2 -2
  49. package/src/components/panels/common/PanelHeader.module.css +24 -0
  50. package/src/components/panels/common/PanelHeader.tsx +22 -0
  51. package/src/components/panels/common/SectionTitle.module.css +13 -0
  52. package/src/components/panels/common/SectionTitle.tsx +10 -0
  53. package/src/components/panels/events/EditEventPanel.tsx +14 -5
  54. package/src/components/panels/events/ManageEventsPanel.tsx +11 -8
  55. package/src/components/panels/events/styles.module.css +6 -64
  56. package/src/components/panels/graphProperties/index.tsx +125 -0
  57. package/src/components/panels/history/index.tsx +2 -2
  58. package/src/components/panels/history/styles.module.css +0 -9
  59. package/src/components/panels/keymaps/index.module.css +3 -13
  60. package/src/components/panels/keymaps/index.tsx +1 -2
  61. package/src/components/panels/layers/index.tsx +20 -15
  62. package/src/components/panels/layers/styles.module.css +9 -12
  63. package/src/components/panels/legend/index.tsx +1 -1
  64. package/src/components/panels/logs/index.module.css +25 -19
  65. package/src/components/panels/logs/index.tsx +7 -7
  66. package/src/components/panels/nodeInputs/InputsGroup.tsx +1 -0
  67. package/src/components/panels/nodeInputs/NodeSettings.tsx +2 -2
  68. package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +1 -1
  69. package/src/components/panels/nodeInputs/OutputsGroup.tsx +2 -12
  70. package/src/components/panels/nodeInputs/index.module.css +99 -75
  71. package/src/components/panels/nodeInputs/index.tsx +21 -11
  72. package/src/components/panels/nodeInputs/useNodeHandlers.ts +2 -2
  73. package/src/components/panels/nodeInputs/useNodeInputsData.ts +23 -43
  74. package/src/components/panels/nodePicker/index.tsx +8 -8
  75. package/src/components/panels/panel/index.module.css +7 -7
  76. package/src/components/panels/search/index.module.css +0 -50
  77. package/src/components/panels/search/index.tsx +2 -2
  78. package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
  79. package/src/components/panels/systemSettings/index.tsx +221 -176
  80. package/src/components/panels/systemSettings/styles.module.css +135 -8
  81. package/src/components/panels/traces/GridLines.tsx +1 -1
  82. package/src/components/panels/traces/TimeGrid.tsx +3 -3
  83. package/src/components/panels/traces/TraceLane.tsx +1 -1
  84. package/src/components/panels/traces/index.module.css +1 -8
  85. package/src/components/panels/traces/index.tsx +8 -4
  86. package/src/components/panels/traces/useDerivedSpans.ts +241 -146
  87. package/src/components/panels/traces/utils.ts +8 -0
  88. package/src/components/panels/variables/CreateVariableScreen.tsx +3 -3
  89. package/src/components/panels/variables/ManageVariablesScreen.tsx +12 -9
  90. package/src/components/panels/variables/index.tsx +2 -2
  91. package/src/components/panels/variables/styles.module.css +4 -91
  92. package/src/components/primitives/icon.module.css +4 -4
  93. package/src/components/sockets/input/index.tsx +9 -2
  94. package/src/components/sockets/input/styles.module.css +2 -3
  95. package/src/components/sockets/output/index.tsx +10 -3
  96. package/src/components/sockets/output/styles.module.css +1 -6
  97. package/src/css/notes.css +135 -0
  98. package/src/css/prosemirror.css +3 -3
  99. package/src/css/rc-dock.css +143 -43
  100. package/src/css/rc-menu.css +56 -55
  101. package/src/css/themes/kiberon.css +127 -0
  102. package/src/css/vars.css +197 -13
  103. package/src/css/vscode-elements.css +124 -0
  104. package/src/generators/CallSubgraphGenerator.tsx +136 -0
  105. package/src/generators/CustomEventOnTriggeredGenerator.tsx +2 -2
  106. package/src/generators/GraphBoundaryGenerator.module.css +32 -0
  107. package/src/generators/GraphBoundaryGenerator.tsx +193 -0
  108. package/src/generators/SequenceGenerator.tsx +2 -2
  109. package/src/generators/SwitchOnIntegerGenerator.tsx +2 -2
  110. package/src/generators/SwitchOnStringGenerator.tsx +2 -2
  111. package/src/generators/callSubgraphSync.ts +126 -0
  112. package/src/generators/registerDefaultGenerators.ts +21 -0
  113. package/src/generators/registerDefaults.ts +26 -0
  114. package/src/hooks/useBehaveGraphFlow.ts +2 -2
  115. package/src/hooks/useFlowHandlers.ts +47 -9
  116. package/src/hooks/useWasdPan.ts +26 -4
  117. package/src/index.css +4 -16
  118. package/src/index.ts +17 -0
  119. package/src/manifest/contributionRegistry.ts +93 -0
  120. package/src/manifest/index.ts +4 -0
  121. package/src/manifest/loadManifest.ts +82 -0
  122. package/src/manifest/manifestPlugin.ts +29 -0
  123. package/src/manifest/passthroughValueType.ts +40 -0
  124. package/src/plugin/alignment/index.ts +22 -12
  125. package/src/plugin/autosave/controller.ts +366 -0
  126. package/src/plugin/autosave/index.tsx +114 -0
  127. package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
  128. package/src/plugin/autosave/panel/index.tsx +1 -0
  129. package/src/plugin/autosave/panel/styles.module.css +56 -0
  130. package/src/plugin/autosave/settings.ts +65 -0
  131. package/src/plugin/autosave/storage.ts +147 -0
  132. package/src/plugin/docs/index.tsx +2 -4
  133. package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
  134. package/src/plugin/docs/panel/index.tsx +15 -194
  135. package/src/plugin/docs/panel/styles.module.css +8 -8
  136. package/src/plugin/graphrunner/actions.ts +258 -185
  137. package/src/plugin/graphrunner/buttons.tsx +34 -26
  138. package/src/plugin/graphrunner/client.ts +4 -1
  139. package/src/plugin/graphrunner/index.tsx +29 -100
  140. package/src/plugin/graphrunner/panel.tsx +2 -2
  141. package/src/plugin/graphrunner/runController.ts +283 -0
  142. package/src/plugin/graphrunner/runner.ts +21 -192
  143. package/src/plugin/graphrunner/store.ts +14 -24
  144. package/src/plugin/graphrunner/styles.module.css +17 -57
  145. package/src/plugin/graphrunner/transport.ts +26 -0
  146. package/src/plugin/graphrunner/types.ts +21 -0
  147. package/src/plugin/graphrunner-local/execution-utils.ts +260 -80
  148. package/src/plugin/graphrunner-local/index.tsx +8 -2
  149. package/src/plugin/graphrunner-local/panel.tsx +131 -175
  150. package/src/plugin/graphrunner-local/styles.module.css +57 -76
  151. package/src/plugin/graphrunner-local/transport.ts +151 -184
  152. package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +2 -0
  153. package/src/plugin/graphrunner-webworker/index.tsx +4 -10
  154. package/src/plugin/graphrunner-webworker/store.ts +9 -0
  155. package/src/plugin/kitchen-sink/index.ts +38 -0
  156. package/src/{layout/dagre.tsx → plugin/layout/dagre.ts} +17 -5
  157. package/src/{layout → plugin/layout}/elk.ts +22 -6
  158. package/src/plugin/layout/index.ts +80 -0
  159. package/src/plugin/notes/FormatToolbar.tsx +200 -0
  160. package/src/plugin/notes/index.tsx +191 -0
  161. package/src/plugin/notes/nodeActions.ts +100 -0
  162. package/src/plugin/notes/note.tsx +20 -0
  163. package/src/plugin/notes/noteImpl.tsx +89 -0
  164. package/src/plugin/realtime/realtimeRunner.ts +58 -4
  165. package/src/specifics/CustomEventOnTriggeredSpecific.tsx +2 -2
  166. package/src/specifics/CustomEventTriggerSpecific.tsx +2 -2
  167. package/src/specifics/VariableGetSpecific.tsx +2 -2
  168. package/src/specifics/VariableSetSpecific.tsx +2 -2
  169. package/src/store/actions.tsx +5 -5
  170. package/src/store/commands.ts +278 -0
  171. package/src/store/contextMenu.ts +192 -0
  172. package/src/store/conversions.ts +47 -0
  173. package/src/store/flow.tsx +23 -38
  174. package/src/store/graphMeta.ts +39 -0
  175. package/src/store/hotKeys.tsx +301 -260
  176. package/src/store/layers.ts +3 -3
  177. package/src/store/registry.ts +12 -4
  178. package/src/store/selection.ts +3 -3
  179. package/src/store/settings.ts +82 -82
  180. package/src/store/settingsSchema.ts +210 -0
  181. package/src/store/tabs.ts +5 -1
  182. package/src/store/traces.ts +3 -3
  183. package/src/system/graph.ts +11 -14
  184. package/src/system/graphSession.ts +172 -0
  185. package/src/system/index.ts +3 -0
  186. package/src/system/notifications.ts +13 -0
  187. package/src/system/persistence.ts +82 -0
  188. package/src/system/plugin.ts +28 -0
  189. package/src/system/provider.tsx +64 -0
  190. package/src/system/system.ts +518 -88
  191. package/src/system/tabLoader.tsx +70 -32
  192. package/src/system/undoRedo.ts +1 -1
  193. package/src/transformers/Uigraph.ts +5 -4
  194. package/src/transformers/contract.ts +87 -0
  195. package/src/transformers/flowToBehave.ts +13 -5
  196. package/src/types/nodes.ts +8 -3
  197. package/src/types.ts +2 -0
  198. package/src/util/autoConvert.ts +200 -0
  199. package/src/util/isValidConnection.ts +23 -2
  200. package/stories/defaults/defaultStoryProvider.tsx +17 -14
  201. package/stories/defaults/systemGenerator.ts +6 -1
  202. package/stories/{components/nodes/comment.stories.tsx → plugins/notes.stories.tsx} +24 -30
  203. package/tests/autoConvert.test.ts +329 -0
  204. package/tests/autosavePlugin.test.ts +204 -0
  205. package/tests/callSubgraphSync.test.ts +148 -0
  206. package/tests/commandRegistry.test.ts +137 -0
  207. package/tests/contract.test.ts +51 -0
  208. package/tests/contractSerialize.test.ts +62 -0
  209. package/tests/deriveSpans.test.ts +71 -0
  210. package/tests/flowToBehave.test.ts +2 -1
  211. package/tests/hotkeys.test.ts +79 -0
  212. package/tests/keepAliveLifecycle.test.ts +167 -0
  213. package/tests/loadManifest.test.ts +113 -0
  214. package/tests/noteMarkdown.test.ts +65 -0
  215. package/tests/notesPlugin.test.ts +162 -0
  216. package/tests/persistence.test.ts +51 -0
  217. package/tests/saveLoad.test.ts +7 -6
  218. package/tests/settings.test.ts +178 -0
  219. package/tests/traceStore.test.ts +46 -0
  220. package/tests/visual/README.md +2 -2
  221. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
  222. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
  223. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
  224. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
  225. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
  226. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
  227. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
  228. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
  229. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
  230. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
  231. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
  232. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
  233. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
  234. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
  235. package/tests/visual/panels.visual.test.tsx +3 -3
  236. package/tests/wasdPan.test.ts +71 -0
  237. package/vitest.config.ts +1 -1
  238. package/vitest.visual.config.ts +7 -0
  239. package/.storybook/vscode.css +0 -814
  240. package/src/components/nodes/comment/FormatToolbar.tsx +0 -118
  241. package/src/components/nodes/comment/comment.tsx +0 -103
  242. package/src/components/nodes/comment/styles.module.css +0 -150
  243. package/src/components/panels/conversation/index.module.css +0 -151
  244. package/src/components/panels/conversation/index.tsx +0 -162
  245. package/src/components/panels/events/CustomEventsEditor.tsx +0 -384
  246. package/src/css/vscode.css +0 -13
  247. package/src/hooks/useDetachNodes.ts +0 -39
  248. package/src/plugin/graphrunner-webworker/types.ts +0 -17
  249. package/src/specifics/registerDefaultSpecifics.ts +0 -5
  250. package/src/store/chat.ts +0 -73
  251. package/src/store/graphRunnerClient.ts +0 -110
@@ -26,29 +26,43 @@ import type {
26
26
  RemoveLinkMessage,
27
27
  DirectExecuteNodeMessage
28
28
  } from '../graphrunner/types.js';
29
- import type { ITransport, TransportState } from '../graphrunner/transport.js';
29
+ import type {
30
+ ITransport,
31
+ IExecutionControl,
32
+ TransportState
33
+ } from '../graphrunner/transport.js';
30
34
  import {
31
35
  Engine,
32
36
  type GraphInstance,
33
37
  type ILifecycleEventEmitter,
34
38
  readGraphFromJSON,
35
39
  validateGraph,
36
- ManualLifecycleEventEmitter,
37
40
  DefaultLogger,
38
41
  type ILogger,
39
42
  Link,
40
- makeGraphApi
43
+ makeGraphApi,
44
+ runSubgraph,
45
+ DEFAULT_SUBGRAPH_MAX_DEPTH
46
+ } from '@kiberon-labs/behave-graph';
47
+ import type {
48
+ IRegistry,
49
+ IGraphApi,
50
+ GraphJSON
41
51
  } from '@kiberon-labs/behave-graph';
42
- import type { IRegistry } from '@kiberon-labs/behave-graph';
43
52
  import type { StoreApi } from 'zustand';
44
53
  import type { LocalGraphRunnerStore } from './store.js';
45
54
  import { sleep } from '@kiberon-labs/behave-graph';
46
55
  import {
56
+ setupTracing,
47
57
  setupVariableChangeTracking,
58
+ prepareRegistryWithDependencies,
48
59
  handleGetServerVariables,
49
60
  handleGetServerEvents,
50
61
  handleGetSocketConstraints,
51
- handleGetNodeTypes
62
+ handleGetNodeTypes,
63
+ executeGraphLifecycle,
64
+ type ActiveRun as BaseActiveRun,
65
+ type MessageContext
52
66
  } from './execution-utils.js';
53
67
  import {
54
68
  SessionManager,
@@ -58,31 +72,22 @@ import {
58
72
  } from '../graphrunner/session.js';
59
73
  import { createNode } from '@kiberon-labs/behave-graph';
60
74
 
61
- interface ActiveRun {
62
- runId: string;
63
- graphId: string;
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 {
64
81
  sessionId: string;
65
- engine: Engine;
66
- graphInstance: GraphInstance;
67
- registry: IRegistry;
68
- status: RunStatus;
69
- startedAt: number;
70
- performance: {
71
- nodesExecuted: number;
72
- eventsEmitted: number;
73
- variableChanges: number;
74
- };
75
- // Step execution control
76
- isPaused: boolean;
77
- executionPhase: 'start' | 'tick' | 'end' | 'completed';
78
- currentTick: number;
79
82
  maxTicks: number;
83
+ /** Whether this run finalizes when its flows drain (default: stay alive). */
84
+ autoEnd: boolean;
80
85
  }
81
86
 
82
87
  /**
83
88
  * Local transport that executes graphs in the browser using the Engine
84
89
  */
85
- export class LocalTransport implements ITransport {
90
+ export class LocalTransport implements ITransport, IExecutionControl {
86
91
  private state: TransportState = 'disconnected';
87
92
  private messageHandlers: Array<(message: ServerGraphRunnerMessage) => void> =
88
93
  [];
@@ -94,6 +99,7 @@ export class LocalTransport implements ITransport {
94
99
  private store: StoreApi<LocalGraphRunnerStore> | null = null;
95
100
  private variables: ServerVariable[];
96
101
  private serverEvents: ServerEvent[];
102
+ private resolveGraph?: (id: string) => GraphJSON | undefined;
97
103
 
98
104
  constructor(
99
105
  registry: IRegistry,
@@ -102,6 +108,10 @@ export class LocalTransport implements ITransport {
102
108
  variables?: ServerVariable[];
103
109
  serverEvents?: ServerEvent[];
104
110
  sessionFactory?: SessionFactory;
111
+ /**
112
+ * Resolve a referenced graph's JSON by id, enabling Call Subgraph nodes.
113
+ */
114
+ resolveGraph?: (id: string) => GraphJSON | undefined;
105
115
  }
106
116
  ) {
107
117
  this.registry = registry;
@@ -109,6 +119,7 @@ export class LocalTransport implements ITransport {
109
119
  this.variables = options?.variables ?? [];
110
120
  this.serverEvents = options?.serverEvents ?? [];
111
121
  this.sessionManager = new SessionManager(options?.sessionFactory);
122
+ this.resolveGraph = options?.resolveGraph;
112
123
  }
113
124
 
114
125
  /**
@@ -438,29 +449,40 @@ export class LocalTransport implements ITransport {
438
449
  };
439
450
  }
440
451
 
441
- if (
442
- !registryToUse.dependencies?.ILifecycleEventEmitter ||
443
- !registryToUse.dependencies?.ILogger
444
- ) {
445
- // Create a new registry with required dependencies injected
446
- registryToUse = {
447
- ...registryToUse,
448
- dependencies: {
449
- ...registryToUse.dependencies,
450
- ILifecycleEventEmitter:
451
- registryToUse.dependencies?.ILifecycleEventEmitter ||
452
- new ManualLifecycleEventEmitter(),
453
- ILogger: transportLogger
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({});
454
481
  }
455
482
  };
456
- } else {
457
- // Replace the existing logger with the transport logger
458
483
  registryToUse = {
459
484
  ...registryToUse,
460
- dependencies: {
461
- ...registryToUse.dependencies,
462
- ILogger: transportLogger
463
- }
485
+ dependencies: { ...registryToUse.dependencies, IGraphApi: graphApi }
464
486
  };
465
487
  }
466
488
 
@@ -508,7 +530,11 @@ export class LocalTransport implements ITransport {
508
530
  isPaused: false,
509
531
  executionPhase: 'start',
510
532
  currentTick: 0,
511
- maxTicks: Infinity // No limit - tick events run until stopped
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
512
538
  };
513
539
 
514
540
  this.activeRuns.set(runId, run);
@@ -541,37 +567,17 @@ export class LocalTransport implements ITransport {
541
567
  sendError: (code, msg, details) => this.sendError(code, msg, details)
542
568
  });
543
569
 
544
- // Set up tracing
570
+ // Set up tracing , shared with the worker runner so the trace event shape
571
+ // and timestamps stay consistent across runners.
545
572
  if (executionOptions.trace) {
546
- engine.onNodeExecutionStart.addListener((node) => {
547
- run.performance.nodesExecuted++;
548
- this.notifyMessage({
549
- type: 'trace',
550
- runId,
551
- graphId: message.graphId,
552
- nodeId: node.id,
553
- event: 'start',
554
- data: { typeName: node.description.typeName },
555
- timestamp: Date.now() - run.startedAt
556
- });
557
- });
558
-
559
- engine.onNodeExecutionEnd.addListener((node) => {
560
- this.notifyMessage({
561
- type: 'trace',
562
- runId,
563
- graphId: message.graphId,
564
- nodeId: node.id,
565
- event: 'end',
566
- data: { typeName: node.description.typeName },
567
- timestamp: Date.now() - run.startedAt
568
- });
573
+ setupTracing(run, message.graphId, {
574
+ sendMessage: this.notifyMessage.bind(this),
575
+ sendError: (code, msg, details) => this.sendError(code, msg, details)
569
576
  });
570
577
  }
571
578
 
572
579
  // Execute graph asynchronously
573
- const autoEnd = executionOptions.autoEnd ?? true;
574
- this.executeGraph(run, message.graphId, autoEnd, session);
580
+ this.executeGraph(run, message.graphId, run.autoEnd, session);
575
581
  } catch (error) {
576
582
  const errorMessage =
577
583
  error instanceof Error ? error.message : String(error);
@@ -600,124 +606,64 @@ export class LocalTransport implements ITransport {
600
606
  autoEnd: boolean,
601
607
  session: Session
602
608
  ): Promise<void> {
603
- try {
604
- // Get lifecycle event emitter from the registry dependencies
605
- const eventEmitter = run.registry.dependencies?.ILifecycleEventEmitter as
606
- | ILifecycleEventEmitter
607
- | undefined;
608
-
609
- // Execute start event
610
- if (run.executionPhase === 'start') {
611
- if (
612
- eventEmitter?.startEvent &&
613
- eventEmitter.startEvent.listenerCount > 0
614
- ) {
615
- eventEmitter.startEvent.emit();
616
- await this.executeWithPauseSupport(run);
617
- }
618
- run.executionPhase = 'tick';
619
- }
620
-
621
- // Execute tick events (runs indefinitely until stopped)
622
- if (run.executionPhase === 'tick') {
623
- if (
624
- eventEmitter?.tickEvent &&
625
- eventEmitter.tickEvent.listenerCount > 0
626
- ) {
627
- // Get tick strategy hook or create default
628
- const tickStrategy =
629
- session.config.tickStrategy ||
630
- this.createSleepTickStrategy(
631
- session.config.executionSettings?.tickInterval ??
632
- this.getTickInterval()
633
- );
634
-
635
- while (!run.isPaused && run.status === 'running') {
636
- eventEmitter.tickEvent.emit();
637
- await this.executeWithPauseSupport(run);
638
- run.currentTick++;
639
-
640
- if (run.isPaused || run.status !== 'running') {
641
- return; // Exit early if paused or stopped
642
- }
643
-
644
- // Call the tick strategy hook to handle timing
645
- await tickStrategy();
646
- }
647
- } else {
648
- // No tick event listeners, move to end phase
649
- run.executionPhase = 'end';
650
- }
651
- }
652
-
653
- // Execute end event
654
- if (run.executionPhase === 'end' && !run.isPaused) {
655
- if (eventEmitter?.endEvent && eventEmitter.endEvent.listenerCount > 0) {
656
- eventEmitter.endEvent.emit();
657
- await this.executeWithPauseSupport(run);
658
- }
659
- run.executionPhase = 'completed';
660
- }
661
-
662
- // Only complete if not paused
663
- if (!run.isPaused && !autoEnd) {
664
- // Run completed successfully
665
- run.status = 'completed';
666
- const elapsedMs = Date.now() - run.startedAt;
667
- const result = null; // Placeholder for result
668
-
669
- // Call session hook for run completed
670
- if (session.config.hooks?.onRunCompleted) {
671
- await session.config.hooks.onRunCompleted(
672
- session,
673
- run.runId,
674
- graphId,
675
- result
676
- );
677
- }
678
-
679
- this.notifyMessage({
680
- type: 'completed',
681
- runId: run.runId,
682
- graphId,
683
- completedAt: Date.now(),
684
- elapsedMs,
685
- result,
686
- performance: run.performance
687
- });
688
-
689
- // Cleanup
690
- run.engine.dispose();
691
- this.activeRuns.delete(run.runId);
692
- this.sessionManager.removeRunFromSession(run.sessionId, run.runId);
693
- this.updateStoreActiveRuns();
694
- this.updateStoreExecutionState(false, false);
695
- }
696
- } catch (error) {
697
- run.status = 'error';
698
- const errorMessage =
699
- error instanceof Error ? error.message : String(error);
700
-
701
- // Call session hook for run error
702
- if (session.config.hooks?.onRunError) {
703
- await session.config.hooks.onRunError(
704
- session,
705
- run.runId,
706
- graphId,
707
- error instanceof Error ? error : new Error(errorMessage)
708
- );
709
- }
609
+ const ctx: MessageContext = {
610
+ sendMessage: this.notifyMessage.bind(this),
611
+ sendError: (code, message, details) =>
612
+ this.sendError(code, message, details)
613
+ };
710
614
 
711
- this.sendError('NODE_EXECUTION_ERROR', errorMessage, {
712
- runId: run.runId,
713
- graphId
714
- });
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
+ );
715
622
 
716
- run.engine.dispose();
623
+ // Tear the run down and re-sync the panel's running / active-runs state.
624
+ const cleanup = (): void => {
717
625
  this.activeRuns.delete(run.runId);
718
626
  this.sessionManager.removeRunFromSession(run.sessionId, run.runId);
719
627
  this.updateStoreActiveRuns();
720
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.
721
667
  }
722
668
  }
723
669
 
@@ -737,11 +683,26 @@ export class LocalTransport implements ITransport {
737
683
  this.store?.getState().executionSpeed ??
738
684
  1.0;
739
685
 
740
- const stepLimit = this.getExecutionStepLimit();
741
686
  const delay =
742
687
  stepDelay + (executionSpeed < 1.0 ? (1.0 - executionSpeed) * 100 : 0);
743
688
 
744
- // Loop while engine has pending work and not paused
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.
745
706
  while (run.engine.hasPending() && !run.isPaused) {
746
707
  // Execute limited number of steps
747
708
  await run.engine.executeAllAsync(5, stepLimit);
@@ -783,7 +744,7 @@ export class LocalTransport implements ITransport {
783
744
 
784
745
  run.isPaused = false;
785
746
  // Continue execution from where we left off
786
- await this.executeGraph(run, run.graphId, true, session);
747
+ await this.executeGraph(run, run.graphId, run.autoEnd, session);
787
748
  }
788
749
 
789
750
  /**
@@ -853,6 +814,7 @@ export class LocalTransport implements ITransport {
853
814
 
854
815
  // Run completed successfully
855
816
  run.status = 'completed';
817
+ run.flushTracing?.();
856
818
  const elapsedMs = Date.now() - run.startedAt;
857
819
  const result = null;
858
820
 
@@ -881,6 +843,10 @@ export class LocalTransport implements ITransport {
881
843
  run.engine.dispose();
882
844
  this.activeRuns.delete(run.runId);
883
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);
884
850
  }
885
851
  }
886
852
  }
@@ -903,6 +869,7 @@ export class LocalTransport implements ITransport {
903
869
  }
904
870
 
905
871
  run.status = 'stopped';
872
+ run.flushTracing?.();
906
873
  this.updateStoreActiveRuns();
907
874
  this.updateStoreExecutionState(false, false);
908
875
  run.engine.dispose();
@@ -411,6 +411,7 @@ export function initializeGraphWorker(options: GraphWorkerOptions): void {
411
411
  }
412
412
 
413
413
  run.status = 'stopped';
414
+ run.flushTracing?.();
414
415
  run.engine.dispose();
415
416
  activeRuns.delete(message.runId);
416
417
 
@@ -570,6 +571,7 @@ export function initializeGraphWorker(options: GraphWorkerOptions): void {
570
571
  }
571
572
 
572
573
  run.status = 'stopped';
574
+ run.flushTracing?.();
573
575
  run.engine.dispose();
574
576
  activeRuns.delete(message.runId);
575
577
 
@@ -12,7 +12,6 @@ import { webWorkerGraphRunnerStoreFactory } from './store.js';
12
12
  import { WebWorkerGraphRunnerPanel } from './panel.js';
13
13
  import { MenuItemElement } from '../../components/menubar/menuItem.js';
14
14
  import { ErrorBoundary } from 'react-error-boundary';
15
- import { setupClientEventListeners } from '../graphrunner/actions.js';
16
15
 
17
16
  export * from './worker-transport.js';
18
17
  export * from './store.js';
@@ -76,20 +75,15 @@ export async function webWorkerGraphRunnerPluginLoader(
76
75
  }
77
76
  });
78
77
 
79
- // Register the graph runner client plugin
80
- // This will create the graphRunnerClientStore and decorate it on the system
78
+ // Register the graph runner client plugin. With skipAutoConnect:false the
79
+ // plugin calls runner.connect(), which wires the persistent client event
80
+ // listeners (trace/logs/lifecycle) on this same client , so we must NOT wire
81
+ // them again here, or every trace span would be recorded twice.
81
82
  await system.registerPlugin(graphRunnerClientPlugin, {
82
83
  client,
83
84
  skipAutoConnect: false
84
85
  });
85
86
 
86
- // Setup persistent event listeners for trace, logs, and run completion
87
- // Access the store from the system after it's been registered by the plugin
88
- const graphRunnerStore = system.runner.store;
89
- if (graphRunnerStore) {
90
- setupClientEventListeners(client, system, graphRunnerStore);
91
- }
92
-
93
87
  // Register the web worker graph runner panel
94
88
  system.tabLoader.register('webWorkerGraphRunner', () => {
95
89
  return {
@@ -87,3 +87,12 @@ export const webWorkerGraphRunnerStoreFactory =
87
87
  setStepDelay: (delay) => set({ stepDelay: delay }),
88
88
  setExecutionSpeed: (speed) => set({ executionSpeed: speed })
89
89
  }));
90
+
91
+ declare module '@/system/system' {
92
+ interface System {
93
+ /**
94
+ * Web Worker Graph Runner store
95
+ */
96
+ webWorkerGraphRunnerStore: StoreApi<WebWorkerGraphRunnerStore>;
97
+ }
98
+ }
@@ -0,0 +1,38 @@
1
+ import { plugin } from '@/system/plugin';
2
+ import type { System } from '@/system/system';
3
+ import { docsPlugin } from '@/plugin/docs';
4
+ import { alignmentPlugin } from '@/plugin/alignment';
5
+ import { layoutPlugin } from '@/plugin/layout';
6
+ import { notesPlugin } from '@/plugin/notes';
7
+ import { autosavePlugin } from '@/plugin/autosave';
8
+
9
+ /**
10
+ * Batteries-included bundle of the standard editor plugins. Register this once
11
+ * instead of wiring each plugin by hand:
12
+ *
13
+ * ```ts
14
+ * const system = new System(registry);
15
+ * system.registerPlugin(kitchenSinkPlugin);
16
+ * ```
17
+ *
18
+ * It currently pulls in:
19
+ * - {@link docsPlugin} — the in-editor node documentation browser;
20
+ * - {@link alignmentPlugin} — node alignment + distribution;
21
+ * - {@link layoutPlugin} — Dagre/ELK auto-layout (heavy deps, opt-in);
22
+ * - {@link notesPlugin} — markdown note nodes (tiptap/prosemirror, opt-in);
23
+ * - {@link autosavePlugin} — client-side local backups of open graphs.
24
+ *
25
+ * It intentionally does **not** register a graph runner: runners
26
+ * ({@link localGraphRunnerPlugin}, the remote client, ...) need host-specific
27
+ * options (a node registry, transport, ...) so hosts wire those themselves.
28
+ */
29
+ export const kitchenSinkPlugin = plugin(
30
+ async (system: System) => {
31
+ await system.registerPlugin(docsPlugin);
32
+ await system.registerPlugin(alignmentPlugin);
33
+ await system.registerPlugin(layoutPlugin);
34
+ await system.registerPlugin(notesPlugin);
35
+ await system.registerPlugin(autosavePlugin);
36
+ },
37
+ { name: 'kitchen-sink' }
38
+ );
@@ -1,6 +1,6 @@
1
1
  import { Position } from 'reactflow';
2
2
  import type { Edge, Node } from 'reactflow';
3
- import dagre from 'dagre';
3
+ import type dagreNS from 'dagre';
4
4
  import type { System } from '@/system';
5
5
  import { pinned } from '@/annotations';
6
6
 
@@ -11,6 +11,18 @@ export type Options = {
11
11
  direction: Direction;
12
12
  };
13
13
 
14
+ /**
15
+ * dagre is only needed when the user actually runs a Dagre layout, so load it
16
+ * lazily (a dynamic import the bundler code-splits into a separate chunk). This
17
+ * keeps it out of the initial load even for hosts that register the layout
18
+ * plugin. The module is fetched once and reused.
19
+ */
20
+ let dagrePromise: Promise<typeof dagreNS> | undefined;
21
+ const getDagre = () => {
22
+ dagrePromise ??= import('dagre').then((m) => m.default ?? m);
23
+ return dagrePromise;
24
+ };
25
+
14
26
  const getDimensions = (node: Node) => {
15
27
  return {
16
28
  width: node.style?.width ?? node.width ?? 300,
@@ -18,9 +30,6 @@ const getDimensions = (node: Node) => {
18
30
  };
19
31
  };
20
32
 
21
- const dagreGraph = new dagre.graphlib!.Graph();
22
- dagreGraph.setDefaultEdgeLabel(() => ({}));
23
-
24
33
  const positionMap: Record<string, Position> = {
25
34
  T: Position.Top,
26
35
  L: Position.Left,
@@ -28,7 +37,7 @@ const positionMap: Record<string, Position> = {
28
37
  B: Position.Bottom
29
38
  };
30
39
 
31
- export function applyDagreLayout(
40
+ export async function applyDagreLayout(
32
41
  system: System,
33
42
  options: Options | undefined = { direction: 'LR' }
34
43
  ) {
@@ -40,6 +49,9 @@ export function applyDagreLayout(
40
49
  return;
41
50
  }
42
51
 
52
+ const dagre = await getDagre();
53
+ const dagreGraph = new dagre.graphlib!.Graph();
54
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
43
55
  dagreGraph.setGraph({ rankdir: direction });
44
56
 
45
57
  // Add nodes to layout: exclude pinned nodes and child nodes inside groups
@@ -1,12 +1,29 @@
1
1
  import type { System } from '@/system';
2
- import ELK, {
3
- type ElkExtendedEdge,
4
- type ElkNode,
5
- type ElkPort
2
+ import type {
3
+ ElkExtendedEdge,
4
+ ElkNode,
5
+ ElkPort
6
6
  } from 'elkjs/lib/elk.bundled.js';
7
7
  import type { Edge, Node } from 'reactflow';
8
8
  import { pinned } from '@/annotations';
9
9
 
10
+ /**
11
+ * elkjs is ~1.4 MB — by far the largest dependency the editor can pull in, yet
12
+ * it is only used when the user explicitly runs an ELK layout. Load it lazily (a
13
+ * dynamic import the bundler code-splits into a separate chunk) so it stays out
14
+ * of the initial load even for consumers of this layout plugin. The instance is
15
+ * created once and reused.
16
+ */
17
+ let elkPromise:
18
+ | Promise<{ layout: (graph: ElkNode) => Promise<ElkNode> }>
19
+ | undefined;
20
+ const getElk = () => {
21
+ elkPromise ??= import('elkjs/lib/elk.bundled.js').then(
22
+ (m) => new m.default()
23
+ );
24
+ return elkPromise;
25
+ };
26
+
10
27
  const layoutOptions = {
11
28
  // 'elk.algorithm': 'layered',
12
29
  'elk.direction': 'RIGHT',
@@ -20,8 +37,6 @@ export type LayoutAlgorithm =
20
37
  | 'org.eclipse.elk.force'
21
38
  | 'org.eclipse.elk.rectpacking';
22
39
 
23
- const elk = new ELK();
24
-
25
40
  const getLayoutedNodes = async (
26
41
  nodes: Node[],
27
42
  edges: Edge[],
@@ -107,6 +122,7 @@ const getLayoutedNodes = async (
107
122
  )
108
123
  };
109
124
 
125
+ const elk = await getElk();
110
126
  const layoutedGraph = await elk.layout(graph);
111
127
 
112
128
  const layoutedNodes = nodes.map((node) => {