@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/cjs/index.cjs CHANGED
@@ -135,8 +135,8 @@ class AbstractWorkbench {
135
135
  }
136
136
 
137
137
  class InMemoryWorkbench extends AbstractWorkbench {
138
- constructor() {
139
- super(...arguments);
138
+ constructor(args) {
139
+ super(args);
140
140
  this._def = { nodes: [], edges: [] };
141
141
  this.listeners = new Map();
142
142
  this.positions = {};
@@ -150,12 +150,17 @@ class InMemoryWorkbench extends AbstractWorkbench {
150
150
  this.viewport = null;
151
151
  this.historyState = undefined;
152
152
  this.copiedData = null;
153
+ this._registry = sparkGraph.createSimpleGraphRegistry();
153
154
  }
154
155
  get def() {
155
156
  return this._def;
156
157
  }
158
+ get registry() {
159
+ return this._registry;
160
+ }
157
161
  setRegistry(registry) {
158
- this.registry = registry;
162
+ this._registry = registry;
163
+ this.emit("registryChanged", { registry });
159
164
  }
160
165
  async load(def) {
161
166
  this._def = { nodes: [...def.nodes], edges: [...def.edges] };
@@ -922,30 +927,20 @@ class AbstractGraphRunner {
922
927
  this.engine.dispose();
923
928
  this.engine = undefined;
924
929
  // Emit status but keep runtime alive
925
- if (this.runningKind) {
926
- this.runningKind = undefined;
927
- this.emit("status", { running: false, engine: undefined });
928
- }
929
- }
930
- async switchEngine(opts) {
931
- if (!this.engine || !this.runtime) {
932
- throw new Error("No engine running to switch from");
933
- }
934
- // Wait for current engine to be idle
935
- await this.whenIdle();
936
- // Capture current state
937
- const currentInputs = { ...this.stagedInputs };
938
- // Stop current engine
939
- this.stop();
940
- // Ensure runtime is in a clean state (resumed)
941
- this.runtime.resume();
942
- // Create and launch new engine (to be implemented by subclasses)
943
- await this.createAndLaunchEngine(opts);
944
- // Re-apply staged inputs to new engine using runner's setInputs method
945
- // This ensures consistency and proper handling of staged inputs
946
- for (const [nodeId, map] of Object.entries(currentInputs)) {
947
- await this.setInputs(nodeId, map);
930
+ if (this.runMode) {
931
+ this.runMode = undefined;
932
+ this.emit("status", { running: false, runMode: undefined });
933
+ }
934
+ }
935
+ setRunMode(runMode) {
936
+ if (!this.engine) {
937
+ throw new Error("Cannot set run mode: engine not running");
948
938
  }
939
+ // Update engine run mode (this will update pause/resume state)
940
+ this.engine.setRunMode(runMode);
941
+ // Update local state and emit status event
942
+ this.runMode = runMode;
943
+ this.emit("status", { running: true, runMode: this.runMode });
949
944
  }
950
945
  getInputDefaults(def) {
951
946
  const out = {};
@@ -975,16 +970,16 @@ class AbstractGraphRunner {
975
970
  this.engine = undefined;
976
971
  this.runtime?.dispose();
977
972
  this.runtime = undefined;
978
- if (this.runningKind) {
979
- this.runningKind = undefined;
980
- this.emit("status", { running: false, engine: undefined });
973
+ if (this.runMode) {
974
+ this.runMode = undefined;
975
+ this.emit("status", { running: false, runMode: undefined });
981
976
  }
982
977
  }
983
978
  isRunning() {
984
979
  return !!this.engine;
985
980
  }
986
- getRunningEngine() {
987
- return this.runningKind;
981
+ getRunMode() {
982
+ return this.runMode;
988
983
  }
989
984
  // Optional undo/redo support
990
985
  async undo() {
@@ -1079,7 +1074,7 @@ class LocalGraphRunner extends AbstractGraphRunner {
1079
1074
  if (!this.runtime)
1080
1075
  throw new Error("Runtime not built");
1081
1076
  // Use shared engine factory
1082
- this.engine = sparkGraph.createEngine(this.runtime, opts);
1077
+ this.engine = new sparkGraph.UnifiedEngine(this.runtime, opts?.runMode);
1083
1078
  if (!this.engine)
1084
1079
  throw new Error("Failed to create engine");
1085
1080
  this.engine.on("value", (e) => this.emit("value", e));
@@ -1087,36 +1082,34 @@ class LocalGraphRunner extends AbstractGraphRunner {
1087
1082
  this.engine.on("invalidate", (e) => this.emit("invalidate", e));
1088
1083
  this.engine.on("stats", (e) => this.emit("stats", e));
1089
1084
  this.engine.launch(opts?.invalidate);
1090
- this.runningKind = opts?.engine ?? "push";
1091
- this.emit("status", { running: true, engine: this.runningKind });
1085
+ this.runMode = opts?.runMode ?? "manual";
1086
+ this.emit("status", { running: true, runMode: this.runMode });
1092
1087
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
1093
1088
  this.engine.setInputs(nodeId, map);
1094
1089
  }
1095
1090
  }
1096
- async step() {
1097
- const eng = this.engine;
1098
- if (eng && eng instanceof sparkGraph.StepEngine)
1099
- await eng.step();
1091
+ async computeNode(nodeId, options) {
1092
+ if (this.engine)
1093
+ await this.engine.computeNode(nodeId, options);
1100
1094
  }
1101
- async computeNode(nodeId) {
1102
- const eng = this.engine;
1103
- if (eng && eng instanceof sparkGraph.PullEngine)
1104
- await eng.computeNode(nodeId);
1095
+ async runFromHere(nodeId) {
1096
+ if (this.engine)
1097
+ await this.engine.runFromHere(nodeId);
1105
1098
  }
1106
- async flush() {
1107
- const eng = this.engine;
1108
- if (eng && eng instanceof sparkGraph.BatchedEngine)
1109
- await eng.flush();
1099
+ cancelNodeRuns(nodeIds) {
1100
+ if (this.engine) {
1101
+ this.engine.cancelNodeRuns(nodeIds);
1102
+ }
1110
1103
  }
1111
1104
  getOutputs(def) {
1112
1105
  const out = {};
1113
- if (!this.runtime)
1106
+ if (!this.engine)
1114
1107
  return out;
1115
1108
  for (const n of def.nodes) {
1116
1109
  const desc = this.registry.nodes.get(n.typeId);
1117
1110
  const handles = Object.keys(desc?.outputs ?? {});
1118
1111
  for (const h of handles) {
1119
- const v = this.runtime.getOutput(n.nodeId, h);
1112
+ const v = this.engine.getOutput(n.nodeId, h);
1120
1113
  if (v !== undefined) {
1121
1114
  if (!out[n.nodeId])
1122
1115
  out[n.nodeId] = {};
@@ -1149,19 +1142,8 @@ class LocalGraphRunner extends AbstractGraphRunner {
1149
1142
  return out;
1150
1143
  }
1151
1144
  triggerExternal(nodeId, event, options) {
1152
- // Handle dry option: pause runtime before triggering, resume after
1153
- const wasPaused = this.runtime?.isPaused() ?? false;
1154
- if (options?.dry && !wasPaused && this.runtime) {
1155
- this.runtime.pause();
1156
- }
1157
- try {
1158
- this.engine?.triggerExternal(nodeId, event);
1159
- }
1160
- finally {
1161
- if (options?.dry && !wasPaused && this.runtime) {
1162
- this.runtime.resume();
1163
- }
1164
- }
1145
+ // Engine handles dry option via AbstractEngine
1146
+ this.engine?.triggerExternal(nodeId, event, options);
1165
1147
  }
1166
1148
  // Batch update multiple inputs on a node and trigger a single run
1167
1149
  setInputs(nodeId, inputs, options) {
@@ -1177,40 +1159,23 @@ class LocalGraphRunner extends AbstractGraphRunner {
1177
1159
  this.stagedInputs[nodeId][handle] = value;
1178
1160
  }
1179
1161
  }
1180
- // Handle dry option: pause runtime before setting inputs, resume after
1181
- const wasPaused = this.runtime?.isPaused() ?? false;
1182
- if (options?.dry && !wasPaused && this.runtime) {
1183
- this.runtime.pause();
1184
- }
1185
- try {
1186
- if (this.engine) {
1187
- this.engine.setInputs(nodeId, inputs);
1188
- }
1189
- else {
1190
- // Not running: emit a single synthetic value event per handle; UI will coalesce
1191
- console.warn("Engine does not exists");
1192
- for (const [handle, value] of Object.entries(inputs)) {
1193
- this.emit("value", { nodeId, handle, value, io: "input" });
1194
- }
1195
- }
1162
+ if (this.engine) {
1163
+ // Engine handles dry option via AbstractEngine
1164
+ this.engine.setInputs(nodeId, inputs, options);
1196
1165
  }
1197
- finally {
1198
- if (options?.dry && !wasPaused && this.runtime) {
1199
- this.runtime.resume();
1166
+ else {
1167
+ // Not running: emit a single synthetic value event per handle; UI will coalesce
1168
+ // Note: dry option doesn't apply when engine doesn't exist (no execution to prevent)
1169
+ console.warn("Engine does not exists");
1170
+ for (const [handle, value] of Object.entries(inputs)) {
1171
+ this.emit("value", { nodeId, handle, value, io: "input" });
1200
1172
  }
1201
1173
  }
1202
1174
  }
1203
1175
  copyOutputs(fromNodeId, toNodeId, options) {
1204
- if (!this.runtime)
1205
- return;
1206
- // Get outputs from source node
1207
- const fromNode = this.runtime.getNodeData(fromNodeId);
1208
- if (!fromNode?.outputs)
1209
- return;
1210
- // Copy outputs to target node using hydrate
1211
- // hydrate already pauses internally, so we don't need to handle dry option here
1212
- // reemit: !options?.dry means don't propagate downstream if dry mode
1213
- this.runtime.hydrate({ outputs: { [toNodeId]: { ...fromNode.outputs } } }, { reemit: !options?.dry });
1176
+ if (this.engine) {
1177
+ this.engine.copyOutputs(fromNodeId, toNodeId, options);
1178
+ }
1214
1179
  }
1215
1180
  async snapshotFull() {
1216
1181
  const def = undefined; // UI will supply def/positions on download for local
@@ -1311,7 +1276,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1311
1276
  }
1312
1277
  this.registryFetching = true;
1313
1278
  try {
1314
- const desc = await client.describeRegistry();
1279
+ const desc = await client.api.describeRegistry();
1315
1280
  // Register types
1316
1281
  for (const t of desc.types) {
1317
1282
  if (t.options) {
@@ -1413,7 +1378,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1413
1378
  }
1414
1379
  }
1415
1380
  /**
1416
- * Build RuntimeApiClient config from RemoteExecutionBackend config.
1381
+ * Build RemoteRuntimeClient config from RemoteExecutionBackend config.
1417
1382
  */
1418
1383
  buildClientConfig(backend) {
1419
1384
  if (backend.kind === "remote-http") {
@@ -1436,7 +1401,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1436
1401
  */
1437
1402
  setupClientSubscriptions(client) {
1438
1403
  // Subscribe to transport status changes
1439
- // Convert RuntimeApiClient.TransportStatus to IGraphRunner.TransportStatus
1404
+ // Convert RemoteRuntimeClient.TransportStatus to IGraphRunner.TransportStatus
1440
1405
  // Only emit status if it matches this runner's ID
1441
1406
  this.transportStatusUnsubscribe = client.onTransportStatus((status) => {
1442
1407
  if (status.runnerId && status.runnerId !== this.runnerId)
@@ -1486,7 +1451,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1486
1451
  }
1487
1452
  };
1488
1453
  // Create client with wrapped custom event handler
1489
- const client = new sparkRemote.RuntimeApiClient(clientConfig, {
1454
+ const client = new sparkRemote.RemoteRuntimeClient(clientConfig, {
1490
1455
  onCustomEvent: wrappedOnCustomEvent,
1491
1456
  runnerId: this.runnerId,
1492
1457
  });
@@ -1536,7 +1501,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1536
1501
  // Auto-handle registry-changed invalidations from remote
1537
1502
  // We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
1538
1503
  this.ensureClient().then(async (client) => {
1539
- const eng = client.getEngine();
1504
+ const eng = client.engine;
1540
1505
  if (!this.listenersBound) {
1541
1506
  eng.on("invalidate", async (e) => {
1542
1507
  if (e.reason === "registry-changed") {
@@ -1600,7 +1565,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1600
1565
  // Remote: forward update and await completion
1601
1566
  const client = await this.ensureClient();
1602
1567
  try {
1603
- await client.update(def, options);
1568
+ await client.api.update(def, options);
1604
1569
  this.emit("invalidate", { reason: "graph-updated" });
1605
1570
  this.lastDef = def;
1606
1571
  }
@@ -1613,13 +1578,13 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1613
1578
  super.launch(def, opts);
1614
1579
  // Remote: build remotely then launch
1615
1580
  this.ensureClient().then(async (client) => {
1616
- await client.build(def);
1581
+ await client.api.build(def);
1617
1582
  // Signal UI after remote build as well
1618
1583
  this.emit("invalidate", { reason: "graph-built" });
1619
1584
  this.lastDef = def;
1620
1585
  // Hydrate current remote inputs/outputs (including defaults) into cache
1621
1586
  try {
1622
- const snap = await client.snapshot();
1587
+ const snap = await client.api.snapshot();
1623
1588
  for (const [nodeId, map] of Object.entries(snap.inputs || {})) {
1624
1589
  for (const [handle, value] of Object.entries(map || {})) {
1625
1590
  this.valueCache.set(`${nodeId}.${handle}`, {
@@ -1648,9 +1613,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1648
1613
  async createAndLaunchEngine(opts) {
1649
1614
  const client = await this.ensureClient();
1650
1615
  // Configure and launch engine on the backend
1651
- await client.launch(opts);
1616
+ await client.api.launch(opts);
1652
1617
  // Get the remote engine proxy and wire up event listeners
1653
- const eng = client.getEngine();
1618
+ const eng = client.engine;
1654
1619
  if (!this.listenersBound) {
1655
1620
  eng.on("value", (e) => {
1656
1621
  this.valueCache.set(`${e.nodeId}.${e.handle}`, {
@@ -1666,11 +1631,11 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1666
1631
  this.listenersBound = true;
1667
1632
  }
1668
1633
  this.engine = eng;
1669
- this.runningKind = opts?.engine ?? "push";
1670
- this.emit("status", { running: true, engine: this.runningKind });
1634
+ this.runMode = opts?.runMode ?? "manual";
1635
+ this.emit("status", { running: true, runMode: this.runMode });
1671
1636
  // Re-apply staged inputs using client.setInputs for consistency
1672
1637
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
1673
- await eng.setInputs(nodeId, map).catch(() => {
1638
+ await eng.setInputs(nodeId, map, undefined).catch(() => {
1674
1639
  // Ignore errors during launch - inputs will be set when user calls setInputs
1675
1640
  });
1676
1641
  }
@@ -1693,45 +1658,27 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1693
1658
  await this.createAndLaunchEngine(opts);
1694
1659
  });
1695
1660
  }
1696
- async switchEngine(opts) {
1661
+ setRunMode(runMode) {
1697
1662
  if (!this.engine) {
1698
- throw new Error("No engine running to switch from");
1699
- }
1700
- // Wait for current engine to be idle
1701
- await this.whenIdle();
1702
- // Capture current state
1703
- const currentInputs = { ...this.stagedInputs };
1704
- // For remote runners, we cannot call this.stop() because it sends a Dispose
1705
- // command that destroys the graphRuntime on the backend. Instead, we rely on
1706
- // the backend's launch() method to dispose the old engine and create a new one.
1707
- // Reconfigure engine on the backend (this will dispose old engine and create new one)
1708
- const client = await this.ensureClient();
1709
- await client.launch(opts);
1710
- // Get the remote engine proxy (should be the same RemoteEngine instance)
1711
- const eng = client.getEngine();
1712
- // Update local state to reflect new engine kind
1713
- // Note: The RemoteEngine instance itself doesn't change, but the backend engine does
1714
- this.engine = eng;
1715
- this.runningKind = opts?.engine ?? "push";
1716
- this.emit("status", { running: true, engine: this.runningKind });
1717
- // Re-apply staged inputs using client.setInputs for consistency
1718
- for (const [nodeId, map] of Object.entries(currentInputs)) {
1719
- await eng.setInputs(nodeId, map).catch(() => {
1720
- // Ignore errors during engine switch - inputs will be set when user calls setInputs
1721
- });
1663
+ throw new Error("Cannot set run mode: engine not running");
1722
1664
  }
1665
+ // Update engine run mode (sends SetRunMode command to backend)
1666
+ this.engine.setRunMode(runMode);
1667
+ // Update local state and emit status event
1668
+ this.runMode = runMode;
1669
+ this.emit("status", { running: true, runMode: this.runMode });
1723
1670
  }
1724
- async step() {
1671
+ async computeNode(nodeId, options) {
1725
1672
  const client = await this.ensureClient();
1726
- await client.step();
1673
+ await client.engine.computeNode(nodeId, options);
1727
1674
  }
1728
- async computeNode(nodeId) {
1675
+ async runFromHere(nodeId) {
1729
1676
  const client = await this.ensureClient();
1730
- await client.computeNode(nodeId);
1677
+ await client.engine.runFromHere(nodeId);
1731
1678
  }
1732
- async flush() {
1679
+ async cancelNodeRuns(nodeIds) {
1733
1680
  const client = await this.ensureClient();
1734
- await client.flush();
1681
+ await client.engine.cancelNodeRuns(nodeIds);
1735
1682
  }
1736
1683
  async setInputs(nodeId, inputs, options) {
1737
1684
  // Update staged inputs (for getInputs to work correctly)
@@ -1747,7 +1694,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1747
1694
  }
1748
1695
  try {
1749
1696
  const client = await this.ensureClient();
1750
- await client.getEngine()?.setInputs(nodeId, inputs, options);
1697
+ await client.engine.setInputs(nodeId, inputs, options);
1751
1698
  }
1752
1699
  catch (err) {
1753
1700
  // Emit synthetic events if connection fails
@@ -1759,20 +1706,20 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1759
1706
  }
1760
1707
  async copyOutputs(fromNodeId, toNodeId, options) {
1761
1708
  const client = await this.ensureClient();
1762
- await client.getEngine()?.copyOutputs(fromNodeId, toNodeId, options);
1709
+ await client.engine.copyOutputs(fromNodeId, toNodeId, options);
1763
1710
  }
1764
1711
  async triggerExternal(nodeId, event, options) {
1765
1712
  const client = await this.ensureClient();
1766
- await client.getEngine()?.triggerExternal(nodeId, event, options);
1713
+ await client.engine.triggerExternal(nodeId, event, options);
1767
1714
  }
1768
1715
  async setViewport(viewport) {
1769
1716
  const client = await this.ensureClient();
1770
- await client.setViewport(viewport);
1717
+ await client.api.setViewport(viewport);
1771
1718
  }
1772
1719
  async coerce(from, to, value) {
1773
1720
  const client = await this.ensureClient();
1774
1721
  try {
1775
- return await client.coerce(from, to, value);
1722
+ return await client.api.coerce(from, to, value);
1776
1723
  }
1777
1724
  catch {
1778
1725
  return value;
@@ -1780,12 +1727,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1780
1727
  }
1781
1728
  async setExtData(data) {
1782
1729
  const client = await this.ensureClient();
1783
- await client.setExtData(data);
1730
+ await client.api.setExtData(data);
1784
1731
  }
1785
1732
  async commit(reason) {
1786
1733
  const client = await this.ensureClient();
1787
1734
  try {
1788
- const history = await client.commit(reason);
1735
+ const history = await client.api.commit(reason);
1789
1736
  return history;
1790
1737
  }
1791
1738
  catch (err) {
@@ -1796,7 +1743,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1796
1743
  async undo() {
1797
1744
  const client = await this.ensureClient();
1798
1745
  try {
1799
- return await client.undo();
1746
+ return await client.api.undo();
1800
1747
  }
1801
1748
  catch {
1802
1749
  return false;
@@ -1805,7 +1752,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1805
1752
  async redo() {
1806
1753
  const client = await this.ensureClient();
1807
1754
  try {
1808
- return await client.redo();
1755
+ return await client.api.redo();
1809
1756
  }
1810
1757
  catch {
1811
1758
  return false;
@@ -1814,7 +1761,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1814
1761
  async snapshotFull() {
1815
1762
  const client = await this.ensureClient();
1816
1763
  try {
1817
- return await client.snapshotFull();
1764
+ return await client.api.snapshotFull();
1818
1765
  }
1819
1766
  catch {
1820
1767
  return { def: undefined, environment: {}, inputs: {}, outputs: {} };
@@ -1825,7 +1772,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1825
1772
  this.hydrateValueCache(payload, { dry: options?.dry });
1826
1773
  // Then sync with backend
1827
1774
  const client = await this.ensureClient();
1828
- await client.applySnapshotFull(payload, { skipBuild: options?.skipBuild });
1775
+ await client.api.applySnapshotFull(payload, {
1776
+ skipBuild: options?.skipBuild,
1777
+ });
1829
1778
  }
1830
1779
  /**
1831
1780
  * Hydrates the local valueCache from a snapshot and emits value events.
@@ -1869,12 +1818,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1869
1818
  async setEnvironment(env, opts) {
1870
1819
  // Use client if available, otherwise ensure client and then set environment
1871
1820
  if (this.client) {
1872
- await this.client.setEnvironment(env, opts);
1821
+ await this.client.api.setEnvironment(env, opts);
1873
1822
  }
1874
1823
  else {
1875
1824
  try {
1876
1825
  const client = await this.ensureClient();
1877
- await client.setEnvironment(env, opts);
1826
+ await client.api.setEnvironment(env, opts);
1878
1827
  }
1879
1828
  catch {
1880
1829
  // Silently fail if connection not available
@@ -1882,7 +1831,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1882
1831
  }
1883
1832
  }
1884
1833
  getEnvironment() {
1885
- // Interface requires sync return, but RuntimeApiClient.getEnvironment() is async.
1834
+ // Interface requires sync return, but RemoteRuntimeClient.getEnvironment() is async.
1886
1835
  // Returns undefined synchronously; callers needing the actual value should:
1887
1836
  // - Use snapshotFull() which includes environment
1888
1837
  // - Call client.getEnvironment() directly if they have access to the client
@@ -3649,9 +3598,10 @@ function computeInvalidatedFromMetadata(metadata) {
3649
3598
  const maxInputTime = Math.max(...Object.values(lastInputAt));
3650
3599
  return maxInputTime > (lastSuccessAt ?? lastRunAt ?? 0);
3651
3600
  }
3652
- function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVersion, children, }) {
3601
+ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
3653
3602
  const [nodeStatus, setNodeStatus] = React.useState({});
3654
3603
  const [edgeStatus, setEdgeStatus] = React.useState({});
3604
+ const [runMode, setRunModeState] = React.useState("manual");
3655
3605
  const [events, setEvents] = React.useState([]);
3656
3606
  const clearEvents = React.useCallback(() => setEvents([]), []);
3657
3607
  const [systemErrors, setSystemErrors] = React.useState([]);
@@ -3716,6 +3666,24 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3716
3666
  const graphUiTick = useWorkbenchGraphUiTick(wb);
3717
3667
  const versionTick = useWorkbenchVersionTick(runner);
3718
3668
  const valuesTick = versionTick + graphTick + graphUiTick;
3669
+ // Keep local runMode state loosely in sync with runner status.
3670
+ // - Seed from runner.getRunMode() on mount if available.
3671
+ // - On status events, update only when a non-undefined runMode is reported,
3672
+ // so the UI preserves the last selected mode after stop().
3673
+ React.useEffect(() => {
3674
+ const initialMode = runner.getRunMode();
3675
+ if (initialMode) {
3676
+ setRunModeState(initialMode);
3677
+ }
3678
+ const offRunnerStatus = runner.on("status", (status) => {
3679
+ if (status.runMode) {
3680
+ setRunModeState(status.runMode);
3681
+ }
3682
+ });
3683
+ return () => {
3684
+ offRunnerStatus();
3685
+ };
3686
+ }, [runner]);
3719
3687
  // Def and IO values
3720
3688
  const inputsMap = React.useMemo(() => runner.getInputs(wb.def), [runner, wb, wb.def, valuesTick]);
3721
3689
  const inputDefaultsMap = React.useMemo(() => runner.getInputDefaults(wb.def), [runner, wb, wb.def, valuesTick]);
@@ -3724,7 +3692,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3724
3692
  const out = {};
3725
3693
  // Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
3726
3694
  for (const n of wb.def.nodes) {
3727
- const effectiveHandles = computeEffectiveHandles(n, registry);
3695
+ const effectiveHandles = computeEffectiveHandles(n, wb.registry);
3728
3696
  const outputsDecl = effectiveHandles.outputs;
3729
3697
  const handles = Object.keys(outputsDecl);
3730
3698
  const cur = {};
@@ -3738,7 +3706,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3738
3706
  out[n.nodeId] = cur;
3739
3707
  }
3740
3708
  return out;
3741
- }, [wb, wb.def, outputsMap, registry]);
3709
+ }, [wb, wb.def, outputsMap, wb.registry, registryVersion]);
3742
3710
  // Initialize nodes and derive invalidated status from persisted metadata
3743
3711
  React.useEffect(() => {
3744
3712
  const workbenchRuntimeState = wb.getRuntimeState() ?? { nodes: {} };
@@ -3816,7 +3784,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3816
3784
  const overrideSize = overrides?.getDefaultNodeSize?.(node.typeId) ?? undefined;
3817
3785
  const size = estimateNodeSize({
3818
3786
  node,
3819
- registry,
3787
+ registry: wb.registry,
3820
3788
  showValues: true,
3821
3789
  overrides: overrideSize,
3822
3790
  });
@@ -3834,7 +3802,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3834
3802
  curX += maxWidth + H_GAP;
3835
3803
  }
3836
3804
  wb.setPositions(pos, { commit: true, reason: "auto-layout" });
3837
- }, [wb, wb.def, registry, overrides?.getDefaultNodeSize]);
3805
+ }, [wb, wb.def, wb.registry, registryVersion, overrides?.getDefaultNodeSize]);
3838
3806
  const updateEdgeType = React.useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
3839
3807
  const triggerExternal = React.useCallback((nodeId, event) => runner.triggerExternal(nodeId, event), [runner]);
3840
3808
  const getNodeDisplayName = React.useCallback((nodeId) => {
@@ -3844,9 +3812,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3844
3812
  const node = wb.def.nodes.find((n) => n.nodeId === nodeId);
3845
3813
  if (!node)
3846
3814
  return nodeId;
3847
- const desc = registry.nodes.get(node.typeId);
3815
+ const desc = wb.registry.nodes.get(node.typeId);
3848
3816
  return desc?.displayName || node.typeId;
3849
- }, [wb, registry]);
3817
+ }, [wb, wb.registry, registryVersion]);
3850
3818
  const setNodeName = React.useCallback((nodeId, name) => {
3851
3819
  wb.setNodeName(nodeId, name, { commit: true, reason: "rename-node" });
3852
3820
  }, [wb]);
@@ -4162,6 +4130,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4162
4130
  }
4163
4131
  return add("runner", "stats")(s);
4164
4132
  });
4133
+ const offWbRegistryChanged = wb.on("registryChanged", (evt) => {
4134
+ setRegistryVersion((v) => v + 1);
4135
+ });
4165
4136
  const offWbGraphChanged = wb.on("graphChanged", (event) => {
4166
4137
  // Clear validation errors for removed nodes
4167
4138
  if (event.change?.type === "removeNode") {
@@ -4318,10 +4289,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4318
4289
  // Registry updates: swap registry and refresh graph validation/UI
4319
4290
  const offRunnerRegistry = runner.on("registry", async (newReg) => {
4320
4291
  try {
4321
- setRegistry(newReg);
4322
4292
  wb.setRegistry(newReg);
4323
4293
  // Increment registry version to trigger UI updates
4324
- setRegistryVersion((v) => v + 1);
4325
4294
  // Trigger a graph update so the UI revalidates with new types/enums/nodes
4326
4295
  try {
4327
4296
  await runner.update(wb.def);
@@ -4392,6 +4361,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4392
4361
  offRunnerError();
4393
4362
  offRunnerInvalidate();
4394
4363
  offRunnerStats();
4364
+ offWbRegistryChanged();
4395
4365
  offWbGraphChanged();
4396
4366
  offWbGraphUiChangedForLog();
4397
4367
  offWbGraphUiChanged();
@@ -4405,18 +4375,39 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4405
4375
  offFlowViewport();
4406
4376
  offWbRuntimeMetadataChanged();
4407
4377
  };
4408
- }, [runner, wb, setRegistry]);
4409
- const isRunning = React.useCallback(() => runner.isRunning(), [runner]);
4410
- const engineKind = React.useCallback(() => runner.getRunningEngine(), [runner]);
4411
- const start = React.useCallback((engine) => {
4412
- try {
4413
- runner.launch(wb.def, { engine });
4414
- }
4415
- catch { }
4416
4378
  }, [runner, wb]);
4379
+ const isRunning = React.useCallback(() => runner.isRunning(), [runner]);
4380
+ const getRunMode = React.useCallback(() => runner.getRunMode(), [runner]);
4417
4381
  const stop = React.useCallback(() => runner.stop(), [runner]);
4418
- const step = React.useCallback(() => runner.step(), [runner]);
4419
- const flush = React.useCallback(() => runner.flush(), [runner]);
4382
+ // Run mode actions
4383
+ const setRunMode = React.useCallback((mode) => {
4384
+ if (mode === runMode)
4385
+ return;
4386
+ const wasRunning = runner.isRunning();
4387
+ if (wasRunning) {
4388
+ // Use setRunMode to change run mode without rebuilding
4389
+ try {
4390
+ runner.setRunMode(mode);
4391
+ setRunModeState(mode);
4392
+ }
4393
+ catch (err) {
4394
+ console.error("Failed to set run mode:", err);
4395
+ }
4396
+ }
4397
+ else {
4398
+ // Just update state if not running (will be applied on next launch)
4399
+ setRunModeState(mode);
4400
+ }
4401
+ }, [runMode, runner]);
4402
+ const runNodeAction = React.useCallback(async (nodeId) => {
4403
+ await runner.computeNode(nodeId);
4404
+ }, [runner]);
4405
+ const runFromHereAction = React.useCallback(async (nodeId) => {
4406
+ await runner.runFromHere(nodeId);
4407
+ }, [runner]);
4408
+ const abortNodeAction = React.useCallback((nodeId) => {
4409
+ runner.cancelNodeRuns([nodeId]);
4410
+ }, [runner]);
4420
4411
  const validationByNode = React.useMemo(() => {
4421
4412
  const inputs = {};
4422
4413
  const outputs = {};
@@ -4483,8 +4474,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4483
4474
  const value = React.useMemo(() => ({
4484
4475
  wb,
4485
4476
  runner,
4486
- registry,
4487
- setRegistry,
4488
4477
  selectedNodeId,
4489
4478
  selectedEdgeId,
4490
4479
  setSelection,
@@ -4510,11 +4499,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4510
4499
  removeRegistryError,
4511
4500
  removeInputValidationError,
4512
4501
  isRunning,
4513
- engineKind,
4514
- start,
4502
+ getRunMode,
4515
4503
  stop,
4516
- step,
4517
- flush,
4504
+ runMode,
4505
+ setRunMode,
4506
+ runNode: runNodeAction,
4507
+ runFromHere: runFromHereAction,
4508
+ abortNode: abortNodeAction,
4518
4509
  runAutoLayout,
4519
4510
  updateEdgeType,
4520
4511
  triggerExternal,
@@ -4526,8 +4517,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4526
4517
  }), [
4527
4518
  wb,
4528
4519
  runner,
4529
- registry,
4530
- setRegistry,
4531
4520
  selectedNodeId,
4532
4521
  selectedEdgeId,
4533
4522
  setSelection,
@@ -4552,11 +4541,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
4552
4541
  events,
4553
4542
  clearEvents,
4554
4543
  isRunning,
4555
- engineKind,
4556
- start,
4544
+ getRunMode,
4557
4545
  stop,
4558
- step,
4559
- flush,
4546
+ runMode,
4547
+ setRunMode,
4548
+ runNodeAction,
4549
+ runFromHereAction,
4550
+ abortNodeAction,
4560
4551
  runAutoLayout,
4561
4552
  wb,
4562
4553
  runner,
@@ -4574,7 +4565,7 @@ function IssueBadge({ level, title, size = 12, className, }) {
4574
4565
  return (jsxRuntime.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" ? (jsxRuntime.jsx(react$1.XCircleIcon, { size: size, weight: "fill" })) : (jsxRuntime.jsx(react$1.WarningCircleIcon, { size: size, weight: "fill" })) }));
4575
4566
  }
4576
4567
 
4577
- function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate, }) {
4568
+ function DefaultNodeHeader({ id, typeId, validation, right, showId, }) {
4578
4569
  const ctx = useWorkbenchContext();
4579
4570
  const [isEditing, setIsEditing] = React.useState(false);
4580
4571
  const [editValue, setEditValue] = React.useState("");
@@ -4589,21 +4580,9 @@ function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate
4589
4580
  const node = ctx.wb.def.nodes.find((n) => n.nodeId === id);
4590
4581
  if (!node)
4591
4582
  return id;
4592
- const desc = ctx.registry.nodes.get(node.typeId);
4583
+ const desc = ctx.wb.registry.nodes.get(node.typeId);
4593
4584
  return desc?.displayName || node.typeId;
4594
4585
  }, [ctx, id, typeId]);
4595
- const handleInvalidate = React.useCallback(() => {
4596
- try {
4597
- if (onInvalidate)
4598
- return onInvalidate();
4599
- const kind = ctx.engineKind?.();
4600
- if (kind === "pull")
4601
- ctx.runner.computeNode(id);
4602
- else
4603
- ctx.triggerExternal?.(id, { type: "invalidate" });
4604
- }
4605
- catch { }
4606
- }, [ctx, id, onInvalidate]);
4607
4586
  const handleDoubleClick = React.useCallback((e) => {
4608
4587
  // Only allow editing if typeId is provided (enables renaming)
4609
4588
  if (!typeId)
@@ -4648,10 +4627,16 @@ function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate
4648
4627
  return (jsxRuntime.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: {
4649
4628
  maxHeight: NODE_HEADER_HEIGHT_PX,
4650
4629
  minHeight: NODE_HEADER_HEIGHT_PX,
4651
- }, children: [isEditing ? (jsxRuntime.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` } })) : (jsxRuntime.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 })), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.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) => {
4652
- e.stopPropagation();
4653
- handleInvalidate();
4654
- }, children: jsxRuntime.jsx(react$1.ArrowClockwiseIcon, { size: 10 }) }), right, validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
4630
+ }, children: [isEditing ? (jsxRuntime.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` } })) : (jsxRuntime.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 })), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [ctx.runMode === "manual" && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { onClick: (e) => {
4631
+ e.stopPropagation();
4632
+ ctx.abortNode(id);
4633
+ }, 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: jsxRuntime.jsx(react$1.StopIcon, { size: 10, weight: "fill" }) }), jsxRuntime.jsx("button", { onClick: (e) => {
4634
+ e.stopPropagation();
4635
+ void ctx.runFromHere(id);
4636
+ }, 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: jsxRuntime.jsx(react$1.PlayIcon, { size: 10, weight: "fill" }) }), jsxRuntime.jsx("button", { onClick: (e) => {
4637
+ e.stopPropagation();
4638
+ void ctx.runNode(id);
4639
+ }, 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: jsxRuntime.jsx(react$1.Circle, { size: 10, weight: "fill" }) })] })), right, validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
4655
4640
  ? "error"
4656
4641
  : "warning", size: 12, className: "w-3 h-3", title: validation.issues
4657
4642
  .map((v) => `${v.code}: ${v.message}`)
@@ -4805,7 +4790,7 @@ const DefaultEdge = React.memo(function DefaultEdge({ id, sourceX, sourceY, targ
4805
4790
  return (jsxRuntime.jsx(react.BaseEdge, { id: id, path: edgePath, style: style, markerEnd: markerEnd }));
4806
4791
  });
4807
4792
 
4808
- function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult) {
4793
+ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult, runNode, runFromHere) {
4809
4794
  return {
4810
4795
  onDelete: () => {
4811
4796
  wb.removeNode(nodeId, { commit: true });
@@ -4825,13 +4810,24 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
4825
4810
  });
4826
4811
  onClose();
4827
4812
  },
4828
- onRunPull: async () => {
4829
- try {
4830
- await runner.computeNode(nodeId);
4813
+ onRunNode: runNode
4814
+ ? async () => {
4815
+ try {
4816
+ await runNode(nodeId);
4817
+ }
4818
+ catch { }
4819
+ onClose();
4831
4820
  }
4832
- catch { }
4833
- onClose();
4834
- },
4821
+ : undefined,
4822
+ onRunFromHere: runFromHere
4823
+ ? async () => {
4824
+ try {
4825
+ await runFromHere(nodeId);
4826
+ }
4827
+ catch { }
4828
+ onClose();
4829
+ }
4830
+ : undefined,
4835
4831
  onBake: async (handleId) => {
4836
4832
  const nodePosition = wb.getPositions()[nodeId] || { x: 0, y: 0 };
4837
4833
  const typeId = outputTypesMap?.[nodeId]?.[handleId];
@@ -5048,7 +5044,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5048
5044
  return String(value ?? "");
5049
5045
  }
5050
5046
  };
5051
- 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();
5047
+ 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();
5052
5048
  const nodeValidationIssues = validationByNode.issues;
5053
5049
  const edgeValidationIssues = validationByEdge.issues;
5054
5050
  const nodeValidationHandles = validationByNode;
@@ -5057,7 +5053,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5057
5053
  const selectedEdge = wb.def.edges.find((e) => e.id === selectedEdgeId);
5058
5054
  // Use computeEffectiveHandles to merge registry defaults with dynamically resolved handles
5059
5055
  const effectiveHandles = selectedNode
5060
- ? computeEffectiveHandles(selectedNode, registry)
5056
+ ? computeEffectiveHandles(selectedNode, wb.registry)
5061
5057
  : { inputs: {}, outputs: {}};
5062
5058
  const inputHandles = Object.entries(effectiveHandles.inputs)
5063
5059
  .filter(([k]) => !sparkGraph.isInputPrivate(effectiveHandles.inputs, k))
@@ -5184,7 +5180,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5184
5180
  setDrafts(nextDrafts);
5185
5181
  if (!shallowEqual(originals, nextOriginals))
5186
5182
  setOriginals(nextOriginals);
5187
- }, [selectedNodeId, selectedNode, registry, valuesTick]);
5183
+ }, [selectedNodeId, selectedNode, wb.registry, registryVersion, valuesTick]);
5188
5184
  const widthClass = debug ? "w-[480px]" : "w-[320px]";
5189
5185
  const deleteEdgeById = (edgeId) => {
5190
5186
  if (!edgeId)
@@ -5204,7 +5200,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5204
5200
  const v = e.target.value;
5205
5201
  const next = v === "" ? undefined : v;
5206
5202
  updateEdgeType(selectedEdge.id, next);
5207
- }, children: [jsxRuntime.jsx("option", { value: "", children: "(infer from source)" }), Array.from(registry.types.keys()).map((tid) => (jsxRuntime.jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), jsxRuntime.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) => {
5203
+ }, children: [jsxRuntime.jsx("option", { value: "", children: "(infer from source)" }), Array.from(wb.registry.types.keys()).map((tid) => (jsxRuntime.jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), jsxRuntime.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) => {
5208
5204
  e.stopPropagation();
5209
5205
  deleteEdgeById(selectedEdge.id);
5210
5206
  }, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.activeRuns &&
@@ -5274,7 +5270,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5274
5270
  setOriginals((o) => ({ ...o, [h]: display }));
5275
5271
  }, ...commonProps, children: [jsxRuntime.jsx("option", { value: "", children: placeholder
5276
5272
  ? `Default: ${placeholder}`
5277
- : "(select)" }), registry.enums
5273
+ : "(select)" }), wb.registry.enums
5278
5274
  .get(typeId)
5279
5275
  ?.options.map((opt) => (jsxRuntime.jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsxRuntime.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: jsxRuntime.jsx(react$1.XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsxRuntime.jsx("div", { className: "flex items-center gap-1 flex-1", children: jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: renderLinkedInputDisplay(typeId, current) }) })) : (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.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
5280
5276
  ? `Default: ${placeholder}`
@@ -5413,7 +5409,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, enab
5413
5409
  !handlers.onRedo && jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxRuntime.jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsxRuntime.jsx("div", { className: "px-2 pb-1", children: jsxRuntime.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() }) }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsxRuntime.jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
5414
5410
  }
5415
5411
 
5416
- function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, enableKeyboardShortcuts = true, keyboardShortcuts = {
5412
+ function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, runMode, enableKeyboardShortcuts = true, keyboardShortcuts = {
5417
5413
  copy: "⌘/Ctrl + C",
5418
5414
  duplicate: "⌘/Ctrl + E",
5419
5415
  duplicateWithEdges: "⌘/Ctrl + Shift + E",
@@ -5456,7 +5452,7 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
5456
5452
  return (jsxRuntime.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) => {
5457
5453
  e.preventDefault();
5458
5454
  e.stopPropagation();
5459
- }, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate with edges", onClick: handlers.onDuplicateWithEdges, shortcut: keyboardShortcuts.duplicateWithEdges, enableKeyboardShortcuts: enableKeyboardShortcuts }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.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 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
5455
+ }, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate with edges", onClick: handlers.onDuplicateWithEdges, shortcut: keyboardShortcuts.duplicateWithEdges, enableKeyboardShortcuts: enableKeyboardShortcuts }), runMode === "manual" && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [handlers.onRunNode && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunNode, children: "Run node" })), handlers.onRunFromHere && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunFromHere, children: "Run from here" }))] })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.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 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
5460
5456
  }
5461
5457
 
5462
5458
  function SelectionContextMenu({ open, clientPos, handlers, enableKeyboardShortcuts = true, keyboardShortcuts = {
@@ -5543,7 +5539,7 @@ function useKeyboardShortcutToast() {
5543
5539
  }
5544
5540
 
5545
5541
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
5546
- const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, registryVersion, runner, engineKind, overrides, } = useWorkbenchContext();
5542
+ const { wb, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, registryVersion, runner, overrides, runNode, runFromHere, runMode, } = useWorkbenchContext();
5547
5543
  const nodeValidation = validationByNode;
5548
5544
  const edgeValidation = validationByEdge.errors;
5549
5545
  const [historyState, setHistoryState] = React.useState(wb.getHistory());
@@ -5621,7 +5617,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5621
5617
  // Build nodeTypes map using UI extension registry
5622
5618
  const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
5623
5619
  const ids = new Set([
5624
- ...Array.from(registry.nodes.keys()),
5620
+ ...Array.from(wb.registry.nodes.keys()),
5625
5621
  ...wb.def.nodes.map((n) => n.typeId),
5626
5622
  ]);
5627
5623
  for (const typeId of ids) {
@@ -5640,7 +5636,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5640
5636
  return { nodeTypes: types, resolveNodeType: resolver };
5641
5637
  // Include uiVersion to recompute when custom renderers are registered
5642
5638
  // Include registryVersion to recompute when registry enums/types change
5643
- }, [wb, registry, uiVersion, ui]);
5639
+ }, [wb, wb.registry, registryVersion, uiVersion, ui]);
5644
5640
  const edgeTypes = React.useMemo(() => {
5645
5641
  // Use default edge renderer override if registered, otherwise use DefaultEdge
5646
5642
  const customEdgeRenderer = ui.getEdgeRenderer();
@@ -5666,7 +5662,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5666
5662
  inputsWithDefaults[n.nodeId] = merged;
5667
5663
  }
5668
5664
  }
5669
- const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), registry, {
5665
+ const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), wb.registry, {
5670
5666
  showValues,
5671
5667
  inputs: inputsWithDefaults,
5672
5668
  inputDefaults: inputDefaultsMap,
@@ -5933,7 +5929,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5933
5929
  });
5934
5930
  return () => off();
5935
5931
  }, [wb]);
5936
- const nodeIds = React.useMemo(() => Array.from(registry.nodes.keys()), [registry, registryVersion]);
5932
+ const nodeIds = React.useMemo(() => Array.from(wb.registry.nodes.keys()), [wb.registry, registryVersion]);
5937
5933
  const defaultContextMenuHandlers = React.useMemo(() => {
5938
5934
  // Get storage from override or use workbench's internal storage
5939
5935
  const storage = overrides?.getCopiedDataStorage
@@ -5981,9 +5977,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5981
5977
  get: () => wb.getCopiedData(),
5982
5978
  set: (data) => wb.setCopiedData(data),
5983
5979
  };
5984
- const baseHandlers = createNodeContextMenuHandlers(nodeAtMenu, wb, runner, registry, outputsMap, outputTypesMap, onCloseNodeMenu, overrides?.getDefaultNodeSize, (data) => {
5980
+ const baseHandlers = createNodeContextMenuHandlers(nodeAtMenu, wb, runner, wb.registry, outputsMap, outputTypesMap, onCloseNodeMenu, overrides?.getDefaultNodeSize, (data) => {
5985
5981
  storage.set(data);
5986
- });
5982
+ }, runNode, runFromHere);
5987
5983
  if (overrides?.getNodeContextMenuHandlers) {
5988
5984
  return overrides.getNodeContextMenuHandlers(wb, nodeAtMenu, baseHandlers);
5989
5985
  }
@@ -5992,7 +5988,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5992
5988
  nodeAtMenu,
5993
5989
  wb,
5994
5990
  runner,
5995
- registry,
5991
+ wb.registry,
5992
+ registryVersion,
5996
5993
  outputsMap,
5997
5994
  outputTypesMap,
5998
5995
  onCloseNodeMenu,
@@ -6000,12 +5997,11 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6000
5997
  overrides?.getNodeContextMenuHandlers,
6001
5998
  overrides?.getCopiedDataStorage,
6002
5999
  ]);
6003
- const canRunPull = React.useMemo(() => engineKind()?.toString() === "pull", [engineKind]);
6004
6000
  const bakeableOutputs = React.useMemo(() => {
6005
6001
  if (!nodeAtMenu)
6006
6002
  return [];
6007
- return getBakeableOutputs(nodeAtMenu, wb, registry, outputTypesMap);
6008
- }, [nodeAtMenu, wb, registry, outputTypesMap]);
6003
+ return getBakeableOutputs(nodeAtMenu, wb, wb.registry, outputTypesMap);
6004
+ }, [nodeAtMenu, wb, wb.registry, registryVersion, outputTypesMap]);
6009
6005
  // Keyboard shortcuts configuration
6010
6006
  const enableKeyboardShortcuts = overrides?.enableKeyboardShortcuts !== false; // Default to true
6011
6007
  const keyboardShortcuts = overrides?.keyboardShortcuts || {
@@ -6201,29 +6197,28 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6201
6197
  if (savedViewport) {
6202
6198
  inst.setViewport(lod.clone(savedViewport));
6203
6199
  }
6204
- }, 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 ? (jsxRuntime.jsx(BackgroundRenderer, {})) : (jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsxRuntime.jsx(MinimapRenderer, {}) : jsxRuntime.jsx(react.MiniMap, {}), ControlsRenderer ? jsxRuntime.jsx(ControlsRenderer, {}) : jsxRuntime.jsx(react.Controls, {}), DefaultContextMenuRenderer ? (jsxRuntime.jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, ...(enableKeyboardShortcuts !== false
6200
+ }, 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 ? (jsxRuntime.jsx(BackgroundRenderer, {})) : (jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsxRuntime.jsx(MinimapRenderer, {}) : jsxRuntime.jsx(react.MiniMap, {}), ControlsRenderer ? jsxRuntime.jsx(ControlsRenderer, {}) : jsxRuntime.jsx(react.Controls, {}), DefaultContextMenuRenderer ? (jsxRuntime.jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, ...(enableKeyboardShortcuts !== false
6205
6201
  ? { enableKeyboardShortcuts, keyboardShortcuts }
6206
- : {}) })) : (jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
6202
+ : {}) })) : (jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
6207
6203
  nodeContextMenuHandlers &&
6208
- (NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, ...(enableKeyboardShortcuts !== false
6204
+ (NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode, wb: wb, ...(enableKeyboardShortcuts !== false
6209
6205
  ? { enableKeyboardShortcuts, keyboardShortcuts }
6210
- : {}) })) : (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts }))), selectionMenuOpen &&
6206
+ : {}) })) : (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode }))), selectionMenuOpen &&
6211
6207
  selectionMenuPos &&
6212
6208
  (SelectionContextMenuRenderer ? (jsxRuntime.jsx(SelectionContextMenuRenderer, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(SelectionContextMenu, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })))] }) }), toast && (jsxRuntime.jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
6213
6209
  });
6214
6210
 
6215
- function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
6216
- const { wb, runner, registry, selectedNodeId, runAutoLayout } = useWorkbenchContext();
6211
+ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
6212
+ const { wb, runner, selectedNodeId, runAutoLayout, runMode, setRunMode, isRunning, } = useWorkbenchContext();
6217
6213
  const [transportStatus, setTransportStatus] = React.useState({
6218
6214
  state: "local",
6219
6215
  });
6220
6216
  const selectedNode = wb.def.nodes.find((n) => n.nodeId === selectedNodeId);
6221
6217
  const effectiveHandles = selectedNode
6222
- ? computeEffectiveHandles(selectedNode, registry)
6218
+ ? computeEffectiveHandles(selectedNode, wb.registry)
6223
6219
  : { inputs: {}, outputs: {}, inputDefaults: {} };
6224
6220
  const [exampleState, setExampleState] = React.useState(example ?? "");
6225
- const isGraphRunning = runner.isRunning();
6226
- const engineKind = runner.getRunningEngine();
6221
+ const isGraphRunning = isRunning();
6227
6222
  // Render Start/Stop button based on transport and runner state
6228
6223
  const renderStartStopButton = React.useCallback(() => {
6229
6224
  // Check if transport is connecting/retrying
@@ -6239,14 +6234,11 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6239
6234
  return (jsxRuntime.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: [jsxRuntime.jsx(react$1.StopIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Stop" })] }));
6240
6235
  }
6241
6236
  return (jsxRuntime.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) => {
6242
- const kind = engine;
6243
- if (!kind)
6244
- return alert("Select an engine first.");
6245
6237
  if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
6246
6238
  return;
6247
6239
  try {
6248
6240
  runner.launch(wb.def, {
6249
- engine: kind,
6241
+ runMode,
6250
6242
  invalidate: evt.shiftKey,
6251
6243
  });
6252
6244
  }
@@ -6254,12 +6246,10 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6254
6246
  const message = err instanceof Error ? err.message : String(err);
6255
6247
  alert(message);
6256
6248
  }
6257
- }, disabled: !engine || !canControl, title: !engine
6258
- ? "Select an engine first"
6259
- : !canControl
6260
- ? "Waiting for connection"
6261
- : "Start engine", children: [jsxRuntime.jsx(react$1.PlayIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6262
- }, [transportStatus, isGraphRunning, runner, engine, wb]);
6249
+ }, disabled: !canControl, title: !canControl
6250
+ ? "Waiting for connection"
6251
+ : `Start ${runMode === "manual" ? "manual" : "auto"} mode`, children: [jsxRuntime.jsx(react$1.PlayIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6252
+ }, [transportStatus, isGraphRunning, runner, runMode, wb]);
6263
6253
  const defaultExamples = React.useMemo(() => [
6264
6254
  {
6265
6255
  id: "simple",
@@ -6366,7 +6356,6 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6366
6356
  // - For remote backend, registry is automatically managed by RemoteGraphRunner
6367
6357
  if (backendKind === "local") {
6368
6358
  if (r) {
6369
- setRegistry(r);
6370
6359
  wb.setRegistry(r);
6371
6360
  }
6372
6361
  }
@@ -6382,15 +6371,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6382
6371
  runAutoLayout();
6383
6372
  setExampleState(key);
6384
6373
  onExampleChange?.(key);
6385
- }, [
6386
- runner,
6387
- wb,
6388
- onExampleChange,
6389
- runAutoLayout,
6390
- examples,
6391
- setRegistry,
6392
- backendKind,
6393
- ]);
6374
+ }, [runner, wb, onExampleChange, runAutoLayout, examples, backendKind]);
6394
6375
  const download$1 = React.useCallback(async () => {
6395
6376
  try {
6396
6377
  await download(wb, runner);
@@ -6450,8 +6431,6 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6450
6431
  return () => off();
6451
6432
  }, [runner, backendKind]);
6452
6433
  React.useEffect(() => {
6453
- if (!engine)
6454
- return;
6455
6434
  if (isGraphRunning)
6456
6435
  return;
6457
6436
  // Only auto-launch for local backend; require explicit Start for remote
@@ -6460,12 +6439,12 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6460
6439
  if (!wb.def.nodes || wb.def.nodes.length === 0)
6461
6440
  return;
6462
6441
  try {
6463
- runner.launch(wb.def, { engine: engine });
6442
+ runner.launch(wb.def, { runMode });
6464
6443
  }
6465
6444
  catch {
6466
6445
  // ignore
6467
6446
  }
6468
- }, [engine, runner, isGraphRunning, wb, backendKind]);
6447
+ }, [runMode, runner, isGraphRunning, wb, backendKind]);
6469
6448
  const baseSetInput = React.useCallback((handle, raw) => {
6470
6449
  if (!selectedNodeId)
6471
6450
  return;
@@ -6561,11 +6540,11 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6561
6540
  return overrides.setInput(baseSetInput, {
6562
6541
  runner,
6563
6542
  selectedNodeId,
6564
- registry,
6543
+ registry: wb.registry,
6565
6544
  });
6566
6545
  }
6567
6546
  return baseSetInput;
6568
- }, [overrides, baseSetInput, runner, selectedNodeId, registry]);
6547
+ }, [overrides, baseSetInput, runner, selectedNodeId, wb.registry]);
6569
6548
  const baseToString = React.useCallback((typeId, value) => {
6570
6549
  if (value === undefined || value === null)
6571
6550
  return "";
@@ -6573,7 +6552,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6573
6552
  if (sparkGraph.isTypedOutput(value)) {
6574
6553
  return baseToString(sparkGraph.getTypedOutputTypeId(value), sparkGraph.getTypedOutputValue(value));
6575
6554
  }
6576
- const pre = preformatValueForDisplay(typeId, value, registry);
6555
+ const pre = preformatValueForDisplay(typeId, value, wb.registry);
6577
6556
  if (pre !== undefined)
6578
6557
  return pre;
6579
6558
  if (typeof value === "object" &&
@@ -6589,7 +6568,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6589
6568
  }
6590
6569
  if (typeId && typeId.startsWith("enum:")) {
6591
6570
  const n = Number(value);
6592
- const label = registry.enums.get(typeId)?.valueToLabel.get(n);
6571
+ const label = wb.registry.enums.get(typeId)?.valueToLabel.get(n);
6593
6572
  return label ?? String(n);
6594
6573
  }
6595
6574
  const round4 = (n) => Math.round(Number(n) * 10000) / 10000;
@@ -6619,54 +6598,34 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
6619
6598
  return String(rounded);
6620
6599
  }
6621
6600
  return String(value);
6622
- }, [registry]);
6601
+ }, [wb.registry]);
6623
6602
  const baseToElement = React.useCallback((typeId, value) => {
6624
6603
  return (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: baseToString(typeId, value) }));
6625
6604
  }, [baseToString]);
6626
6605
  const toString = React.useMemo(() => {
6627
6606
  if (overrides?.toString)
6628
- return overrides.toString(baseToString, { registry });
6607
+ return overrides.toString(baseToString, { registry: wb.registry });
6629
6608
  return baseToString;
6630
- }, [overrides, baseToString, registry]);
6609
+ }, [overrides, baseToString, wb.registry]);
6631
6610
  // Optional: toElement (not currently consumed by core UI)
6632
6611
  // Consumers can access it by passing through their own node renderers.
6633
6612
  const toElement = React.useMemo(() => {
6634
6613
  if (overrides?.toElement)
6635
- return overrides.toElement(baseToElement, { registry });
6614
+ return overrides.toElement(baseToElement, { registry: wb.registry });
6636
6615
  return baseToElement;
6637
- }, [overrides, baseToElement, registry]);
6638
- return (jsxRuntime.jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxRuntime.jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [isGraphRunning ? (jsxRuntime.jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", engineKind] })) : (jsxRuntime.jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxRuntime.jsxs("span", { className: "ml-2 flex items-center gap-1 text-xs", title: transportStatus.kind || undefined, children: [transportStatus.state === "local" && (jsxRuntime.jsx(react$1.PlugsConnectedIcon, { size: 14, className: "text-gray-500" })), transportStatus.state === "connecting" && (jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 14, className: "text-amber-600 animate-pulse" })), transportStatus.state === "connected" && (jsxRuntime.jsx(react$1.WifiHighIcon, { size: 14, className: "text-green-600" })), transportStatus.state === "disconnected" && (jsxRuntime.jsx(react$1.WifiSlashIcon, { size: 14, className: "text-red-600" })), transportStatus.state === "retrying" && (jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 14, className: "text-amber-700 animate-pulse" }))] }), jsxRuntime.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: [jsxRuntime.jsx("option", { value: "", children: "Select Example\u2026" }), examples.map((ex) => (jsxRuntime.jsx("option", { value: ex.id, children: ex.label }, ex.id)))] }), jsxRuntime.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: [jsxRuntime.jsx("option", { value: "local", children: "Local" }), jsxRuntime.jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsxRuntime.jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsxRuntime.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 && (jsxRuntime.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) })), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: engineKind ?? engine ?? "", onChange: async (e) => {
6639
- const kind = e.target.value || undefined;
6640
- const currentEngine = runner.getRunningEngine();
6641
- // If engine is running and user selected a different engine, switch it
6642
- if (runner.isRunning() &&
6643
- currentEngine &&
6644
- kind &&
6645
- kind !== currentEngine) {
6646
- try {
6647
- await runner.switchEngine({
6648
- engine: kind,
6649
- batched: { flushIntervalMs: 0 },
6650
- hybrid: { windowMs: 250, batchThreshold: 3 },
6651
- });
6652
- onEngineChange?.(kind);
6653
- }
6654
- catch (err) {
6655
- const message = err instanceof Error ? err.message : String(err);
6656
- alert(`Failed to switch engine: ${message}`);
6657
- // Reset dropdown to current engine
6658
- e.target.value = currentEngine;
6659
- }
6660
- }
6661
- else {
6662
- // Normal change when not running
6663
- onEngineChange?.(kind);
6616
+ }, [overrides, baseToElement, wb.registry]);
6617
+ return (jsxRuntime.jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxRuntime.jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [isGraphRunning ? (jsxRuntime.jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", runMode === "manual" ? "Manual" : "Auto"] })) : (jsxRuntime.jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxRuntime.jsxs("span", { className: "ml-2 flex items-center gap-1 text-xs", title: transportStatus.kind || undefined, children: [transportStatus.state === "local" && (jsxRuntime.jsx(react$1.PlugsConnectedIcon, { size: 14, className: "text-gray-500" })), transportStatus.state === "connecting" && (jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 14, className: "text-amber-600 animate-pulse" })), transportStatus.state === "connected" && (jsxRuntime.jsx(react$1.WifiHighIcon, { size: 14, className: "text-green-600" })), transportStatus.state === "disconnected" && (jsxRuntime.jsx(react$1.WifiSlashIcon, { size: 14, className: "text-red-600" })), transportStatus.state === "retrying" && (jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 14, className: "text-amber-700 animate-pulse" }))] }), jsxRuntime.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: [jsxRuntime.jsx("option", { value: "", children: "Select Example\u2026" }), examples.map((ex) => (jsxRuntime.jsx("option", { value: ex.id, children: ex.label }, ex.id)))] }), jsxRuntime.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: [jsxRuntime.jsx("option", { value: "local", children: "Local" }), jsxRuntime.jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsxRuntime.jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsxRuntime.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 && (jsxRuntime.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) })), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: runMode, onChange: async (e) => {
6618
+ const mode = e.target.value;
6619
+ if (mode !== runMode) {
6620
+ await setRunMode(mode);
6664
6621
  }
6665
- }, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }), engineKind === "step" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
6622
+ }, disabled: isGraphRunning, title: isGraphRunning
6623
+ ? "Stop before switching run mode"
6624
+ : "Select run mode", children: [jsxRuntime.jsx("option", { value: "manual", children: "Manual" }), jsxRuntime.jsx("option", { value: "auto", children: "Auto" })] }), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
6666
6625
  await downloadCanvasThumbnail(canvasContainerRef.current);
6667
6626
  }, title: "Download Flow Thumbnail (SVG)", children: jsxRuntime.jsx(react$1.ImageIcon, { size: 24 }) }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx(react$1.BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx(react$1.ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", ref: canvasContainerRef, children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
6668
6627
  }
6669
- function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
6628
+ function WorkbenchStudio({ example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
6670
6629
  const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
6671
6630
  const [wb] = React.useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
6672
6631
  // Store previous runner for cleanup
@@ -6736,7 +6695,7 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
6736
6695
  runner.dispose();
6737
6696
  onBackendKindChange(v);
6738
6697
  }, [isGraphRunning]);
6739
- return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, uiVersion: uiVersion, children: jsxRuntime.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 }) }));
6698
+ return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, overrides: overrides, uiVersion: uiVersion, children: jsxRuntime.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 }) }));
6740
6699
  }
6741
6700
 
6742
6701
  exports.AbstractWorkbench = AbstractWorkbench;