@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
@@ -13,15 +13,16 @@ import type { StoreApi } from 'zustand';
13
13
  import type { GraphRunnerClientStore } from './store';
14
14
  import { GraphRunnerButtons } from './buttons';
15
15
  import { plugin } from '@/system/plugin';
16
- import { isBehaveNode } from '@/util/isBehaveNode';
17
- import type { Node } from 'reactflow';
18
16
  import { GraphRunner } from './runner';
17
+ import { GraphRunController } from './runController';
18
+ import type { GraphSession } from '@/system/graphSession';
19
19
 
20
20
  export * from './types';
21
21
  export * from './client';
22
22
  export * from './transport';
23
23
  export * from './panel';
24
24
  export * from './runner';
25
+ export * from './runController';
25
26
  export * from './store';
26
27
  export * from './session';
27
28
  /**
@@ -86,25 +87,33 @@ export async function graphRunnerClientPluginLoader(
86
87
  }
87
88
  }
88
89
 
89
- // Create and decorate actions
90
+ // Shared connection. Per-session run controllers are created below.
90
91
  const runner = new GraphRunner(system, store);
91
92
  system.decorate('runner', runner);
92
93
 
93
- // Add toolbar buttons for graph execution control
94
+ // Attach a run controller to every graph session (existing + future) so each
95
+ // open graph runs independently over the shared connection. The editor applies
96
+ // this to graphs already open and to every graph created later, and tears the
97
+ // controller down when a graph's tab is closed.
98
+ system.registerSessionExtension((session: GraphSession) => {
99
+ if (!session.runController) {
100
+ session.decorate(
101
+ 'runController',
102
+ new GraphRunController(session, runner)
103
+ );
104
+ }
105
+ return () => {
106
+ session.runController?.dispose();
107
+ session.decorate('runController', undefined);
108
+ };
109
+ });
110
+
111
+ // Add toolbar buttons for graph execution control. Rendered inside each graph
112
+ // tab, GraphRunnerButtons resolves its own session's controller.
94
113
  system.toolbarStore.getState().addGroup({
95
114
  id: 'graph-runner-controls',
96
115
  label: 'Graph Runner',
97
- buttons: [
98
- <GraphRunnerButtons
99
- key="graph-runner-buttons"
100
- store={store}
101
- onPlay={() => runner.play()}
102
- onPause={() => runner.pause()}
103
- onResume={() => runner.resume()}
104
- onStep={() => runner.step()}
105
- onStop={() => runner.stop()}
106
- />
107
- ]
116
+ buttons: [<GraphRunnerButtons key="graph-runner-buttons" />]
108
117
  });
109
118
 
110
119
  system.hotKeyStore.getState().register({
@@ -112,10 +121,12 @@ export async function graphRunnerClientPluginLoader(
112
121
  description: 'Triggers playing the graph',
113
122
  trigger: 'p',
114
123
  handler: () => {
115
- if (runner.store.getState().isExecuting) {
116
- runner.stop();
124
+ const controller = system.session?.runController;
125
+ if (!controller) return;
126
+ if (controller.store.getState().isExecuting) {
127
+ controller.stop();
117
128
  } else {
118
- runner.play();
129
+ controller.play();
119
130
  }
120
131
  }
121
132
  });
@@ -163,88 +174,6 @@ export async function graphRunnerClientPluginLoader(
163
174
  }
164
175
  }
165
176
 
166
- system.pubsub.subscribe('node:added', (_, node: Node) => {
167
- if (!isBehaveNode(node)) {
168
- return;
169
- }
170
-
171
- const client = runner.store.getState().client;
172
- const currentRunId = runner.store.getState().currentRunId;
173
- const graphId = runner.store.getState().currentGraphId;
174
-
175
- // Only send if we have an active run and realtime is enabled
176
- if (client && currentRunId && graphId) {
177
- const capabilities = runner.store.getState().connectionInfo.capabilities;
178
- if (capabilities?.realtime) {
179
- client.addNode(
180
- currentRunId,
181
- node.id,
182
- node.data.type,
183
- node.data as Record<string, unknown>,
184
- node.position
185
- );
186
- }
187
- }
188
- });
189
-
190
- system.pubsub.subscribe('edge:added', (_, edge) => {
191
- const client = runner.store.getState().client;
192
- const currentRunId = runner.store.getState().currentRunId;
193
- const graphId = runner.store.getState().currentGraphId;
194
-
195
- // Only send if we have an active run and realtime is enabled
196
- if (client && currentRunId && graphId && edge.source && edge.target) {
197
- const capabilities = runner.store.getState().connectionInfo.capabilities;
198
- if (capabilities?.realtime) {
199
- client.createLink(
200
- currentRunId,
201
- edge.source,
202
- edge.sourceHandle || '',
203
- edge.target,
204
- edge.targetHandle || ''
205
- );
206
- }
207
- }
208
- });
209
-
210
- system.pubsub.subscribe('edge:removed', (_, edge) => {
211
- const client = runner.store.getState().client;
212
- const currentRunId = runner.store.getState().currentRunId;
213
- const graphId = runner.store.getState().currentGraphId;
214
-
215
- // Only send if we have an active run and realtime is enabled
216
- if (client && currentRunId && graphId && edge.source && edge.target) {
217
- const capabilities = runner.store.getState().connectionInfo.capabilities;
218
- if (capabilities?.realtime) {
219
- client.removeLink(
220
- currentRunId,
221
- edge.source,
222
- edge.sourceHandle || '',
223
- edge.target,
224
- edge.targetHandle || ''
225
- );
226
- }
227
- }
228
- });
229
-
230
- system.pubsub.subscribe('node:removed', (_, node: Node) => {
231
- if (!isBehaveNode(node)) {
232
- return;
233
- }
234
-
235
- const client = runner.store.getState().client;
236
- const currentRunId = runner.store.getState().currentRunId;
237
- const graphId = runner.store.getState().currentGraphId;
238
-
239
- // Only send if we have an active run and realtime is enabled
240
- if (client && currentRunId && graphId) {
241
- const capabilities = runner.store.getState().connectionInfo.capabilities;
242
- if (capabilities?.realtime) {
243
- client.removeNode(currentRunId, node.id);
244
- }
245
- }
246
- });
247
-
248
177
  if (!options.skipAutoConnect) {
249
178
  await runner.connect();
250
179
  }
@@ -348,8 +348,8 @@ export const GraphRunnerPanel: React.FC<GraphRunnerPanelProps> = ({
348
348
  ).toLocaleTimeString();
349
349
  const directionColor =
350
350
  activity.direction === 'sent'
351
- ? 'var(--vscode-gitDecoration-modifiedResourceForeground)'
352
- : 'var(--vscode-gitDecoration-addedResourceForeground)';
351
+ ? 'var(--ds-git-modified)'
352
+ : 'var(--ds-git-added)';
353
353
 
354
354
  return (
355
355
  <div key={activity.id} className={styles.messageCard}>
@@ -0,0 +1,283 @@
1
+ import { createStore, type StoreApi } from 'zustand';
2
+ import type { GraphSession } from '@/system/graphSession';
3
+ import type { GraphRunner } from './runner';
4
+ import { buildUIGraphJSON } from '../../transformers/Uigraph';
5
+ import { isBehaveNode } from '@/util/isBehaveNode';
6
+ import { supportsExecutionControl } from './transport';
7
+
8
+ // Contribute the per-session run controller to the graph session as a typed,
9
+ // plugin-owned property. Core no longer declares this field , the graph runner
10
+ // plugin attaches it via `session.decorate('runController', …)` from a session
11
+ // extension (see ./index.tsx).
12
+ declare module '@/system/graphSession' {
13
+ interface IGraphSession {
14
+ runController?: GraphRunController;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Per-run state for a single graph. Each {@link GraphSession} owns one of these,
20
+ * so multiple graphs can run independently and concurrently.
21
+ */
22
+ export interface RunControllerStore {
23
+ currentRunId: string | null;
24
+ currentGraphId: string | null;
25
+ isExecuting: boolean;
26
+ isPaused: boolean;
27
+ setCurrentRunId: (runId: string | null) => void;
28
+ setCurrentGraphId: (graphId: string | null) => void;
29
+ setIsExecuting: (isExecuting: boolean) => void;
30
+ setIsPaused: (isPaused: boolean) => void;
31
+ }
32
+
33
+ const runControllerStoreFactory = (): StoreApi<RunControllerStore> =>
34
+ createStore<RunControllerStore>((set) => ({
35
+ currentRunId: null,
36
+ currentGraphId: null,
37
+ isExecuting: false,
38
+ isPaused: false,
39
+ setCurrentRunId: (currentRunId) => set({ currentRunId }),
40
+ setCurrentGraphId: (currentGraphId) => set({ currentGraphId }),
41
+ setIsExecuting: (isExecuting) => set({ isExecuting }),
42
+ setIsPaused: (isPaused) => set({ isPaused })
43
+ }));
44
+
45
+ /**
46
+ * Drives execution for a single graph session. Run lifecycle and run state are
47
+ * per-session; the underlying connection/client is shared via {@link GraphRunner}.
48
+ * Incoming server messages are routed back to the originating controller by run
49
+ * id (see `GraphRunner.runIndex`).
50
+ */
51
+ export class GraphRunController {
52
+ public readonly session: GraphSession;
53
+ public readonly runner: GraphRunner;
54
+ public readonly store: StoreApi<RunControllerStore> =
55
+ runControllerStoreFactory();
56
+ private readonly disposers: Array<() => void> = [];
57
+
58
+ constructor(session: GraphSession, runner: GraphRunner) {
59
+ this.session = session;
60
+ this.runner = runner;
61
+ this.setupRealtimeForwarding();
62
+ }
63
+
64
+ private get notifications() {
65
+ return this.session.editor.notifications;
66
+ }
67
+
68
+ /** Run the graph for this session. */
69
+ async play(): Promise<void> {
70
+ const { clearLogsOnRun, clearTracesOnRun } = this.runner.store.getState();
71
+
72
+ if (clearLogsOnRun) {
73
+ this.session.logsStore.getState().clear();
74
+ }
75
+ if (clearTracesOnRun) {
76
+ this.session.traceStore.getState().clear();
77
+ }
78
+
79
+ const graphId = this.session.id;
80
+ const uiGraphData = buildUIGraphJSON(this.session);
81
+ try {
82
+ await this.runRemotely(graphId, { graph: uiGraphData.flow });
83
+ } catch {
84
+ // Error already surfaced in runRemotely
85
+ }
86
+ }
87
+
88
+ async runRemotely(
89
+ graphId: string,
90
+ options?: { graph?: unknown; inputs?: unknown }
91
+ ): Promise<void> {
92
+ const client = this.runner.store.getState().client;
93
+ const { enableTracing } = this.runner.store.getState();
94
+ const { setCurrentRunId, setCurrentGraphId, setIsExecuting, setIsPaused } =
95
+ this.store.getState();
96
+
97
+ if (!client) {
98
+ this.notifications.error('No graph runner connection');
99
+ throw new Error('No graph runner connection');
100
+ }
101
+ if (this.store.getState().isExecuting) {
102
+ return;
103
+ }
104
+
105
+ try {
106
+ const runId = await client.runGraph(graphId, {
107
+ ...options,
108
+ trace: enableTracing
109
+ });
110
+
111
+ setCurrentRunId(runId);
112
+ setCurrentGraphId(graphId);
113
+ setIsExecuting(true);
114
+ setIsPaused(false);
115
+ this.runner.registerRun(runId, this);
116
+
117
+ this.notifications.info(`Graph execution started: ${graphId}`);
118
+ } catch (error) {
119
+ const message = error instanceof Error ? error.message : String(error);
120
+ setIsExecuting(false);
121
+ setIsPaused(false);
122
+ setCurrentRunId(null);
123
+ setCurrentGraphId(null);
124
+ this.notifications.error(`Failed to run graph: ${message}`);
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ async stop(): Promise<void> {
130
+ const client = this.runner.store.getState().client;
131
+ const { currentRunId } = this.store.getState();
132
+ if (!client || !currentRunId) return;
133
+
134
+ try {
135
+ await client.stopGraph(currentRunId);
136
+ this.notifications.info('Stopping graph execution');
137
+ this.finishRun();
138
+ } catch (error) {
139
+ const message = error instanceof Error ? error.message : String(error);
140
+ this.notifications.error(`Failed to stop graph: ${message}`);
141
+ }
142
+ }
143
+
144
+ async pause(): Promise<void> {
145
+ const client = this.runner.store.getState().client;
146
+ const { currentRunId, setIsPaused } = this.store.getState();
147
+ if (!client || !currentRunId) return;
148
+
149
+ try {
150
+ const transport = client.transport;
151
+ if (supportsExecutionControl(transport)) {
152
+ transport.pauseExecution(currentRunId);
153
+ setIsPaused(true);
154
+ this.notifications.info('Execution paused');
155
+ } else {
156
+ await this.stop();
157
+ }
158
+ } catch (error) {
159
+ const message = error instanceof Error ? error.message : String(error);
160
+ this.notifications.error(`Failed to pause graph: ${message}`);
161
+ }
162
+ }
163
+
164
+ async resume(): Promise<void> {
165
+ const client = this.runner.store.getState().client;
166
+ const { currentRunId, setIsPaused } = this.store.getState();
167
+ if (!client || !currentRunId) return;
168
+
169
+ try {
170
+ const transport = client.transport;
171
+ if (supportsExecutionControl(transport)) {
172
+ setIsPaused(false);
173
+ this.notifications.info('Resuming execution');
174
+ await transport.resumeExecution(currentRunId);
175
+ }
176
+ } catch (error) {
177
+ const message = error instanceof Error ? error.message : String(error);
178
+ this.notifications.error(`Failed to resume graph: ${message}`);
179
+ }
180
+ }
181
+
182
+ async step(): Promise<void> {
183
+ const client = this.runner.store.getState().client;
184
+ const { currentRunId, setIsPaused } = this.store.getState();
185
+ if (!client || !currentRunId) return;
186
+
187
+ try {
188
+ const transport = client.transport;
189
+ if (supportsExecutionControl(transport)) {
190
+ setIsPaused(true);
191
+ await transport.stepExecution(currentRunId);
192
+ } else {
193
+ this.notifications.info(
194
+ 'Step execution not supported for this transport'
195
+ );
196
+ }
197
+ } catch (error) {
198
+ const message = error instanceof Error ? error.message : String(error);
199
+ this.notifications.error(`Failed to step graph: ${message}`);
200
+ }
201
+ }
202
+
203
+ /** Reset run state after completion/stop/error and unregister the run. */
204
+ finishRun(): void {
205
+ const runId = this.store.getState().currentRunId;
206
+ if (runId) this.runner.unregisterRun(runId);
207
+ const s = this.store.getState();
208
+ s.setIsExecuting(false);
209
+ s.setIsPaused(false);
210
+ s.setCurrentRunId(null);
211
+ s.setCurrentGraphId(null);
212
+ }
213
+
214
+ /**
215
+ * Forward live graph edits to the server while this graph is running, tagged
216
+ * with this controller's run id so concurrent graphs stay isolated.
217
+ */
218
+ private setupRealtimeForwarding(): void {
219
+ const pubsub = this.session.pubsub;
220
+ const canForward = () => {
221
+ const client = this.runner.store.getState().client;
222
+ const runId = this.store.getState().currentRunId;
223
+ const caps = this.runner.store.getState().connectionInfo.capabilities;
224
+ return client && runId && caps?.realtime ? { client, runId } : null;
225
+ };
226
+
227
+ const tokens = [
228
+ pubsub.subscribe('node:added', (_, node) => {
229
+ if (!isBehaveNode(node)) return;
230
+ const ctx = canForward();
231
+ if (!ctx) return;
232
+ ctx.client.addNode(
233
+ ctx.runId,
234
+ node.id,
235
+ node.data.type,
236
+ node.data as Record<string, unknown>,
237
+ node.position
238
+ );
239
+ }),
240
+ pubsub.subscribe('edge:added', (_, edge) => {
241
+ const ctx = canForward();
242
+ if (!ctx || !edge.source || !edge.target) return;
243
+ ctx.client.createLink(
244
+ ctx.runId,
245
+ edge.source,
246
+ edge.sourceHandle || '',
247
+ edge.target,
248
+ edge.targetHandle || ''
249
+ );
250
+ }),
251
+ pubsub.subscribe('edge:removed', (_, edge) => {
252
+ const ctx = canForward();
253
+ if (!ctx || !edge.source || !edge.target) return;
254
+ ctx.client.removeLink(
255
+ ctx.runId,
256
+ edge.source,
257
+ edge.sourceHandle || '',
258
+ edge.target,
259
+ edge.targetHandle || ''
260
+ );
261
+ }),
262
+ pubsub.subscribe('node:removed', (_, node) => {
263
+ if (!isBehaveNode(node)) return;
264
+ const ctx = canForward();
265
+ if (!ctx) return;
266
+ ctx.client.removeNode(ctx.runId, node.id);
267
+ })
268
+ ];
269
+
270
+ for (const token of tokens) {
271
+ if (typeof token === 'string') {
272
+ this.disposers.push(() => pubsub.unsubscribe(token));
273
+ }
274
+ }
275
+ }
276
+
277
+ dispose(): void {
278
+ const runId = this.store.getState().currentRunId;
279
+ if (runId) this.runner.unregisterRun(runId);
280
+ this.disposers.forEach((d) => d());
281
+ this.disposers.length = 0;
282
+ }
283
+ }