@bian-womp/spark-workbench 0.1.29 → 0.2.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 +336 -240
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/index.d.ts +3 -1
  4. package/lib/cjs/src/index.d.ts.map +1 -1
  5. package/lib/cjs/src/misc/DefaultNode.d.ts +3 -2
  6. package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
  7. package/lib/cjs/src/misc/Inspector.d.ts +3 -1
  8. package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
  9. package/lib/cjs/src/misc/WorkbenchStudio.d.ts +6 -4
  10. package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
  11. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +2 -2
  12. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  13. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts +2 -2
  14. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  15. package/lib/cjs/src/misc/hooks.d.ts +3 -3
  16. package/lib/cjs/src/misc/hooks.d.ts.map +1 -1
  17. package/lib/cjs/src/misc/mapping.d.ts +1 -1
  18. package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
  19. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +29 -0
  20. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -0
  21. package/lib/cjs/src/runtime/{GraphRunner.d.ts → IGraphRunner.d.ts} +21 -31
  22. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -0
  23. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +16 -0
  24. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -0
  25. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +27 -0
  26. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -0
  27. package/lib/esm/index.js +332 -237
  28. package/lib/esm/index.js.map +1 -1
  29. package/lib/esm/src/index.d.ts +3 -1
  30. package/lib/esm/src/index.d.ts.map +1 -1
  31. package/lib/esm/src/misc/DefaultNode.d.ts +3 -2
  32. package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
  33. package/lib/esm/src/misc/Inspector.d.ts +3 -1
  34. package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
  35. package/lib/esm/src/misc/WorkbenchStudio.d.ts +6 -4
  36. package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
  37. package/lib/esm/src/misc/context/WorkbenchContext.d.ts +2 -2
  38. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  39. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts +2 -2
  40. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  41. package/lib/esm/src/misc/hooks.d.ts +3 -3
  42. package/lib/esm/src/misc/hooks.d.ts.map +1 -1
  43. package/lib/esm/src/misc/mapping.d.ts +1 -1
  44. package/lib/esm/src/misc/mapping.d.ts.map +1 -1
  45. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +29 -0
  46. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -0
  47. package/lib/esm/src/runtime/{GraphRunner.d.ts → IGraphRunner.d.ts} +21 -31
  48. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -0
  49. package/lib/esm/src/runtime/LocalGraphRunner.d.ts +16 -0
  50. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -0
  51. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +27 -0
  52. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -0
  53. package/package.json +8 -8
  54. package/lib/cjs/src/runtime/GraphRunner.d.ts.map +0 -1
  55. package/lib/esm/src/runtime/GraphRunner.d.ts.map +0 -1
package/lib/cjs/index.cjs CHANGED
@@ -5,7 +5,7 @@ var sparkRemote = require('@bian-womp/spark-remote');
5
5
  var React = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var react = require('@phosphor-icons/react');
8
- var ReactFlow = require('reactflow');
8
+ var react$1 = require('@xyflow/react');
9
9
  var cx = require('classnames');
10
10
 
11
11
  class DefaultUIExtensionRegistry {
@@ -319,104 +319,295 @@ class CLIWorkbench {
319
319
  }
320
320
  }
321
321
 
322
- class GraphRunner {
322
+ class AbstractGraphRunner {
323
323
  constructor(registry, backend) {
324
324
  this.registry = registry;
325
+ this.backend = backend;
325
326
  this.listeners = new Map();
326
327
  this.stagedInputs = {};
327
- this.backend = { kind: "local" };
328
- if (backend)
329
- this.backend = backend;
330
- // Emit initial transport status
331
- if (this.backend.kind === "local")
332
- this.emit("transport", { state: "local" });
333
328
  }
334
- build(def) {
335
- if (this.backend.kind === "local") {
336
- const builder = new sparkGraph.GraphBuilder(this.registry);
337
- this.runtime = builder.build(def);
338
- // Signal UI that freshly built graph should be considered invalidated
339
- this.emit("invalidate", { reason: "graph-built" });
329
+ launch(def, opts) {
330
+ if (this.engine) {
331
+ throw new Error("Engine already running. Stop the current engine first.");
332
+ }
333
+ }
334
+ setInput(nodeId, handle, value) {
335
+ if (!this.stagedInputs[nodeId])
336
+ this.stagedInputs[nodeId] = {};
337
+ this.stagedInputs[nodeId][handle] = value;
338
+ if (this.engine) {
339
+ this.engine.setInput(nodeId, handle, value);
340
+ }
341
+ else {
342
+ // Emit a value event so UI updates even when engine isn't running
343
+ this.emit("value", { nodeId, handle, value, io: "input" });
344
+ }
345
+ }
346
+ // Batch update multiple inputs on a node and trigger a single run
347
+ setInputs(nodeId, inputs) {
348
+ if (!inputs)
340
349
  return;
350
+ if (!this.stagedInputs[nodeId])
351
+ this.stagedInputs[nodeId] = {};
352
+ Object.assign(this.stagedInputs[nodeId], inputs);
353
+ if (this.engine) {
354
+ // Running: set all inputs
355
+ this.engine.setInputs(nodeId, inputs);
341
356
  }
342
- // Remote: no-op here; build is performed on remote server during launch
357
+ else {
358
+ // Not running: emit a single synthetic value event per handle; UI will coalesce
359
+ console.warn("Engine does not exists");
360
+ for (const [handle, value] of Object.entries(inputs)) {
361
+ this.emit("value", { nodeId, handle, value, io: "input" });
362
+ }
363
+ }
364
+ }
365
+ async whenIdle() {
366
+ await this.engine?.whenIdle();
367
+ }
368
+ on(event, handler) {
369
+ if (!this.listeners.has(event))
370
+ this.listeners.set(event, new Set());
371
+ const set = this.listeners.get(event);
372
+ set.add(handler);
373
+ return () => set.delete(handler);
374
+ }
375
+ emit(event, payload) {
376
+ const set = this.listeners.get(event);
377
+ if (set)
378
+ for (const h of Array.from(set))
379
+ h(payload);
380
+ }
381
+ dispose() {
382
+ this.engine?.dispose();
383
+ this.engine = undefined;
384
+ this.runtime?.dispose();
385
+ this.runtime = undefined;
386
+ if (this.runningKind) {
387
+ this.runningKind = undefined;
388
+ this.emit("status", { running: false, engine: undefined });
389
+ }
390
+ }
391
+ isRunning() {
392
+ return !!this.engine;
393
+ }
394
+ getRunningEngine() {
395
+ return this.runningKind;
396
+ }
397
+ }
398
+
399
+ class LocalGraphRunner extends AbstractGraphRunner {
400
+ constructor(registry) {
401
+ super(registry, { kind: "local" });
402
+ this.emit("transport", { state: "local" });
403
+ }
404
+ build(def) {
405
+ const builder = new sparkGraph.GraphBuilder(this.registry);
406
+ this.runtime = builder.build(def);
407
+ // Signal UI that freshly built graph should be considered invalidated
408
+ this.emit("invalidate", { reason: "graph-built" });
343
409
  }
344
410
  update(def) {
345
- if (this.backend.kind === "local") {
346
- if (!this.runtime)
347
- return;
348
- // Prevent mid-run churn while wiring changes are applied
349
- this.runtime.pause();
350
- this.runtime.update(def, this.registry);
351
- this.runtime.resume();
352
- this.emit("invalidate", { reason: "graph-updated" });
411
+ if (!this.runtime)
353
412
  return;
413
+ // Prevent mid-run churn while wiring changes are applied
414
+ this.runtime.pause();
415
+ this.runtime.update(def, this.registry);
416
+ this.runtime.resume();
417
+ this.emit("invalidate", { reason: "graph-updated" });
418
+ }
419
+ launch(def, opts) {
420
+ super.launch(def, opts);
421
+ this.build(def);
422
+ if (!this.runtime)
423
+ throw new Error("Runtime not built");
424
+ const rt = this.runtime;
425
+ switch (opts.engine) {
426
+ case "push":
427
+ this.engine = new sparkGraph.PushEngine(rt);
428
+ break;
429
+ case "batched":
430
+ this.engine = new sparkGraph.BatchedEngine(rt, {
431
+ flushIntervalMs: opts.batched?.flushIntervalMs ?? 0,
432
+ });
433
+ break;
434
+ case "pull":
435
+ this.engine = new sparkGraph.PullEngine(rt);
436
+ break;
437
+ case "hybrid":
438
+ this.engine = new sparkGraph.HybridEngine(rt, {
439
+ windowMs: opts.hybrid?.windowMs ?? 250,
440
+ batchThreshold: opts.hybrid?.batchThreshold ?? 3,
441
+ });
442
+ break;
443
+ case "step":
444
+ this.engine = new sparkGraph.StepEngine(rt);
445
+ break;
446
+ default:
447
+ throw new Error("Unknown engine kind");
448
+ }
449
+ this.engine.on("value", (e) => this.emit("value", e));
450
+ this.engine.on("error", (e) => this.emit("error", e));
451
+ this.engine.on("invalidate", (e) => this.emit("invalidate", e));
452
+ this.engine.on("stats", (e) => this.emit("stats", e));
453
+ this.engine.launch();
454
+ this.runningKind = opts.engine;
455
+ this.emit("status", { running: true, engine: this.runningKind });
456
+ for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
457
+ this.engine.setInputs(nodeId, map);
458
+ }
459
+ }
460
+ async step() {
461
+ const eng = this.engine;
462
+ if (eng instanceof sparkGraph.StepEngine)
463
+ await eng.step();
464
+ }
465
+ async computeNode(nodeId) {
466
+ const eng = this.engine;
467
+ if (eng instanceof sparkGraph.PullEngine)
468
+ await eng.computeNode(nodeId);
469
+ }
470
+ flush() {
471
+ const eng = this.engine;
472
+ if (eng instanceof sparkGraph.BatchedEngine)
473
+ eng.flush();
474
+ }
475
+ getOutputs(def) {
476
+ const out = {};
477
+ if (!this.runtime)
478
+ return out;
479
+ for (const n of def.nodes) {
480
+ const desc = this.registry.nodes.get(n.typeId);
481
+ const handles = Object.keys(desc?.outputs ?? {});
482
+ for (const h of handles) {
483
+ const v = this.runtime.getOutput(n.nodeId, h);
484
+ if (v !== undefined) {
485
+ if (!out[n.nodeId])
486
+ out[n.nodeId] = {};
487
+ out[n.nodeId][h] = v;
488
+ }
489
+ }
354
490
  }
491
+ return out;
492
+ }
493
+ getInputs(def) {
494
+ const out = {};
495
+ for (const n of def.nodes) {
496
+ const staged = this.stagedInputs[n.nodeId] ?? {};
497
+ const runtimeInputs = this.runtime
498
+ ? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
499
+ : {};
500
+ if (this.isRunning()) {
501
+ out[n.nodeId] = runtimeInputs;
502
+ }
503
+ else {
504
+ const merged = { ...runtimeInputs, ...staged };
505
+ if (Object.keys(merged).length > 0)
506
+ out[n.nodeId] = merged;
507
+ }
508
+ }
509
+ return out;
510
+ }
511
+ dispose() {
512
+ super.dispose();
513
+ this.runtime = undefined;
514
+ this.emit("transport", { state: "local" });
515
+ }
516
+ }
517
+
518
+ class RemoteGraphRunner extends AbstractGraphRunner {
519
+ constructor(registry, backend) {
520
+ super(registry, backend);
521
+ this.valueCache = new Map();
522
+ this.listenersBound = false;
523
+ // Auto-handle registry-changed invalidations from remote
524
+ // We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
525
+ this.ensureRemoteRunner().then(async (runner) => {
526
+ const eng = runner.getEngine();
527
+ if (!this.listenersBound) {
528
+ eng.on("invalidate", async (e) => {
529
+ if (e.reason === "registry-changed") {
530
+ try {
531
+ const deltas = Array.isArray(e.deltas) ? e.deltas : [];
532
+ for (const d of deltas) {
533
+ if (!d || typeof d !== "object")
534
+ continue;
535
+ if (d.kind === "register-enum") {
536
+ this.registry.registerEnum({
537
+ id: d.id,
538
+ displayName: d.displayName,
539
+ options: d.options,
540
+ opts: d.opts,
541
+ });
542
+ }
543
+ else if (d.kind === "register-type") {
544
+ if (!this.registry.types.has(d.id)) {
545
+ this.registry.registerType({
546
+ id: d.id,
547
+ displayName: d.displayName,
548
+ validate: (_v) => true,
549
+ });
550
+ }
551
+ }
552
+ else if (d.kind === "register-node") {
553
+ if (!this.registry.nodes.has(d.desc?.id)) {
554
+ this.registry.registerNode({
555
+ id: String(d.desc?.id || ""),
556
+ categoryId: String(d.desc?.categoryId || "compute"),
557
+ displayName: d.desc?.displayName,
558
+ inputs: d.desc?.inputs || {},
559
+ outputs: d.desc?.outputs || {},
560
+ impl: () => { },
561
+ });
562
+ }
563
+ }
564
+ }
565
+ this.emit("registry", this.registry);
566
+ // Trigger update so validation/UI refreshes using last known graph
567
+ try {
568
+ if (this.lastDef)
569
+ this.update(this.lastDef);
570
+ }
571
+ catch {
572
+ console.error("Failed to update graph definition after registry changed");
573
+ }
574
+ }
575
+ catch {
576
+ console.error("Failed to handle registry changed event");
577
+ }
578
+ }
579
+ });
580
+ }
581
+ });
582
+ }
583
+ build(def) {
584
+ console.warn("Unsupported operation for remote runner");
585
+ }
586
+ update(def) {
355
587
  // Remote: forward update; ignore errors (fire-and-forget)
356
- void this.ensureRemote().then(async (rc) => {
588
+ this.ensureRemoteRunner().then(async (runner) => {
357
589
  try {
358
- await rc.runner.update(def);
590
+ await runner.update(def);
359
591
  this.emit("invalidate", { reason: "graph-updated" });
592
+ this.lastDef = def;
360
593
  }
361
594
  catch { }
362
595
  });
363
596
  }
364
597
  launch(def, opts) {
365
- if (this.engine) {
366
- throw new Error("Engine already running. Stop the current engine first.");
367
- }
368
- if (this.backend.kind === "local") {
369
- this.build(def);
370
- if (!this.runtime)
371
- throw new Error("Runtime not built");
372
- const rt = this.runtime;
373
- switch (opts.engine) {
374
- case "push":
375
- this.engine = new sparkGraph.PushEngine(rt);
376
- break;
377
- case "batched":
378
- this.engine = new sparkGraph.BatchedEngine(rt, {
379
- flushIntervalMs: opts.batched?.flushIntervalMs ?? 0,
380
- });
381
- break;
382
- case "pull":
383
- this.engine = new sparkGraph.PullEngine(rt);
384
- break;
385
- case "hybrid":
386
- this.engine = new sparkGraph.HybridEngine(rt, {
387
- windowMs: opts.hybrid?.windowMs ?? 250,
388
- batchThreshold: opts.hybrid?.batchThreshold ?? 3,
389
- });
390
- break;
391
- case "step":
392
- this.engine = new sparkGraph.StepEngine(rt);
393
- break;
394
- default:
395
- throw new Error("Unknown engine kind");
396
- }
397
- this.engine.on("value", (e) => this.emit("value", e));
398
- this.engine.on("error", (e) => this.emit("error", e));
399
- this.engine.on("invalidate", (e) => this.emit("invalidate", e));
400
- this.engine.on("stats", (e) => this.emit("stats", e));
401
- this.engine.launch();
402
- this.runningKind = opts.engine;
403
- this.emit("status", { running: true, engine: this.runningKind });
404
- for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
405
- this.engine.setInputs(nodeId, map);
406
- }
407
- return;
408
- }
598
+ super.launch(def, opts);
409
599
  // Remote: build remotely then launch
410
- void this.ensureRemote().then(async (rc) => {
411
- await rc.runner.build(def);
600
+ this.ensureRemoteRunner().then(async (runner) => {
601
+ await runner.build(def);
412
602
  // Signal UI after remote build as well
413
603
  this.emit("invalidate", { reason: "graph-built" });
604
+ this.lastDef = def;
414
605
  // Hydrate current remote inputs/outputs (including defaults) into cache
415
606
  try {
416
- const snap = await rc.runner.snapshot();
607
+ const snap = await runner.snapshot();
417
608
  for (const [nodeId, map] of Object.entries(snap.inputs || {})) {
418
609
  for (const [handle, value] of Object.entries(map || {})) {
419
- rc.valueCache.set(`${nodeId}.${handle}`, {
610
+ this.valueCache.set(`${nodeId}.${handle}`, {
420
611
  io: "input",
421
612
  value,
422
613
  });
@@ -425,7 +616,7 @@ class GraphRunner {
425
616
  }
426
617
  for (const [nodeId, map] of Object.entries(snap.outputs || {})) {
427
618
  for (const [handle, value] of Object.entries(map || {})) {
428
- rc.valueCache.set(`${nodeId}.${handle}`, {
619
+ this.valueCache.set(`${nodeId}.${handle}`, {
429
620
  io: "output",
430
621
  value,
431
622
  });
@@ -436,10 +627,10 @@ class GraphRunner {
436
627
  catch {
437
628
  console.error("Failed to hydrate remote inputs/outputs");
438
629
  }
439
- const eng = rc.runner.getEngine();
440
- if (!rc.listenersBound) {
630
+ const eng = runner.getEngine();
631
+ if (!this.listenersBound) {
441
632
  eng.on("value", (e) => {
442
- rc.valueCache.set(`${e.nodeId}.${e.handle}`, {
633
+ this.valueCache.set(`${e.nodeId}.${e.handle}`, {
443
634
  io: e.io,
444
635
  value: e.value,
445
636
  runtimeTypeId: e.runtimeTypeId,
@@ -449,7 +640,7 @@ class GraphRunner {
449
640
  eng.on("error", (e) => this.emit("error", e));
450
641
  eng.on("invalidate", (e) => this.emit("invalidate", e));
451
642
  eng.on("stats", (e) => this.emit("stats", e));
452
- rc.listenersBound = true;
643
+ this.listenersBound = true;
453
644
  }
454
645
  this.engine = eng;
455
646
  this.engine.launch();
@@ -460,85 +651,18 @@ class GraphRunner {
460
651
  }
461
652
  });
462
653
  }
463
- setInput(nodeId, handle, value) {
464
- if (!this.stagedInputs[nodeId])
465
- this.stagedInputs[nodeId] = {};
466
- this.stagedInputs[nodeId][handle] = value;
467
- if (this.engine) {
468
- this.engine.setInput(nodeId, handle, value);
469
- }
470
- else {
471
- // Emit a value event so UI updates even when engine isn't running
472
- this.emit("value", { nodeId, handle, value, io: "input" });
473
- }
474
- }
475
- // Batch update multiple inputs on a node and trigger a single run
476
- setInputs(nodeId, inputs) {
477
- if (!inputs)
478
- return;
479
- if (!this.stagedInputs[nodeId])
480
- this.stagedInputs[nodeId] = {};
481
- Object.assign(this.stagedInputs[nodeId], inputs);
482
- // Local running: pause, set all inputs, resume, schedule a single recompute
483
- if (this.backend.kind === "local" && this.engine && this.runtime) {
484
- this.engine.setInputs(nodeId, inputs);
485
- }
486
- // Remote running: forward inputs individually (no batch API available)
487
- else if (this.engine &&
488
- this.backend.kind !== "local" &&
489
- this.engine instanceof sparkRemote.RemoteEngine) {
490
- this.engine.setInputs(nodeId, inputs);
491
- }
492
- // Not running: emit value events so UI reflects staged values
493
- else if (!this.engine) {
494
- // Not running: emit a single synthetic value event per handle; UI will coalesce
495
- console.warn("Remote engine does not exists");
496
- for (const [handle, value] of Object.entries(inputs)) {
497
- this.emit("value", { nodeId, handle, value, io: "input" });
498
- }
499
- }
500
- }
501
654
  async step() {
502
- if (this.backend.kind !== "local")
503
- return; // unsupported remotely
504
- const eng = this.engine;
505
- if (eng instanceof sparkGraph.StepEngine)
506
- await eng.step();
655
+ console.warn("Unsupported operation for remote runner");
507
656
  }
508
657
  async computeNode(nodeId) {
509
- if (this.backend.kind !== "local")
510
- return; // unsupported remotely
511
- const eng = this.engine;
512
- if (eng instanceof sparkGraph.PullEngine)
513
- await eng.computeNode(nodeId);
658
+ console.warn("Unsupported operation for remote runner");
514
659
  }
515
660
  flush() {
516
- if (this.backend.kind !== "local")
517
- return; // unsupported remotely
518
- const eng = this.engine;
519
- if (eng instanceof sparkGraph.BatchedEngine)
520
- eng.flush();
661
+ console.warn("Unsupported operation for remote runner");
521
662
  }
522
663
  getOutputs(def) {
523
664
  const out = {};
524
- if (this.backend.kind === "local") {
525
- if (!this.runtime)
526
- return out;
527
- for (const n of def.nodes) {
528
- const desc = this.registry.nodes.get(n.typeId);
529
- const handles = Object.keys(desc?.outputs ?? {});
530
- for (const h of handles) {
531
- const v = this.runtime.getOutput(n.nodeId, h);
532
- if (v !== undefined) {
533
- if (!out[n.nodeId])
534
- out[n.nodeId] = {};
535
- out[n.nodeId][h] = v;
536
- }
537
- }
538
- }
539
- return out;
540
- }
541
- const cache = this.remote?.valueCache;
665
+ const cache = this.valueCache;
542
666
  if (!cache)
543
667
  return out;
544
668
  for (const n of def.nodes) {
@@ -558,31 +682,14 @@ class GraphRunner {
558
682
  }
559
683
  getInputs(def) {
560
684
  const out = {};
561
- if (this.backend.kind === "local") {
562
- for (const n of def.nodes) {
563
- const staged = this.stagedInputs[n.nodeId] ?? {};
564
- const runtimeInputs = this.runtime
565
- ? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
566
- : {};
567
- if (this.isRunning()) {
568
- out[n.nodeId] = runtimeInputs;
569
- }
570
- else {
571
- const merged = { ...runtimeInputs, ...staged };
572
- if (Object.keys(merged).length > 0)
573
- out[n.nodeId] = merged;
574
- }
575
- }
576
- return out;
577
- }
578
- const cache = this.remote?.valueCache;
685
+ const cache = this.valueCache;
579
686
  for (const n of def.nodes) {
580
687
  const staged = this.stagedInputs[n.nodeId] ?? {};
581
688
  const desc = this.registry.nodes.get(n.typeId);
582
689
  const handles = Object.keys(desc?.inputs ?? {});
583
690
  const cur = {};
584
691
  for (const h of handles) {
585
- const rec = cache?.get(`${n.nodeId}.${h}`);
692
+ const rec = cache.get(`${n.nodeId}.${h}`);
586
693
  if (rec && rec.io === "input")
587
694
  cur[h] = rec.value;
588
695
  }
@@ -592,52 +699,21 @@ class GraphRunner {
592
699
  }
593
700
  return out;
594
701
  }
595
- async whenIdle() {
596
- await this.engine?.whenIdle();
597
- }
598
- on(event, handler) {
599
- if (!this.listeners.has(event))
600
- this.listeners.set(event, new Set());
601
- const set = this.listeners.get(event);
602
- set.add(handler);
603
- return () => set.delete(handler);
604
- }
605
- emit(event, payload) {
606
- const set = this.listeners.get(event);
607
- if (set)
608
- for (const h of Array.from(set))
609
- h(payload);
610
- }
611
702
  dispose() {
612
- this.engine?.dispose();
613
- this.engine = undefined;
614
- this.runtime?.dispose();
615
- this.runtime = undefined;
616
- this.remote = undefined;
617
- if (this.runningKind) {
618
- this.runningKind = undefined;
619
- this.emit("status", { running: false, engine: undefined });
620
- }
621
- const kind = this.backend.kind === "local"
622
- ? undefined
623
- : this.backend.kind;
703
+ super.dispose();
704
+ this.runner = undefined;
705
+ this.transport = undefined;
624
706
  this.emit("transport", {
625
- state: this.backend.kind === "local" ? "local" : "disconnected",
626
- kind,
707
+ state: "disconnected",
708
+ kind: this.backend.kind,
627
709
  });
628
710
  }
629
- isRunning() {
630
- return !!this.engine;
631
- }
632
- getRunningEngine() {
633
- return this.runningKind;
634
- }
635
711
  // Ensure remote transport/runner
636
- async ensureRemote() {
637
- if (this.remote)
638
- return this.remote;
712
+ async ensureRemoteRunner() {
713
+ if (this.runner)
714
+ return this.runner;
639
715
  let transport;
640
- const kind = this.backend.kind === "remote-http" ? "remote-http" : "remote-ws";
716
+ const kind = this.backend.kind;
641
717
  this.emit("transport", { state: "connecting", kind });
642
718
  if (this.backend.kind === "remote-http") {
643
719
  if (!sparkRemote.HttpPollingTransport)
@@ -655,14 +731,12 @@ class GraphRunner {
655
731
  throw new Error("Remote backend not configured");
656
732
  }
657
733
  const runner = new sparkRemote.RemoteRunner(transport);
658
- this.remote = {
659
- runner,
660
- transport,
661
- valueCache: new Map(),
662
- listenersBound: false,
663
- };
734
+ this.runner = runner;
735
+ this.transport = transport;
736
+ this.valueCache.clear();
737
+ this.listenersBound = false;
664
738
  this.emit("transport", { state: "connected", kind });
665
- return this.remote;
739
+ return runner;
666
740
  }
667
741
  }
668
742
 
@@ -1335,6 +1409,23 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1335
1409
  });
1336
1410
  const off7 = wb.on("error", add("workbench", "error"));
1337
1411
  wb.refreshValidation();
1412
+ // Registry updates: swap registry and refresh graph validation/UI
1413
+ const offReg = runner.on("registry", (newReg) => {
1414
+ try {
1415
+ setRegistry(newReg);
1416
+ wb.setRegistry(newReg);
1417
+ // Trigger a graph update so the UI revalidates with new types/enums/nodes
1418
+ try {
1419
+ runner.update(wb.export());
1420
+ }
1421
+ catch {
1422
+ console.error("Failed to update graph definition after registry changed");
1423
+ }
1424
+ }
1425
+ catch {
1426
+ console.error("Failed to handle registry changed event");
1427
+ }
1428
+ });
1338
1429
  return () => {
1339
1430
  off1();
1340
1431
  off2();
@@ -1347,6 +1438,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1347
1438
  off5b();
1348
1439
  off6();
1349
1440
  off7();
1441
+ offReg();
1350
1442
  };
1351
1443
  }, [runner, wb]);
1352
1444
  // Push incremental updates into running engine without full reload
@@ -1528,7 +1620,7 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
1528
1620
  return (jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Events" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: hideWorkbench, onChange: (e) => onHideWorkbenchChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Hide workbench" })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: autoScroll, onChange: (e) => onAutoScrollChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Auto scroll" })] }), jsxRuntime.jsx("button", { onClick: clearEvents, className: "text-xs px-2 py-0.5 border border-gray-300 rounded", children: "Clear" })] })] }), jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto text-[11px] leading-4 divide-y divide-gray-200", children: rows.map((ev, idx) => (jsxRuntime.jsxs("div", { className: "opacity-85 odd:bg-gray-50 px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx("span", { className: "w-8 shrink-0 text-right text-gray-500 select-none", children: idx + 1 }), jsxRuntime.jsxs("span", { className: "text-gray-500", children: [new Date(ev.at).toLocaleTimeString(), " \u00B7 ", ev.source, ":", ev.type] })] }), jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap ml-10", children: renderPayload(ev.payload) })] }, `${ev.at}:${idx}`))) })] }));
1529
1621
  }
1530
1622
 
1531
- function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, setInput, }) {
1623
+ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, contextPanel, setInput, }) {
1532
1624
  const safeToString = (typeId, value) => {
1533
1625
  try {
1534
1626
  if (typeof toString === "function") {
@@ -1617,7 +1709,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
1617
1709
  setOriginals(nextOriginals);
1618
1710
  }, [selectedNodeId, selectedDesc, valuesTick]);
1619
1711
  const widthClass = debug ? "w-[480px]" : "w-[320px]";
1620
- return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedEdge.typeId] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.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 ??
1712
+ return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && (jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel })), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedEdge.typeId] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.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 ??
1621
1713
  selectedNodeStatus.lastError) }))] })), jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
1622
1714
  const typeId = sparkGraph.getInputTypeId(selectedDesc?.inputs, h);
1623
1715
  const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
@@ -1722,10 +1814,8 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
1722
1814
  const vIssues = validation.inputs.filter((v) => v.handle === entry.id);
1723
1815
  const hasAny = vIssues.length > 0;
1724
1816
  const hasErr = vIssues.some((v) => v.level === "error");
1725
- const title = vIssues
1726
- .map((v) => `${v.code}: ${v.message}`)
1727
- .join("; ");
1728
- return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "target", position: ReactFlow.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
1817
+ const title = vIssues.map((v) => `${v.code}: ${v.message}`).join("; ");
1818
+ return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(react$1.Handle, { id: entry.id, type: "target", position: react$1.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
1729
1819
  top: topFor(i) - 8,
1730
1820
  right: "50%",
1731
1821
  whiteSpace: "nowrap",
@@ -1736,11 +1826,9 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
1736
1826
  const vIssues = validation.outputs.filter((v) => v.handle === entry.id);
1737
1827
  const hasAny = vIssues.length > 0;
1738
1828
  const hasErr = vIssues.some((v) => v.level === "error");
1739
- const title = vIssues
1740
- .map((v) => `${v.code}: ${v.message}`)
1741
- .join("; ");
1829
+ const title = vIssues.map((v) => `${v.code}: ${v.message}`).join("; ");
1742
1830
  const resolved = resolveOutputDisplay(outputValues?.[entry.id], entry.typeId);
1743
- return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "source", position: ReactFlow.Position.Right, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400 !rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { right: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute right-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
1831
+ return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(react$1.Handle, { id: entry.id, type: "source", position: react$1.Position.Right, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400 !rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { right: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute right-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
1744
1832
  top: topFor(i) - 8,
1745
1833
  textAlign: "right",
1746
1834
  left: "50%",
@@ -1754,7 +1842,7 @@ DefaultNode.displayName = "DefaultNode";
1754
1842
 
1755
1843
  function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
1756
1844
  const { registry } = useWorkbenchContext();
1757
- const rf = ReactFlow.useReactFlow();
1845
+ const rf = react$1.useReactFlow();
1758
1846
  const ids = Array.from(registry.nodes.keys());
1759
1847
  const [query, setQuery] = React.useState("");
1760
1848
  const q = query.trim().toLowerCase();
@@ -1961,7 +2049,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, r
1961
2049
  selectedNodeIds: new Set(sel.nodes),
1962
2050
  selectedEdgeIds: new Set(sel.edges),
1963
2051
  });
1964
- console.info(nodeTypes, out);
2052
+ // console.info(nodeTypes, out);
1965
2053
  return out;
1966
2054
  }, [
1967
2055
  showValues,
@@ -2003,7 +2091,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, r
2003
2091
  const addNodeAt = (typeId, pos) => {
2004
2092
  wb.addNode({ typeId, position: pos });
2005
2093
  };
2006
- return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, onInit: (inst) => (rfInstanceRef.current = inst), children: [jsxRuntime.jsx(ReactFlow.Background, {}), jsxRuntime.jsx(ReactFlow.MiniMap, {}), jsxRuntime.jsx(ReactFlow.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: () => setMenuOpen(false) }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: () => setNodeMenuOpen(false) })] }) }));
2094
+ return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(react$1.ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, onInit: (inst) => (rfInstanceRef.current = inst), children: [jsxRuntime.jsx(react$1.Background, {}), jsxRuntime.jsx(react$1.MiniMap, {}), jsxRuntime.jsx(react$1.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: () => setMenuOpen(false) }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: () => setNodeMenuOpen(false) })] }) }));
2007
2095
  });
2008
2096
 
2009
2097
  function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
@@ -2080,7 +2168,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2080
2168
  runAutoLayout();
2081
2169
  };
2082
2170
  onInit({ wb, runner, setInitialGraph });
2083
- }, [onInit, wb, runner, runAutoLayout]);
2171
+ }, [onInit, wb, runner, runAutoLayout, registry, setRegistry]);
2084
2172
  // Expose change callback on graph/value changes
2085
2173
  React.useEffect(() => {
2086
2174
  if (!onChange)
@@ -2235,6 +2323,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2235
2323
  return;
2236
2324
  if (runner.isRunning())
2237
2325
  return;
2326
+ // Only auto-launch for local backend; require explicit Start for remote
2327
+ if (backendKind !== "local")
2328
+ return;
2238
2329
  const d = wb.export();
2239
2330
  if (!d.nodes || d.nodes.length === 0)
2240
2331
  return;
@@ -2247,14 +2338,14 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2247
2338
  catch {
2248
2339
  // ignore
2249
2340
  }
2250
- }, [engine, runner, wb]);
2341
+ }, [engine, runner, wb, backendKind]);
2251
2342
  // When switching to remote backend, auto-hydrate registry from backend
2252
2343
  React.useEffect(() => {
2253
2344
  if (backendKind === "remote-http" && httpBaseUrl) {
2254
- void hydrateFromBackend("remote-http", httpBaseUrl);
2345
+ hydrateFromBackend("remote-http", httpBaseUrl);
2255
2346
  }
2256
2347
  else if (backendKind === "remote-ws" && wsUrl) {
2257
- void hydrateFromBackend("remote-ws", wsUrl);
2348
+ hydrateFromBackend("remote-ws", wsUrl);
2258
2349
  }
2259
2350
  }, [backendKind, httpBaseUrl, wsUrl, hydrateFromBackend]);
2260
2351
  React.useEffect(() => {
@@ -2441,18 +2532,22 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2441
2532
  catch (err) {
2442
2533
  alert(String(err?.message ?? err));
2443
2534
  }
2444
- }, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement })] })] }));
2535
+ }, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement, contextPanel: overrides?.contextPanel })] })] }));
2445
2536
  }
2446
2537
  function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, overrides, onInit, onChange, }) {
2447
2538
  const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
2448
2539
  const [wb] = React.useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
2449
2540
  const runner = React.useMemo(() => {
2450
- const backend = backendKind === "remote-http"
2451
- ? { kind: "remote-http", baseUrl: httpBaseUrl }
2452
- : backendKind === "remote-ws"
2453
- ? { kind: "remote-ws", url: wsUrl }
2454
- : { kind: "local" };
2455
- return new GraphRunner(registry, backend);
2541
+ if (backendKind === "remote-http") {
2542
+ return new RemoteGraphRunner(registry, {
2543
+ kind: "remote-http",
2544
+ baseUrl: httpBaseUrl,
2545
+ });
2546
+ }
2547
+ if (backendKind === "remote-ws") {
2548
+ return new RemoteGraphRunner(registry, { kind: "remote-ws", url: wsUrl });
2549
+ }
2550
+ return new LocalGraphRunner(registry);
2456
2551
  }, [registry, backendKind, httpBaseUrl, wsUrl]);
2457
2552
  // Allow external UI registration (e.g., node renderers) with access to wb
2458
2553
  React.useEffect(() => {
@@ -2470,9 +2565,10 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
2470
2565
  exports.AbstractWorkbench = AbstractWorkbench;
2471
2566
  exports.CLIWorkbench = CLIWorkbench;
2472
2567
  exports.DefaultUIExtensionRegistry = DefaultUIExtensionRegistry;
2473
- exports.GraphRunner = GraphRunner;
2474
2568
  exports.InMemoryWorkbench = InMemoryWorkbench;
2475
2569
  exports.Inspector = Inspector;
2570
+ exports.LocalGraphRunner = LocalGraphRunner;
2571
+ exports.RemoteGraphRunner = RemoteGraphRunner;
2476
2572
  exports.WorkbenchCanvas = WorkbenchCanvas;
2477
2573
  exports.WorkbenchContext = WorkbenchContext;
2478
2574
  exports.WorkbenchProvider = WorkbenchProvider;