@bian-womp/spark-workbench 0.2.33 → 0.2.35

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 (37) hide show
  1. package/lib/cjs/index.cjs +226 -103
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
  4. package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
  5. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  6. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +1 -0
  7. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  8. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  9. package/lib/cjs/src/misc/mapping.d.ts +2 -0
  10. package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
  11. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +4 -2
  12. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/IGraphRunner.d.ts +3 -2
  14. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  15. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +3 -2
  16. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  17. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +3 -3
  18. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  19. package/lib/esm/index.js +226 -103
  20. package/lib/esm/index.js.map +1 -1
  21. package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
  22. package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
  23. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  24. package/lib/esm/src/misc/context/WorkbenchContext.d.ts +1 -0
  25. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  26. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  27. package/lib/esm/src/misc/mapping.d.ts +2 -0
  28. package/lib/esm/src/misc/mapping.d.ts.map +1 -1
  29. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +4 -2
  30. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  31. package/lib/esm/src/runtime/IGraphRunner.d.ts +3 -2
  32. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  33. package/lib/esm/src/runtime/LocalGraphRunner.d.ts +3 -2
  34. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  35. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +3 -3
  36. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  37. package/package.json +4 -4
package/lib/esm/index.js CHANGED
@@ -337,6 +337,7 @@ class AbstractGraphRunner {
337
337
  if (this.engine) {
338
338
  throw new Error("Engine already running. Stop the current engine first.");
339
339
  }
340
+ this.currentDef = def;
340
341
  }
341
342
  setInput(nodeId, handle, value) {
342
343
  if (!this.stagedInputs[nodeId])
@@ -393,6 +394,7 @@ class AbstractGraphRunner {
393
394
  this.engine = undefined;
394
395
  this.runtime?.dispose();
395
396
  this.runtime = undefined;
397
+ this.currentDef = undefined;
396
398
  if (this.runningKind) {
397
399
  this.runningKind = undefined;
398
400
  this.emit("status", { running: false, engine: undefined });
@@ -427,12 +429,14 @@ class LocalGraphRunner extends AbstractGraphRunner {
427
429
  this.emit("transport", { state: "local" });
428
430
  }
429
431
  build(def) {
432
+ this.currentDef = def;
430
433
  const builder = new GraphBuilder(this.registry);
431
434
  this.runtime = builder.build(def);
432
435
  // Signal UI that freshly built graph should be considered invalidated
433
436
  this.emit("invalidate", { reason: "graph-built" });
434
437
  }
435
438
  update(def) {
439
+ this.currentDef = def;
436
440
  if (!this.runtime)
437
441
  return;
438
442
  // Prevent mid-run churn while wiring changes are applied
@@ -497,11 +501,11 @@ class LocalGraphRunner extends AbstractGraphRunner {
497
501
  if (eng instanceof BatchedEngine)
498
502
  eng.flush();
499
503
  }
500
- getOutputs(def) {
504
+ getOutputs() {
501
505
  const out = {};
502
- if (!this.runtime)
506
+ if (!this.runtime || !this.currentDef)
503
507
  return out;
504
- for (const n of def.nodes) {
508
+ for (const n of this.currentDef.nodes) {
505
509
  const desc = this.registry.nodes.get(n.typeId);
506
510
  const handles = Object.keys(desc?.outputs ?? {});
507
511
  for (const h of handles) {
@@ -515,15 +519,17 @@ class LocalGraphRunner extends AbstractGraphRunner {
515
519
  }
516
520
  return out;
517
521
  }
518
- getInputs(def) {
522
+ getInputs() {
519
523
  const out = {};
520
- for (const n of def.nodes) {
524
+ if (!this.currentDef)
525
+ return out;
526
+ for (const n of this.currentDef.nodes) {
521
527
  const staged = this.stagedInputs[n.nodeId] ?? {};
522
528
  const runtimeInputs = this.runtime
523
529
  ? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
524
530
  : {};
525
531
  // Build inbound handle set for this node from current def
526
- const inbound = new Set(def.edges
532
+ const inbound = new Set(this.currentDef.edges
527
533
  .filter((e) => e.target.nodeId === n.nodeId)
528
534
  .map((e) => e.target.handle));
529
535
  // Merge staged only for non-inbound handles so UI reflects runtime values for wired inputs
@@ -537,26 +543,22 @@ class LocalGraphRunner extends AbstractGraphRunner {
537
543
  }
538
544
  return out;
539
545
  }
546
+ getInputDefaults() {
547
+ const out = {};
548
+ if (!this.currentDef)
549
+ return out;
550
+ for (const n of this.currentDef.nodes) {
551
+ const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
552
+ if (Object.keys(dynDefaults).length > 0) {
553
+ out[n.nodeId] = dynDefaults;
554
+ }
555
+ }
556
+ return out;
557
+ }
540
558
  async snapshotFull() {
541
559
  const def = undefined; // UI will supply def/positions on download for local
542
- const inputs = this.getInputs(this.runtime
543
- ? {
544
- nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
545
- nodeId: id,
546
- typeId: "",
547
- })),
548
- edges: [],
549
- }
550
- : { nodes: [], edges: [] });
551
- const outputs = this.getOutputs(this.runtime
552
- ? {
553
- nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
554
- nodeId: id,
555
- typeId: "",
556
- })),
557
- edges: [],
558
- }
559
- : { nodes: [], edges: [] });
560
+ const inputs = this.getInputs();
561
+ const outputs = this.getOutputs();
560
562
  const environment = this.getEnvironment() || {};
561
563
  return { def, environment, inputs, outputs };
562
564
  }
@@ -565,7 +567,10 @@ class LocalGraphRunner extends AbstractGraphRunner {
565
567
  this.build(payload.def);
566
568
  this.setEnvironment?.(payload.environment || {}, { merge: false });
567
569
  // Hydrate via runtime for exact restore and re-emit
568
- this.runtime?.hydrate({ inputs: payload.inputs || {}, outputs: payload.outputs || {} }, { reemit: true });
570
+ this.runtime?.hydrate({
571
+ inputs: payload.inputs || {},
572
+ outputs: payload.outputs || {},
573
+ });
569
574
  }
570
575
  dispose() {
571
576
  super.dispose();
@@ -630,8 +635,8 @@ class RemoteGraphRunner extends AbstractGraphRunner {
630
635
  this.emit("registry", this.registry);
631
636
  // Trigger update so validation/UI refreshes using last known graph
632
637
  try {
633
- if (this.lastDef)
634
- this.update(this.lastDef);
638
+ if (this.currentDef)
639
+ this.update(this.currentDef);
635
640
  }
636
641
  catch {
637
642
  console.error("Failed to update graph definition after registry changed");
@@ -649,12 +654,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
649
654
  console.warn("Unsupported operation for remote runner");
650
655
  }
651
656
  update(def) {
657
+ this.currentDef = def;
652
658
  // Remote: forward update; ignore errors (fire-and-forget)
653
659
  this.ensureRemoteRunner().then(async (runner) => {
654
660
  try {
655
661
  await runner.update(def);
656
662
  this.emit("invalidate", { reason: "graph-updated" });
657
- this.lastDef = def;
658
663
  }
659
664
  catch { }
660
665
  });
@@ -666,7 +671,6 @@ class RemoteGraphRunner extends AbstractGraphRunner {
666
671
  await runner.build(def);
667
672
  // Signal UI after remote build as well
668
673
  this.emit("invalidate", { reason: "graph-built" });
669
- this.lastDef = def;
670
674
  // Hydrate current remote inputs/outputs (including defaults) into cache
671
675
  try {
672
676
  const snap = await runner.snapshot();
@@ -802,12 +806,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
802
806
  // For now, we expose an async helper on RemoteRunner. Keep sync signature per interface.
803
807
  return undefined;
804
808
  }
805
- getOutputs(def) {
809
+ getOutputs() {
806
810
  const out = {};
807
811
  const cache = this.valueCache;
808
- if (!cache)
812
+ if (!cache || !this.currentDef)
809
813
  return out;
810
- for (const n of def.nodes) {
814
+ for (const n of this.currentDef.nodes) {
811
815
  const resolved = n.resolvedHandles?.outputs;
812
816
  const desc = this.registry.nodes.get(n.typeId);
813
817
  const handles = Object.keys(resolved ?? desc?.outputs ?? {});
@@ -823,17 +827,19 @@ class RemoteGraphRunner extends AbstractGraphRunner {
823
827
  }
824
828
  return out;
825
829
  }
826
- getInputs(def) {
830
+ getInputs() {
827
831
  const out = {};
828
832
  const cache = this.valueCache;
829
- for (const n of def.nodes) {
833
+ if (!this.currentDef)
834
+ return out;
835
+ for (const n of this.currentDef.nodes) {
830
836
  const staged = this.stagedInputs[n.nodeId] ?? {};
831
837
  const resolved = n.resolvedHandles?.inputs;
832
838
  const desc = this.registry.nodes.get(n.typeId);
833
839
  const handles = Object.keys(resolved ?? desc?.inputs ?? {});
834
840
  const cur = {};
835
841
  // Build inbound handle set for this node to honor wiring precedence
836
- const inbound = new Set(def.edges
842
+ const inbound = new Set(this.currentDef.edges
837
843
  .filter((e) => e.target.nodeId === n.nodeId)
838
844
  .map((e) => e.target.handle));
839
845
  for (const h of handles) {
@@ -852,6 +858,18 @@ class RemoteGraphRunner extends AbstractGraphRunner {
852
858
  }
853
859
  return out;
854
860
  }
861
+ getInputDefaults() {
862
+ const out = {};
863
+ if (!this.currentDef)
864
+ return out;
865
+ for (const n of this.currentDef.nodes) {
866
+ const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
867
+ if (Object.keys(dynDefaults).length > 0) {
868
+ out[n.nodeId] = dynDefaults;
869
+ }
870
+ }
871
+ return out;
872
+ }
855
873
  dispose() {
856
874
  super.dispose();
857
875
  this.runner = undefined;
@@ -1603,6 +1621,7 @@ function toReactFlow(def, positions, registry, opts) {
1603
1621
  renderWidth: initialWidth,
1604
1622
  renderHeight: initialHeight,
1605
1623
  inputValues: opts.inputs?.[n.nodeId],
1624
+ inputDefaults: opts.inputDefaults?.[n.nodeId],
1606
1625
  outputValues: opts.outputs?.[n.nodeId],
1607
1626
  status: opts.nodeStatus?.[n.nodeId],
1608
1627
  validation: {
@@ -1627,7 +1646,7 @@ function toReactFlow(def, positions, registry, opts) {
1627
1646
  });
1628
1647
  const edges = def.edges.map((e) => {
1629
1648
  const st = opts.edgeStatus?.[e.id];
1630
- const isRunning = !!st?.activeRuns;
1649
+ const isRunning = (st?.activeRuns || 0) > 0;
1631
1650
  const hasError = !!st?.lastError;
1632
1651
  const isInvalidEdge = !!opts.edgeValidation?.[e.id];
1633
1652
  const sourceMissing = !validHandleMap[e.source.nodeId]?.outputs.has(e.source.handle);
@@ -1664,7 +1683,7 @@ function getNodeBorderClassNames(args) {
1664
1683
  const hasError = !!status.lastError;
1665
1684
  const hasValidationError = issues.some((i) => i.level === "error");
1666
1685
  const hasValidationWarning = !hasValidationError && issues.length > 0;
1667
- const isRunning = !!status.activeRuns;
1686
+ const isRunning = (status.activeRuns || 0) > 0;
1668
1687
  const isInvalid = !!status.invalidated && !isRunning && !hasError;
1669
1688
  // Keep border width constant to avoid layout reflow on selection toggles
1670
1689
  const borderWidth = "border";
@@ -1768,8 +1787,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1768
1787
  const valuesTick = versionTick + graphTick + graphUiTick;
1769
1788
  // Def and IO values
1770
1789
  const def = wb.export();
1771
- const inputsMap = useMemo(() => runner.getInputs(def), [runner, def, valuesTick]);
1772
- const outputsMap = useMemo(() => runner.getOutputs(def), [runner, def, valuesTick]);
1790
+ const inputsMap = useMemo(() => runner.getInputs(), [runner, valuesTick]);
1791
+ const inputDefaultsMap = useMemo(() => runner.getInputDefaults(), [runner, valuesTick]);
1792
+ const outputsMap = useMemo(() => runner.getOutputs(), [runner, valuesTick]);
1773
1793
  const outputTypesMap = useMemo(() => {
1774
1794
  const out = {};
1775
1795
  // Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
@@ -1932,7 +1952,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1932
1952
  }
1933
1953
  catch { }
1934
1954
  };
1935
- const off1 = runner.on("value", (e) => {
1955
+ const offRunnerValue = runner.on("value", (e) => {
1936
1956
  if (e?.io === "input") {
1937
1957
  const nodeId = e?.nodeId;
1938
1958
  setNodeStatus((s) => ({
@@ -1942,7 +1962,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1942
1962
  }
1943
1963
  return add("runner", "value")(e);
1944
1964
  });
1945
- const off2 = runner.on("error", (e) => {
1965
+ const offRunnerError = runner.on("error", (e) => {
1946
1966
  const edgeError = e;
1947
1967
  const nodeError = e;
1948
1968
  const registryError = e;
@@ -1995,14 +2015,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1995
2015
  }
1996
2016
  return add("runner", "error")(e);
1997
2017
  });
1998
- const off3 = runner.on("invalidate", (e) => {
2018
+ const offRunnerInvalidate = runner.on("invalidate", (e) => {
1999
2019
  // After build/update, pull resolved handles and merge in-place (no graphChanged)
2000
2020
  if (e?.reason === "graph-updated" || e?.reason === "graph-built") {
2001
2021
  refreshResolvedHandles();
2002
2022
  }
2003
2023
  return add("runner", "invalidate")(e);
2004
2024
  });
2005
- const off3b = runner.on("stats", (s) => {
2025
+ const offRunnerStats = runner.on("stats", (s) => {
2006
2026
  if (!s)
2007
2027
  return;
2008
2028
  if (s.kind === "node-start") {
@@ -2049,8 +2069,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2049
2069
  const isValidRunId = runId && typeof runId === "string" && runId.length > 0;
2050
2070
  setNodeStatus((prev) => {
2051
2071
  const current = prev[id]?.activeRuns ?? 0;
2052
- const nextActive = current - 1;
2053
2072
  const currentRunIds = prev[id]?.activeRunIds ?? [];
2073
+ if (isValidRunId && !currentRunIds.includes(runId)) {
2074
+ console.warn(`[WorkbenchContext] node-done event for unknown runId: node=${id} runId=${runId}`, { event: s, currentRunIds });
2075
+ return prev; // Ignore stale event
2076
+ }
2077
+ const nextActive = Math.max(0, current - 1); // Prevent negative values
2054
2078
  const nextRunIds = isValidRunId
2055
2079
  ? currentRunIds.filter((rid) => rid !== runId)
2056
2080
  : currentRunIds;
@@ -2095,15 +2119,17 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2095
2119
  const current = prev[id]?.activeRuns ?? 0;
2096
2120
  return {
2097
2121
  ...prev,
2098
- [id]: { ...prev[id], activeRuns: current - 1 },
2122
+ [id]: { ...prev[id], activeRuns: Math.max(0, current - 1) }, // Prevent negative values
2099
2123
  };
2100
2124
  });
2101
2125
  }
2102
2126
  return add("runner", "stats")(s);
2103
2127
  });
2104
- const off4 = wb.on("graphChanged", add("workbench", "graphChanged"));
2128
+ const offWbGraphChanged = wb.on("graphChanged", add("workbench", "graphChanged"));
2129
+ const offWbGraphUiChanged = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
2130
+ const offWbValidationChanged = wb.on("validationChanged", add("workbench", "validationChanged"));
2105
2131
  // Ensure newly added nodes start as invalidated until first evaluation
2106
- const off4c = wb.on("graphChanged", (e) => {
2132
+ const offWbAddNode = wb.on("graphChanged", (e) => {
2107
2133
  const change = e.change;
2108
2134
  if (change?.type === "addNode" && typeof change.nodeId === "string") {
2109
2135
  const id = change.nodeId;
@@ -2113,17 +2139,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2113
2139
  }));
2114
2140
  }
2115
2141
  });
2116
- const off4b = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
2117
- const off5 = wb.on("validationChanged", add("workbench", "validationChanged"));
2118
- const off5b = wb.on("validationChanged", (r) => setValidation(r));
2119
- const off6 = wb.on("selectionChanged", (sel) => {
2142
+ const offWbdSetValidation = wb.on("validationChanged", (r) => setValidation(r));
2143
+ const offWbSelectionChanged = wb.on("selectionChanged", (sel) => {
2120
2144
  setSelectedNodeId(sel.nodes?.[0]);
2121
2145
  setSelectedEdgeId(sel.edges?.[0]);
2122
2146
  });
2123
- const off7 = wb.on("error", add("workbench", "error"));
2124
- wb.refreshValidation();
2147
+ const offWbError = wb.on("error", add("workbench", "error"));
2125
2148
  // Registry updates: swap registry and refresh graph validation/UI
2126
- const offReg = runner.on("registry", (newReg) => {
2149
+ const offRunnerRegistry = runner.on("registry", (newReg) => {
2127
2150
  try {
2128
2151
  setRegistry(newReg);
2129
2152
  wb.setRegistry(newReg);
@@ -2139,29 +2162,40 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2139
2162
  console.error("Failed to handle registry changed event");
2140
2163
  }
2141
2164
  });
2165
+ // Handle transport disconnect: reset runtime status when connection is lost
2166
+ const offRunnerTransport = runner.on("transport", (t) => {
2167
+ if (t.state === "disconnected") {
2168
+ console.info("[WorkbenchContext] Transport disconnected, resetting node status");
2169
+ setNodeStatus({});
2170
+ setEdgeStatus({});
2171
+ setFallbackStarts({});
2172
+ errorRunsRef.current = {};
2173
+ }
2174
+ });
2175
+ wb.refreshValidation();
2142
2176
  return () => {
2143
- off1();
2144
- off2();
2145
- off3();
2146
- off3b();
2147
- off4();
2148
- off4b();
2149
- off4c();
2150
- off5();
2151
- off5b();
2152
- off6();
2153
- off7();
2154
- offReg();
2177
+ offRunnerValue();
2178
+ offRunnerError();
2179
+ offRunnerInvalidate();
2180
+ offRunnerStats();
2181
+ offWbGraphChanged();
2182
+ offWbGraphUiChanged();
2183
+ offWbValidationChanged();
2184
+ offWbError();
2185
+ offWbAddNode();
2186
+ offWbdSetValidation();
2187
+ offWbSelectionChanged();
2188
+ offRunnerRegistry();
2189
+ offRunnerTransport();
2155
2190
  };
2156
2191
  }, [runner, wb]);
2157
- // Push incremental updates into running engine without full reload
2192
+ // Keep runner.currentDef in sync with the workbench graph at all times.
2193
+ // When an engine/runtime exists, this also pushes incremental wiring changes into it.
2158
2194
  useEffect(() => {
2159
- if (runner.isRunning()) {
2160
- try {
2161
- runner.update(def);
2162
- }
2163
- catch { }
2195
+ try {
2196
+ runner.update(def);
2164
2197
  }
2198
+ catch { }
2165
2199
  }, [runner, def, graphTick]);
2166
2200
  const validationByNode = useMemo(() => {
2167
2201
  const inputs = {};
@@ -2250,6 +2284,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2250
2284
  edgeStatus,
2251
2285
  valuesTick,
2252
2286
  inputsMap,
2287
+ inputDefaultsMap,
2253
2288
  outputsMap,
2254
2289
  outputTypesMap,
2255
2290
  validationByNode,
@@ -2292,6 +2327,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2292
2327
  removeSystemError,
2293
2328
  removeRegistryError,
2294
2329
  inputsMap,
2330
+ inputDefaultsMap,
2295
2331
  outputsMap,
2296
2332
  validationByNode,
2297
2333
  validationByEdge,
@@ -2377,7 +2413,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2377
2413
  return String(value ?? "");
2378
2414
  }
2379
2415
  };
2380
- const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
2416
+ const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
2381
2417
  const nodeValidationIssues = validationByNode.issues;
2382
2418
  const edgeValidationIssues = validationByEdge.issues;
2383
2419
  const nodeValidationHandles = validationByNode;
@@ -2392,8 +2428,33 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2392
2428
  .filter(([k]) => !isInputPrivate(effectiveHandles.inputs, k))
2393
2429
  .map(([k]) => k);
2394
2430
  const outputHandles = Object.keys(effectiveHandles.outputs);
2395
- const nodeInputs = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
2431
+ const nodeInputsRaw = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
2432
+ const nodeInputsDefaults = selectedNodeId
2433
+ ? inputDefaultsMap[selectedNodeId] ?? {}
2434
+ : {};
2435
+ // Keep defaults separate for placeholder use (don't merge into nodeInputs)
2436
+ const nodeInputs = nodeInputsRaw;
2396
2437
  const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
2438
+ // Helper to truncate long values
2439
+ const truncateValue = (str, maxLen = 50) => {
2440
+ if (str.length <= maxLen)
2441
+ return str;
2442
+ const start = Math.floor(maxLen / 2) - 5;
2443
+ const end = str.length - Math.floor(maxLen / 2) + 5;
2444
+ return `${str.slice(0, start)}...${str.slice(end)}`;
2445
+ };
2446
+ // Helper to copy to clipboard
2447
+ const copyToClipboard = (text) => {
2448
+ navigator.clipboard.writeText(text).catch(() => {
2449
+ // Fallback for older browsers
2450
+ const textarea = document.createElement("textarea");
2451
+ textarea.value = text;
2452
+ document.body.appendChild(textarea);
2453
+ textarea.select();
2454
+ document.execCommand("copy");
2455
+ document.body.removeChild(textarea);
2456
+ });
2457
+ };
2397
2458
  const selectedNodeStatus = selectedNodeId
2398
2459
  ? nodeStatus?.[selectedNodeId]
2399
2460
  : undefined;
@@ -2479,19 +2540,30 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2479
2540
  }, children: [jsx("option", { value: "", children: "(infer from source)" }), Array.from(registry.types.keys()).map((tid) => (jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxs("li", { className: "flex items-center gap-1", children: [jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsx("span", { children: `${m.code}: ${m.message}` }), jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
2480
2541
  e.stopPropagation();
2481
2542
  deleteEdgeById(selectedEdge.id);
2482
- }, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxs("div", { children: [selectedNode && (jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxs("div", { children: ["Type: ", selectedNode.typeId] }), selectedNodeStatus?.activeRuns &&
2543
+ }, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxs("div", { children: [selectedNode && (jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.activeRuns &&
2483
2544
  selectedNodeStatus.activeRuns > 0 && (jsxs("div", { className: "mt-1 text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1", children: [jsxs("div", { className: "font-semibold", children: ["Running (", selectedNodeStatus.activeRuns, ")"] }), selectedNodeStatus.activeRunIds &&
2484
2545
  selectedNodeStatus.activeRunIds.length > 0 ? (jsxs("div", { className: "mt-1", children: [jsx("div", { className: "text-[10px] text-blue-600", children: "RunIds:" }), jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedNodeStatus.activeRunIds.map((runId, idx) => (jsx("span", { className: "text-[10px] px-1.5 py-0.5 bg-blue-100 border border-blue-300 rounded font-mono", children: runId }, idx))) })] })) : (jsx("div", { className: "text-[10px] text-blue-600 mt-1", children: "RunIds not available (some runs may have started without runId)" }))] })), !!selectedNodeStatus?.lastError && (jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 break-words", children: String(selectedNodeStatus.lastError?.message ??
2485
2546
  selectedNodeStatus.lastError) }))] })), jsxs("div", { className: "mb-2", children: [jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
2486
2547
  const typeId = getInputTypeId(effectiveHandles.inputs, h);
2487
2548
  const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
2488
2549
  e.target.handle === h);
2550
+ const inbound = new Set(def.edges
2551
+ .filter((e) => e.target.nodeId === selectedNodeId &&
2552
+ e.target.handle === h)
2553
+ .map((e) => e.target.handle));
2554
+ const hasDefault = !inbound.has(h) && nodeInputsDefaults[h] !== undefined;
2555
+ const defaultStr = hasDefault
2556
+ ? safeToString(typeId, nodeInputsDefaults[h])
2557
+ : undefined;
2489
2558
  const commonProps = {
2490
2559
  style: { flex: 1 },
2491
2560
  disabled: isLinked,
2492
2561
  };
2493
2562
  const current = nodeInputs[h];
2563
+ const hasValue = current !== undefined && current !== null;
2494
2564
  const value = drafts[h] ?? safeToString(typeId, current);
2565
+ const displayValue = hasValue ? value : "";
2566
+ const placeholder = hasDefault ? defaultStr : undefined;
2495
2567
  const onChangeText = (text) => setDrafts((d) => ({ ...d, [h]: text }));
2496
2568
  const commit = () => {
2497
2569
  const draft = drafts[h];
@@ -2504,6 +2576,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2504
2576
  const orig = originals[h] ?? safeToString(typeId, current);
2505
2577
  setDrafts((d) => ({ ...d, [h]: orig }));
2506
2578
  };
2579
+ const clearInput = () => {
2580
+ setInput(h, undefined);
2581
+ setDrafts((d) => ({ ...d, [h]: "" }));
2582
+ setOriginals((o) => ({ ...o, [h]: "" }));
2583
+ };
2507
2584
  const isEnum = typeId?.startsWith("enum:");
2508
2585
  const inIssues = selectedNodeHandleValidation.inputs.filter((m) => m.handle === h);
2509
2586
  const hasValidation = inIssues.length > 0;
@@ -2511,25 +2588,43 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2511
2588
  const title = inIssues
2512
2589
  .map((v) => `${v.code}: ${v.message}`)
2513
2590
  .join("; ");
2514
- return (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-32 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: typeId })] }), hasValidation && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full", value: current !== undefined && current !== null
2515
- ? String(current)
2516
- : "", onChange: (e) => {
2517
- const val = e.target.value;
2518
- const raw = val === "" ? undefined : Number(val);
2519
- setInput(h, raw);
2520
- // keep drafts/originals in sync with label for display elsewhere
2521
- const display = safeToString(typeId, raw);
2522
- setDrafts((d) => ({ ...d, [h]: display }));
2523
- setOriginals((o) => ({ ...o, [h]: display }));
2524
- }, ...commonProps, children: [jsx("option", { value: "", children: "(select)" }), registry.enums.get(typeId)?.options.map((opt) => (jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] })) : isLinked ? (toElement(typeId, current)) : (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full", placeholder: isLinked ? "wired" : undefined, value: value, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
2525
- if (e.key === "Enter")
2526
- commit();
2527
- if (e.key === "Escape")
2528
- revert();
2529
- }, ...commonProps }))] }, h));
2530
- }))] }), jsxs("div", { children: [jsx("div", { className: "font-semibold mb-1", children: "Outputs" }), outputHandles.length === 0 ? (jsx("div", { className: "text-gray-500", children: "No outputs" })) : (outputHandles.map((h) => (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-20 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsx("div", { className: "flex-1", children: (() => {
2591
+ return (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-32 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: typeId })] }), hasValidation && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", value: current !== undefined && current !== null
2592
+ ? String(current)
2593
+ : "", onChange: (e) => {
2594
+ const val = e.target.value;
2595
+ const raw = val === "" ? undefined : Number(val);
2596
+ setInput(h, raw);
2597
+ // keep drafts/originals in sync with label for display elsewhere
2598
+ const display = safeToString(typeId, raw);
2599
+ setDrafts((d) => ({ ...d, [h]: display }));
2600
+ setOriginals((o) => ({ ...o, [h]: display }));
2601
+ }, ...commonProps, children: [jsx("option", { value: "", children: placeholder
2602
+ ? `Default: ${placeholder}`
2603
+ : "(select)" }), registry.enums
2604
+ .get(typeId)
2605
+ ?.options.map((opt) => (jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsx(XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsx("div", { className: "flex items-center gap-1 flex-1", children: jsx("div", { className: "flex-1 min-w-0", children: (() => {
2606
+ const displayStr = safeToString(typeId, current);
2607
+ const isLong = displayStr.length > 50;
2608
+ const truncated = isLong
2609
+ ? truncateValue(displayStr)
2610
+ : displayStr;
2611
+ return (jsxs("div", { className: "flex items-center gap-1", children: [jsx("span", { className: "truncate", children: truncated }), isLong && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsx(CopyIcon, { size: 14 }) }))] }));
2612
+ })() }) })) : (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", placeholder: placeholder
2613
+ ? `Default: ${placeholder}`
2614
+ : undefined, value: displayValue, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
2615
+ if (e.key === "Enter")
2616
+ commit();
2617
+ if (e.key === "Escape")
2618
+ revert();
2619
+ }, ...commonProps }), hasValue && !isLinked && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsx(XCircleIcon, { size: 16 }) }))] }))] }, h));
2620
+ }))] }), jsxs("div", { children: [jsx("div", { className: "font-semibold mb-1", children: "Outputs" }), outputHandles.length === 0 ? (jsx("div", { className: "text-gray-500", children: "No outputs" })) : (outputHandles.map((h) => (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-20 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsx("div", { className: "flex-1 min-w-0", children: (() => {
2531
2621
  const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], effectiveHandles.outputs[h]);
2532
- return toElement(typeId, value);
2622
+ const displayStr = safeToString(typeId, value);
2623
+ const isLong = displayStr.length > 50;
2624
+ const truncated = isLong
2625
+ ? truncateValue(displayStr)
2626
+ : displayStr;
2627
+ return (jsxs("div", { className: "flex items-center gap-1", children: [jsx("span", { className: "truncate", children: truncated }), isLong && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsx(CopyIcon, { size: 14 }) }))] }));
2533
2628
  })() }), (() => {
2534
2629
  const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
2535
2630
  if (outIssues.length === 0)
@@ -2671,7 +2766,7 @@ function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate,
2671
2766
  .join("; ") })), showId && jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
2672
2767
  }
2673
2768
  function DefaultNodeContent({ data, isConnectable, }) {
2674
- const { showValues, inputValues, outputValues, toString } = data;
2769
+ const { showValues, inputValues, inputDefaults, outputValues, toString } = data;
2675
2770
  const inputEntries = data.inputHandles ?? [];
2676
2771
  const outputEntries = data.outputHandles ?? [];
2677
2772
  const status = data.status ?? { activeRuns: 0 };
@@ -2700,14 +2795,23 @@ function DefaultNodeContent({ data, isConnectable, }) {
2700
2795
  if (!showValues)
2701
2796
  return undefined;
2702
2797
  if (kind === "input") {
2703
- const txt = toString(entry.typeId, inputValues?.[entry.id]);
2704
- return typeof txt === "string" ? txt : String(txt);
2798
+ const value = inputValues?.[entry.id];
2799
+ const isDefault = value !== undefined &&
2800
+ inputDefaults?.[entry.id] !== undefined &&
2801
+ JSON.stringify(value) ===
2802
+ JSON.stringify(inputDefaults[entry.id]);
2803
+ const txt = toString(entry.typeId, value);
2804
+ const str = typeof txt === "string" ? txt : String(txt);
2805
+ return { text: str, isDefault };
2705
2806
  }
2706
2807
  const resolved = resolveOutputDisplay(outputValues?.[entry.id], entry.typeId);
2707
2808
  const txt = toString(resolved.typeId, resolved.value);
2708
- return typeof txt === "string" ? txt : String(txt);
2809
+ return {
2810
+ text: typeof txt === "string" ? txt : String(txt),
2811
+ isDefault: false,
2812
+ };
2709
2813
  })();
2710
- return (jsxs("span", { className: "flex items-center gap-1 w-full", children: [kind === "output" ? (jsxs(Fragment, { children: [valueText !== undefined ? (jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText })) : (jsx("span", { style: { flex: 1, minWidth: 0, maxWidth: "100%" } })), jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) })] })) : (jsxs(Fragment, { children: [jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) }), valueText !== undefined && (jsx("span", { className: "opacity-60 truncate pr-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText }))] })), hasAny && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
2814
+ return (jsxs("span", { className: `flex items-center gap-1 w-full ${valueText?.isDefault ? "text-gray-400" : ""}`, children: [kind === "output" ? (jsxs(Fragment, { children: [valueText !== undefined ? (jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text })) : (jsx("span", { style: { flex: 1, minWidth: 0, maxWidth: "100%" } })), jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) })] })) : (jsxs(Fragment, { children: [jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) }), valueText !== undefined && (jsx("span", { className: `truncate pr-1 ${valueText.isDefault ? "text-gray-400" : "opacity-60"}`, style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text }))] })), hasAny && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
2711
2815
  } })] }));
2712
2816
  }
2713
2817
 
@@ -3004,7 +3108,7 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
3004
3108
  }
3005
3109
 
3006
3110
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
3007
- const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
3111
+ const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
3008
3112
  const nodeValidation = validationByNode;
3009
3113
  const edgeValidation = validationByEdge.errors;
3010
3114
  // Keep stable references for nodes/edges to avoid unnecessary updates
@@ -3099,9 +3203,28 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3099
3203
  const { nodes, edges } = useMemo(() => {
3100
3204
  const def = wb.export();
3101
3205
  const sel = wb.getSelection();
3206
+ // Merge defaults with inputs for node display (defaults shown in lighter gray)
3207
+ const inputsWithDefaults = {};
3208
+ for (const n of def.nodes) {
3209
+ const nodeInputs = inputsMap[n.nodeId] ?? {};
3210
+ const nodeDefaults = inputDefaultsMap[n.nodeId] ?? {};
3211
+ const inbound = new Set(def.edges
3212
+ .filter((e) => e.target.nodeId === n.nodeId)
3213
+ .map((e) => e.target.handle));
3214
+ const merged = { ...nodeInputs };
3215
+ for (const [h, v] of Object.entries(nodeDefaults)) {
3216
+ if (!inbound.has(h) && merged[h] === undefined) {
3217
+ merged[h] = v;
3218
+ }
3219
+ }
3220
+ if (Object.keys(merged).length > 0) {
3221
+ inputsWithDefaults[n.nodeId] = merged;
3222
+ }
3223
+ }
3102
3224
  const out = toReactFlow(def, wb.getPositions(), registry, {
3103
3225
  showValues,
3104
- inputs: inputsMap,
3226
+ inputs: inputsWithDefaults,
3227
+ inputDefaults: inputDefaultsMap,
3105
3228
  outputs: outputsMap,
3106
3229
  resolveNodeType,
3107
3230
  toString,
@@ -3340,7 +3463,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3340
3463
  const off1 = wb.on("graphChanged", () => {
3341
3464
  try {
3342
3465
  const cur = wb.export();
3343
- const inputs = runner.getInputs(cur);
3466
+ const inputs = runner.getInputs();
3344
3467
  onChange({ def: cur, inputs });
3345
3468
  }
3346
3469
  catch { }
@@ -3348,7 +3471,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3348
3471
  const off2 = runner.on("value", () => {
3349
3472
  try {
3350
3473
  const cur = wb.export();
3351
- const inputs = runner.getInputs(cur);
3474
+ const inputs = runner.getInputs();
3352
3475
  onChange({ def: cur, inputs });
3353
3476
  }
3354
3477
  catch { }
@@ -3394,7 +3517,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3394
3517
  const downloadGraph = useCallback(() => {
3395
3518
  try {
3396
3519
  const def = wb.export();
3397
- const inputs = runner.getInputs(def);
3520
+ const inputs = runner.getInputs();
3398
3521
  const payload = { def, inputs };
3399
3522
  const pretty = JSON.stringify(payload, null, 2);
3400
3523
  const blob = new Blob([pretty], { type: "application/json" });