@particle-academy/fancy-flow 0.71.0 → 0.72.1

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 (53) hide show
  1. package/dist/{chunk-3L47KJ6Q.js → chunk-3RXQGYGH.js} +3 -3
  2. package/dist/{chunk-3L47KJ6Q.js.map → chunk-3RXQGYGH.js.map} +1 -1
  3. package/dist/{chunk-PNTDTSMR.js → chunk-7DJ56PUA.js} +4 -4
  4. package/dist/{chunk-PNTDTSMR.js.map → chunk-7DJ56PUA.js.map} +1 -1
  5. package/dist/{chunk-EVGSL2EM.js → chunk-EX34GCKK.js} +162 -151
  6. package/dist/chunk-EX34GCKK.js.map +1 -0
  7. package/dist/{chunk-XI4AZD2S.js → chunk-GQ5LEA7Q.js} +3 -3
  8. package/dist/{chunk-XI4AZD2S.js.map → chunk-GQ5LEA7Q.js.map} +1 -1
  9. package/dist/{chunk-D2OBU3CE.js → chunk-K7KO3UJ3.js} +3 -3
  10. package/dist/{chunk-D2OBU3CE.js.map → chunk-K7KO3UJ3.js.map} +1 -1
  11. package/dist/{chunk-FPWJYSXX.js → chunk-LYYQO7EY.js} +4 -4
  12. package/dist/{chunk-FPWJYSXX.js.map → chunk-LYYQO7EY.js.map} +1 -1
  13. package/dist/{chunk-KQCX6KN7.js → chunk-VAF2SWTC.js} +3 -3
  14. package/dist/{chunk-KQCX6KN7.js.map → chunk-VAF2SWTC.js.map} +1 -1
  15. package/dist/durable/index.d.cts +90 -16
  16. package/dist/durable/index.d.ts +90 -16
  17. package/dist/durable.cjs +160 -83
  18. package/dist/durable.cjs.map +1 -1
  19. package/dist/durable.js +79 -14
  20. package/dist/durable.js.map +1 -1
  21. package/dist/engine.cjs +82 -71
  22. package/dist/engine.cjs.map +1 -1
  23. package/dist/engine.d.cts +2 -2
  24. package/dist/engine.d.ts +2 -2
  25. package/dist/engine.js +4 -4
  26. package/dist/index.cjs +128 -117
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +2 -2
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +12 -12
  31. package/dist/registry.cjs +82 -71
  32. package/dist/registry.cjs.map +1 -1
  33. package/dist/registry.js +2 -2
  34. package/dist/{run-cohort-DL9JzzPU.d.cts → run-cohort-DNpPa0mq.d.cts} +1 -1
  35. package/dist/{run-cohort-CjUBWg6X.d.ts → run-cohort-rgT_FX3V.d.ts} +1 -1
  36. package/dist/{run-flow-B_8hgO5_.d.cts → run-flow-BLP9rXfO.d.ts} +22 -1
  37. package/dist/{run-flow-CxEGBOxd.d.ts → run-flow-pW0PllaZ.d.cts} +22 -1
  38. package/dist/runtime/index.d.cts +3 -3
  39. package/dist/runtime/index.d.ts +3 -3
  40. package/dist/runtime.cjs +82 -71
  41. package/dist/runtime.cjs.map +1 -1
  42. package/dist/runtime.js +3 -3
  43. package/dist/schema.cjs +82 -71
  44. package/dist/schema.cjs.map +1 -1
  45. package/dist/schema.js +3 -3
  46. package/dist/screens.cjs +82 -71
  47. package/dist/screens.cjs.map +1 -1
  48. package/dist/screens.js +4 -4
  49. package/dist/ux.cjs +82 -71
  50. package/dist/ux.cjs.map +1 -1
  51. package/dist/ux.js +1 -1
  52. package/package.json +1 -1
  53. package/dist/chunk-EVGSL2EM.js.map +0 -1
package/dist/engine.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-B_8hgO5_.cjs';
1
+ export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-pW0PllaZ.cjs';
2
2
  import { F as FlowGraph, R as RunEvent, N as NodeExecutor, W as WorkflowResolver, a as FlowNode } from './types-B-Syk9-M.cjs';
3
3
  export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, E as ExecutorRegistry, e as FlowEdge, f as FlowNodeData, g as FlowNodeKind, h as NodeRunStatus, i as NoteNodeData, O as OutputNodeData, P as PortDescriptor, c as RunIdentity, d as RunIdentityJson, S as SubgraphNodeData, j as TriggerNodeData, k as escapeSegment } from './types-B-Syk9-M.cjs';
4
- export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, r as runCohort } from './run-cohort-DL9JzzPU.cjs';
4
+ export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, r as runCohort } from './run-cohort-DNpPa0mq.cjs';
5
5
  export { K as KindSchemaOverlay, a as KindSchemaOverlayResult, b as applyKindSchemaOverlay, c as clearKindSchemaOverlays, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, r as registerNodeKind, e as resolveKindId, u as unregisterNodeKind, v as validateConfig } from './registry-B6SiTaLZ.cjs';
6
6
  export { b as CapabilityRequirement, C as ConfigField, E as EmitsRelation, M as ManifestProblem, c as ManifestValidation, d as NODE_MANIFEST_SCHEMA_VERSION, a as NodeCategory, N as NodeKindDefinition, e as NodePackageManifest, f as NodeRuntimeId, g as NodeRuntimeSpec, O as OutputField, h as OutputShape, P as PortSpec, S as SideEffects, i as checkCapabilities, j as checkRuntimeSupport, s as satisfiesRange, v as validateNodeManifest } from './types-BdkXxjFh.cjs';
7
7
  export { E as EvaluateOptions, a as ExprContext, b as ExprValue, R as Resolution, U as UnresolvedPathError, c as UnresolvedPolicy, e as evaluateConfig, d as evaluateExpression, r as resolvePath, t as text, f as truthy, g as tryResolvePath } from './expr-BfXDrs4H.cjs';
package/dist/engine.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-CxEGBOxd.js';
1
+ export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-BLP9rXfO.js';
2
2
  import { F as FlowGraph, R as RunEvent, N as NodeExecutor, W as WorkflowResolver, a as FlowNode } from './types-B-Syk9-M.js';
3
3
  export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, E as ExecutorRegistry, e as FlowEdge, f as FlowNodeData, g as FlowNodeKind, h as NodeRunStatus, i as NoteNodeData, O as OutputNodeData, P as PortDescriptor, c as RunIdentity, d as RunIdentityJson, S as SubgraphNodeData, j as TriggerNodeData, k as escapeSegment } from './types-B-Syk9-M.js';
4
- export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, r as runCohort } from './run-cohort-CjUBWg6X.js';
4
+ export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, r as runCohort } from './run-cohort-rgT_FX3V.js';
5
5
  export { K as KindSchemaOverlay, a as KindSchemaOverlayResult, b as applyKindSchemaOverlay, c as clearKindSchemaOverlays, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, r as registerNodeKind, e as resolveKindId, u as unregisterNodeKind, v as validateConfig } from './registry-JUcN_xWK.js';
6
6
  export { b as CapabilityRequirement, C as ConfigField, E as EmitsRelation, M as ManifestProblem, c as ManifestValidation, d as NODE_MANIFEST_SCHEMA_VERSION, a as NodeCategory, N as NodeKindDefinition, e as NodePackageManifest, f as NodeRuntimeId, g as NodeRuntimeSpec, O as OutputField, h as OutputShape, P as PortSpec, S as SideEffects, i as checkCapabilities, j as checkRuntimeSupport, s as satisfiesRange, v as validateNodeManifest } from './types-CbekKMMO.js';
7
7
  export { E as EvaluateOptions, a as ExprContext, b as ExprValue, R as Resolution, U as UnresolvedPathError, c as UnresolvedPolicy, e as evaluateConfig, d as evaluateExpression, r as resolvePath, t as text, f as truthy, g as tryResolvePath } from './expr-BfXDrs4H.js';
package/dist/engine.js CHANGED
@@ -1,9 +1,9 @@
1
- export { runCohort } from './chunk-D2OBU3CE.js';
1
+ export { runCohort } from './chunk-K7KO3UJ3.js';
2
2
  import { decodePause } from './chunk-UEOE6B52.js';
3
3
  export { LEGACY_PAUSE_PREFIXES, PAUSE_PREFIX, decodePause, encodePause, isPause, pauseForHuman } from './chunk-UEOE6B52.js';
4
- export { checkGraphConnectivity, mayFloat } from './chunk-KQCX6KN7.js';
5
- import { runFlow, getNodeKind, resolveNodePorts } from './chunk-EVGSL2EM.js';
6
- export { RunIdentity, UnresolvedPathError, applyKindSchemaOverlay, clearKindSchemaOverlays, defaultConfigFor, escapeSegment, evaluateConfig, evaluateExpression, getNodeKind, kindIds, listNodeKinds, registerNodeKind, resolveKindId, resolvePath, runFlow, text, truthy, tryResolvePath, unregisterNodeKind, validateConfig } from './chunk-EVGSL2EM.js';
4
+ export { checkGraphConnectivity, mayFloat } from './chunk-VAF2SWTC.js';
5
+ import { runFlow, getNodeKind, resolveNodePorts } from './chunk-EX34GCKK.js';
6
+ export { RunIdentity, UnresolvedPathError, applyKindSchemaOverlay, clearKindSchemaOverlays, defaultConfigFor, escapeSegment, evaluateConfig, evaluateExpression, getNodeKind, kindIds, listNodeKinds, registerNodeKind, resolveKindId, resolvePath, runFlow, text, truthy, tryResolvePath, unregisterNodeKind, validateConfig } from './chunk-EX34GCKK.js';
7
7
  import { isResolutionFailure, registerLlmClient, registerWorkflowResolver } from './chunk-2QXKDLGT.js';
8
8
 
9
9
  // src/marketplace/manifest.ts
package/dist/index.cjs CHANGED
@@ -1899,70 +1899,6 @@ function resolveNodePorts(node, kind) {
1899
1899
  };
1900
1900
  }
1901
1901
 
1902
- // src/expressions/variables.ts
1903
- var ROOT = "$json";
1904
- function expr(path) {
1905
- return `{{ ${path} }}`;
1906
- }
1907
- function outputFieldsFor(node) {
1908
- const kindName = node.data?.kind ?? node.type;
1909
- if (!kindName) return [];
1910
- const kind = getNodeKind(kindName);
1911
- const shape = kind?.outputShape;
1912
- if (!shape) return [];
1913
- if (typeof shape === "function") {
1914
- try {
1915
- const config = node.data?.config ?? {};
1916
- return shape(config) ?? [];
1917
- } catch {
1918
- return [];
1919
- }
1920
- }
1921
- return shape;
1922
- }
1923
- function baseVariables() {
1924
- return [
1925
- { expression: expr(ROOT), path: ROOT, type: "unknown", description: "The whole incoming value." }
1926
- ];
1927
- }
1928
- function availableVariables(graph, nodeId) {
1929
- const out = baseVariables();
1930
- const seen = /* @__PURE__ */ new Set([ROOT]);
1931
- const byId = new Map(graph.nodes.map((n) => [n.id, n]));
1932
- const upstreamIds = graph.edges.filter((e) => e.target === nodeId).map((e) => e.source);
1933
- for (const id2 of upstreamIds) {
1934
- if (id2 === nodeId) continue;
1935
- const upstream = byId.get(id2);
1936
- if (!upstream) continue;
1937
- const kindName = upstream.data?.kind ?? upstream.type;
1938
- const label = upstream.data?.label ?? (kindName ? getNodeKind(kindName)?.label : void 0);
1939
- for (const field of outputFieldsFor(upstream)) {
1940
- const path = `${ROOT}.${field.path}`;
1941
- if (seen.has(path)) continue;
1942
- seen.add(path);
1943
- out.push({
1944
- expression: expr(path),
1945
- path,
1946
- type: field.type,
1947
- description: field.description,
1948
- source: label
1949
- });
1950
- }
1951
- }
1952
- return out;
1953
- }
1954
- function describeExpressionGrammar() {
1955
- return {
1956
- forms: [
1957
- { syntax: "{{ $json }}", meaning: "The whole value arriving on this node's input." },
1958
- { syntax: "{{ $json.field }}", meaning: "One key of that value. Dot-paths nest: $json.user.email." },
1959
- { syntax: "{{ $input.field }}", meaning: "An alias for $json \u2014 the same value, either spelling." },
1960
- { syntax: "{{ in }}", meaning: "The input by its context key, rather than through the alias." }
1961
- ],
1962
- note: "Dot-paths only \u2014 no arithmetic, comparisons or function calls; anything else resolves to nothing. A field that is exactly one expression keeps the resolved value's type; mixed text interpolates as a string. fancy-flow's own runFlow does not interpolate: it hands config to your executor verbatim, and the fancy-flow-php runtime is what resolves these."
1963
- };
1964
- }
1965
-
1966
1902
  // src/runtime/run-identity.ts
1967
1903
  function escapeSegment(value) {
1968
1904
  return value.replace(/%/g, "%25").replace(/\//g, "%2F").replace(/#/g, "%23");
@@ -2306,6 +2242,127 @@ function specForLane(lane) {
2306
2242
  };
2307
2243
  }
2308
2244
 
2245
+ // src/expressions/variables.ts
2246
+ var ROOT = "$json";
2247
+ function expr(path) {
2248
+ return `{{ ${path} }}`;
2249
+ }
2250
+ function outputFieldsFor(node) {
2251
+ const kindName = node.data?.kind ?? node.type;
2252
+ if (!kindName) return [];
2253
+ const kind = getNodeKind(kindName);
2254
+ const shape = kind?.outputShape;
2255
+ if (!shape) return [];
2256
+ if (typeof shape === "function") {
2257
+ try {
2258
+ const config = node.data?.config ?? {};
2259
+ return shape(config) ?? [];
2260
+ } catch {
2261
+ return [];
2262
+ }
2263
+ }
2264
+ return shape;
2265
+ }
2266
+ function baseVariables() {
2267
+ return [
2268
+ { expression: expr(ROOT), path: ROOT, type: "unknown", description: "The whole incoming value." }
2269
+ ];
2270
+ }
2271
+ function availableVariables(graph, nodeId) {
2272
+ const out = baseVariables();
2273
+ const seen = /* @__PURE__ */ new Set([ROOT]);
2274
+ const byId = new Map(graph.nodes.map((n) => [n.id, n]));
2275
+ const upstreamIds = graph.edges.filter((e) => e.target === nodeId).map((e) => e.source);
2276
+ for (const id2 of upstreamIds) {
2277
+ if (id2 === nodeId) continue;
2278
+ const upstream = byId.get(id2);
2279
+ if (!upstream) continue;
2280
+ const kindName = upstream.data?.kind ?? upstream.type;
2281
+ const label = upstream.data?.label ?? (kindName ? getNodeKind(kindName)?.label : void 0);
2282
+ for (const field of outputFieldsFor(upstream)) {
2283
+ const path = `${ROOT}.${field.path}`;
2284
+ if (seen.has(path)) continue;
2285
+ seen.add(path);
2286
+ out.push({
2287
+ expression: expr(path),
2288
+ path,
2289
+ type: field.type,
2290
+ description: field.description,
2291
+ source: label
2292
+ });
2293
+ }
2294
+ }
2295
+ return out;
2296
+ }
2297
+ function describeExpressionGrammar() {
2298
+ return {
2299
+ forms: [
2300
+ { syntax: "{{ $json }}", meaning: "The whole value arriving on this node's input." },
2301
+ { syntax: "{{ $json.field }}", meaning: "One key of that value. Dot-paths nest: $json.user.email." },
2302
+ { syntax: "{{ $input.field }}", meaning: "An alias for $json \u2014 the same value, either spelling." },
2303
+ { syntax: "{{ in }}", meaning: "The input by its context key, rather than through the alias." }
2304
+ ],
2305
+ note: "Dot-paths only \u2014 no arithmetic, comparisons or function calls; anything else resolves to nothing. A field that is exactly one expression keeps the resolved value's type; mixed text interpolates as a string. fancy-flow's own runFlow does not interpolate: it hands config to your executor verbatim, and the fancy-flow-php runtime is what resolves these."
2306
+ };
2307
+ }
2308
+
2309
+ // src/runtime/undelivered-edges.ts
2310
+ function undeliveredEdgeWarnings(target, incoming, portValues, completed, nodesById) {
2311
+ const events = [];
2312
+ for (const e of incoming) {
2313
+ const handle = e.sourceHandle ?? "out";
2314
+ if (portValues.has(`${e.source}:${handle}`) || !completed.has(e.source)) continue;
2315
+ if (possiblePortIds(nodesById.get(e.source)).includes(handle)) continue;
2316
+ events.push({
2317
+ type: "log",
2318
+ nodeId: target.id,
2319
+ level: "warn",
2320
+ message: undeliveredEdgeMessage(e, target, portValues, nodesById),
2321
+ detail: { edge: e.id, source: e.source, sourceHandle: handle }
2322
+ });
2323
+ }
2324
+ return events;
2325
+ }
2326
+ function possiblePortIds(node) {
2327
+ if (!node) return ["out"];
2328
+ const own = node.data?.outputs;
2329
+ if (Array.isArray(own)) return own.map((p2) => p2.id);
2330
+ const kind = getNodeKind(node.data?.kind ?? node.type ?? "");
2331
+ if (!kind) return ["out"];
2332
+ const config = nodeConfig(node);
2333
+ const representative = typeof kind.outputs === "function" && !configDeclaresPorts(kind.name, config);
2334
+ const ids = (resolvePortSpec(kind.outputs, representative ? defaultConfigFor(kind) : config) ?? []).map((p2) => p2.id);
2335
+ return ids.length > 0 ? ids : ["out"];
2336
+ }
2337
+ function configDeclaresPorts(kindName, config) {
2338
+ const bare = kindName.startsWith("@") ? kindName.slice(kindName.lastIndexOf("/") + 1) : kindName;
2339
+ if (bare === "switch_case") {
2340
+ const cases = config.cases;
2341
+ return !!cases && typeof cases === "object" && Object.values(cases).some((port) => typeof port === "string" && port !== "");
2342
+ }
2343
+ if (bare === "llm_router") {
2344
+ const routes = config.routes;
2345
+ return Array.isArray(routes) && routes.some((route) => route?.port != null && route.port !== "");
2346
+ }
2347
+ return true;
2348
+ }
2349
+ function undeliveredEdgeMessage(edge, target, portValues, nodesById) {
2350
+ const handle = edge.sourceHandle ?? "out";
2351
+ const prefix = `${edge.source}:`;
2352
+ const available = [];
2353
+ for (const key of portValues.keys()) {
2354
+ if (key.startsWith(prefix)) available.push(key.slice(prefix.length));
2355
+ }
2356
+ let message = `Edge ${edge.id} reads port "${handle}" from node ${edge.source}, which never publishes it \u2014 nothing would reach ${target.id} at run time.`;
2357
+ if (available.length > 0) message += ` Available: ${available.join(", ")}.`;
2358
+ const source = nodesById.get(edge.source);
2359
+ if (source && outputFieldsFor(source).some((field) => field?.path === handle)) {
2360
+ message += ` Note: "${handle}" is a FIELD this node emits, not a port \u2014 read it downstream as {{ in.${handle} }} rather than naming it as a source handle.`;
2361
+ }
2362
+ if (edge.sourceHandle != null) message += " Leave sourceHandle off to read the node's output.";
2363
+ return message;
2364
+ }
2365
+
2309
2366
  // src/runtime/workflow-props.ts
2310
2367
  function typeOf(value) {
2311
2368
  if (value === null) return "null";
@@ -2364,7 +2421,7 @@ function resolveWorkflowProps(declared, passed) {
2364
2421
  // src/runtime/run-flow.ts
2365
2422
  async function runFlow(graph, executors, onEvent = () => {
2366
2423
  }, options = {}) {
2367
- const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {}, entryNodes } = options;
2424
+ const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {}, entryNodes, nodeExecutors } = options;
2368
2425
  const run = options.run === void 0 ? void 0 : RunIdentity.from(options.run);
2369
2426
  const outputs = {};
2370
2427
  const portValues = /* @__PURE__ */ new Map();
@@ -2427,17 +2484,8 @@ async function runFlow(graph, executors, onEvent = () => {
2427
2484
  onEvent({ type: "node-status", nodeId: node.id, status: "idle", text: "skipped" });
2428
2485
  continue;
2429
2486
  }
2430
- for (const e of incoming) {
2431
- const handle = e.sourceHandle ?? "out";
2432
- if (portValues.has(`${e.source}:${handle}`) || !completed.has(e.source)) continue;
2433
- if (possiblePortIds(nodesById.get(e.source)).includes(handle)) continue;
2434
- onEvent({
2435
- type: "log",
2436
- nodeId: node.id,
2437
- level: "warn",
2438
- message: undeliveredEdgeMessage(e, node, portValues, nodesById),
2439
- detail: { edge: e.id, source: e.source, sourceHandle: handle }
2440
- });
2487
+ for (const warning of undeliveredEdgeWarnings(node, incoming, portValues, completed, nodesById)) {
2488
+ onEvent(warning);
2441
2489
  }
2442
2490
  if (incoming.length > 0) {
2443
2491
  const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? "out"}`));
@@ -2461,7 +2509,7 @@ async function runFlow(graph, executors, onEvent = () => {
2461
2509
  onEvent({ type: "node-status", nodeId: node.id, status: "running" });
2462
2510
  announce(onEvent, node, "start");
2463
2511
  const inputs = collectInputs(node, incoming, portValues, initialInputs, props, declaresProps);
2464
- const exec = pickExecutor(executors, node);
2512
+ const exec = pickExecutor(executors, node, nodeExecutors);
2465
2513
  if (!exec) {
2466
2514
  const tried = executorLookupIds(node);
2467
2515
  const msg = `No executor registered for kind=${node.type} \u2014 tried ${tried.map((id2) => `"${id2}"`).join(", ")}. Key your registry by one of those.`;
@@ -2581,7 +2629,9 @@ function executorLookupIds(node) {
2581
2629
  ids.push("*");
2582
2630
  return [...new Set(ids)];
2583
2631
  }
2584
- function pickExecutor(executors, node) {
2632
+ function pickExecutor(executors, node, nodeExecutors) {
2633
+ const pinned = nodeExecutors && Object.prototype.hasOwnProperty.call(nodeExecutors, node.id) ? nodeExecutors[node.id] : void 0;
2634
+ if (pinned) return pinned;
2585
2635
  for (const id2 of executorLookupIds(node)) {
2586
2636
  if (executors[id2]) return executors[id2];
2587
2637
  }
@@ -2601,45 +2651,6 @@ function activatedPorts(node, result) {
2601
2651
  const declared = resolveNodePorts(node, kind).outputs?.map((p2) => p2.id);
2602
2652
  return { ports: declared?.length ? declared : ["out"], value: result };
2603
2653
  }
2604
- function possiblePortIds(node) {
2605
- if (!node) return ["out"];
2606
- const own = node.data?.outputs;
2607
- if (Array.isArray(own)) return own.map((p2) => p2.id);
2608
- const kind = getNodeKind(node.data?.kind ?? node.type ?? "");
2609
- if (!kind) return ["out"];
2610
- const config = nodeConfig(node);
2611
- const representative = typeof kind.outputs === "function" && !configDeclaresPorts(kind.name, config);
2612
- const ids = (resolvePortSpec(kind.outputs, representative ? defaultConfigFor(kind) : config) ?? []).map((p2) => p2.id);
2613
- return ids.length > 0 ? ids : ["out"];
2614
- }
2615
- function configDeclaresPorts(kindName, config) {
2616
- const bare = kindName.startsWith("@") ? kindName.slice(kindName.lastIndexOf("/") + 1) : kindName;
2617
- if (bare === "switch_case") {
2618
- const cases = config.cases;
2619
- return !!cases && typeof cases === "object" && Object.values(cases).some((port) => typeof port === "string" && port !== "");
2620
- }
2621
- if (bare === "llm_router") {
2622
- const routes = config.routes;
2623
- return Array.isArray(routes) && routes.some((route) => route?.port != null && route.port !== "");
2624
- }
2625
- return true;
2626
- }
2627
- function undeliveredEdgeMessage(edge, target, portValues, nodesById) {
2628
- const handle = edge.sourceHandle ?? "out";
2629
- const prefix = `${edge.source}:`;
2630
- const available = [];
2631
- for (const key of portValues.keys()) {
2632
- if (key.startsWith(prefix)) available.push(key.slice(prefix.length));
2633
- }
2634
- let message = `Edge ${edge.id} reads port "${handle}" from node ${edge.source}, which never publishes it \u2014 nothing would reach ${target.id} at run time.`;
2635
- if (available.length > 0) message += ` Available: ${available.join(", ")}.`;
2636
- const source = nodesById.get(edge.source);
2637
- if (source && outputFieldsFor(source).some((field) => field?.path === handle)) {
2638
- message += ` Note: "${handle}" is a FIELD this node emits, not a port \u2014 read it downstream as {{ in.${handle} }} rather than naming it as a source handle.`;
2639
- }
2640
- if (edge.sourceHandle != null) message += " Leave sourceHandle off to read the node's output.";
2641
- return message;
2642
- }
2643
2654
  function announce(onEvent, node, phase) {
2644
2655
  const data = node.data;
2645
2656
  const raw = phase === "start" ? data?.startingMsg : data?.stoppingMsg;