@bian-womp/spark-workbench 0.2.93 → 0.3.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 (69) hide show
  1. package/lib/cjs/index.cjs +252 -293
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/core/AbstractWorkbench.d.ts +8 -7
  4. package/lib/cjs/src/core/AbstractWorkbench.d.ts.map +1 -1
  5. package/lib/cjs/src/core/InMemoryWorkbench.d.ts +4 -2
  6. package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
  7. package/lib/cjs/src/core/contracts.d.ts +5 -2
  8. package/lib/cjs/src/core/contracts.d.ts.map +1 -1
  9. package/lib/cjs/src/examples/reactflow/App.d.ts.map +1 -1
  10. package/lib/cjs/src/index.d.ts +1 -0
  11. package/lib/cjs/src/index.d.ts.map +1 -1
  12. package/lib/cjs/src/misc/DefaultNodeHeader.d.ts +1 -2
  13. package/lib/cjs/src/misc/DefaultNodeHeader.d.ts.map +1 -1
  14. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  15. package/lib/cjs/src/misc/WorkbenchStudio.d.ts +1 -3
  16. package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
  17. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +8 -8
  18. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  19. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts +1 -4
  20. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  21. package/lib/cjs/src/misc/context-menu/ContextMenuHandlers.d.ts +6 -2
  22. package/lib/cjs/src/misc/context-menu/ContextMenuHandlers.d.ts.map +1 -1
  23. package/lib/cjs/src/misc/context-menu/ContextMenuHelpers.d.ts +1 -1
  24. package/lib/cjs/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -1
  25. package/lib/cjs/src/misc/context-menu/NodeContextMenu.d.ts +1 -1
  26. package/lib/cjs/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
  27. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +9 -7
  28. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  29. package/lib/cjs/src/runtime/IGraphRunner.d.ts +18 -7
  30. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  31. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +7 -4
  32. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  33. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +11 -9
  34. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  35. package/lib/esm/index.js +255 -296
  36. package/lib/esm/index.js.map +1 -1
  37. package/lib/esm/src/core/AbstractWorkbench.d.ts +8 -7
  38. package/lib/esm/src/core/AbstractWorkbench.d.ts.map +1 -1
  39. package/lib/esm/src/core/InMemoryWorkbench.d.ts +4 -2
  40. package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
  41. package/lib/esm/src/core/contracts.d.ts +5 -2
  42. package/lib/esm/src/core/contracts.d.ts.map +1 -1
  43. package/lib/esm/src/examples/reactflow/App.d.ts.map +1 -1
  44. package/lib/esm/src/index.d.ts +1 -0
  45. package/lib/esm/src/index.d.ts.map +1 -1
  46. package/lib/esm/src/misc/DefaultNodeHeader.d.ts +1 -2
  47. package/lib/esm/src/misc/DefaultNodeHeader.d.ts.map +1 -1
  48. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  49. package/lib/esm/src/misc/WorkbenchStudio.d.ts +1 -3
  50. package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
  51. package/lib/esm/src/misc/context/WorkbenchContext.d.ts +8 -8
  52. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  53. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts +1 -4
  54. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  55. package/lib/esm/src/misc/context-menu/ContextMenuHandlers.d.ts +6 -2
  56. package/lib/esm/src/misc/context-menu/ContextMenuHandlers.d.ts.map +1 -1
  57. package/lib/esm/src/misc/context-menu/ContextMenuHelpers.d.ts +1 -1
  58. package/lib/esm/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -1
  59. package/lib/esm/src/misc/context-menu/NodeContextMenu.d.ts +1 -1
  60. package/lib/esm/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
  61. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +9 -7
  62. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  63. package/lib/esm/src/runtime/IGraphRunner.d.ts +18 -7
  64. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  65. package/lib/esm/src/runtime/LocalGraphRunner.d.ts +7 -4
  66. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  67. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +11 -9
  68. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  69. package/package.json +4 -4
package/lib/esm/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { generateId, GraphBuilder, getTypedOutputValue, isTypedOutput, getInputTypeId, createEngine, StepEngine, PullEngine, BatchedEngine, getTypedOutputTypeId, isInputPrivate, offsetImportedPositions, createSimpleGraphRegistry, createSimpleGraphDef, createAsyncGraphDef, createAsyncGraphRegistry, createProgressGraphDef, createProgressGraphRegistry, createValidationGraphDef, createValidationGraphRegistry } from '@bian-womp/spark-graph';
1
+ import { generateId, createSimpleGraphRegistry, GraphBuilder, getTypedOutputValue, isTypedOutput, getInputTypeId, UnifiedEngine, getTypedOutputTypeId, isInputPrivate, offsetImportedPositions, createSimpleGraphDef, createAsyncGraphDef, createAsyncGraphRegistry, createProgressGraphDef, createProgressGraphRegistry, createValidationGraphDef, createValidationGraphRegistry } from '@bian-womp/spark-graph';
2
2
  import lod from 'lodash';
3
- import { RuntimeApiClient } from '@bian-womp/spark-remote';
3
+ import { RemoteRuntimeClient } from '@bian-womp/spark-remote';
4
4
  import { Position, Handle, NodeResizer, getBezierPath, BaseEdge, useReactFlow, ReactFlowProvider, ReactFlow, Background, BackgroundVariant, MiniMap, Controls } from '@xyflow/react';
5
5
  import React, { useCallback, useState, useRef, useEffect, useMemo, createContext, useContext, useImperativeHandle } from 'react';
6
6
  import cx from 'classnames';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { XCircleIcon, WarningCircleIcon, ArrowClockwiseIcon, CopyIcon, TrashIcon, XIcon, ClockClockwiseIcon, StopIcon, PlayIcon, PlugsConnectedIcon, WifiHighIcon, WifiSlashIcon, PlayPauseIcon, LightningIcon, TreeStructureIcon, CornersOutIcon, DownloadIcon, UploadIcon, ImageIcon, BugBeetleIcon, ListBulletsIcon } from '@phosphor-icons/react';
8
+ import { XCircleIcon, WarningCircleIcon, StopIcon, PlayIcon, Circle, CopyIcon, TrashIcon, XIcon, ClockClockwiseIcon, PlugsConnectedIcon, WifiHighIcon, WifiSlashIcon, TreeStructureIcon, CornersOutIcon, DownloadIcon, UploadIcon, ImageIcon, BugBeetleIcon, ListBulletsIcon } from '@phosphor-icons/react';
9
9
 
10
10
  class DefaultUIExtensionRegistry {
11
11
  constructor() {
@@ -133,8 +133,8 @@ class AbstractWorkbench {
133
133
  }
134
134
 
135
135
  class InMemoryWorkbench extends AbstractWorkbench {
136
- constructor() {
137
- super(...arguments);
136
+ constructor(args) {
137
+ super(args);
138
138
  this._def = { nodes: [], edges: [] };
139
139
  this.listeners = new Map();
140
140
  this.positions = {};
@@ -148,12 +148,17 @@ class InMemoryWorkbench extends AbstractWorkbench {
148
148
  this.viewport = null;
149
149
  this.historyState = undefined;
150
150
  this.copiedData = null;
151
+ this._registry = createSimpleGraphRegistry();
151
152
  }
152
153
  get def() {
153
154
  return this._def;
154
155
  }
156
+ get registry() {
157
+ return this._registry;
158
+ }
155
159
  setRegistry(registry) {
156
- this.registry = registry;
160
+ this._registry = registry;
161
+ this.emit("registryChanged", { registry });
157
162
  }
158
163
  async load(def) {
159
164
  this._def = { nodes: [...def.nodes], edges: [...def.edges] };
@@ -920,30 +925,20 @@ class AbstractGraphRunner {
920
925
  this.engine.dispose();
921
926
  this.engine = undefined;
922
927
  // Emit status but keep runtime alive
923
- if (this.runningKind) {
924
- this.runningKind = undefined;
925
- this.emit("status", { running: false, engine: undefined });
926
- }
927
- }
928
- async switchEngine(opts) {
929
- if (!this.engine || !this.runtime) {
930
- throw new Error("No engine running to switch from");
931
- }
932
- // Wait for current engine to be idle
933
- await this.whenIdle();
934
- // Capture current state
935
- const currentInputs = { ...this.stagedInputs };
936
- // Stop current engine
937
- this.stop();
938
- // Ensure runtime is in a clean state (resumed)
939
- this.runtime.resume();
940
- // Create and launch new engine (to be implemented by subclasses)
941
- await this.createAndLaunchEngine(opts);
942
- // Re-apply staged inputs to new engine using runner's setInputs method
943
- // This ensures consistency and proper handling of staged inputs
944
- for (const [nodeId, map] of Object.entries(currentInputs)) {
945
- await this.setInputs(nodeId, map);
928
+ if (this.runMode) {
929
+ this.runMode = undefined;
930
+ this.emit("status", { running: false, runMode: undefined });
931
+ }
932
+ }
933
+ setRunMode(runMode) {
934
+ if (!this.engine) {
935
+ throw new Error("Cannot set run mode: engine not running");
946
936
  }
937
+ // Update engine run mode (this will update pause/resume state)
938
+ this.engine.setRunMode(runMode);
939
+ // Update local state and emit status event
940
+ this.runMode = runMode;
941
+ this.emit("status", { running: true, runMode: this.runMode });
947
942
  }
948
943
  getInputDefaults(def) {
949
944
  const out = {};
@@ -973,16 +968,16 @@ class AbstractGraphRunner {
973
968
  this.engine = undefined;
974
969
  this.runtime?.dispose();
975
970
  this.runtime = undefined;
976
- if (this.runningKind) {
977
- this.runningKind = undefined;
978
- this.emit("status", { running: false, engine: undefined });
971
+ if (this.runMode) {
972
+ this.runMode = undefined;
973
+ this.emit("status", { running: false, runMode: undefined });
979
974
  }
980
975
  }
981
976
  isRunning() {
982
977
  return !!this.engine;
983
978
  }
984
- getRunningEngine() {
985
- return this.runningKind;
979
+ getRunMode() {
980
+ return this.runMode;
986
981
  }
987
982
  // Optional undo/redo support
988
983
  async undo() {
@@ -1077,7 +1072,7 @@ class LocalGraphRunner extends AbstractGraphRunner {
1077
1072
  if (!this.runtime)
1078
1073
  throw new Error("Runtime not built");
1079
1074
  // Use shared engine factory
1080
- this.engine = createEngine(this.runtime, opts);
1075
+ this.engine = new UnifiedEngine(this.runtime, opts?.runMode);
1081
1076
  if (!this.engine)
1082
1077
  throw new Error("Failed to create engine");
1083
1078
  this.engine.on("value", (e) => this.emit("value", e));
@@ -1085,36 +1080,34 @@ class LocalGraphRunner extends AbstractGraphRunner {
1085
1080
  this.engine.on("invalidate", (e) => this.emit("invalidate", e));
1086
1081
  this.engine.on("stats", (e) => this.emit("stats", e));
1087
1082
  this.engine.launch(opts?.invalidate);
1088
- this.runningKind = opts?.engine ?? "push";
1089
- this.emit("status", { running: true, engine: this.runningKind });
1083
+ this.runMode = opts?.runMode ?? "manual";
1084
+ this.emit("status", { running: true, runMode: this.runMode });
1090
1085
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
1091
1086
  this.engine.setInputs(nodeId, map);
1092
1087
  }
1093
1088
  }
1094
- async step() {
1095
- const eng = this.engine;
1096
- if (eng && eng instanceof StepEngine)
1097
- await eng.step();
1089
+ async computeNode(nodeId, options) {
1090
+ if (this.engine)
1091
+ await this.engine.computeNode(nodeId, options);
1098
1092
  }
1099
- async computeNode(nodeId) {
1100
- const eng = this.engine;
1101
- if (eng && eng instanceof PullEngine)
1102
- await eng.computeNode(nodeId);
1093
+ async runFromHere(nodeId) {
1094
+ if (this.engine)
1095
+ await this.engine.runFromHere(nodeId);
1103
1096
  }
1104
- async flush() {
1105
- const eng = this.engine;
1106
- if (eng && eng instanceof BatchedEngine)
1107
- await eng.flush();
1097
+ cancelNodeRuns(nodeIds) {
1098
+ if (this.engine) {
1099
+ this.engine.cancelNodeRuns(nodeIds);
1100
+ }
1108
1101
  }
1109
1102
  getOutputs(def) {
1110
1103
  const out = {};
1111
- if (!this.runtime)
1104
+ if (!this.engine)
1112
1105
  return out;
1113
1106
  for (const n of def.nodes) {
1114
1107
  const desc = this.registry.nodes.get(n.typeId);
1115
1108
  const handles = Object.keys(desc?.outputs ?? {});
1116
1109
  for (const h of handles) {
1117
- const v = this.runtime.getOutput(n.nodeId, h);
1110
+ const v = this.engine.getOutput(n.nodeId, h);
1118
1111
  if (v !== undefined) {
1119
1112
  if (!out[n.nodeId])
1120
1113
  out[n.nodeId] = {};
@@ -1147,19 +1140,8 @@ class LocalGraphRunner extends AbstractGraphRunner {
1147
1140
  return out;
1148
1141
  }
1149
1142
  triggerExternal(nodeId, event, options) {
1150
- // Handle dry option: pause runtime before triggering, resume after
1151
- const wasPaused = this.runtime?.isPaused() ?? false;
1152
- if (options?.dry && !wasPaused && this.runtime) {
1153
- this.runtime.pause();
1154
- }
1155
- try {
1156
- this.engine?.triggerExternal(nodeId, event);
1157
- }
1158
- finally {
1159
- if (options?.dry && !wasPaused && this.runtime) {
1160
- this.runtime.resume();
1161
- }
1162
- }
1143
+ // Engine handles dry option via AbstractEngine
1144
+ this.engine?.triggerExternal(nodeId, event, options);
1163
1145
  }
1164
1146
  // Batch update multiple inputs on a node and trigger a single run
1165
1147
  setInputs(nodeId, inputs, options) {
@@ -1175,40 +1157,23 @@ class LocalGraphRunner extends AbstractGraphRunner {
1175
1157
  this.stagedInputs[nodeId][handle] = value;
1176
1158
  }
1177
1159
  }
1178
- // Handle dry option: pause runtime before setting inputs, resume after
1179
- const wasPaused = this.runtime?.isPaused() ?? false;
1180
- if (options?.dry && !wasPaused && this.runtime) {
1181
- this.runtime.pause();
1182
- }
1183
- try {
1184
- if (this.engine) {
1185
- this.engine.setInputs(nodeId, inputs);
1186
- }
1187
- else {
1188
- // Not running: emit a single synthetic value event per handle; UI will coalesce
1189
- console.warn("Engine does not exists");
1190
- for (const [handle, value] of Object.entries(inputs)) {
1191
- this.emit("value", { nodeId, handle, value, io: "input" });
1192
- }
1193
- }
1160
+ if (this.engine) {
1161
+ // Engine handles dry option via AbstractEngine
1162
+ this.engine.setInputs(nodeId, inputs, options);
1194
1163
  }
1195
- finally {
1196
- if (options?.dry && !wasPaused && this.runtime) {
1197
- this.runtime.resume();
1164
+ else {
1165
+ // Not running: emit a single synthetic value event per handle; UI will coalesce
1166
+ // Note: dry option doesn't apply when engine doesn't exist (no execution to prevent)
1167
+ console.warn("Engine does not exists");
1168
+ for (const [handle, value] of Object.entries(inputs)) {
1169
+ this.emit("value", { nodeId, handle, value, io: "input" });
1198
1170
  }
1199
1171
  }
1200
1172
  }
1201
1173
  copyOutputs(fromNodeId, toNodeId, options) {
1202
- if (!this.runtime)
1203
- return;
1204
- // Get outputs from source node
1205
- const fromNode = this.runtime.getNodeData(fromNodeId);
1206
- if (!fromNode?.outputs)
1207
- return;
1208
- // Copy outputs to target node using hydrate
1209
- // hydrate already pauses internally, so we don't need to handle dry option here
1210
- // reemit: !options?.dry means don't propagate downstream if dry mode
1211
- this.runtime.hydrate({ outputs: { [toNodeId]: { ...fromNode.outputs } } }, { reemit: !options?.dry });
1174
+ if (this.engine) {
1175
+ this.engine.copyOutputs(fromNodeId, toNodeId, options);
1176
+ }
1212
1177
  }
1213
1178
  async snapshotFull() {
1214
1179
  const def = undefined; // UI will supply def/positions on download for local
@@ -1309,7 +1274,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1309
1274
  }
1310
1275
  this.registryFetching = true;
1311
1276
  try {
1312
- const desc = await client.describeRegistry();
1277
+ const desc = await client.api.describeRegistry();
1313
1278
  // Register types
1314
1279
  for (const t of desc.types) {
1315
1280
  if (t.options) {
@@ -1411,7 +1376,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1411
1376
  }
1412
1377
  }
1413
1378
  /**
1414
- * Build RuntimeApiClient config from RemoteExecutionBackend config.
1379
+ * Build RemoteRuntimeClient config from RemoteExecutionBackend config.
1415
1380
  */
1416
1381
  buildClientConfig(backend) {
1417
1382
  if (backend.kind === "remote-http") {
@@ -1434,7 +1399,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1434
1399
  */
1435
1400
  setupClientSubscriptions(client) {
1436
1401
  // Subscribe to transport status changes
1437
- // Convert RuntimeApiClient.TransportStatus to IGraphRunner.TransportStatus
1402
+ // Convert RemoteRuntimeClient.TransportStatus to IGraphRunner.TransportStatus
1438
1403
  // Only emit status if it matches this runner's ID
1439
1404
  this.transportStatusUnsubscribe = client.onTransportStatus((status) => {
1440
1405
  if (status.runnerId && status.runnerId !== this.runnerId)
@@ -1484,7 +1449,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1484
1449
  }
1485
1450
  };
1486
1451
  // Create client with wrapped custom event handler
1487
- const client = new RuntimeApiClient(clientConfig, {
1452
+ const client = new RemoteRuntimeClient(clientConfig, {
1488
1453
  onCustomEvent: wrappedOnCustomEvent,
1489
1454
  runnerId: this.runnerId,
1490
1455
  });
@@ -1534,7 +1499,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1534
1499
  // Auto-handle registry-changed invalidations from remote
1535
1500
  // We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
1536
1501
  this.ensureClient().then(async (client) => {
1537
- const eng = client.getEngine();
1502
+ const eng = client.engine;
1538
1503
  if (!this.listenersBound) {
1539
1504
  eng.on("invalidate", async (e) => {
1540
1505
  if (e.reason === "registry-changed") {
@@ -1598,7 +1563,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1598
1563
  // Remote: forward update and await completion
1599
1564
  const client = await this.ensureClient();
1600
1565
  try {
1601
- await client.update(def, options);
1566
+ await client.api.update(def, options);
1602
1567
  this.emit("invalidate", { reason: "graph-updated" });
1603
1568
  this.lastDef = def;
1604
1569
  }
@@ -1611,13 +1576,13 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1611
1576
  super.launch(def, opts);
1612
1577
  // Remote: build remotely then launch
1613
1578
  this.ensureClient().then(async (client) => {
1614
- await client.build(def);
1579
+ await client.api.build(def);
1615
1580
  // Signal UI after remote build as well
1616
1581
  this.emit("invalidate", { reason: "graph-built" });
1617
1582
  this.lastDef = def;
1618
1583
  // Hydrate current remote inputs/outputs (including defaults) into cache
1619
1584
  try {
1620
- const snap = await client.snapshot();
1585
+ const snap = await client.api.snapshot();
1621
1586
  for (const [nodeId, map] of Object.entries(snap.inputs || {})) {
1622
1587
  for (const [handle, value] of Object.entries(map || {})) {
1623
1588
  this.valueCache.set(`${nodeId}.${handle}`, {
@@ -1646,9 +1611,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1646
1611
  async createAndLaunchEngine(opts) {
1647
1612
  const client = await this.ensureClient();
1648
1613
  // Configure and launch engine on the backend
1649
- await client.launch(opts);
1614
+ await client.api.launch(opts);
1650
1615
  // Get the remote engine proxy and wire up event listeners
1651
- const eng = client.getEngine();
1616
+ const eng = client.engine;
1652
1617
  if (!this.listenersBound) {
1653
1618
  eng.on("value", (e) => {
1654
1619
  this.valueCache.set(`${e.nodeId}.${e.handle}`, {
@@ -1664,11 +1629,11 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1664
1629
  this.listenersBound = true;
1665
1630
  }
1666
1631
  this.engine = eng;
1667
- this.runningKind = opts?.engine ?? "push";
1668
- this.emit("status", { running: true, engine: this.runningKind });
1632
+ this.runMode = opts?.runMode ?? "manual";
1633
+ this.emit("status", { running: true, runMode: this.runMode });
1669
1634
  // Re-apply staged inputs using client.setInputs for consistency
1670
1635
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
1671
- await eng.setInputs(nodeId, map).catch(() => {
1636
+ await eng.setInputs(nodeId, map, undefined).catch(() => {
1672
1637
  // Ignore errors during launch - inputs will be set when user calls setInputs
1673
1638
  });
1674
1639
  }
@@ -1691,45 +1656,27 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1691
1656
  await this.createAndLaunchEngine(opts);
1692
1657
  });
1693
1658
  }
1694
- async switchEngine(opts) {
1659
+ setRunMode(runMode) {
1695
1660
  if (!this.engine) {
1696
- throw new Error("No engine running to switch from");
1697
- }
1698
- // Wait for current engine to be idle
1699
- await this.whenIdle();
1700
- // Capture current state
1701
- const currentInputs = { ...this.stagedInputs };
1702
- // For remote runners, we cannot call this.stop() because it sends a Dispose
1703
- // command that destroys the graphRuntime on the backend. Instead, we rely on
1704
- // the backend's launch() method to dispose the old engine and create a new one.
1705
- // Reconfigure engine on the backend (this will dispose old engine and create new one)
1706
- const client = await this.ensureClient();
1707
- await client.launch(opts);
1708
- // Get the remote engine proxy (should be the same RemoteEngine instance)
1709
- const eng = client.getEngine();
1710
- // Update local state to reflect new engine kind
1711
- // Note: The RemoteEngine instance itself doesn't change, but the backend engine does
1712
- this.engine = eng;
1713
- this.runningKind = opts?.engine ?? "push";
1714
- this.emit("status", { running: true, engine: this.runningKind });
1715
- // Re-apply staged inputs using client.setInputs for consistency
1716
- for (const [nodeId, map] of Object.entries(currentInputs)) {
1717
- await eng.setInputs(nodeId, map).catch(() => {
1718
- // Ignore errors during engine switch - inputs will be set when user calls setInputs
1719
- });
1661
+ throw new Error("Cannot set run mode: engine not running");
1720
1662
  }
1663
+ // Update engine run mode (sends SetRunMode command to backend)
1664
+ this.engine.setRunMode(runMode);
1665
+ // Update local state and emit status event
1666
+ this.runMode = runMode;
1667
+ this.emit("status", { running: true, runMode: this.runMode });
1721
1668
  }
1722
- async step() {
1669
+ async computeNode(nodeId, options) {
1723
1670
  const client = await this.ensureClient();
1724
- await client.step();
1671
+ await client.engine.computeNode(nodeId, options);
1725
1672
  }
1726
- async computeNode(nodeId) {
1673
+ async runFromHere(nodeId) {
1727
1674
  const client = await this.ensureClient();
1728
- await client.computeNode(nodeId);
1675
+ await client.engine.runFromHere(nodeId);
1729
1676
  }
1730
- async flush() {
1677
+ async cancelNodeRuns(nodeIds) {
1731
1678
  const client = await this.ensureClient();
1732
- await client.flush();
1679
+ await client.engine.cancelNodeRuns(nodeIds);
1733
1680
  }
1734
1681
  async setInputs(nodeId, inputs, options) {
1735
1682
  // Update staged inputs (for getInputs to work correctly)
@@ -1745,7 +1692,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1745
1692
  }
1746
1693
  try {
1747
1694
  const client = await this.ensureClient();
1748
- await client.getEngine()?.setInputs(nodeId, inputs, options);
1695
+ await client.engine.setInputs(nodeId, inputs, options);
1749
1696
  }
1750
1697
  catch (err) {
1751
1698
  // Emit synthetic events if connection fails
@@ -1757,20 +1704,20 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1757
1704
  }
1758
1705
  async copyOutputs(fromNodeId, toNodeId, options) {
1759
1706
  const client = await this.ensureClient();
1760
- await client.getEngine()?.copyOutputs(fromNodeId, toNodeId, options);
1707
+ await client.engine.copyOutputs(fromNodeId, toNodeId, options);
1761
1708
  }
1762
1709
  async triggerExternal(nodeId, event, options) {
1763
1710
  const client = await this.ensureClient();
1764
- await client.getEngine()?.triggerExternal(nodeId, event, options);
1711
+ await client.engine.triggerExternal(nodeId, event, options);
1765
1712
  }
1766
1713
  async setViewport(viewport) {
1767
1714
  const client = await this.ensureClient();
1768
- await client.setViewport(viewport);
1715
+ await client.api.setViewport(viewport);
1769
1716
  }
1770
1717
  async coerce(from, to, value) {
1771
1718
  const client = await this.ensureClient();
1772
1719
  try {
1773
- return await client.coerce(from, to, value);
1720
+ return await client.api.coerce(from, to, value);
1774
1721
  }
1775
1722
  catch {
1776
1723
  return value;
@@ -1778,12 +1725,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1778
1725
  }
1779
1726
  async setExtData(data) {
1780
1727
  const client = await this.ensureClient();
1781
- await client.setExtData(data);
1728
+ await client.api.setExtData(data);
1782
1729
  }
1783
1730
  async commit(reason) {
1784
1731
  const client = await this.ensureClient();
1785
1732
  try {
1786
- const history = await client.commit(reason);
1733
+ const history = await client.api.commit(reason);
1787
1734
  return history;
1788
1735
  }
1789
1736
  catch (err) {
@@ -1794,7 +1741,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1794
1741
  async undo() {
1795
1742
  const client = await this.ensureClient();
1796
1743
  try {
1797
- return await client.undo();
1744
+ return await client.api.undo();
1798
1745
  }
1799
1746
  catch {
1800
1747
  return false;
@@ -1803,7 +1750,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1803
1750
  async redo() {
1804
1751
  const client = await this.ensureClient();
1805
1752
  try {
1806
- return await client.redo();
1753
+ return await client.api.redo();
1807
1754
  }
1808
1755
  catch {
1809
1756
  return false;
@@ -1812,7 +1759,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1812
1759
  async snapshotFull() {
1813
1760
  const client = await this.ensureClient();
1814
1761
  try {
1815
- return await client.snapshotFull();
1762
+ return await client.api.snapshotFull();
1816
1763
  }
1817
1764
  catch {
1818
1765
  return { def: undefined, environment: {}, inputs: {}, outputs: {} };
@@ -1823,7 +1770,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1823
1770
  this.hydrateValueCache(payload, { dry: options?.dry });
1824
1771
  // Then sync with backend
1825
1772
  const client = await this.ensureClient();
1826
- await client.applySnapshotFull(payload, { skipBuild: options?.skipBuild });
1773
+ await client.api.applySnapshotFull(payload, {
1774
+ skipBuild: options?.skipBuild,
1775
+ });
1827
1776
  }
1828
1777
  /**
1829
1778
  * Hydrates the local valueCache from a snapshot and emits value events.
@@ -1867,12 +1816,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1867
1816
  async setEnvironment(env, opts) {
1868
1817
  // Use client if available, otherwise ensure client and then set environment
1869
1818
  if (this.client) {
1870
- await this.client.setEnvironment(env, opts);
1819
+ await this.client.api.setEnvironment(env, opts);
1871
1820
  }
1872
1821
  else {
1873
1822
  try {
1874
1823
  const client = await this.ensureClient();
1875
- await client.setEnvironment(env, opts);
1824
+ await client.api.setEnvironment(env, opts);
1876
1825
  }
1877
1826
  catch {
1878
1827
  // Silently fail if connection not available
@@ -1880,7 +1829,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1880
1829
  }
1881
1830
  }
1882
1831
  getEnvironment() {
1883
- // Interface requires sync return, but RuntimeApiClient.getEnvironment() is async.
1832
+ // Interface requires sync return, but RemoteRuntimeClient.getEnvironment() is async.
1884
1833
  // Returns undefined synchronously; callers needing the actual value should:
1885
1834
  // - Use snapshotFull() which includes environment
1886
1835
  // - Call client.getEnvironment() directly if they have access to the client
@@ -3647,9 +3596,10 @@ function computeInvalidatedFromMetadata(metadata) {
3647
3596
  const maxInputTime = Math.max(...Object.values(lastInputAt));
3648
3597
  return maxInputTime > (lastSuccessAt ?? lastRunAt ?? 0);
3649
3598
  }
3650
- function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVersion, children, }) {
3599
+ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
3651
3600
  const [nodeStatus, setNodeStatus] = useState({});
3652
3601
  const [edgeStatus, setEdgeStatus] = useState({});
3602
+ const [runMode, setRunModeState] = useState("manual");
3653
3603
  const [events, setEvents] = useState([]);
3654
3604
  const clearEvents = useCallback(() => setEvents([]), []);
3655
3605
  const [systemErrors, setSystemErrors] = useState([]);
@@ -3714,6 +3664,24 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3714
3664
  const graphUiTick = useWorkbenchGraphUiTick(wb);
3715
3665
  const versionTick = useWorkbenchVersionTick(runner);
3716
3666
  const valuesTick = versionTick + graphTick + graphUiTick;
3667
+ // Keep local runMode state loosely in sync with runner status.
3668
+ // - Seed from runner.getRunMode() on mount if available.
3669
+ // - On status events, update only when a non-undefined runMode is reported,
3670
+ // so the UI preserves the last selected mode after stop().
3671
+ useEffect(() => {
3672
+ const initialMode = runner.getRunMode();
3673
+ if (initialMode) {
3674
+ setRunModeState(initialMode);
3675
+ }
3676
+ const offRunnerStatus = runner.on("status", (status) => {
3677
+ if (status.runMode) {
3678
+ setRunModeState(status.runMode);
3679
+ }
3680
+ });
3681
+ return () => {
3682
+ offRunnerStatus();
3683
+ };
3684
+ }, [runner]);
3717
3685
  // Def and IO values
3718
3686
  const inputsMap = useMemo(() => runner.getInputs(wb.def), [runner, wb, wb.def, valuesTick]);
3719
3687
  const inputDefaultsMap = useMemo(() => runner.getInputDefaults(wb.def), [runner, wb, wb.def, valuesTick]);
@@ -3722,7 +3690,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3722
3690
  const out = {};
3723
3691
  // Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
3724
3692
  for (const n of wb.def.nodes) {
3725
- const effectiveHandles = computeEffectiveHandles(n, registry);
3693
+ const effectiveHandles = computeEffectiveHandles(n, wb.registry);
3726
3694
  const outputsDecl = effectiveHandles.outputs;
3727
3695
  const handles = Object.keys(outputsDecl);
3728
3696
  const cur = {};
@@ -3736,7 +3704,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3736
3704
  out[n.nodeId] = cur;
3737
3705
  }
3738
3706
  return out;
3739
- }, [wb, wb.def, outputsMap, registry]);
3707
+ }, [wb, wb.def, outputsMap, wb.registry, registryVersion]);
3740
3708
  // Initialize nodes and derive invalidated status from persisted metadata
3741
3709
  useEffect(() => {
3742
3710
  const workbenchRuntimeState = wb.getRuntimeState() ?? { nodes: {} };
@@ -3814,7 +3782,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3814
3782
  const overrideSize = overrides?.getDefaultNodeSize?.(node.typeId) ?? undefined;
3815
3783
  const size = estimateNodeSize({
3816
3784
  node,
3817
- registry,
3785
+ registry: wb.registry,
3818
3786
  showValues: true,
3819
3787
  overrides: overrideSize,
3820
3788
  });
@@ -3832,7 +3800,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3832
3800
  curX += maxWidth + H_GAP;
3833
3801
  }
3834
3802
  wb.setPositions(pos, { commit: true, reason: "auto-layout" });
3835
- }, [wb, wb.def, registry, overrides?.getDefaultNodeSize]);
3803
+ }, [wb, wb.def, wb.registry, registryVersion, overrides?.getDefaultNodeSize]);
3836
3804
  const updateEdgeType = useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
3837
3805
  const triggerExternal = useCallback((nodeId, event) => runner.triggerExternal(nodeId, event), [runner]);
3838
3806
  const getNodeDisplayName = useCallback((nodeId) => {
@@ -3842,9 +3810,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3842
3810
  const node = wb.def.nodes.find((n) => n.nodeId === nodeId);
3843
3811
  if (!node)
3844
3812
  return nodeId;
3845
- const desc = registry.nodes.get(node.typeId);
3813
+ const desc = wb.registry.nodes.get(node.typeId);
3846
3814
  return desc?.displayName || node.typeId;
3847
- }, [wb, registry]);
3815
+ }, [wb, wb.registry, registryVersion]);
3848
3816
  const setNodeName = useCallback((nodeId, name) => {
3849
3817
  wb.setNodeName(nodeId, name, { commit: true, reason: "rename-node" });
3850
3818
  }, [wb]);
@@ -4160,6 +4128,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4160
4128
  }
4161
4129
  return add("runner", "stats")(s);
4162
4130
  });
4131
+ const offWbRegistryChanged = wb.on("registryChanged", (evt) => {
4132
+ setRegistryVersion((v) => v + 1);
4133
+ });
4163
4134
  const offWbGraphChanged = wb.on("graphChanged", (event) => {
4164
4135
  // Clear validation errors for removed nodes
4165
4136
  if (event.change?.type === "removeNode") {
@@ -4316,10 +4287,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4316
4287
  // Registry updates: swap registry and refresh graph validation/UI
4317
4288
  const offRunnerRegistry = runner.on("registry", async (newReg) => {
4318
4289
  try {
4319
- setRegistry(newReg);
4320
4290
  wb.setRegistry(newReg);
4321
4291
  // Increment registry version to trigger UI updates
4322
- setRegistryVersion((v) => v + 1);
4323
4292
  // Trigger a graph update so the UI revalidates with new types/enums/nodes
4324
4293
  try {
4325
4294
  await runner.update(wb.def);
@@ -4390,6 +4359,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4390
4359
  offRunnerError();
4391
4360
  offRunnerInvalidate();
4392
4361
  offRunnerStats();
4362
+ offWbRegistryChanged();
4393
4363
  offWbGraphChanged();
4394
4364
  offWbGraphUiChangedForLog();
4395
4365
  offWbGraphUiChanged();
@@ -4403,18 +4373,39 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4403
4373
  offFlowViewport();
4404
4374
  offWbRuntimeMetadataChanged();
4405
4375
  };
4406
- }, [runner, wb, setRegistry]);
4407
- const isRunning = useCallback(() => runner.isRunning(), [runner]);
4408
- const engineKind = useCallback(() => runner.getRunningEngine(), [runner]);
4409
- const start = useCallback((engine) => {
4410
- try {
4411
- runner.launch(wb.def, { engine });
4412
- }
4413
- catch { }
4414
4376
  }, [runner, wb]);
4377
+ const isRunning = useCallback(() => runner.isRunning(), [runner]);
4378
+ const getRunMode = useCallback(() => runner.getRunMode(), [runner]);
4415
4379
  const stop = useCallback(() => runner.stop(), [runner]);
4416
- const step = useCallback(() => runner.step(), [runner]);
4417
- const flush = useCallback(() => runner.flush(), [runner]);
4380
+ // Run mode actions
4381
+ const setRunMode = useCallback((mode) => {
4382
+ if (mode === runMode)
4383
+ return;
4384
+ const wasRunning = runner.isRunning();
4385
+ if (wasRunning) {
4386
+ // Use setRunMode to change run mode without rebuilding
4387
+ try {
4388
+ runner.setRunMode(mode);
4389
+ setRunModeState(mode);
4390
+ }
4391
+ catch (err) {
4392
+ console.error("Failed to set run mode:", err);
4393
+ }
4394
+ }
4395
+ else {
4396
+ // Just update state if not running (will be applied on next launch)
4397
+ setRunModeState(mode);
4398
+ }
4399
+ }, [runMode, runner]);
4400
+ const runNodeAction = useCallback(async (nodeId) => {
4401
+ await runner.computeNode(nodeId);
4402
+ }, [runner]);
4403
+ const runFromHereAction = useCallback(async (nodeId) => {
4404
+ await runner.runFromHere(nodeId);
4405
+ }, [runner]);
4406
+ const abortNodeAction = useCallback((nodeId) => {
4407
+ runner.cancelNodeRuns([nodeId]);
4408
+ }, [runner]);
4418
4409
  const validationByNode = useMemo(() => {
4419
4410
  const inputs = {};
4420
4411
  const outputs = {};
@@ -4481,8 +4472,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4481
4472
  const value = useMemo(() => ({
4482
4473
  wb,
4483
4474
  runner,
4484
- registry,
4485
- setRegistry,
4486
4475
  selectedNodeId,
4487
4476
  selectedEdgeId,
4488
4477
  setSelection,
@@ -4508,11 +4497,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4508
4497
  removeRegistryError,
4509
4498
  removeInputValidationError,
4510
4499
  isRunning,
4511
- engineKind,
4512
- start,
4500
+ getRunMode,
4513
4501
  stop,
4514
- step,
4515
- flush,
4502
+ runMode,
4503
+ setRunMode,
4504
+ runNode: runNodeAction,
4505
+ runFromHere: runFromHereAction,
4506
+ abortNode: abortNodeAction,
4516
4507
  runAutoLayout,
4517
4508
  updateEdgeType,
4518
4509
  triggerExternal,
@@ -4524,8 +4515,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4524
4515
  }), [
4525
4516
  wb,
4526
4517
  runner,
4527
- registry,
4528
- setRegistry,
4529
4518
  selectedNodeId,
4530
4519
  selectedEdgeId,
4531
4520
  setSelection,
@@ -4550,11 +4539,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4550
4539
  events,
4551
4540
  clearEvents,
4552
4541
  isRunning,
4553
- engineKind,
4554
- start,
4542
+ getRunMode,
4555
4543
  stop,
4556
- step,
4557
- flush,
4544
+ runMode,
4545
+ setRunMode,
4546
+ runNodeAction,
4547
+ runFromHereAction,
4548
+ abortNodeAction,
4558
4549
  runAutoLayout,
4559
4550
  wb,
4560
4551
  runner,
@@ -4572,7 +4563,7 @@ function IssueBadge({ level, title, size = 12, className, }) {
4572
4563
  return (jsx("button", { type: "button", className: `inline-flex items-center justify-center shrink-0 ${colorClass} ${className ?? ""}`, title: title, style: { width: size, height: size }, children: level === "error" ? (jsx(XCircleIcon, { size: size, weight: "fill" })) : (jsx(WarningCircleIcon, { size: size, weight: "fill" })) }));
4573
4564
  }
4574
4565
 
4575
- function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate, }) {
4566
+ function DefaultNodeHeader({ id, typeId, validation, right, showId, }) {
4576
4567
  const ctx = useWorkbenchContext();
4577
4568
  const [isEditing, setIsEditing] = React.useState(false);
4578
4569
  const [editValue, setEditValue] = React.useState("");
@@ -4587,21 +4578,9 @@ function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate
4587
4578
  const node = ctx.wb.def.nodes.find((n) => n.nodeId === id);
4588
4579
  if (!node)
4589
4580
  return id;
4590
- const desc = ctx.registry.nodes.get(node.typeId);
4581
+ const desc = ctx.wb.registry.nodes.get(node.typeId);
4591
4582
  return desc?.displayName || node.typeId;
4592
4583
  }, [ctx, id, typeId]);
4593
- const handleInvalidate = React.useCallback(() => {
4594
- try {
4595
- if (onInvalidate)
4596
- return onInvalidate();
4597
- const kind = ctx.engineKind?.();
4598
- if (kind === "pull")
4599
- ctx.runner.computeNode(id);
4600
- else
4601
- ctx.triggerExternal?.(id, { type: "invalidate" });
4602
- }
4603
- catch { }
4604
- }, [ctx, id, onInvalidate]);
4605
4584
  const handleDoubleClick = React.useCallback((e) => {
4606
4585
  // Only allow editing if typeId is provided (enables renaming)
4607
4586
  if (!typeId)
@@ -4646,10 +4625,16 @@ function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate
4646
4625
  return (jsxs("div", { className: "flex items-center justify-center px-2 border-b border-solid border-gray-500 dark:border-gray-400 text-gray-600 dark:text-gray-300", style: {
4647
4626
  maxHeight: NODE_HEADER_HEIGHT_PX,
4648
4627
  minHeight: NODE_HEADER_HEIGHT_PX,
4649
- }, children: [isEditing ? (jsx("input", { ref: inputRef, type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: handleSave, onKeyDown: handleKeyDown, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), className: "flex-1 h-full text-sm bg-transparent border border-blue-500 rounded px-1 outline-none wb-nodrag", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` } })) : (jsx("strong", { className: `react-flow__node-title flex-1 h-full text-sm select-none truncate ${typeId ? "cursor-text" : ""}`, style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, onDoubleClick: handleDoubleClick, title: typeId ? "Double-click to rename" : undefined, children: displayName })), jsxs("div", { className: "flex items-center gap-1", children: [jsx("button", { className: "w-4 h-4 border border-gray-400 rounded text-[10px] leading-3 flex items-center justify-center", title: "Invalidate and re-run", onClick: (e) => {
4650
- e.stopPropagation();
4651
- handleInvalidate();
4652
- }, children: jsx(ArrowClockwiseIcon, { size: 10 }) }), right, validation.issues && validation.issues.length > 0 && (jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
4628
+ }, children: [isEditing ? (jsx("input", { ref: inputRef, type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: handleSave, onKeyDown: handleKeyDown, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), className: "flex-1 h-full text-sm bg-transparent border border-blue-500 rounded px-1 outline-none wb-nodrag", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` } })) : (jsx("strong", { className: `react-flow__node-title flex-1 h-full text-sm select-none truncate ${typeId ? "cursor-text" : ""}`, style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, onDoubleClick: handleDoubleClick, title: typeId ? "Double-click to rename" : undefined, children: displayName })), jsxs("div", { className: "flex items-center gap-1", children: [ctx.runMode === "manual" && (jsxs(Fragment, { children: [jsx("button", { onClick: (e) => {
4629
+ e.stopPropagation();
4630
+ ctx.abortNode(id);
4631
+ }, className: "w-4 h-4 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded text-gray-600 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 transition-colors", title: "Abort node", children: jsx(StopIcon, { size: 10, weight: "fill" }) }), jsx("button", { onClick: (e) => {
4632
+ e.stopPropagation();
4633
+ void ctx.runFromHere(id);
4634
+ }, className: "w-4 h-4 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded text-gray-600 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 transition-colors", title: "Run from here", children: jsx(PlayIcon, { size: 10, weight: "fill" }) }), jsx("button", { onClick: (e) => {
4635
+ e.stopPropagation();
4636
+ void ctx.runNode(id);
4637
+ }, className: "w-4 h-4 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded text-gray-600 dark:text-gray-400 hover:text-green-600 dark:hover:text-green-400 transition-colors", title: "Run node", children: jsx(Circle, { size: 10, weight: "fill" }) })] })), right, validation.issues && validation.issues.length > 0 && (jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
4653
4638
  ? "error"
4654
4639
  : "warning", size: 12, className: "w-3 h-3", title: validation.issues
4655
4640
  .map((v) => `${v.code}: ${v.message}`)
@@ -4803,7 +4788,7 @@ const DefaultEdge = React.memo(function DefaultEdge({ id, sourceX, sourceY, targ
4803
4788
  return (jsx(BaseEdge, { id: id, path: edgePath, style: style, markerEnd: markerEnd }));
4804
4789
  });
4805
4790
 
4806
- function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult) {
4791
+ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult, runNode, runFromHere) {
4807
4792
  return {
4808
4793
  onDelete: () => {
4809
4794
  wb.removeNode(nodeId, { commit: true });
@@ -4823,13 +4808,24 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
4823
4808
  });
4824
4809
  onClose();
4825
4810
  },
4826
- onRunPull: async () => {
4827
- try {
4828
- await runner.computeNode(nodeId);
4811
+ onRunNode: runNode
4812
+ ? async () => {
4813
+ try {
4814
+ await runNode(nodeId);
4815
+ }
4816
+ catch { }
4817
+ onClose();
4829
4818
  }
4830
- catch { }
4831
- onClose();
4832
- },
4819
+ : undefined,
4820
+ onRunFromHere: runFromHere
4821
+ ? async () => {
4822
+ try {
4823
+ await runFromHere(nodeId);
4824
+ }
4825
+ catch { }
4826
+ onClose();
4827
+ }
4828
+ : undefined,
4833
4829
  onBake: async (handleId) => {
4834
4830
  const nodePosition = wb.getPositions()[nodeId] || { x: 0, y: 0 };
4835
4831
  const typeId = outputTypesMap?.[nodeId]?.[handleId];
@@ -5046,7 +5042,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5046
5042
  return String(value ?? "");
5047
5043
  }
5048
5044
  };
5049
- const { wb, registry, selectedNodeId, selectedEdgeId, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, inputValidationErrors, clearSystemErrors, clearRegistryErrors, clearInputValidationErrors, removeSystemError, removeRegistryError, removeInputValidationError, } = useWorkbenchContext();
5045
+ const { wb, registryVersion, selectedNodeId, selectedEdgeId, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, inputValidationErrors, clearSystemErrors, clearRegistryErrors, clearInputValidationErrors, removeSystemError, removeRegistryError, removeInputValidationError, } = useWorkbenchContext();
5050
5046
  const nodeValidationIssues = validationByNode.issues;
5051
5047
  const edgeValidationIssues = validationByEdge.issues;
5052
5048
  const nodeValidationHandles = validationByNode;
@@ -5055,7 +5051,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5055
5051
  const selectedEdge = wb.def.edges.find((e) => e.id === selectedEdgeId);
5056
5052
  // Use computeEffectiveHandles to merge registry defaults with dynamically resolved handles
5057
5053
  const effectiveHandles = selectedNode
5058
- ? computeEffectiveHandles(selectedNode, registry)
5054
+ ? computeEffectiveHandles(selectedNode, wb.registry)
5059
5055
  : { inputs: {}, outputs: {}};
5060
5056
  const inputHandles = Object.entries(effectiveHandles.inputs)
5061
5057
  .filter(([k]) => !isInputPrivate(effectiveHandles.inputs, k))
@@ -5182,7 +5178,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5182
5178
  setDrafts(nextDrafts);
5183
5179
  if (!shallowEqual(originals, nextOriginals))
5184
5180
  setOriginals(nextOriginals);
5185
- }, [selectedNodeId, selectedNode, registry, valuesTick]);
5181
+ }, [selectedNodeId, selectedNode, wb.registry, registryVersion, valuesTick]);
5186
5182
  const widthClass = debug ? "w-[480px]" : "w-[320px]";
5187
5183
  const deleteEdgeById = (edgeId) => {
5188
5184
  if (!edgeId)
@@ -5202,7 +5198,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5202
5198
  const v = e.target.value;
5203
5199
  const next = v === "" ? undefined : v;
5204
5200
  updateEdgeType(selectedEdge.id, next);
5205
- }, children: [jsx("option", { value: "", children: "(infer from source)" }), Array.from(registry.types.keys()).map((tid) => (jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxs("li", { className: "flex items-center gap-1", children: [jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsx("span", { children: `${m.code}: ${m.message}` }), jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
5201
+ }, children: [jsx("option", { value: "", children: "(infer from source)" }), Array.from(wb.registry.types.keys()).map((tid) => (jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxs("li", { className: "flex items-center gap-1", children: [jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsx("span", { children: `${m.code}: ${m.message}` }), jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
5206
5202
  e.stopPropagation();
5207
5203
  deleteEdgeById(selectedEdge.id);
5208
5204
  }, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxs("div", { children: [selectedNode && (jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.activeRuns &&
@@ -5272,7 +5268,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5272
5268
  setOriginals((o) => ({ ...o, [h]: display }));
5273
5269
  }, ...commonProps, children: [jsx("option", { value: "", children: placeholder
5274
5270
  ? `Default: ${placeholder}`
5275
- : "(select)" }), registry.enums
5271
+ : "(select)" }), wb.registry.enums
5276
5272
  .get(typeId)
5277
5273
  ?.options.map((opt) => (jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsx(XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsx("div", { className: "flex items-center gap-1 flex-1", children: jsx("div", { className: "flex-1 min-w-0", children: renderLinkedInputDisplay(typeId, current) }) })) : (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1 select-text", placeholder: placeholder
5278
5274
  ? `Default: ${placeholder}`
@@ -5411,7 +5407,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, enab
5411
5407
  !handlers.onRedo && jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsx("div", { className: "px-2 pb-1", children: jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 rounded px-2 py-1 text-sm outline-none focus:border-gray-400 select-text", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
5412
5408
  }
5413
5409
 
5414
- function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, enableKeyboardShortcuts = true, keyboardShortcuts = {
5410
+ function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, runMode, enableKeyboardShortcuts = true, keyboardShortcuts = {
5415
5411
  copy: "⌘/Ctrl + C",
5416
5412
  duplicate: "⌘/Ctrl + E",
5417
5413
  duplicateWithEdges: "⌘/Ctrl + Shift + E",
@@ -5454,7 +5450,7 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
5454
5450
  return (jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
5455
5451
  e.preventDefault();
5456
5452
  e.stopPropagation();
5457
- }, children: [jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsx(ContextMenuButton, { label: "Duplicate with edges", onClick: handlers.onDuplicateWithEdges, shortcut: keyboardShortcuts.duplicateWithEdges, enableKeyboardShortcuts: enableKeyboardShortcuts }), canRunPull && (jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsx("div", { className: "h-px bg-gray-200 my-1" }), jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" }), bakeableOutputs.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "h-px bg-gray-200 my-1" }), jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
5453
+ }, children: [jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsx(ContextMenuButton, { label: "Duplicate with edges", onClick: handlers.onDuplicateWithEdges, shortcut: keyboardShortcuts.duplicateWithEdges, enableKeyboardShortcuts: enableKeyboardShortcuts }), runMode === "manual" && (jsxs(Fragment, { children: [handlers.onRunNode && (jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunNode, children: "Run node" })), handlers.onRunFromHere && (jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunFromHere, children: "Run from here" }))] })), jsx("div", { className: "h-px bg-gray-200 my-1" }), jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" }), bakeableOutputs.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "h-px bg-gray-200 my-1" }), jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
5458
5454
  }
5459
5455
 
5460
5456
  function SelectionContextMenu({ open, clientPos, handlers, enableKeyboardShortcuts = true, keyboardShortcuts = {
@@ -5541,7 +5537,7 @@ function useKeyboardShortcutToast() {
5541
5537
  }
5542
5538
 
5543
5539
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
5544
- const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, registryVersion, runner, engineKind, overrides, } = useWorkbenchContext();
5540
+ const { wb, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, registryVersion, runner, overrides, runNode, runFromHere, runMode, } = useWorkbenchContext();
5545
5541
  const nodeValidation = validationByNode;
5546
5542
  const edgeValidation = validationByEdge.errors;
5547
5543
  const [historyState, setHistoryState] = useState(wb.getHistory());
@@ -5619,7 +5615,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5619
5615
  // Build nodeTypes map using UI extension registry
5620
5616
  const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
5621
5617
  const ids = new Set([
5622
- ...Array.from(registry.nodes.keys()),
5618
+ ...Array.from(wb.registry.nodes.keys()),
5623
5619
  ...wb.def.nodes.map((n) => n.typeId),
5624
5620
  ]);
5625
5621
  for (const typeId of ids) {
@@ -5638,7 +5634,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5638
5634
  return { nodeTypes: types, resolveNodeType: resolver };
5639
5635
  // Include uiVersion to recompute when custom renderers are registered
5640
5636
  // Include registryVersion to recompute when registry enums/types change
5641
- }, [wb, registry, uiVersion, ui]);
5637
+ }, [wb, wb.registry, registryVersion, uiVersion, ui]);
5642
5638
  const edgeTypes = useMemo(() => {
5643
5639
  // Use default edge renderer override if registered, otherwise use DefaultEdge
5644
5640
  const customEdgeRenderer = ui.getEdgeRenderer();
@@ -5664,7 +5660,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5664
5660
  inputsWithDefaults[n.nodeId] = merged;
5665
5661
  }
5666
5662
  }
5667
- const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), registry, {
5663
+ const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), wb.registry, {
5668
5664
  showValues,
5669
5665
  inputs: inputsWithDefaults,
5670
5666
  inputDefaults: inputDefaultsMap,
@@ -5931,7 +5927,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5931
5927
  });
5932
5928
  return () => off();
5933
5929
  }, [wb]);
5934
- const nodeIds = useMemo(() => Array.from(registry.nodes.keys()), [registry, registryVersion]);
5930
+ const nodeIds = useMemo(() => Array.from(wb.registry.nodes.keys()), [wb.registry, registryVersion]);
5935
5931
  const defaultContextMenuHandlers = useMemo(() => {
5936
5932
  // Get storage from override or use workbench's internal storage
5937
5933
  const storage = overrides?.getCopiedDataStorage
@@ -5979,9 +5975,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5979
5975
  get: () => wb.getCopiedData(),
5980
5976
  set: (data) => wb.setCopiedData(data),
5981
5977
  };
5982
- const baseHandlers = createNodeContextMenuHandlers(nodeAtMenu, wb, runner, registry, outputsMap, outputTypesMap, onCloseNodeMenu, overrides?.getDefaultNodeSize, (data) => {
5978
+ const baseHandlers = createNodeContextMenuHandlers(nodeAtMenu, wb, runner, wb.registry, outputsMap, outputTypesMap, onCloseNodeMenu, overrides?.getDefaultNodeSize, (data) => {
5983
5979
  storage.set(data);
5984
- });
5980
+ }, runNode, runFromHere);
5985
5981
  if (overrides?.getNodeContextMenuHandlers) {
5986
5982
  return overrides.getNodeContextMenuHandlers(wb, nodeAtMenu, baseHandlers);
5987
5983
  }
@@ -5990,7 +5986,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5990
5986
  nodeAtMenu,
5991
5987
  wb,
5992
5988
  runner,
5993
- registry,
5989
+ wb.registry,
5990
+ registryVersion,
5994
5991
  outputsMap,
5995
5992
  outputTypesMap,
5996
5993
  onCloseNodeMenu,
@@ -5998,12 +5995,11 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5998
5995
  overrides?.getNodeContextMenuHandlers,
5999
5996
  overrides?.getCopiedDataStorage,
6000
5997
  ]);
6001
- const canRunPull = useMemo(() => engineKind()?.toString() === "pull", [engineKind]);
6002
5998
  const bakeableOutputs = useMemo(() => {
6003
5999
  if (!nodeAtMenu)
6004
6000
  return [];
6005
- return getBakeableOutputs(nodeAtMenu, wb, registry, outputTypesMap);
6006
- }, [nodeAtMenu, wb, registry, outputTypesMap]);
6001
+ return getBakeableOutputs(nodeAtMenu, wb, wb.registry, outputTypesMap);
6002
+ }, [nodeAtMenu, wb, wb.registry, registryVersion, outputTypesMap]);
6007
6003
  // Keyboard shortcuts configuration
6008
6004
  const enableKeyboardShortcuts = overrides?.enableKeyboardShortcuts !== false; // Default to true
6009
6005
  const keyboardShortcuts = overrides?.keyboardShortcuts || {
@@ -6199,29 +6195,28 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6199
6195
  if (savedViewport) {
6200
6196
  inst.setViewport(lod.clone(savedViewport));
6201
6197
  }
6202
- }, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", children: [BackgroundRenderer ? (jsx(BackgroundRenderer, {})) : (jsx(Background, { id: "workbench-canvas-background", variant: BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsx(MinimapRenderer, {}) : jsx(MiniMap, {}), ControlsRenderer ? jsx(ControlsRenderer, {}) : jsx(Controls, {}), DefaultContextMenuRenderer ? (jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, ...(enableKeyboardShortcuts !== false
6198
+ }, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", children: [BackgroundRenderer ? (jsx(BackgroundRenderer, {})) : (jsx(Background, { id: "workbench-canvas-background", variant: BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsx(MinimapRenderer, {}) : jsx(MiniMap, {}), ControlsRenderer ? jsx(ControlsRenderer, {}) : jsx(Controls, {}), DefaultContextMenuRenderer ? (jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, ...(enableKeyboardShortcuts !== false
6203
6199
  ? { enableKeyboardShortcuts, keyboardShortcuts }
6204
- : {}) })) : (jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
6200
+ : {}) })) : (jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
6205
6201
  nodeContextMenuHandlers &&
6206
- (NodeContextMenuRenderer ? (jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, ...(enableKeyboardShortcuts !== false
6202
+ (NodeContextMenuRenderer ? (jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode, wb: wb, ...(enableKeyboardShortcuts !== false
6207
6203
  ? { enableKeyboardShortcuts, keyboardShortcuts }
6208
- : {}) })) : (jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts }))), selectionMenuOpen &&
6204
+ : {}) })) : (jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode }))), selectionMenuOpen &&
6209
6205
  selectionMenuPos &&
6210
6206
  (SelectionContextMenuRenderer ? (jsx(SelectionContextMenuRenderer, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })) : (jsx(SelectionContextMenu, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })))] }) }), toast && (jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
6211
6207
  });
6212
6208
 
6213
- function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
6214
- const { wb, runner, registry, selectedNodeId, runAutoLayout } = useWorkbenchContext();
6209
+ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
6210
+ const { wb, runner, selectedNodeId, runAutoLayout, runMode, setRunMode, isRunning, } = useWorkbenchContext();
6215
6211
  const [transportStatus, setTransportStatus] = useState({
6216
6212
  state: "local",
6217
6213
  });
6218
6214
  const selectedNode = wb.def.nodes.find((n) => n.nodeId === selectedNodeId);
6219
6215
  const effectiveHandles = selectedNode
6220
- ? computeEffectiveHandles(selectedNode, registry)
6216
+ ? computeEffectiveHandles(selectedNode, wb.registry)
6221
6217
  : { inputs: {}, outputs: {}, inputDefaults: {} };
6222
6218
  const [exampleState, setExampleState] = useState(example ?? "");
6223
- const isGraphRunning = runner.isRunning();
6224
- const engineKind = runner.getRunningEngine();
6219
+ const isGraphRunning = isRunning();
6225
6220
  // Render Start/Stop button based on transport and runner state
6226
6221
  const renderStartStopButton = useCallback(() => {
6227
6222
  // Check if transport is connecting/retrying
@@ -6237,14 +6232,11 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6237
6232
  return (jsxs("button", { className: "border rounded px-2 py-1.5 text-red-700 border-red-600 flex items-center gap-1 disabled:opacity-50 disabled:text-gray-400 disabled:border-gray-300", onClick: () => runner.stop(), disabled: !canControl, title: canControl ? "Stop engine" : "Waiting for connection", children: [jsx(StopIcon, { size: 16, weight: "fill" }), jsx("span", { className: "font-medium ml-1", children: "Stop" })] }));
6238
6233
  }
6239
6234
  return (jsxs("button", { className: "border rounded px-2 py-1.5 text-green-700 border-green-600 flex items-center gap-1 disabled:text-gray-400 disabled:border-gray-300 disabled:opacity-50", onClick: (evt) => {
6240
- const kind = engine;
6241
- if (!kind)
6242
- return alert("Select an engine first.");
6243
6235
  if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
6244
6236
  return;
6245
6237
  try {
6246
6238
  runner.launch(wb.def, {
6247
- engine: kind,
6239
+ runMode,
6248
6240
  invalidate: evt.shiftKey,
6249
6241
  });
6250
6242
  }
@@ -6252,12 +6244,10 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6252
6244
  const message = err instanceof Error ? err.message : String(err);
6253
6245
  alert(message);
6254
6246
  }
6255
- }, disabled: !engine || !canControl, title: !engine
6256
- ? "Select an engine first"
6257
- : !canControl
6258
- ? "Waiting for connection"
6259
- : "Start engine", children: [jsx(PlayIcon, { size: 16, weight: "fill" }), jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6260
- }, [transportStatus, isGraphRunning, runner, engine, wb]);
6247
+ }, disabled: !canControl, title: !canControl
6248
+ ? "Waiting for connection"
6249
+ : `Start ${runMode === "manual" ? "manual" : "auto"} mode`, children: [jsx(PlayIcon, { size: 16, weight: "fill" }), jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6250
+ }, [transportStatus, isGraphRunning, runner, runMode, wb]);
6261
6251
  const defaultExamples = useMemo(() => [
6262
6252
  {
6263
6253
  id: "simple",
@@ -6364,7 +6354,6 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6364
6354
  // - For remote backend, registry is automatically managed by RemoteGraphRunner
6365
6355
  if (backendKind === "local") {
6366
6356
  if (r) {
6367
- setRegistry(r);
6368
6357
  wb.setRegistry(r);
6369
6358
  }
6370
6359
  }
@@ -6380,15 +6369,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6380
6369
  runAutoLayout();
6381
6370
  setExampleState(key);
6382
6371
  onExampleChange?.(key);
6383
- }, [
6384
- runner,
6385
- wb,
6386
- onExampleChange,
6387
- runAutoLayout,
6388
- examples,
6389
- setRegistry,
6390
- backendKind,
6391
- ]);
6372
+ }, [runner, wb, onExampleChange, runAutoLayout, examples, backendKind]);
6392
6373
  const download$1 = useCallback(async () => {
6393
6374
  try {
6394
6375
  await download(wb, runner);
@@ -6448,8 +6429,6 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6448
6429
  return () => off();
6449
6430
  }, [runner, backendKind]);
6450
6431
  useEffect(() => {
6451
- if (!engine)
6452
- return;
6453
6432
  if (isGraphRunning)
6454
6433
  return;
6455
6434
  // Only auto-launch for local backend; require explicit Start for remote
@@ -6458,12 +6437,12 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6458
6437
  if (!wb.def.nodes || wb.def.nodes.length === 0)
6459
6438
  return;
6460
6439
  try {
6461
- runner.launch(wb.def, { engine: engine });
6440
+ runner.launch(wb.def, { runMode });
6462
6441
  }
6463
6442
  catch {
6464
6443
  // ignore
6465
6444
  }
6466
- }, [engine, runner, isGraphRunning, wb, backendKind]);
6445
+ }, [runMode, runner, isGraphRunning, wb, backendKind]);
6467
6446
  const baseSetInput = useCallback((handle, raw) => {
6468
6447
  if (!selectedNodeId)
6469
6448
  return;
@@ -6559,11 +6538,11 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6559
6538
  return overrides.setInput(baseSetInput, {
6560
6539
  runner,
6561
6540
  selectedNodeId,
6562
- registry,
6541
+ registry: wb.registry,
6563
6542
  });
6564
6543
  }
6565
6544
  return baseSetInput;
6566
- }, [overrides, baseSetInput, runner, selectedNodeId, registry]);
6545
+ }, [overrides, baseSetInput, runner, selectedNodeId, wb.registry]);
6567
6546
  const baseToString = useCallback((typeId, value) => {
6568
6547
  if (value === undefined || value === null)
6569
6548
  return "";
@@ -6571,7 +6550,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6571
6550
  if (isTypedOutput(value)) {
6572
6551
  return baseToString(getTypedOutputTypeId(value), getTypedOutputValue(value));
6573
6552
  }
6574
- const pre = preformatValueForDisplay(typeId, value, registry);
6553
+ const pre = preformatValueForDisplay(typeId, value, wb.registry);
6575
6554
  if (pre !== undefined)
6576
6555
  return pre;
6577
6556
  if (typeof value === "object" &&
@@ -6587,7 +6566,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6587
6566
  }
6588
6567
  if (typeId && typeId.startsWith("enum:")) {
6589
6568
  const n = Number(value);
6590
- const label = registry.enums.get(typeId)?.valueToLabel.get(n);
6569
+ const label = wb.registry.enums.get(typeId)?.valueToLabel.get(n);
6591
6570
  return label ?? String(n);
6592
6571
  }
6593
6572
  const round4 = (n) => Math.round(Number(n) * 10000) / 10000;
@@ -6617,54 +6596,34 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6617
6596
  return String(rounded);
6618
6597
  }
6619
6598
  return String(value);
6620
- }, [registry]);
6599
+ }, [wb.registry]);
6621
6600
  const baseToElement = useCallback((typeId, value) => {
6622
6601
  return (jsx("span", { className: "ml-1 opacity-60", children: baseToString(typeId, value) }));
6623
6602
  }, [baseToString]);
6624
6603
  const toString = useMemo(() => {
6625
6604
  if (overrides?.toString)
6626
- return overrides.toString(baseToString, { registry });
6605
+ return overrides.toString(baseToString, { registry: wb.registry });
6627
6606
  return baseToString;
6628
- }, [overrides, baseToString, registry]);
6607
+ }, [overrides, baseToString, wb.registry]);
6629
6608
  // Optional: toElement (not currently consumed by core UI)
6630
6609
  // Consumers can access it by passing through their own node renderers.
6631
6610
  const toElement = useMemo(() => {
6632
6611
  if (overrides?.toElement)
6633
- return overrides.toElement(baseToElement, { registry });
6612
+ return overrides.toElement(baseToElement, { registry: wb.registry });
6634
6613
  return baseToElement;
6635
- }, [overrides, baseToElement, registry]);
6636
- return (jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [isGraphRunning ? (jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", engineKind] })) : (jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxs("span", { className: "ml-2 flex items-center gap-1 text-xs", title: transportStatus.kind || undefined, children: [transportStatus.state === "local" && (jsx(PlugsConnectedIcon, { size: 14, className: "text-gray-500" })), transportStatus.state === "connecting" && (jsx(ClockClockwiseIcon, { size: 14, className: "text-amber-600 animate-pulse" })), transportStatus.state === "connected" && (jsx(WifiHighIcon, { size: 14, className: "text-green-600" })), transportStatus.state === "disconnected" && (jsx(WifiSlashIcon, { size: 14, className: "text-red-600" })), transportStatus.state === "retrying" && (jsx(ClockClockwiseIcon, { size: 14, className: "text-amber-700 animate-pulse" }))] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: exampleState, onChange: (e) => applyExample(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching example" : undefined, children: [jsx("option", { value: "", children: "Select Example\u2026" }), examples.map((ex) => (jsx("option", { value: ex.id, children: ex.label }, ex.id)))] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching backend" : undefined, children: [jsx("option", { value: "local", children: "Local" }), jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && !!onWsUrlChange && (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: engineKind ?? engine ?? "", onChange: async (e) => {
6637
- const kind = e.target.value || undefined;
6638
- const currentEngine = runner.getRunningEngine();
6639
- // If engine is running and user selected a different engine, switch it
6640
- if (runner.isRunning() &&
6641
- currentEngine &&
6642
- kind &&
6643
- kind !== currentEngine) {
6644
- try {
6645
- await runner.switchEngine({
6646
- engine: kind,
6647
- batched: { flushIntervalMs: 0 },
6648
- hybrid: { windowMs: 250, batchThreshold: 3 },
6649
- });
6650
- onEngineChange?.(kind);
6651
- }
6652
- catch (err) {
6653
- const message = err instanceof Error ? err.message : String(err);
6654
- alert(`Failed to switch engine: ${message}`);
6655
- // Reset dropdown to current engine
6656
- e.target.value = currentEngine;
6657
- }
6658
- }
6659
- else {
6660
- // Normal change when not running
6661
- onEngineChange?.(kind);
6614
+ }, [overrides, baseToElement, wb.registry]);
6615
+ return (jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [isGraphRunning ? (jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", runMode === "manual" ? "Manual" : "Auto"] })) : (jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxs("span", { className: "ml-2 flex items-center gap-1 text-xs", title: transportStatus.kind || undefined, children: [transportStatus.state === "local" && (jsx(PlugsConnectedIcon, { size: 14, className: "text-gray-500" })), transportStatus.state === "connecting" && (jsx(ClockClockwiseIcon, { size: 14, className: "text-amber-600 animate-pulse" })), transportStatus.state === "connected" && (jsx(WifiHighIcon, { size: 14, className: "text-green-600" })), transportStatus.state === "disconnected" && (jsx(WifiSlashIcon, { size: 14, className: "text-red-600" })), transportStatus.state === "retrying" && (jsx(ClockClockwiseIcon, { size: 14, className: "text-amber-700 animate-pulse" }))] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: exampleState, onChange: (e) => applyExample(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching example" : undefined, children: [jsx("option", { value: "", children: "Select Example\u2026" }), examples.map((ex) => (jsx("option", { value: ex.id, children: ex.label }, ex.id)))] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching backend" : undefined, children: [jsx("option", { value: "local", children: "Local" }), jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && !!onWsUrlChange && (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: runMode, onChange: async (e) => {
6616
+ const mode = e.target.value;
6617
+ if (mode !== runMode) {
6618
+ await setRunMode(mode);
6662
6619
  }
6663
- }, children: [jsx("option", { value: "", children: "Select Engine\u2026" }), jsx("option", { value: "push", children: "Push" }), jsx("option", { value: "batched", children: "Batched" }), jsx("option", { value: "pull", children: "Pull" }), jsx("option", { value: "hybrid", children: "Hybrid" }), jsx("option", { value: "step", children: "Step" })] }), engineKind === "step" && (jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsx(PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsx(LightningIcon, { size: 24 }) })), renderStartStopButton(), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsx(TreeStructureIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsx(CornersOutIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsx(DownloadIcon, { size: 24 }) }), jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsx(UploadIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
6620
+ }, disabled: isGraphRunning, title: isGraphRunning
6621
+ ? "Stop before switching run mode"
6622
+ : "Select run mode", children: [jsx("option", { value: "manual", children: "Manual" }), jsx("option", { value: "auto", children: "Auto" })] }), renderStartStopButton(), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsx(TreeStructureIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsx(CornersOutIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsx(DownloadIcon, { size: 24 }) }), jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsx(UploadIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
6664
6623
  await downloadCanvasThumbnail(canvasContainerRef.current);
6665
6624
  }, title: "Download Flow Thumbnail (SVG)", children: jsx(ImageIcon, { size: 24 }) }), jsxs("label", { className: "flex items-center gap-1", children: [jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsx(BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxs("label", { className: "flex items-center gap-1", children: [jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsx(ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), jsxs("div", { className: "flex flex-1 min-h-0", children: [jsx("div", { className: "flex-1 min-w-0", ref: canvasContainerRef, children: jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
6666
6625
  }
6667
- function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
6626
+ function WorkbenchStudio({ example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
6668
6627
  const [registry, setRegistry] = useState(createSimpleGraphRegistry());
6669
6628
  const [wb] = useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
6670
6629
  // Store previous runner for cleanup
@@ -6734,7 +6693,7 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
6734
6693
  runner.dispose();
6735
6694
  onBackendKindChange(v);
6736
6695
  }, [isGraphRunning]);
6737
- return (jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, uiVersion: uiVersion, children: jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: onBackendKindChangeWithDispose, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, overrides: overrides, onInit: onInit, onChange: onChange }) }));
6696
+ return (jsx(WorkbenchProvider, { wb: wb, runner: runner, overrides: overrides, uiVersion: uiVersion, children: jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, backendKind: backendKind, onBackendKindChange: onBackendKindChangeWithDispose, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, overrides: overrides, onInit: onInit, onChange: onChange }) }));
6738
6697
  }
6739
6698
 
6740
6699
  export { AbstractWorkbench, CLIWorkbench, DefaultEdge, DefaultNode, DefaultNodeContent, DefaultNodeHeader, DefaultUIExtensionRegistry, InMemoryWorkbench, Inspector, LocalGraphRunner, NodeHandles, RemoteGraphRunner, WorkbenchCanvas, WorkbenchContext, WorkbenchProvider, WorkbenchStudio, captureCanvasThumbnail, computeEffectiveHandles, countVisibleHandles, createCopyHandler, createDefaultContextMenuHandlers, createHandleBounds, createHandleLayout, createNodeContextMenuHandlers, createNodeCopyHandler, createSelectionContextMenuHandlers, download, downloadCanvasThumbnail, estimateNodeSize, excludeViewportFromUIState, formatDataUrlAsLabel, formatDeclaredTypeSignature, getBakeableOutputs, getHandleBoundsX, getHandleBoundsY, getHandleClassName, getHandleLayoutY, getNodeBorderClassNames, isValidViewport, layoutNode, mergeUIState, preformatValueForDisplay, prettyHandle, resolveOutputDisplay, summarizeDeep, toReactFlow, upload, useQueryParamBoolean, useQueryParamString, useThrottledValue, useWorkbenchBridge, useWorkbenchContext, useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick };