@bian-womp/spark-workbench 0.2.94 → 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 (55) hide show
  1. package/lib/cjs/index.cjs +204 -240
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/examples/reactflow/App.d.ts.map +1 -1
  4. package/lib/cjs/src/index.d.ts +1 -0
  5. package/lib/cjs/src/index.d.ts.map +1 -1
  6. package/lib/cjs/src/misc/DefaultNodeHeader.d.ts +1 -2
  7. package/lib/cjs/src/misc/DefaultNodeHeader.d.ts.map +1 -1
  8. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  9. package/lib/cjs/src/misc/WorkbenchStudio.d.ts +1 -3
  10. package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
  11. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +8 -6
  12. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  13. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  14. package/lib/cjs/src/misc/context-menu/ContextMenuHandlers.d.ts +6 -2
  15. package/lib/cjs/src/misc/context-menu/ContextMenuHandlers.d.ts.map +1 -1
  16. package/lib/cjs/src/misc/context-menu/ContextMenuHelpers.d.ts +1 -1
  17. package/lib/cjs/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -1
  18. package/lib/cjs/src/misc/context-menu/NodeContextMenu.d.ts +1 -1
  19. package/lib/cjs/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
  20. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +9 -7
  21. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  22. package/lib/cjs/src/runtime/IGraphRunner.d.ts +18 -7
  23. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  24. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +7 -4
  25. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  26. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +11 -9
  27. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  28. package/lib/esm/index.js +207 -243
  29. package/lib/esm/index.js.map +1 -1
  30. package/lib/esm/src/examples/reactflow/App.d.ts.map +1 -1
  31. package/lib/esm/src/index.d.ts +1 -0
  32. package/lib/esm/src/index.d.ts.map +1 -1
  33. package/lib/esm/src/misc/DefaultNodeHeader.d.ts +1 -2
  34. package/lib/esm/src/misc/DefaultNodeHeader.d.ts.map +1 -1
  35. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  36. package/lib/esm/src/misc/WorkbenchStudio.d.ts +1 -3
  37. package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
  38. package/lib/esm/src/misc/context/WorkbenchContext.d.ts +8 -6
  39. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  40. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  41. package/lib/esm/src/misc/context-menu/ContextMenuHandlers.d.ts +6 -2
  42. package/lib/esm/src/misc/context-menu/ContextMenuHandlers.d.ts.map +1 -1
  43. package/lib/esm/src/misc/context-menu/ContextMenuHelpers.d.ts +1 -1
  44. package/lib/esm/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -1
  45. package/lib/esm/src/misc/context-menu/NodeContextMenu.d.ts +1 -1
  46. package/lib/esm/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
  47. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +9 -7
  48. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  49. package/lib/esm/src/runtime/IGraphRunner.d.ts +18 -7
  50. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  51. package/lib/esm/src/runtime/LocalGraphRunner.d.ts +7 -4
  52. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  53. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +11 -9
  54. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  55. package/package.json +4 -4
package/lib/esm/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { generateId, createSimpleGraphRegistry, GraphBuilder, getTypedOutputValue, isTypedOutput, getInputTypeId, createEngine, StepEngine, PullEngine, BatchedEngine, getTypedOutputTypeId, isInputPrivate, offsetImportedPositions, 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() {
@@ -925,31 +925,21 @@ class AbstractGraphRunner {
925
925
  this.engine.dispose();
926
926
  this.engine = undefined;
927
927
  // Emit status but keep runtime alive
928
- if (this.runningKind) {
929
- this.runningKind = undefined;
930
- this.emit("status", { running: false, engine: undefined });
931
- }
932
- }
933
- async switchEngine(opts) {
934
- if (!this.engine || !this.runtime) {
935
- throw new Error("No engine running to switch from");
936
- }
937
- // Wait for current engine to be idle
938
- await this.whenIdle();
939
- // Capture current state
940
- const currentInputs = { ...this.stagedInputs };
941
- // Stop current engine
942
- this.stop();
943
- // Ensure runtime is in a clean state (resumed)
944
- this.runtime.resume();
945
- // Create and launch new engine (to be implemented by subclasses)
946
- await this.createAndLaunchEngine(opts);
947
- // Re-apply staged inputs to new engine using runner's setInputs method
948
- // This ensures consistency and proper handling of staged inputs
949
- for (const [nodeId, map] of Object.entries(currentInputs)) {
950
- await this.setInputs(nodeId, map);
928
+ if (this.runMode) {
929
+ this.runMode = undefined;
930
+ this.emit("status", { running: false, runMode: undefined });
951
931
  }
952
932
  }
933
+ setRunMode(runMode) {
934
+ if (!this.engine) {
935
+ throw new Error("Cannot set run mode: engine not running");
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 });
942
+ }
953
943
  getInputDefaults(def) {
954
944
  const out = {};
955
945
  for (const n of def.nodes) {
@@ -978,16 +968,16 @@ class AbstractGraphRunner {
978
968
  this.engine = undefined;
979
969
  this.runtime?.dispose();
980
970
  this.runtime = undefined;
981
- if (this.runningKind) {
982
- this.runningKind = undefined;
983
- this.emit("status", { running: false, engine: undefined });
971
+ if (this.runMode) {
972
+ this.runMode = undefined;
973
+ this.emit("status", { running: false, runMode: undefined });
984
974
  }
985
975
  }
986
976
  isRunning() {
987
977
  return !!this.engine;
988
978
  }
989
- getRunningEngine() {
990
- return this.runningKind;
979
+ getRunMode() {
980
+ return this.runMode;
991
981
  }
992
982
  // Optional undo/redo support
993
983
  async undo() {
@@ -1082,7 +1072,7 @@ class LocalGraphRunner extends AbstractGraphRunner {
1082
1072
  if (!this.runtime)
1083
1073
  throw new Error("Runtime not built");
1084
1074
  // Use shared engine factory
1085
- this.engine = createEngine(this.runtime, opts);
1075
+ this.engine = new UnifiedEngine(this.runtime, opts?.runMode);
1086
1076
  if (!this.engine)
1087
1077
  throw new Error("Failed to create engine");
1088
1078
  this.engine.on("value", (e) => this.emit("value", e));
@@ -1090,36 +1080,34 @@ class LocalGraphRunner extends AbstractGraphRunner {
1090
1080
  this.engine.on("invalidate", (e) => this.emit("invalidate", e));
1091
1081
  this.engine.on("stats", (e) => this.emit("stats", e));
1092
1082
  this.engine.launch(opts?.invalidate);
1093
- this.runningKind = opts?.engine ?? "push";
1094
- this.emit("status", { running: true, engine: this.runningKind });
1083
+ this.runMode = opts?.runMode ?? "manual";
1084
+ this.emit("status", { running: true, runMode: this.runMode });
1095
1085
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
1096
1086
  this.engine.setInputs(nodeId, map);
1097
1087
  }
1098
1088
  }
1099
- async step() {
1100
- const eng = this.engine;
1101
- if (eng && eng instanceof StepEngine)
1102
- await eng.step();
1089
+ async computeNode(nodeId, options) {
1090
+ if (this.engine)
1091
+ await this.engine.computeNode(nodeId, options);
1103
1092
  }
1104
- async computeNode(nodeId) {
1105
- const eng = this.engine;
1106
- if (eng && eng instanceof PullEngine)
1107
- await eng.computeNode(nodeId);
1093
+ async runFromHere(nodeId) {
1094
+ if (this.engine)
1095
+ await this.engine.runFromHere(nodeId);
1108
1096
  }
1109
- async flush() {
1110
- const eng = this.engine;
1111
- if (eng && eng instanceof BatchedEngine)
1112
- await eng.flush();
1097
+ cancelNodeRuns(nodeIds) {
1098
+ if (this.engine) {
1099
+ this.engine.cancelNodeRuns(nodeIds);
1100
+ }
1113
1101
  }
1114
1102
  getOutputs(def) {
1115
1103
  const out = {};
1116
- if (!this.runtime)
1104
+ if (!this.engine)
1117
1105
  return out;
1118
1106
  for (const n of def.nodes) {
1119
1107
  const desc = this.registry.nodes.get(n.typeId);
1120
1108
  const handles = Object.keys(desc?.outputs ?? {});
1121
1109
  for (const h of handles) {
1122
- const v = this.runtime.getOutput(n.nodeId, h);
1110
+ const v = this.engine.getOutput(n.nodeId, h);
1123
1111
  if (v !== undefined) {
1124
1112
  if (!out[n.nodeId])
1125
1113
  out[n.nodeId] = {};
@@ -1152,19 +1140,8 @@ class LocalGraphRunner extends AbstractGraphRunner {
1152
1140
  return out;
1153
1141
  }
1154
1142
  triggerExternal(nodeId, event, options) {
1155
- // Handle dry option: pause runtime before triggering, resume after
1156
- const wasPaused = this.runtime?.isPaused() ?? false;
1157
- if (options?.dry && !wasPaused && this.runtime) {
1158
- this.runtime.pause();
1159
- }
1160
- try {
1161
- this.engine?.triggerExternal(nodeId, event);
1162
- }
1163
- finally {
1164
- if (options?.dry && !wasPaused && this.runtime) {
1165
- this.runtime.resume();
1166
- }
1167
- }
1143
+ // Engine handles dry option via AbstractEngine
1144
+ this.engine?.triggerExternal(nodeId, event, options);
1168
1145
  }
1169
1146
  // Batch update multiple inputs on a node and trigger a single run
1170
1147
  setInputs(nodeId, inputs, options) {
@@ -1180,40 +1157,23 @@ class LocalGraphRunner extends AbstractGraphRunner {
1180
1157
  this.stagedInputs[nodeId][handle] = value;
1181
1158
  }
1182
1159
  }
1183
- // Handle dry option: pause runtime before setting inputs, resume after
1184
- const wasPaused = this.runtime?.isPaused() ?? false;
1185
- if (options?.dry && !wasPaused && this.runtime) {
1186
- this.runtime.pause();
1187
- }
1188
- try {
1189
- if (this.engine) {
1190
- this.engine.setInputs(nodeId, inputs);
1191
- }
1192
- else {
1193
- // Not running: emit a single synthetic value event per handle; UI will coalesce
1194
- console.warn("Engine does not exists");
1195
- for (const [handle, value] of Object.entries(inputs)) {
1196
- this.emit("value", { nodeId, handle, value, io: "input" });
1197
- }
1198
- }
1160
+ if (this.engine) {
1161
+ // Engine handles dry option via AbstractEngine
1162
+ this.engine.setInputs(nodeId, inputs, options);
1199
1163
  }
1200
- finally {
1201
- if (options?.dry && !wasPaused && this.runtime) {
1202
- 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" });
1203
1170
  }
1204
1171
  }
1205
1172
  }
1206
1173
  copyOutputs(fromNodeId, toNodeId, options) {
1207
- if (!this.runtime)
1208
- return;
1209
- // Get outputs from source node
1210
- const fromNode = this.runtime.getNodeData(fromNodeId);
1211
- if (!fromNode?.outputs)
1212
- return;
1213
- // Copy outputs to target node using hydrate
1214
- // hydrate already pauses internally, so we don't need to handle dry option here
1215
- // reemit: !options?.dry means don't propagate downstream if dry mode
1216
- this.runtime.hydrate({ outputs: { [toNodeId]: { ...fromNode.outputs } } }, { reemit: !options?.dry });
1174
+ if (this.engine) {
1175
+ this.engine.copyOutputs(fromNodeId, toNodeId, options);
1176
+ }
1217
1177
  }
1218
1178
  async snapshotFull() {
1219
1179
  const def = undefined; // UI will supply def/positions on download for local
@@ -1314,7 +1274,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1314
1274
  }
1315
1275
  this.registryFetching = true;
1316
1276
  try {
1317
- const desc = await client.describeRegistry();
1277
+ const desc = await client.api.describeRegistry();
1318
1278
  // Register types
1319
1279
  for (const t of desc.types) {
1320
1280
  if (t.options) {
@@ -1416,7 +1376,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1416
1376
  }
1417
1377
  }
1418
1378
  /**
1419
- * Build RuntimeApiClient config from RemoteExecutionBackend config.
1379
+ * Build RemoteRuntimeClient config from RemoteExecutionBackend config.
1420
1380
  */
1421
1381
  buildClientConfig(backend) {
1422
1382
  if (backend.kind === "remote-http") {
@@ -1439,7 +1399,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1439
1399
  */
1440
1400
  setupClientSubscriptions(client) {
1441
1401
  // Subscribe to transport status changes
1442
- // Convert RuntimeApiClient.TransportStatus to IGraphRunner.TransportStatus
1402
+ // Convert RemoteRuntimeClient.TransportStatus to IGraphRunner.TransportStatus
1443
1403
  // Only emit status if it matches this runner's ID
1444
1404
  this.transportStatusUnsubscribe = client.onTransportStatus((status) => {
1445
1405
  if (status.runnerId && status.runnerId !== this.runnerId)
@@ -1489,7 +1449,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1489
1449
  }
1490
1450
  };
1491
1451
  // Create client with wrapped custom event handler
1492
- const client = new RuntimeApiClient(clientConfig, {
1452
+ const client = new RemoteRuntimeClient(clientConfig, {
1493
1453
  onCustomEvent: wrappedOnCustomEvent,
1494
1454
  runnerId: this.runnerId,
1495
1455
  });
@@ -1539,7 +1499,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1539
1499
  // Auto-handle registry-changed invalidations from remote
1540
1500
  // We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
1541
1501
  this.ensureClient().then(async (client) => {
1542
- const eng = client.getEngine();
1502
+ const eng = client.engine;
1543
1503
  if (!this.listenersBound) {
1544
1504
  eng.on("invalidate", async (e) => {
1545
1505
  if (e.reason === "registry-changed") {
@@ -1603,7 +1563,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1603
1563
  // Remote: forward update and await completion
1604
1564
  const client = await this.ensureClient();
1605
1565
  try {
1606
- await client.update(def, options);
1566
+ await client.api.update(def, options);
1607
1567
  this.emit("invalidate", { reason: "graph-updated" });
1608
1568
  this.lastDef = def;
1609
1569
  }
@@ -1616,13 +1576,13 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1616
1576
  super.launch(def, opts);
1617
1577
  // Remote: build remotely then launch
1618
1578
  this.ensureClient().then(async (client) => {
1619
- await client.build(def);
1579
+ await client.api.build(def);
1620
1580
  // Signal UI after remote build as well
1621
1581
  this.emit("invalidate", { reason: "graph-built" });
1622
1582
  this.lastDef = def;
1623
1583
  // Hydrate current remote inputs/outputs (including defaults) into cache
1624
1584
  try {
1625
- const snap = await client.snapshot();
1585
+ const snap = await client.api.snapshot();
1626
1586
  for (const [nodeId, map] of Object.entries(snap.inputs || {})) {
1627
1587
  for (const [handle, value] of Object.entries(map || {})) {
1628
1588
  this.valueCache.set(`${nodeId}.${handle}`, {
@@ -1651,9 +1611,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1651
1611
  async createAndLaunchEngine(opts) {
1652
1612
  const client = await this.ensureClient();
1653
1613
  // Configure and launch engine on the backend
1654
- await client.launch(opts);
1614
+ await client.api.launch(opts);
1655
1615
  // Get the remote engine proxy and wire up event listeners
1656
- const eng = client.getEngine();
1616
+ const eng = client.engine;
1657
1617
  if (!this.listenersBound) {
1658
1618
  eng.on("value", (e) => {
1659
1619
  this.valueCache.set(`${e.nodeId}.${e.handle}`, {
@@ -1669,11 +1629,11 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1669
1629
  this.listenersBound = true;
1670
1630
  }
1671
1631
  this.engine = eng;
1672
- this.runningKind = opts?.engine ?? "push";
1673
- this.emit("status", { running: true, engine: this.runningKind });
1632
+ this.runMode = opts?.runMode ?? "manual";
1633
+ this.emit("status", { running: true, runMode: this.runMode });
1674
1634
  // Re-apply staged inputs using client.setInputs for consistency
1675
1635
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
1676
- await eng.setInputs(nodeId, map).catch(() => {
1636
+ await eng.setInputs(nodeId, map, undefined).catch(() => {
1677
1637
  // Ignore errors during launch - inputs will be set when user calls setInputs
1678
1638
  });
1679
1639
  }
@@ -1696,45 +1656,27 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1696
1656
  await this.createAndLaunchEngine(opts);
1697
1657
  });
1698
1658
  }
1699
- async switchEngine(opts) {
1659
+ setRunMode(runMode) {
1700
1660
  if (!this.engine) {
1701
- throw new Error("No engine running to switch from");
1702
- }
1703
- // Wait for current engine to be idle
1704
- await this.whenIdle();
1705
- // Capture current state
1706
- const currentInputs = { ...this.stagedInputs };
1707
- // For remote runners, we cannot call this.stop() because it sends a Dispose
1708
- // command that destroys the graphRuntime on the backend. Instead, we rely on
1709
- // the backend's launch() method to dispose the old engine and create a new one.
1710
- // Reconfigure engine on the backend (this will dispose old engine and create new one)
1711
- const client = await this.ensureClient();
1712
- await client.launch(opts);
1713
- // Get the remote engine proxy (should be the same RemoteEngine instance)
1714
- const eng = client.getEngine();
1715
- // Update local state to reflect new engine kind
1716
- // Note: The RemoteEngine instance itself doesn't change, but the backend engine does
1717
- this.engine = eng;
1718
- this.runningKind = opts?.engine ?? "push";
1719
- this.emit("status", { running: true, engine: this.runningKind });
1720
- // Re-apply staged inputs using client.setInputs for consistency
1721
- for (const [nodeId, map] of Object.entries(currentInputs)) {
1722
- await eng.setInputs(nodeId, map).catch(() => {
1723
- // Ignore errors during engine switch - inputs will be set when user calls setInputs
1724
- });
1661
+ throw new Error("Cannot set run mode: engine not running");
1725
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 });
1726
1668
  }
1727
- async step() {
1669
+ async computeNode(nodeId, options) {
1728
1670
  const client = await this.ensureClient();
1729
- await client.step();
1671
+ await client.engine.computeNode(nodeId, options);
1730
1672
  }
1731
- async computeNode(nodeId) {
1673
+ async runFromHere(nodeId) {
1732
1674
  const client = await this.ensureClient();
1733
- await client.computeNode(nodeId);
1675
+ await client.engine.runFromHere(nodeId);
1734
1676
  }
1735
- async flush() {
1677
+ async cancelNodeRuns(nodeIds) {
1736
1678
  const client = await this.ensureClient();
1737
- await client.flush();
1679
+ await client.engine.cancelNodeRuns(nodeIds);
1738
1680
  }
1739
1681
  async setInputs(nodeId, inputs, options) {
1740
1682
  // Update staged inputs (for getInputs to work correctly)
@@ -1750,7 +1692,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1750
1692
  }
1751
1693
  try {
1752
1694
  const client = await this.ensureClient();
1753
- await client.getEngine()?.setInputs(nodeId, inputs, options);
1695
+ await client.engine.setInputs(nodeId, inputs, options);
1754
1696
  }
1755
1697
  catch (err) {
1756
1698
  // Emit synthetic events if connection fails
@@ -1762,20 +1704,20 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1762
1704
  }
1763
1705
  async copyOutputs(fromNodeId, toNodeId, options) {
1764
1706
  const client = await this.ensureClient();
1765
- await client.getEngine()?.copyOutputs(fromNodeId, toNodeId, options);
1707
+ await client.engine.copyOutputs(fromNodeId, toNodeId, options);
1766
1708
  }
1767
1709
  async triggerExternal(nodeId, event, options) {
1768
1710
  const client = await this.ensureClient();
1769
- await client.getEngine()?.triggerExternal(nodeId, event, options);
1711
+ await client.engine.triggerExternal(nodeId, event, options);
1770
1712
  }
1771
1713
  async setViewport(viewport) {
1772
1714
  const client = await this.ensureClient();
1773
- await client.setViewport(viewport);
1715
+ await client.api.setViewport(viewport);
1774
1716
  }
1775
1717
  async coerce(from, to, value) {
1776
1718
  const client = await this.ensureClient();
1777
1719
  try {
1778
- return await client.coerce(from, to, value);
1720
+ return await client.api.coerce(from, to, value);
1779
1721
  }
1780
1722
  catch {
1781
1723
  return value;
@@ -1783,12 +1725,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1783
1725
  }
1784
1726
  async setExtData(data) {
1785
1727
  const client = await this.ensureClient();
1786
- await client.setExtData(data);
1728
+ await client.api.setExtData(data);
1787
1729
  }
1788
1730
  async commit(reason) {
1789
1731
  const client = await this.ensureClient();
1790
1732
  try {
1791
- const history = await client.commit(reason);
1733
+ const history = await client.api.commit(reason);
1792
1734
  return history;
1793
1735
  }
1794
1736
  catch (err) {
@@ -1799,7 +1741,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1799
1741
  async undo() {
1800
1742
  const client = await this.ensureClient();
1801
1743
  try {
1802
- return await client.undo();
1744
+ return await client.api.undo();
1803
1745
  }
1804
1746
  catch {
1805
1747
  return false;
@@ -1808,7 +1750,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1808
1750
  async redo() {
1809
1751
  const client = await this.ensureClient();
1810
1752
  try {
1811
- return await client.redo();
1753
+ return await client.api.redo();
1812
1754
  }
1813
1755
  catch {
1814
1756
  return false;
@@ -1817,7 +1759,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1817
1759
  async snapshotFull() {
1818
1760
  const client = await this.ensureClient();
1819
1761
  try {
1820
- return await client.snapshotFull();
1762
+ return await client.api.snapshotFull();
1821
1763
  }
1822
1764
  catch {
1823
1765
  return { def: undefined, environment: {}, inputs: {}, outputs: {} };
@@ -1828,7 +1770,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1828
1770
  this.hydrateValueCache(payload, { dry: options?.dry });
1829
1771
  // Then sync with backend
1830
1772
  const client = await this.ensureClient();
1831
- await client.applySnapshotFull(payload, { skipBuild: options?.skipBuild });
1773
+ await client.api.applySnapshotFull(payload, {
1774
+ skipBuild: options?.skipBuild,
1775
+ });
1832
1776
  }
1833
1777
  /**
1834
1778
  * Hydrates the local valueCache from a snapshot and emits value events.
@@ -1872,12 +1816,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1872
1816
  async setEnvironment(env, opts) {
1873
1817
  // Use client if available, otherwise ensure client and then set environment
1874
1818
  if (this.client) {
1875
- await this.client.setEnvironment(env, opts);
1819
+ await this.client.api.setEnvironment(env, opts);
1876
1820
  }
1877
1821
  else {
1878
1822
  try {
1879
1823
  const client = await this.ensureClient();
1880
- await client.setEnvironment(env, opts);
1824
+ await client.api.setEnvironment(env, opts);
1881
1825
  }
1882
1826
  catch {
1883
1827
  // Silently fail if connection not available
@@ -1885,7 +1829,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1885
1829
  }
1886
1830
  }
1887
1831
  getEnvironment() {
1888
- // Interface requires sync return, but RuntimeApiClient.getEnvironment() is async.
1832
+ // Interface requires sync return, but RemoteRuntimeClient.getEnvironment() is async.
1889
1833
  // Returns undefined synchronously; callers needing the actual value should:
1890
1834
  // - Use snapshotFull() which includes environment
1891
1835
  // - Call client.getEnvironment() directly if they have access to the client
@@ -3655,6 +3599,7 @@ function computeInvalidatedFromMetadata(metadata) {
3655
3599
  function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
3656
3600
  const [nodeStatus, setNodeStatus] = useState({});
3657
3601
  const [edgeStatus, setEdgeStatus] = useState({});
3602
+ const [runMode, setRunModeState] = useState("manual");
3658
3603
  const [events, setEvents] = useState([]);
3659
3604
  const clearEvents = useCallback(() => setEvents([]), []);
3660
3605
  const [systemErrors, setSystemErrors] = useState([]);
@@ -3719,6 +3664,24 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
3719
3664
  const graphUiTick = useWorkbenchGraphUiTick(wb);
3720
3665
  const versionTick = useWorkbenchVersionTick(runner);
3721
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]);
3722
3685
  // Def and IO values
3723
3686
  const inputsMap = useMemo(() => runner.getInputs(wb.def), [runner, wb, wb.def, valuesTick]);
3724
3687
  const inputDefaultsMap = useMemo(() => runner.getInputDefaults(wb.def), [runner, wb, wb.def, valuesTick]);
@@ -4412,16 +4375,37 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4412
4375
  };
4413
4376
  }, [runner, wb]);
4414
4377
  const isRunning = useCallback(() => runner.isRunning(), [runner]);
4415
- const engineKind = useCallback(() => runner.getRunningEngine(), [runner]);
4416
- const start = useCallback((engine) => {
4417
- try {
4418
- runner.launch(wb.def, { engine });
4419
- }
4420
- catch { }
4421
- }, [runner, wb]);
4378
+ const getRunMode = useCallback(() => runner.getRunMode(), [runner]);
4422
4379
  const stop = useCallback(() => runner.stop(), [runner]);
4423
- const step = useCallback(() => runner.step(), [runner]);
4424
- 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]);
4425
4409
  const validationByNode = useMemo(() => {
4426
4410
  const inputs = {};
4427
4411
  const outputs = {};
@@ -4513,11 +4497,13 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4513
4497
  removeRegistryError,
4514
4498
  removeInputValidationError,
4515
4499
  isRunning,
4516
- engineKind,
4517
- start,
4500
+ getRunMode,
4518
4501
  stop,
4519
- step,
4520
- flush,
4502
+ runMode,
4503
+ setRunMode,
4504
+ runNode: runNodeAction,
4505
+ runFromHere: runFromHereAction,
4506
+ abortNode: abortNodeAction,
4521
4507
  runAutoLayout,
4522
4508
  updateEdgeType,
4523
4509
  triggerExternal,
@@ -4553,11 +4539,13 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4553
4539
  events,
4554
4540
  clearEvents,
4555
4541
  isRunning,
4556
- engineKind,
4557
- start,
4542
+ getRunMode,
4558
4543
  stop,
4559
- step,
4560
- flush,
4544
+ runMode,
4545
+ setRunMode,
4546
+ runNodeAction,
4547
+ runFromHereAction,
4548
+ abortNodeAction,
4561
4549
  runAutoLayout,
4562
4550
  wb,
4563
4551
  runner,
@@ -4575,7 +4563,7 @@ function IssueBadge({ level, title, size = 12, className, }) {
4575
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" })) }));
4576
4564
  }
4577
4565
 
4578
- function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate, }) {
4566
+ function DefaultNodeHeader({ id, typeId, validation, right, showId, }) {
4579
4567
  const ctx = useWorkbenchContext();
4580
4568
  const [isEditing, setIsEditing] = React.useState(false);
4581
4569
  const [editValue, setEditValue] = React.useState("");
@@ -4593,18 +4581,6 @@ function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate
4593
4581
  const desc = ctx.wb.registry.nodes.get(node.typeId);
4594
4582
  return desc?.displayName || node.typeId;
4595
4583
  }, [ctx, id, typeId]);
4596
- const handleInvalidate = React.useCallback(() => {
4597
- try {
4598
- if (onInvalidate)
4599
- return onInvalidate();
4600
- const kind = ctx.engineKind?.();
4601
- if (kind === "pull")
4602
- ctx.runner.computeNode(id);
4603
- else
4604
- ctx.triggerExternal?.(id, { type: "invalidate" });
4605
- }
4606
- catch { }
4607
- }, [ctx, id, onInvalidate]);
4608
4584
  const handleDoubleClick = React.useCallback((e) => {
4609
4585
  // Only allow editing if typeId is provided (enables renaming)
4610
4586
  if (!typeId)
@@ -4649,10 +4625,16 @@ function DefaultNodeHeader({ id, typeId, validation, right, showId, onInvalidate
4649
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: {
4650
4626
  maxHeight: NODE_HEADER_HEIGHT_PX,
4651
4627
  minHeight: NODE_HEADER_HEIGHT_PX,
4652
- }, 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) => {
4653
- e.stopPropagation();
4654
- handleInvalidate();
4655
- }, 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")
4656
4638
  ? "error"
4657
4639
  : "warning", size: 12, className: "w-3 h-3", title: validation.issues
4658
4640
  .map((v) => `${v.code}: ${v.message}`)
@@ -4806,7 +4788,7 @@ const DefaultEdge = React.memo(function DefaultEdge({ id, sourceX, sourceY, targ
4806
4788
  return (jsx(BaseEdge, { id: id, path: edgePath, style: style, markerEnd: markerEnd }));
4807
4789
  });
4808
4790
 
4809
- 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) {
4810
4792
  return {
4811
4793
  onDelete: () => {
4812
4794
  wb.removeNode(nodeId, { commit: true });
@@ -4826,13 +4808,24 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
4826
4808
  });
4827
4809
  onClose();
4828
4810
  },
4829
- onRunPull: async () => {
4830
- try {
4831
- await runner.computeNode(nodeId);
4811
+ onRunNode: runNode
4812
+ ? async () => {
4813
+ try {
4814
+ await runNode(nodeId);
4815
+ }
4816
+ catch { }
4817
+ onClose();
4832
4818
  }
4833
- catch { }
4834
- onClose();
4835
- },
4819
+ : undefined,
4820
+ onRunFromHere: runFromHere
4821
+ ? async () => {
4822
+ try {
4823
+ await runFromHere(nodeId);
4824
+ }
4825
+ catch { }
4826
+ onClose();
4827
+ }
4828
+ : undefined,
4836
4829
  onBake: async (handleId) => {
4837
4830
  const nodePosition = wb.getPositions()[nodeId] || { x: 0, y: 0 };
4838
4831
  const typeId = outputTypesMap?.[nodeId]?.[handleId];
@@ -5414,7 +5407,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, enab
5414
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" })) })] }));
5415
5408
  }
5416
5409
 
5417
- function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, enableKeyboardShortcuts = true, keyboardShortcuts = {
5410
+ function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, runMode, enableKeyboardShortcuts = true, keyboardShortcuts = {
5418
5411
  copy: "⌘/Ctrl + C",
5419
5412
  duplicate: "⌘/Ctrl + E",
5420
5413
  duplicateWithEdges: "⌘/Ctrl + Shift + E",
@@ -5457,7 +5450,7 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
5457
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) => {
5458
5451
  e.preventDefault();
5459
5452
  e.stopPropagation();
5460
- }, 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)))] }))] }));
5461
5454
  }
5462
5455
 
5463
5456
  function SelectionContextMenu({ open, clientPos, handlers, enableKeyboardShortcuts = true, keyboardShortcuts = {
@@ -5544,7 +5537,7 @@ function useKeyboardShortcutToast() {
5544
5537
  }
5545
5538
 
5546
5539
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
5547
- const { wb, 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();
5548
5541
  const nodeValidation = validationByNode;
5549
5542
  const edgeValidation = validationByEdge.errors;
5550
5543
  const [historyState, setHistoryState] = useState(wb.getHistory());
@@ -5984,7 +5977,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5984
5977
  };
5985
5978
  const baseHandlers = createNodeContextMenuHandlers(nodeAtMenu, wb, runner, wb.registry, outputsMap, outputTypesMap, onCloseNodeMenu, overrides?.getDefaultNodeSize, (data) => {
5986
5979
  storage.set(data);
5987
- });
5980
+ }, runNode, runFromHere);
5988
5981
  if (overrides?.getNodeContextMenuHandlers) {
5989
5982
  return overrides.getNodeContextMenuHandlers(wb, nodeAtMenu, baseHandlers);
5990
5983
  }
@@ -6002,7 +5995,6 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6002
5995
  overrides?.getNodeContextMenuHandlers,
6003
5996
  overrides?.getCopiedDataStorage,
6004
5997
  ]);
6005
- const canRunPull = useMemo(() => engineKind()?.toString() === "pull", [engineKind]);
6006
5998
  const bakeableOutputs = useMemo(() => {
6007
5999
  if (!nodeAtMenu)
6008
6000
  return [];
@@ -6207,15 +6199,15 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6207
6199
  ? { enableKeyboardShortcuts, keyboardShortcuts }
6208
6200
  : {}) })) : (jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
6209
6201
  nodeContextMenuHandlers &&
6210
- (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
6211
6203
  ? { enableKeyboardShortcuts, keyboardShortcuts }
6212
- : {}) })) : (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 &&
6213
6205
  selectionMenuPos &&
6214
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))] }));
6215
6207
  });
6216
6208
 
6217
- function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
6218
- const { wb, runner, 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();
6219
6211
  const [transportStatus, setTransportStatus] = useState({
6220
6212
  state: "local",
6221
6213
  });
@@ -6224,8 +6216,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6224
6216
  ? computeEffectiveHandles(selectedNode, wb.registry)
6225
6217
  : { inputs: {}, outputs: {}, inputDefaults: {} };
6226
6218
  const [exampleState, setExampleState] = useState(example ?? "");
6227
- const isGraphRunning = runner.isRunning();
6228
- const engineKind = runner.getRunningEngine();
6219
+ const isGraphRunning = isRunning();
6229
6220
  // Render Start/Stop button based on transport and runner state
6230
6221
  const renderStartStopButton = useCallback(() => {
6231
6222
  // Check if transport is connecting/retrying
@@ -6241,14 +6232,11 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6241
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" })] }));
6242
6233
  }
6243
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) => {
6244
- const kind = engine;
6245
- if (!kind)
6246
- return alert("Select an engine first.");
6247
6235
  if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
6248
6236
  return;
6249
6237
  try {
6250
6238
  runner.launch(wb.def, {
6251
- engine: kind,
6239
+ runMode,
6252
6240
  invalidate: evt.shiftKey,
6253
6241
  });
6254
6242
  }
@@ -6256,12 +6244,10 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6256
6244
  const message = err instanceof Error ? err.message : String(err);
6257
6245
  alert(message);
6258
6246
  }
6259
- }, disabled: !engine || !canControl, title: !engine
6260
- ? "Select an engine first"
6261
- : !canControl
6262
- ? "Waiting for connection"
6263
- : "Start engine", children: [jsx(PlayIcon, { size: 16, weight: "fill" }), jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6264
- }, [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]);
6265
6251
  const defaultExamples = useMemo(() => [
6266
6252
  {
6267
6253
  id: "simple",
@@ -6443,8 +6429,6 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6443
6429
  return () => off();
6444
6430
  }, [runner, backendKind]);
6445
6431
  useEffect(() => {
6446
- if (!engine)
6447
- return;
6448
6432
  if (isGraphRunning)
6449
6433
  return;
6450
6434
  // Only auto-launch for local backend; require explicit Start for remote
@@ -6453,12 +6437,12 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6453
6437
  if (!wb.def.nodes || wb.def.nodes.length === 0)
6454
6438
  return;
6455
6439
  try {
6456
- runner.launch(wb.def, { engine: engine });
6440
+ runner.launch(wb.def, { runMode });
6457
6441
  }
6458
6442
  catch {
6459
6443
  // ignore
6460
6444
  }
6461
- }, [engine, runner, isGraphRunning, wb, backendKind]);
6445
+ }, [runMode, runner, isGraphRunning, wb, backendKind]);
6462
6446
  const baseSetInput = useCallback((handle, raw) => {
6463
6447
  if (!selectedNodeId)
6464
6448
  return;
@@ -6628,38 +6612,18 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6628
6612
  return overrides.toElement(baseToElement, { registry: wb.registry });
6629
6613
  return baseToElement;
6630
6614
  }, [overrides, baseToElement, wb.registry]);
6631
- 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) => {
6632
- const kind = e.target.value || undefined;
6633
- const currentEngine = runner.getRunningEngine();
6634
- // If engine is running and user selected a different engine, switch it
6635
- if (runner.isRunning() &&
6636
- currentEngine &&
6637
- kind &&
6638
- kind !== currentEngine) {
6639
- try {
6640
- await runner.switchEngine({
6641
- engine: kind,
6642
- batched: { flushIntervalMs: 0 },
6643
- hybrid: { windowMs: 250, batchThreshold: 3 },
6644
- });
6645
- onEngineChange?.(kind);
6646
- }
6647
- catch (err) {
6648
- const message = err instanceof Error ? err.message : String(err);
6649
- alert(`Failed to switch engine: ${message}`);
6650
- // Reset dropdown to current engine
6651
- e.target.value = currentEngine;
6652
- }
6653
- }
6654
- else {
6655
- // Normal change when not running
6656
- onEngineChange?.(kind);
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);
6657
6619
  }
6658
- }, 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 () => {
6659
6623
  await downloadCanvasThumbnail(canvasContainerRef.current);
6660
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 })] })] }));
6661
6625
  }
6662
- 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, }) {
6663
6627
  const [registry, setRegistry] = useState(createSimpleGraphRegistry());
6664
6628
  const [wb] = useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
6665
6629
  // Store previous runner for cleanup
@@ -6729,7 +6693,7 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
6729
6693
  runner.dispose();
6730
6694
  onBackendKindChange(v);
6731
6695
  }, [isGraphRunning]);
6732
- return (jsx(WorkbenchProvider, { wb: wb, runner: runner, 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 }) }));
6733
6697
  }
6734
6698
 
6735
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 };