langflower 0.0.9 → 0.0.10

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 (91) hide show
  1. package/package.json +2 -2
  2. package/ui-dist/index.html +2 -2
  3. package/ui-dist/main-Z7CPFXFA.js +313 -0
  4. package/ui-dist/styles-2TMDAUD7.css +1 -0
  5. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-reducer.js +0 -12
  6. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.d.ts +2 -7
  7. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.js +1 -1
  8. package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.d.ts +0 -3
  9. package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.js +22 -36
  10. package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.d.ts +1 -7
  11. package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.js +12 -35
  12. package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.d.ts +3 -10
  13. package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.js +9 -14
  14. package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.d.ts +0 -3
  15. package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.js +2 -15
  16. package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.d.ts +1 -0
  17. package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.js +1 -0
  18. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.d.ts +1 -1
  19. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.js +1 -1
  20. package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.d.ts +1 -0
  21. package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.js +1 -0
  22. package/vendor/common-nodes/dist/ai/nodes/critique/node.d.ts +3 -3
  23. package/vendor/common-nodes/dist/ai/nodes/critique/node.js +5 -18
  24. package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.d.ts +3 -3
  25. package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.js +3 -10
  26. package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.d.ts +3 -3
  27. package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.js +2 -9
  28. package/vendor/common-nodes/dist/ai/nodes/review/node.d.ts +3 -3
  29. package/vendor/common-nodes/dist/ai/nodes/review/node.js +5 -18
  30. package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.d.ts +5 -5
  31. package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.js +211 -96
  32. package/vendor/common-nodes/dist/catalog.js +2 -0
  33. package/vendor/common-nodes/dist/mcp/mcp-http/node.d.ts +4 -4
  34. package/vendor/common-nodes/dist/mcp/mcp-http/node.js +9 -10
  35. package/vendor/common-nodes/dist/mcp/mcp-stdio/node.d.ts +4 -4
  36. package/vendor/common-nodes/dist/mcp/mcp-stdio/node.js +9 -10
  37. package/vendor/common-nodes/dist/tools/collect-agent-tool-handles.d.ts +4 -4
  38. package/vendor/common-nodes/dist/tools/collect-agent-tool-handles.js +3 -11
  39. package/vendor/common-nodes/dist/tools/inventory-tool-round.d.ts +0 -10
  40. package/vendor/common-nodes/dist/tools/inventory-tool-round.js +0 -84
  41. package/vendor/common-nodes/dist/tools/tool-collection/node.d.ts +20 -0
  42. package/vendor/common-nodes/dist/tools/tool-collection/node.js +42 -0
  43. package/vendor/common-nodes/package.json +1 -5
  44. package/vendor/compiler/package.json +1 -1
  45. package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.d.ts +3 -7
  46. package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.js +4 -44
  47. package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.d.ts +3 -5
  48. package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.js +3 -5
  49. package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.d.ts +1 -0
  50. package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.js +1 -0
  51. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.d.ts +3 -4
  52. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.js +1 -1
  53. package/vendor/node-sdk/dist/node-factory/define-reactive-node/types.d.ts +2 -4
  54. package/vendor/node-sdk/dist/node-factory/define-reactive-node/ui-schema-inference.d.ts +1 -1
  55. package/vendor/node-sdk/package.json +1 -1
  56. package/vendor/runtime/dist/port-feed-override.d.ts +13 -0
  57. package/vendor/runtime/dist/port-feed-override.js +21 -0
  58. package/vendor/runtime/dist/port-signal-from-response.d.ts +15 -0
  59. package/vendor/runtime/dist/port-signal-from-response.js +48 -0
  60. package/vendor/runtime/dist/runtime-runner.d.ts +8 -3
  61. package/vendor/runtime/dist/runtime-runner.js +102 -81
  62. package/vendor/runtime/dist/runtime.d.ts +3 -2
  63. package/vendor/runtime/dist/runtime.js +1 -1
  64. package/vendor/runtime/dist/testing/workflows/workflow-events.d.ts +2 -0
  65. package/vendor/runtime/dist/testing/workflows/workflow-events.js +7 -5
  66. package/vendor/runtime/dist/types.d.ts +25 -8
  67. package/vendor/runtime/dist/types.js +3 -0
  68. package/vendor/runtime/package.json +1 -1
  69. package/vendor/server/dist/bridge/build-execution-context.js +3 -3
  70. package/vendor/server/dist/bridge/get-live-wired-tools.d.ts +3 -2
  71. package/vendor/server/dist/bridge/get-live-wired-tools.js +8 -5
  72. package/vendor/server/dist/bridge/wire-runner-handlers.js +2 -2
  73. package/vendor/server/dist/bridge/wire-workflow-handlers.js +23 -9
  74. package/vendor/server/dist/checkpoint/run-checkpoint-session.js +3 -3
  75. package/vendor/server/dist/session/reset-session-execution-feed.d.ts +6 -0
  76. package/vendor/server/dist/session/reset-session-execution-feed.js +8 -0
  77. package/vendor/server/skeleton/nodes/my-nodes/package.json +1 -1
  78. package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +36 -22
  79. package/vendor/server/skeleton/skills/langflower-helper/architecture.md +7 -1
  80. package/vendor/server/skeleton/skills/langflower-helper/layout.md +3 -2
  81. package/vendor/server/skeleton/skills/langflower-workflow-writer/SKILL.md +12 -8
  82. package/vendor/server/skeleton/workflows/kb-create.json +15 -43
  83. package/vendor/server/skeleton/workflows/kb-navigate.json +8 -22
  84. package/vendor/server/skeleton/workflows/simple-coder.json +18 -46
  85. package/vendor/server/skeleton/workflows/starter.json +8 -22
  86. package/vendor/shared/dist/execution/derive-run-settle-outcome.js +2 -2
  87. package/vendor/shared/dist/execution/derive-run-settle-outcome.test.js +11 -8
  88. package/vendor/shared/dist/langflower-ws-waits.d.ts +3 -1
  89. package/vendor/shared/dist/langflower-ws-waits.js +2 -2
  90. package/ui-dist/main-2JJGCNTD.js +0 -313
  91. package/ui-dist/styles-EOXODOI7.css +0 -1
@@ -1,60 +1,24 @@
1
- import { combineStatefulObservables, isError, isInactive, isLoading, isSuccess, statefulObservable, } from '@rx-evo/stateful-observable';
1
+ import { combineStatefulObservables, serializeResponse, statefulObservable, } from '@rx-evo/stateful-observable';
2
2
  import { BehaviorSubject, filter, merge, of, Subject, Subscription, tap, zip, } from 'rxjs';
3
3
  import { checkpointPortIdForSlot, getBypassConnection, isBypassPort, } from './bypass-ports.js';
4
4
  import { normalizePortErrorValue } from './normalize-port-error-value.js';
5
5
  import { edgePortSlotKey, parseSlotKey, slotKey, } from './port-meta.js';
6
6
  import { clusterHasChatEntry, collectClusterSlotKeys, } from './runtime-helpers.js';
7
+ const toResponseDto = (response) => {
8
+ if (response === null || response === undefined) {
9
+ return null;
10
+ }
11
+ return serializeResponse(response);
12
+ };
7
13
  const isConnectionInactive = (connection) => {
8
14
  let inactive = true;
9
15
  const sub = connection.raw$.subscribe((raw) => {
10
- // Same dual-package Symbol hazard as loading (BUG-2026-07-23 /
11
- // ADR-028): CJS `isInactive` can miss an ESM inactive sentinel.
12
- inactive = isInactive(raw) || isSymbolStateSentinel(raw);
16
+ const dto = toResponseDto(raw);
17
+ inactive = dto === null || 'inactive' in dto;
13
18
  });
14
19
  sub.unsubscribe();
15
20
  return inactive;
16
21
  };
17
- /**
18
- * `@rx-evo` status sentinels are `{ state: Symbol(...) }` (loading / inactive).
19
- * When the sentinel is created by a different module instance than the
20
- * `isLoading` / `isInactive` guards (CJS/ESM dual-package hazard), identity
21
- * checks fail and `isSuccess` may return true — telemetry then emits
22
- * `state:'value'` with that object (JSON → `{}`), or `applyPortDefaults`
23
- * skips seeding and LLM `combineInputs` stalls.
24
- */
25
- const isSymbolStateSentinel = (signal) => {
26
- if (typeof signal !== 'object' ||
27
- signal === null ||
28
- Array.isArray(signal)) {
29
- return false;
30
- }
31
- const keys = Object.keys(signal);
32
- if (keys.length !== 1 || keys[0] !== 'state') {
33
- return false;
34
- }
35
- const state = signal.state;
36
- return typeof state === 'symbol';
37
- };
38
- const resolveSignalState = (response) => {
39
- const signal = response;
40
- if (isError(signal)) {
41
- return {
42
- state: 'error',
43
- value: normalizePortErrorValue(signal.error),
44
- };
45
- }
46
- if (isLoading(signal) || isSymbolStateSentinel(signal)) {
47
- return { state: 'pending', value: undefined };
48
- }
49
- if (isSuccess(signal)) {
50
- return { state: 'value', value: signal };
51
- }
52
- return null;
53
- };
54
- /** Drop errors and loading so edges only carry success values. */
55
- const isEdgeForwardable = (response) => !isError(response) &&
56
- !isLoading(response) &&
57
- !isSymbolStateSentinel(response);
58
22
  export class RuntimeRunner {
59
23
  editor;
60
24
  static EMPTY_EVENT_LOG = [];
@@ -63,6 +27,8 @@ export class RuntimeRunner {
63
27
  logEvents;
64
28
  eventLogBuffer = [];
65
29
  activeRun;
30
+ pendingWireRunId;
31
+ pendingWire;
66
32
  disposed = false;
67
33
  status$ = this.statusSubject.asObservable();
68
34
  /** Synchronous snapshot of {@link status$}. */
@@ -143,6 +109,7 @@ export class RuntimeRunner {
143
109
  this.finishRun(resolvedRunId);
144
110
  return resolvedRunId;
145
111
  }
112
+ this.assertResumeOutputSnapshots(completedNodeIds, outputSnapshots);
146
113
  return this.runScope(new Set(nodes.map((node) => node.nodeId)), new Set(edges.map((edge) => edge.edgeId)), options.initialPayload, resolvedRunId, {
147
114
  completedNodeIds,
148
115
  outputSnapshots,
@@ -169,6 +136,7 @@ export class RuntimeRunner {
169
136
  return false;
170
137
  }
171
138
  const runId = this.runScope(cluster.nodeIds, cluster.edgeIds);
139
+ this.flushPendingWire();
172
140
  const pushedRunId = this.pushIntoActiveRun(cfg);
173
141
  if (pushedRunId === false) {
174
142
  this.interrupt('cancel');
@@ -181,6 +149,7 @@ export class RuntimeRunner {
181
149
  if (this.statusSubject.value !== 'running') {
182
150
  return;
183
151
  }
152
+ this.cancelPendingWire();
184
153
  this.teardownRun();
185
154
  this.setStatus('stopped');
186
155
  }
@@ -206,6 +175,7 @@ export class RuntimeRunner {
206
175
  return;
207
176
  }
208
177
  if (this.statusSubject.value === 'running') {
178
+ this.cancelPendingWire();
209
179
  this.teardownRun();
210
180
  }
211
181
  this.disposed = true;
@@ -239,18 +209,26 @@ export class RuntimeRunner {
239
209
  }
240
210
  this.eventsSubject.next(event);
241
211
  }
242
- emitPortEvent(_runId, portDir, nodeId, portId, portIdx, state, value, edgeIds, feed) {
212
+ emitPortEvent(_runId, portDir, nodeId, portId, portIdx, raw, edgeIds, defaultFeed) {
213
+ const dto = toResponseDto(raw);
214
+ if (dto === null) {
215
+ return false;
216
+ }
217
+ const response = 'error' in dto
218
+ ? { error: normalizePortErrorValue(dto.error) }
219
+ : dto;
220
+ const feed = defaultFeed ?? null;
243
221
  const portIdStr = typeof portId === 'string' ? portId : String(portId);
244
222
  this.emitRunnerEvent([
245
223
  portDir,
246
224
  nodeId,
247
225
  portIdStr,
248
- state,
249
- value,
226
+ response,
250
227
  portIdx,
251
228
  edgeIds,
252
- feed ?? null,
229
+ feed,
253
230
  ]);
231
+ return 'value' in response;
254
232
  }
255
233
  canPreparePushedInput(node, portId) {
256
234
  const connection = node.inputs[portId];
@@ -268,6 +246,7 @@ export class RuntimeRunner {
268
246
  edge.toPort[1] === 0);
269
247
  }
270
248
  pushIntoActiveRun(cfg) {
249
+ this.flushPendingWire();
271
250
  const run = this.activeRun;
272
251
  if (run === undefined || !run.scopeNodeIds.has(cfg.nodeId)) {
273
252
  return false;
@@ -281,7 +260,7 @@ export class RuntimeRunner {
281
260
  if (existing !== undefined) {
282
261
  // Telemetry before deliver so HITL input-received precedes cascade
283
262
  // (output-emitted / downstream input-received) in the event log.
284
- this.emitPortEvent(run.runId, 'in', cfg.nodeId, cfg.portId, 0, 'value', cfg.payload, []);
263
+ this.emitPortEvent(run.runId, 'in', cfg.nodeId, cfg.portId, 0, cfg.payload, []);
285
264
  existing.source.next(cfg.payload);
286
265
  return run.runId;
287
266
  }
@@ -300,15 +279,15 @@ export class RuntimeRunner {
300
279
  connection,
301
280
  });
302
281
  run.pushedInputSources.set(key, { source });
303
- this.emitPortEvent(run.runId, 'in', cfg.nodeId, cfg.portId, 0, 'value', cfg.payload, []);
282
+ this.emitPortEvent(run.runId, 'in', cfg.nodeId, cfg.portId, 0, cfg.payload, []);
304
283
  source.next(cfg.payload);
305
284
  return run.runId;
306
285
  }
307
286
  /**
308
287
  * Returns `output` wrapped with a `tap` that emits `output-emitted`
309
- * telemetry, then `filter(isEdgeForwardable)` so `ResponseError` and
310
- * loading sentinels stay visible to Observability on the source node but
311
- * are not forwarded on edges (loading must not appear as `{}` downstream).
288
+ * telemetry, then forward only success values on edges so errors and
289
+ * loading sentinels stay visible on the source node (loading must not
290
+ * appear as `{}` downstream).
312
291
  * The wrapper is the value the downstream connects to, so the event fires
313
292
  * *as part of* the dataflow delivery — there is no separate refCounted
314
293
  * subscriber, which is what previously reordered events against downstream
@@ -322,12 +301,8 @@ export class RuntimeRunner {
322
301
  return output
323
302
  .pipe(tap({
324
303
  next: (response) => {
325
- const resolved = resolveSignalState(response);
326
- if (resolved === null) {
327
- return;
328
- }
329
- this.emitPortEvent(run.runId, 'out', nodeId, portId, 0, resolved.state, resolved.value, edgeIds, output.meta.feed);
330
- if (resolved.state === 'value' &&
304
+ const emittedValue = this.emitPortEvent(run.runId, 'out', nodeId, portId, 0, response, edgeIds, output.meta.feed);
305
+ if (emittedValue &&
331
306
  node !== false &&
332
307
  node.stopsRun === true) {
333
308
  const finishRunId = run.runId;
@@ -338,7 +313,10 @@ export class RuntimeRunner {
338
313
  });
339
314
  }
340
315
  },
341
- }), filter(isEdgeForwardable))
316
+ }), filter((response) => {
317
+ const dto = toResponseDto(response);
318
+ return dto !== null && 'value' in dto;
319
+ }))
342
320
  .with({ meta: output.meta });
343
321
  }
344
322
  /**
@@ -353,11 +331,7 @@ export class RuntimeRunner {
353
331
  return source
354
332
  .pipe(tap({
355
333
  next: (response) => {
356
- const resolved = resolveSignalState(response);
357
- if (resolved === null) {
358
- return;
359
- }
360
- this.emitPortEvent(run.runId, 'in', nodeId, portId, slotIndex, resolved.state, resolved.value, edgeIds);
334
+ this.emitPortEvent(run.runId, 'in', nodeId, portId, slotIndex, response, edgeIds);
361
335
  },
362
336
  }))
363
337
  .with({ meta: source.meta });
@@ -373,25 +347,76 @@ export class RuntimeRunner {
373
347
  this.setStatus('idle');
374
348
  this.teardownRun();
375
349
  }
350
+ assertResumeOutputSnapshots(completedNodeIds, outputSnapshots) {
351
+ const nodesById = new Map(this.editor.getNodes().map((node) => [node.nodeId, node]));
352
+ for (const edge of this.editor.getEdges()) {
353
+ if (!completedNodeIds.has(edge.fromNodeId) ||
354
+ completedNodeIds.has(edge.toNodeId)) {
355
+ continue;
356
+ }
357
+ const fromNode = nodesById.get(edge.fromNodeId);
358
+ if (fromNode === undefined) {
359
+ continue;
360
+ }
361
+ const [fromPortId, fromSlotIndex] = edge.fromPort;
362
+ const snapshotPortId = checkpointPortIdForSlot(fromNode, fromPortId, fromSlotIndex);
363
+ const snapshotValue = outputSnapshots
364
+ .get(edge.fromNodeId)
365
+ ?.get(snapshotPortId);
366
+ if (snapshotValue === undefined) {
367
+ throw new Error(`Resume missing output snapshot for ${edge.fromNodeId}.${fromPortId}`);
368
+ }
369
+ }
370
+ }
371
+ cancelPendingWire() {
372
+ this.pendingWire = undefined;
373
+ this.pendingWireRunId = undefined;
374
+ }
375
+ flushPendingWire() {
376
+ const wire = this.pendingWire;
377
+ if (wire === undefined) {
378
+ return;
379
+ }
380
+ this.pendingWire = undefined;
381
+ this.pendingWireRunId = undefined;
382
+ wire();
383
+ }
376
384
  runScope(scopeNodeIds, scopeEdgeIds, initialPayload, runId, resumeOverlay) {
377
385
  this.assertNotDisposed();
378
386
  if (this.statusSubject.value === 'running') {
379
387
  return false;
380
388
  }
381
389
  const resolvedRunId = runId ?? crypto.randomUUID();
382
- this.editor.setLocked(true);
383
- try {
384
- this.activeRun = this.wireScope(resolvedRunId, scopeEdgeIds, scopeNodeIds, initialPayload, resumeOverlay);
385
- }
386
- catch (error) {
387
- this.editor.setLocked(false);
388
- throw error;
389
- }
390
390
  this.setStatus('running');
391
+ this.editor.setLocked(true);
392
+ this.pendingWireRunId = resolvedRunId;
393
+ this.pendingWire = () => {
394
+ try {
395
+ this.activeRun = this.wireScope(resolvedRunId, scopeEdgeIds, scopeNodeIds, initialPayload, resumeOverlay);
396
+ }
397
+ catch (error) {
398
+ this.editor.setLocked(false);
399
+ this.setStatus('idle');
400
+ throw error;
401
+ }
402
+ };
403
+ queueMicrotask(() => {
404
+ if (this.pendingWireRunId !== resolvedRunId) {
405
+ return;
406
+ }
407
+ try {
408
+ this.flushPendingWire();
409
+ }
410
+ catch {
411
+ // Status/lock already restored in pendingWire.
412
+ }
413
+ });
391
414
  return resolvedRunId;
392
415
  }
393
416
  teardownRun() {
417
+ this.cancelPendingWire();
394
418
  if (this.activeRun === undefined) {
419
+ this.editor.setLocked(false);
395
420
  return;
396
421
  }
397
422
  for (const wired of this.activeRun.wiredSlots) {
@@ -429,7 +454,7 @@ export class RuntimeRunner {
429
454
  connection.connect(of(seed.value).pipe(tap({
430
455
  next: (value) => {
431
456
  if (typeof seed.portId !== 'symbol') {
432
- this.emitPortEvent(runId, 'in', nodeId, seed.portId, seed.slotIndex, 'value', value, []);
457
+ this.emitPortEvent(runId, 'in', nodeId, seed.portId, seed.slotIndex, value, []);
433
458
  }
434
459
  },
435
460
  })));
@@ -467,7 +492,7 @@ export class RuntimeRunner {
467
492
  }
468
493
  connection.connect(of(defaultValue).pipe(tap({
469
494
  next: (value) => {
470
- this.emitPortEvent(runId, 'in', nodeId, portId, 0, 'value', value, []);
495
+ this.emitPortEvent(runId, 'in', nodeId, portId, 0, value, []);
471
496
  },
472
497
  })));
473
498
  wiredSlotKeys.add(key);
@@ -701,11 +726,7 @@ export class RuntimeRunner {
701
726
  }).with({ meta });
702
727
  group.connection.connect(combined.pipe(tap({
703
728
  next: (response) => {
704
- const resolved = resolveSignalState(response);
705
- if (resolved === null) {
706
- return;
707
- }
708
- this.emitPortEvent(run.runId, 'in', group.nodeId, group.portId, 0, resolved.state, resolved.value, group.edges.map((entry) => entry.edge.edgeId));
729
+ this.emitPortEvent(run.runId, 'in', group.nodeId, group.portId, 0, response, group.edges.map((entry) => entry.edge.edgeId));
709
730
  },
710
731
  })));
711
732
  wiredSlots.push({
@@ -6,9 +6,10 @@ export { RuntimeEditor } from './runtime-editor.js';
6
6
  export { graphHasCycle } from './runtime-helpers.js';
7
7
  export type { GraphCluster } from './runtime-helpers.js';
8
8
  export { RuntimeRunner } from './runtime-runner.js';
9
- export type { MetaFromStatefulObservable, NodeId, EdgeId, RunId, PortMeta, RuntimeEdge, RuntimeEditorApi, RuntimeFeedPortMeta, RuntimeFeedRole, PortTelemetry, RuntimeDoneTelemetry, RuntimeNode, RuntimePortSignalState, RuntimeResumeOptions, RuntimeRunnerApi, RuntimeRunnerEvent, RuntimeRunnerStatus, RuntimeSeedPortValue, RuntimeWireType, SwapNodeResult, } from './types.js';
10
- export { isPortTelemetry, isRuntimeDone } from './types.js';
9
+ export type { MetaFromStatefulObservable, NodeId, EdgeId, RunId, PortMeta, RuntimeEdge, RuntimeEditorApi, RuntimeFeedPortMeta, RuntimeFeedRole, PortTelemetry, RuntimeDoneTelemetry, RuntimeNode, RuntimeResumeOptions, RuntimeRunnerApi, RuntimeRunnerEvent, RuntimeRunnerStatus, RuntimeSeedPortValue, RuntimeWireType, SwapNodeResult, } from './types.js';
10
+ export { isPortErrorTelemetry, isPortPendingTelemetry, isPortTelemetry, isPortValueTelemetry, isRuntimeDone, } from './types.js';
11
11
  export type { RuntimeOptions };
12
+ export type { ResponseDto } from '@rx-evo/stateful-observable';
12
13
  export declare class RuntimeFacade {
13
14
  readonly editor: RuntimeEditor;
14
15
  readonly runner: RuntimeRunner;
@@ -4,7 +4,7 @@ export { bypassOutputPortId, parseBypassOutputPortId } from './bypass-ports.js';
4
4
  export { RuntimeEditor } from './runtime-editor.js';
5
5
  export { graphHasCycle } from './runtime-helpers.js';
6
6
  export { RuntimeRunner } from './runtime-runner.js';
7
- export { isPortTelemetry, isRuntimeDone } from './types.js';
7
+ export { isPortErrorTelemetry, isPortPendingTelemetry, isPortTelemetry, isPortValueTelemetry, isRuntimeDone, } from './types.js';
8
8
  export class RuntimeFacade {
9
9
  editor;
10
10
  runner;
@@ -20,3 +20,5 @@ export declare function runAndCollectEvents(runtime: RuntimeRunner | RuntimeHarn
20
20
  export declare function noDoneWithin(runtime: RuntimeRunner | RuntimeHarness, ms: number, runId?: RunId): Promise<boolean>;
21
21
  export declare const edgeIdsFromPortEvent: (event: PortTelemetry) => readonly EdgeId[];
22
22
  export declare const portDirLabel: (event: PortTelemetry) => "input-received" | "output-emitted";
23
+ export declare const dtoHas: (dtos: readonly unknown[], key: "pending" | "value" | "error" | "inactive") => boolean;
24
+ export declare const dtoIndex: (dtos: readonly unknown[], key: "pending" | "value" | "error" | "inactive") => number;
@@ -1,5 +1,5 @@
1
1
  import { filter, firstValueFrom } from 'rxjs';
2
- import { isPortTelemetry, isRuntimeDone, } from '../../types.js';
2
+ import { isPortValueTelemetry, isRuntimeDone, } from '../../types.js';
3
3
  import { RuntimeEditor } from '../../runtime-editor.js';
4
4
  import { RuntimeRunner } from '../../runtime-runner.js';
5
5
  export function createRuntimeHarness(options) {
@@ -18,13 +18,13 @@ export function wireEdge(editor, edge) {
18
18
  throw new Error(`Failed to wire ${edge.fromNodeId}.${edge.fromPort[0]} → ${edge.toNodeId}.${edge.toPort[0]}`);
19
19
  }
20
20
  }
21
- const isOutputValue = (event) => isPortTelemetry(event) && event[0] === 'out' && event[3] === 'value';
21
+ const isOutputValue = (event) => isPortValueTelemetry(event) && event[0] === 'out';
22
22
  export function outputValues(events, nodeId, portId, _runId) {
23
23
  return events
24
24
  .filter((event) => isOutputValue(event) &&
25
25
  event[1] === nodeId &&
26
26
  event[2] === portId)
27
- .map((event) => event[4]);
27
+ .map((event) => event[3].value);
28
28
  }
29
29
  export async function waitForOutput(runtime, nodeId, portId, _runId) {
30
30
  return waitForOutputMatching(runtime, nodeId, portId, () => true, _runId);
@@ -34,7 +34,7 @@ async function waitForOutputMatching(runtime, nodeId, portId, predicate, _runId)
34
34
  return firstValueFrom(runner.events$.pipe(filter((event) => isOutputValue(event) &&
35
35
  event[1] === nodeId &&
36
36
  event[2] === portId &&
37
- predicate(event[4]))));
37
+ predicate(event[3].value))));
38
38
  }
39
39
  export async function runAndCollectEvents(runtime, startRun, settleMs = 50) {
40
40
  const events = [];
@@ -61,5 +61,7 @@ export async function noDoneWithin(runtime, ms, runId) {
61
61
  ]);
62
62
  return result === 'timeout';
63
63
  }
64
- export const edgeIdsFromPortEvent = (event) => event[6];
64
+ export const edgeIdsFromPortEvent = (event) => event[5];
65
65
  export const portDirLabel = (event) => event[0] === 'out' ? 'output-emitted' : 'input-received';
66
+ export const dtoHas = (dtos, key) => dtos.some((dto) => typeof dto === 'object' && dto !== null && key in dto);
67
+ export const dtoIndex = (dtos, key) => dtos.findIndex((dto) => typeof dto === 'object' && dto !== null && key in dto);
@@ -1,4 +1,4 @@
1
- import type { StatefulConnection, StatefulObservable } from '@rx-evo/stateful-observable';
1
+ import type { ResponseDto, StatefulConnection, StatefulObservable } from '@rx-evo/stateful-observable';
2
2
  import type { Observable } from 'rxjs';
3
3
  import type { GraphCluster } from './runtime-helpers.js';
4
4
  export type RuntimeWireType = string & {
@@ -83,7 +83,7 @@ export type MetaFromStatefulObservable<T extends StatefulObservable<unknown, unk
83
83
  *
84
84
  * ## Errors
85
85
  * Node handlers surface failures via RxJS / `StatefulObservable` (`catchError`,
86
- * port `error$`). Runner telemetry emits `output-emitted` with `state: 'error'`
86
+ * port `error$`). Runner telemetry emits `ResponseDto` `{ error }`
87
87
  * on the source; wiring then drops `ResponseError` so edges do not cascade
88
88
  * error chrome to downstream nodes.
89
89
  *
@@ -118,17 +118,16 @@ export type MetaFromStatefulObservable<T extends StatefulObservable<unknown, unk
118
118
  * optional {@link RuntimeNode.stopsRun} completion.
119
119
  */
120
120
  export type RuntimeRunnerStatus = 'idle' | 'running' | 'stopped';
121
- export type RuntimePortSignalState = 'pending' | 'value' | 'error';
122
121
  /**
123
122
  * Port signal tuple — `portDir` `'out'` = output emitted, `'in'` = input received.
124
- * No per-frame `runId`; session snapshots own run correlation.
123
+ * Slot 3 is `@rx-evo` {@link ResponseDto} (JSON-safe pending / inactive / error /
124
+ * value). No per-frame `runId`; session snapshots own run correlation.
125
125
  */
126
126
  export type PortTelemetry = readonly [
127
127
  portDir: 'in' | 'out',
128
128
  nodeId: NodeId,
129
129
  portId: string,
130
- state: RuntimePortSignalState,
131
- value: unknown,
130
+ response: ResponseDto<unknown>,
132
131
  portIdx: number,
133
132
  edgeIds: readonly EdgeId[],
134
133
  /** Absent feed meta — use `null` (never `undefined`; JSON arrays drop undefined). */
@@ -138,6 +137,21 @@ export type PortTelemetry = readonly [
138
137
  export type RuntimeDoneTelemetry = readonly ['done'] | readonly ['done', RunId];
139
138
  export type RuntimeRunnerEvent = PortTelemetry | RuntimeDoneTelemetry;
140
139
  export declare const isPortTelemetry: (event: RuntimeRunnerEvent) => event is PortTelemetry;
140
+ export declare const isPortValueTelemetry: (event: RuntimeRunnerEvent) => event is PortTelemetry & {
141
+ readonly 3: Extract<ResponseDto<unknown>, {
142
+ value: unknown;
143
+ }>;
144
+ };
145
+ export declare const isPortErrorTelemetry: (event: RuntimeRunnerEvent) => event is PortTelemetry & {
146
+ readonly 3: Extract<ResponseDto<unknown>, {
147
+ error: unknown;
148
+ }>;
149
+ };
150
+ export declare const isPortPendingTelemetry: (event: RuntimeRunnerEvent) => event is PortTelemetry & {
151
+ readonly 3: {
152
+ readonly pending: true;
153
+ };
154
+ };
141
155
  export declare const isRuntimeDone: (event: RuntimeRunnerEvent) => event is RuntimeDoneTelemetry;
142
156
  /** Seed value pushed into an input port slot on {@link RuntimeRunner.start}. */
143
157
  export type RuntimeSeedPortValue = {
@@ -309,7 +323,9 @@ export type RuntimeEditorApi = {
309
323
  export type RuntimeRunnerApi = {
310
324
  /**
311
325
  * Wire every cluster that does not contain a {@link RuntimeNode.chatEntry}
312
- * node, seed source inputs, set status to `'running'`.
326
+ * node. Assigns `runId` and sets `'running'` **synchronously**, then wires
327
+ * ports on a microtask so callers (WS `runner.started`) can fence before
328
+ * any `runner.port` telemetry.
313
329
  *
314
330
  * Chat-entry clusters are omitted — start them via
315
331
  * {@link RuntimeRunnerApi.pushIntoInput}. When every cluster is chat-entry
@@ -326,7 +342,8 @@ export type RuntimeRunnerApi = {
326
342
  start(initialPayload?: Readonly<Record<string, ReadonlyArray<RuntimeSeedPortValue>>>, runId?: RunId): RunId | false;
327
343
  /**
328
344
  * Run-from-node: wire the weakly connected cluster containing `nodeId`
329
- * (orphan nodes elsewhere on the canvas are not wired).
345
+ * (orphan nodes elsewhere on the canvas are not wired). Same runId /
346
+ * `'running'` sync + microtask wiring as {@link RuntimeRunnerApi.start}.
330
347
  *
331
348
  * Run stays `'running'` until {@link RuntimeRunnerApi.interrupt} or a
332
349
  * {@link RuntimeNode.stopsRun} node in scope emits.
@@ -1,2 +1,5 @@
1
1
  export const isPortTelemetry = (event) => Array.isArray(event) && (event[0] === 'in' || event[0] === 'out');
2
+ export const isPortValueTelemetry = (event) => isPortTelemetry(event) && 'value' in event[3];
3
+ export const isPortErrorTelemetry = (event) => isPortTelemetry(event) && 'error' in event[3];
4
+ export const isPortPendingTelemetry = (event) => isPortTelemetry(event) && 'pending' in event[3];
2
5
  export const isRuntimeDone = (event) => Array.isArray(event) && event[0] === 'done';
@@ -15,7 +15,7 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@rx-evo/stateful-observable": "^0.5.2",
18
+ "@rx-evo/stateful-observable": "^0.6.0",
19
19
  "rxjs": "^7.8.1"
20
20
  },
21
21
  "files": [
@@ -73,8 +73,9 @@ export const buildExecutionContext = async (context, runId, node, hooks, preload
73
73
  webFetch,
74
74
  };
75
75
  const enabledToolIds = toolPermissionsToEnabledIds(toolPermissions);
76
- const toolHandles = wrapBuiltinToolHandles(harness, enabledToolIds, permission);
77
- const mcpHandles = filterMcpHandlesByIds(runMcpHandles ?? [], parseEnabledMcpIds(node.params.enabledMcpIds));
76
+ const builtinHandles = wrapBuiltinToolHandles(harness, enabledToolIds, permission);
77
+ const mcpTools = filterMcpHandlesByIds(runMcpHandles ?? [], parseEnabledMcpIds(node.params.enabledMcpIds)).flatMap((handle) => handle.tools);
78
+ const toolHandles = [...builtinHandles, ...mcpTools];
78
79
  const base = {
79
80
  projectDir: context.projectDir,
80
81
  runId,
@@ -85,7 +86,6 @@ export const buildExecutionContext = async (context, runId, node, hooks, preload
85
86
  params: node.params,
86
87
  })?.uiSchema ?? []),
87
88
  ...(toolHandles.length > 0 ? { toolHandles } : {}),
88
- ...(mcpHandles.length > 0 ? { mcpHandles } : {}),
89
89
  };
90
90
  const defaultChat = parseDefaultChatModel(config.model);
91
91
  return attachRunHostServices(base, {
@@ -1,8 +1,9 @@
1
1
  import type { ToolHandle } from '@langflower/node-sdk';
2
2
  import type { LangflowerSession } from '../session/langflower-session.js';
3
3
  /**
4
- * Activate current editor `tools` outputs (including post-swap instances)
5
- * so a later inbound peek in {@link getLiveWiredTools} sees live handles.
4
+ * Activate current editor `tools` / `subagent-registration` outputs
5
+ * (including post-swap instances) so a later inbound peek in
6
+ * {@link getLiveWiredTools} sees live handles.
6
7
  */
7
8
  export declare const refreshLiveWiredToolPacks: (session: LangflowerSession) => void;
8
9
  /**
@@ -40,14 +40,17 @@ const peekPortValue = (port) => {
40
40
  return peeked;
41
41
  };
42
42
  /**
43
- * Activate current editor `tools` outputs (including post-swap instances)
44
- * so a later inbound peek in {@link getLiveWiredTools} sees live handles.
43
+ * Activate current editor `tools` / `subagent-registration` outputs
44
+ * (including post-swap instances) so a later inbound peek in
45
+ * {@link getLiveWiredTools} sees live handles.
45
46
  */
46
47
  export const refreshLiveWiredToolPacks = (session) => {
47
48
  for (const node of session.runtime.editor.getNodes()) {
48
- const port = resolveToolsOutput(node.outputs, 'tools');
49
- if (port !== undefined) {
50
- peekPortValue(port);
49
+ for (const portId of ['tools', 'subagent-registration']) {
50
+ const port = resolveToolsOutput(node.outputs, portId);
51
+ if (port !== undefined) {
52
+ peekPortValue(port);
53
+ }
51
54
  }
52
55
  }
53
56
  };
@@ -3,6 +3,7 @@ import { buildWorkflowFingerprint, } from '@langflower/shared/langflower.js';
3
3
  import { Subscription } from 'rxjs';
4
4
  import { listResumableCheckpoints } from '../checkpoint/list-resumable-checkpoints.js';
5
5
  import { resolveCheckpointBoundary } from '../checkpoint/resolve-checkpoint-boundary.js';
6
+ import { resetSessionExecutionFeed } from '../session/reset-session-execution-feed.js';
6
7
  import { buildContextSeeds, applyObservableContextSeeds, } from './build-execution-context.js';
7
8
  import { getLiveWiredTools } from './get-live-wired-tools.js';
8
9
  import { createLangflowerToolsRpc } from './langflower-tools-rpc.js';
@@ -293,8 +294,7 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
293
294
  if (session.runnerStatus === 'running') {
294
295
  return;
295
296
  }
296
- session.runtime.runner.clearEventLog();
297
- session.runId = undefined;
297
+ resetSessionExecutionFeed(session);
298
298
  // Re-broadcast the (now empty) feed to every tab.
299
299
  bridgeEmit(bridge, 'executionFeed.snapshot', session.buildExecutionFeed());
300
300
  }));