@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.
- package/dist/{chunk-3L47KJ6Q.js → chunk-3RXQGYGH.js} +3 -3
- package/dist/{chunk-3L47KJ6Q.js.map → chunk-3RXQGYGH.js.map} +1 -1
- package/dist/{chunk-PNTDTSMR.js → chunk-7DJ56PUA.js} +4 -4
- package/dist/{chunk-PNTDTSMR.js.map → chunk-7DJ56PUA.js.map} +1 -1
- package/dist/{chunk-EVGSL2EM.js → chunk-EX34GCKK.js} +162 -151
- package/dist/chunk-EX34GCKK.js.map +1 -0
- package/dist/{chunk-XI4AZD2S.js → chunk-GQ5LEA7Q.js} +3 -3
- package/dist/{chunk-XI4AZD2S.js.map → chunk-GQ5LEA7Q.js.map} +1 -1
- package/dist/{chunk-D2OBU3CE.js → chunk-K7KO3UJ3.js} +3 -3
- package/dist/{chunk-D2OBU3CE.js.map → chunk-K7KO3UJ3.js.map} +1 -1
- package/dist/{chunk-FPWJYSXX.js → chunk-LYYQO7EY.js} +4 -4
- package/dist/{chunk-FPWJYSXX.js.map → chunk-LYYQO7EY.js.map} +1 -1
- package/dist/{chunk-KQCX6KN7.js → chunk-VAF2SWTC.js} +3 -3
- package/dist/{chunk-KQCX6KN7.js.map → chunk-VAF2SWTC.js.map} +1 -1
- package/dist/durable/index.d.cts +90 -16
- package/dist/durable/index.d.ts +90 -16
- package/dist/durable.cjs +160 -83
- package/dist/durable.cjs.map +1 -1
- package/dist/durable.js +79 -14
- package/dist/durable.js.map +1 -1
- package/dist/engine.cjs +82 -71
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.cts +2 -2
- package/dist/engine.d.ts +2 -2
- package/dist/engine.js +4 -4
- package/dist/index.cjs +128 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +12 -12
- package/dist/registry.cjs +82 -71
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +2 -2
- package/dist/{run-cohort-DL9JzzPU.d.cts → run-cohort-DNpPa0mq.d.cts} +1 -1
- package/dist/{run-cohort-CjUBWg6X.d.ts → run-cohort-rgT_FX3V.d.ts} +1 -1
- package/dist/{run-flow-B_8hgO5_.d.cts → run-flow-BLP9rXfO.d.ts} +22 -1
- package/dist/{run-flow-CxEGBOxd.d.ts → run-flow-pW0PllaZ.d.cts} +22 -1
- package/dist/runtime/index.d.cts +3 -3
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime.cjs +82 -71
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +3 -3
- package/dist/schema.cjs +82 -71
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +3 -3
- package/dist/screens.cjs +82 -71
- package/dist/screens.cjs.map +1 -1
- package/dist/screens.js +4 -4
- package/dist/ux.cjs +82 -71
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EVGSL2EM.js.map +0 -1
package/dist/durable.cjs
CHANGED
|
@@ -52,8 +52,10 @@ var InMemoryClaimStore = class {
|
|
|
52
52
|
}
|
|
53
53
|
skip(runKey, nodeId) {
|
|
54
54
|
const entry = this.entry(runKey, nodeId);
|
|
55
|
+
if (entry.status === NodeRunStatus.SKIPPED) return false;
|
|
55
56
|
entry.status = NodeRunStatus.SKIPPED;
|
|
56
57
|
entry.ports = [];
|
|
58
|
+
return true;
|
|
57
59
|
}
|
|
58
60
|
fail(runKey, nodeId, error) {
|
|
59
61
|
const entry = this.entry(runKey, nodeId);
|
|
@@ -242,24 +244,6 @@ function resolveNodePorts(node, kind) {
|
|
|
242
244
|
};
|
|
243
245
|
}
|
|
244
246
|
|
|
245
|
-
// src/expressions/variables.ts
|
|
246
|
-
function outputFieldsFor(node) {
|
|
247
|
-
const kindName = node.data?.kind ?? node.type;
|
|
248
|
-
if (!kindName) return [];
|
|
249
|
-
const kind = getNodeKind(kindName);
|
|
250
|
-
const shape = kind?.outputShape;
|
|
251
|
-
if (!shape) return [];
|
|
252
|
-
if (typeof shape === "function") {
|
|
253
|
-
try {
|
|
254
|
-
const config = node.data?.config ?? {};
|
|
255
|
-
return shape(config) ?? [];
|
|
256
|
-
} catch {
|
|
257
|
-
return [];
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return shape;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
247
|
// src/runtime/run-identity.ts
|
|
264
248
|
function escapeSegment(value) {
|
|
265
249
|
return value.replace(/%/g, "%25").replace(/\//g, "%2F").replace(/#/g, "%23");
|
|
@@ -603,6 +587,81 @@ function specForLane(lane) {
|
|
|
603
587
|
};
|
|
604
588
|
}
|
|
605
589
|
|
|
590
|
+
// src/expressions/variables.ts
|
|
591
|
+
function outputFieldsFor(node) {
|
|
592
|
+
const kindName = node.data?.kind ?? node.type;
|
|
593
|
+
if (!kindName) return [];
|
|
594
|
+
const kind = getNodeKind(kindName);
|
|
595
|
+
const shape = kind?.outputShape;
|
|
596
|
+
if (!shape) return [];
|
|
597
|
+
if (typeof shape === "function") {
|
|
598
|
+
try {
|
|
599
|
+
const config = node.data?.config ?? {};
|
|
600
|
+
return shape(config) ?? [];
|
|
601
|
+
} catch {
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return shape;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/runtime/undelivered-edges.ts
|
|
609
|
+
function undeliveredEdgeWarnings(target, incoming, portValues, completed, nodesById) {
|
|
610
|
+
const events = [];
|
|
611
|
+
for (const e of incoming) {
|
|
612
|
+
const handle = e.sourceHandle ?? "out";
|
|
613
|
+
if (portValues.has(`${e.source}:${handle}`) || !completed.has(e.source)) continue;
|
|
614
|
+
if (possiblePortIds(nodesById.get(e.source)).includes(handle)) continue;
|
|
615
|
+
events.push({
|
|
616
|
+
type: "log",
|
|
617
|
+
nodeId: target.id,
|
|
618
|
+
level: "warn",
|
|
619
|
+
message: undeliveredEdgeMessage(e, target, portValues, nodesById),
|
|
620
|
+
detail: { edge: e.id, source: e.source, sourceHandle: handle }
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
return events;
|
|
624
|
+
}
|
|
625
|
+
function possiblePortIds(node) {
|
|
626
|
+
if (!node) return ["out"];
|
|
627
|
+
const own = node.data?.outputs;
|
|
628
|
+
if (Array.isArray(own)) return own.map((p) => p.id);
|
|
629
|
+
const kind = getNodeKind(node.data?.kind ?? node.type ?? "");
|
|
630
|
+
if (!kind) return ["out"];
|
|
631
|
+
const config = nodeConfig(node);
|
|
632
|
+
const representative = typeof kind.outputs === "function" && !configDeclaresPorts(kind.name, config);
|
|
633
|
+
const ids = (resolvePortSpec(kind.outputs, representative ? defaultConfigFor(kind) : config) ?? []).map((p) => p.id);
|
|
634
|
+
return ids.length > 0 ? ids : ["out"];
|
|
635
|
+
}
|
|
636
|
+
function configDeclaresPorts(kindName, config) {
|
|
637
|
+
const bare = kindName.startsWith("@") ? kindName.slice(kindName.lastIndexOf("/") + 1) : kindName;
|
|
638
|
+
if (bare === "switch_case") {
|
|
639
|
+
const cases = config.cases;
|
|
640
|
+
return !!cases && typeof cases === "object" && Object.values(cases).some((port) => typeof port === "string" && port !== "");
|
|
641
|
+
}
|
|
642
|
+
if (bare === "llm_router") {
|
|
643
|
+
const routes = config.routes;
|
|
644
|
+
return Array.isArray(routes) && routes.some((route) => route?.port != null && route.port !== "");
|
|
645
|
+
}
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
function undeliveredEdgeMessage(edge, target, portValues, nodesById) {
|
|
649
|
+
const handle = edge.sourceHandle ?? "out";
|
|
650
|
+
const prefix = `${edge.source}:`;
|
|
651
|
+
const available = [];
|
|
652
|
+
for (const key of portValues.keys()) {
|
|
653
|
+
if (key.startsWith(prefix)) available.push(key.slice(prefix.length));
|
|
654
|
+
}
|
|
655
|
+
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.`;
|
|
656
|
+
if (available.length > 0) message += ` Available: ${available.join(", ")}.`;
|
|
657
|
+
const source = nodesById.get(edge.source);
|
|
658
|
+
if (source && outputFieldsFor(source).some((field) => field?.path === handle)) {
|
|
659
|
+
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.`;
|
|
660
|
+
}
|
|
661
|
+
if (edge.sourceHandle != null) message += " Leave sourceHandle off to read the node's output.";
|
|
662
|
+
return message;
|
|
663
|
+
}
|
|
664
|
+
|
|
606
665
|
// src/runtime/workflow-props.ts
|
|
607
666
|
function typeOf(value) {
|
|
608
667
|
if (value === null) return "null";
|
|
@@ -661,7 +720,7 @@ function resolveWorkflowProps(declared, passed) {
|
|
|
661
720
|
// src/runtime/run-flow.ts
|
|
662
721
|
async function runFlow(graph, executors, onEvent = () => {
|
|
663
722
|
}, options = {}) {
|
|
664
|
-
const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {}, entryNodes } = options;
|
|
723
|
+
const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {}, entryNodes, nodeExecutors } = options;
|
|
665
724
|
const run = options.run === void 0 ? void 0 : RunIdentity.from(options.run);
|
|
666
725
|
const outputs = {};
|
|
667
726
|
const portValues = /* @__PURE__ */ new Map();
|
|
@@ -724,17 +783,8 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
724
783
|
onEvent({ type: "node-status", nodeId: node.id, status: "idle", text: "skipped" });
|
|
725
784
|
continue;
|
|
726
785
|
}
|
|
727
|
-
for (const
|
|
728
|
-
|
|
729
|
-
if (portValues.has(`${e.source}:${handle}`) || !completed.has(e.source)) continue;
|
|
730
|
-
if (possiblePortIds(nodesById.get(e.source)).includes(handle)) continue;
|
|
731
|
-
onEvent({
|
|
732
|
-
type: "log",
|
|
733
|
-
nodeId: node.id,
|
|
734
|
-
level: "warn",
|
|
735
|
-
message: undeliveredEdgeMessage(e, node, portValues, nodesById),
|
|
736
|
-
detail: { edge: e.id, source: e.source, sourceHandle: handle }
|
|
737
|
-
});
|
|
786
|
+
for (const warning of undeliveredEdgeWarnings(node, incoming, portValues, completed, nodesById)) {
|
|
787
|
+
onEvent(warning);
|
|
738
788
|
}
|
|
739
789
|
if (incoming.length > 0) {
|
|
740
790
|
const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? "out"}`));
|
|
@@ -758,7 +808,7 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
758
808
|
onEvent({ type: "node-status", nodeId: node.id, status: "running" });
|
|
759
809
|
announce(onEvent, node, "start");
|
|
760
810
|
const inputs = collectInputs(node, incoming, portValues, initialInputs, props, declaresProps);
|
|
761
|
-
const exec = pickExecutor(executors, node);
|
|
811
|
+
const exec = pickExecutor(executors, node, nodeExecutors);
|
|
762
812
|
if (!exec) {
|
|
763
813
|
const tried = executorLookupIds(node);
|
|
764
814
|
const msg = `No executor registered for kind=${node.type} \u2014 tried ${tried.map((id) => `"${id}"`).join(", ")}. Key your registry by one of those.`;
|
|
@@ -878,7 +928,9 @@ function executorLookupIds(node) {
|
|
|
878
928
|
ids.push("*");
|
|
879
929
|
return [...new Set(ids)];
|
|
880
930
|
}
|
|
881
|
-
function pickExecutor(executors, node) {
|
|
931
|
+
function pickExecutor(executors, node, nodeExecutors) {
|
|
932
|
+
const pinned = nodeExecutors && Object.prototype.hasOwnProperty.call(nodeExecutors, node.id) ? nodeExecutors[node.id] : void 0;
|
|
933
|
+
if (pinned) return pinned;
|
|
882
934
|
for (const id of executorLookupIds(node)) {
|
|
883
935
|
if (executors[id]) return executors[id];
|
|
884
936
|
}
|
|
@@ -898,45 +950,6 @@ function activatedPorts(node, result) {
|
|
|
898
950
|
const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);
|
|
899
951
|
return { ports: declared?.length ? declared : ["out"], value: result };
|
|
900
952
|
}
|
|
901
|
-
function possiblePortIds(node) {
|
|
902
|
-
if (!node) return ["out"];
|
|
903
|
-
const own = node.data?.outputs;
|
|
904
|
-
if (Array.isArray(own)) return own.map((p) => p.id);
|
|
905
|
-
const kind = getNodeKind(node.data?.kind ?? node.type ?? "");
|
|
906
|
-
if (!kind) return ["out"];
|
|
907
|
-
const config = nodeConfig(node);
|
|
908
|
-
const representative = typeof kind.outputs === "function" && !configDeclaresPorts(kind.name, config);
|
|
909
|
-
const ids = (resolvePortSpec(kind.outputs, representative ? defaultConfigFor(kind) : config) ?? []).map((p) => p.id);
|
|
910
|
-
return ids.length > 0 ? ids : ["out"];
|
|
911
|
-
}
|
|
912
|
-
function configDeclaresPorts(kindName, config) {
|
|
913
|
-
const bare = kindName.startsWith("@") ? kindName.slice(kindName.lastIndexOf("/") + 1) : kindName;
|
|
914
|
-
if (bare === "switch_case") {
|
|
915
|
-
const cases = config.cases;
|
|
916
|
-
return !!cases && typeof cases === "object" && Object.values(cases).some((port) => typeof port === "string" && port !== "");
|
|
917
|
-
}
|
|
918
|
-
if (bare === "llm_router") {
|
|
919
|
-
const routes = config.routes;
|
|
920
|
-
return Array.isArray(routes) && routes.some((route) => route?.port != null && route.port !== "");
|
|
921
|
-
}
|
|
922
|
-
return true;
|
|
923
|
-
}
|
|
924
|
-
function undeliveredEdgeMessage(edge, target, portValues, nodesById) {
|
|
925
|
-
const handle = edge.sourceHandle ?? "out";
|
|
926
|
-
const prefix = `${edge.source}:`;
|
|
927
|
-
const available = [];
|
|
928
|
-
for (const key of portValues.keys()) {
|
|
929
|
-
if (key.startsWith(prefix)) available.push(key.slice(prefix.length));
|
|
930
|
-
}
|
|
931
|
-
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.`;
|
|
932
|
-
if (available.length > 0) message += ` Available: ${available.join(", ")}.`;
|
|
933
|
-
const source = nodesById.get(edge.source);
|
|
934
|
-
if (source && outputFieldsFor(source).some((field) => field?.path === handle)) {
|
|
935
|
-
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.`;
|
|
936
|
-
}
|
|
937
|
-
if (edge.sourceHandle != null) message += " Leave sourceHandle off to read the node's output.";
|
|
938
|
-
return message;
|
|
939
|
-
}
|
|
940
953
|
function announce(onEvent, node, phase) {
|
|
941
954
|
const data = node.data;
|
|
942
955
|
const raw = phase === "start" ? data?.startingMsg : data?.stoppingMsg;
|
|
@@ -2704,20 +2717,35 @@ var Frontier = {
|
|
|
2704
2717
|
(entry) => entry.status === NodeRunStatus.CLAIMED || entry.status === NodeRunStatus.PAUSED
|
|
2705
2718
|
);
|
|
2706
2719
|
},
|
|
2707
|
-
/**
|
|
2720
|
+
/**
|
|
2721
|
+
* Persist the skip cascade so the next pass does not recompute it.
|
|
2722
|
+
*
|
|
2723
|
+
* Resolves to the ids THIS call settled, in cascade order. A node another
|
|
2724
|
+
* caller had already settled is left out — its store reported `false` — so a
|
|
2725
|
+
* caller acting on "I skipped this" acts once per node, not once per racer. A
|
|
2726
|
+
* store whose `skip` returns nothing counts every id as settled here.
|
|
2727
|
+
*/
|
|
2708
2728
|
async settleSkips(store, runKey, skipped) {
|
|
2709
|
-
|
|
2729
|
+
const settled = [];
|
|
2730
|
+
for (const nodeId of skipped) {
|
|
2731
|
+
if (await store.skip(runKey, nodeId) !== false) settled.push(nodeId);
|
|
2732
|
+
}
|
|
2733
|
+
return settled;
|
|
2710
2734
|
}
|
|
2711
2735
|
};
|
|
2712
2736
|
|
|
2713
2737
|
// src/durable/replay.ts
|
|
2714
2738
|
var BOUNDARY = "fancy-flow:node-boundary";
|
|
2739
|
+
var FENCE_PORT = "fancy-flow:fenced";
|
|
2715
2740
|
async function replayUpTo(graph, nodeId, executors, options = {}) {
|
|
2716
|
-
const
|
|
2741
|
+
const fencedOff = /* @__PURE__ */ new Set();
|
|
2742
|
+
const fence = (ctx) => {
|
|
2743
|
+
fencedOff.add(ctx.node.id);
|
|
2744
|
+
return { __port: FENCE_PORT, value: null };
|
|
2745
|
+
};
|
|
2746
|
+
const fences = {};
|
|
2717
2747
|
for (const node of graph.nodes) {
|
|
2718
|
-
if (node.id !== nodeId)
|
|
2719
|
-
fenced[node.id] = (ctx) => ctx.abort(BOUNDARY);
|
|
2720
|
-
}
|
|
2748
|
+
if (node.id !== nodeId) fences[node.id] = fence;
|
|
2721
2749
|
}
|
|
2722
2750
|
const ports = {};
|
|
2723
2751
|
const collect = (event) => {
|
|
@@ -2727,12 +2755,17 @@ async function replayUpTo(graph, nodeId, executors, options = {}) {
|
|
|
2727
2755
|
}
|
|
2728
2756
|
options.onEvent?.(event);
|
|
2729
2757
|
};
|
|
2730
|
-
const result = await runFlow(graph,
|
|
2758
|
+
const result = await runFlow(graph, executors, collect, {
|
|
2731
2759
|
initialInputs: options.initialInputs ?? {},
|
|
2732
2760
|
resumeOutputs: options.resumeOutputs ?? {},
|
|
2733
2761
|
depth: options.depth ?? 0,
|
|
2734
|
-
run: options.run
|
|
2762
|
+
run: options.run,
|
|
2763
|
+
nodeExecutors: fences
|
|
2735
2764
|
});
|
|
2765
|
+
for (const id of fencedOff) {
|
|
2766
|
+
delete result.outputs[id];
|
|
2767
|
+
delete ports[id];
|
|
2768
|
+
}
|
|
2736
2769
|
return {
|
|
2737
2770
|
result,
|
|
2738
2771
|
ports,
|
|
@@ -2911,10 +2944,19 @@ var Coordinator = class {
|
|
|
2911
2944
|
*
|
|
2912
2945
|
* Also settles the skip cascade, because a skip is a decision the frontier
|
|
2913
2946
|
* just made and a second caller must not make it again.
|
|
2947
|
+
*
|
|
2948
|
+
* And it is where a skipped node's undelivered-edge warnings are emitted. A
|
|
2949
|
+
* skipped node never gets a job, so it never replays to warn for itself: its
|
|
2950
|
+
* warning used to be raised inside OTHER jobs' replays, carrying its node id,
|
|
2951
|
+
* and {@link forward} filtered it out. Asking at the skip decision — the one
|
|
2952
|
+
* place that knows the node will never run — delivers it, and asking only for
|
|
2953
|
+
* the nodes this call actually settled delivers it once.
|
|
2914
2954
|
*/
|
|
2915
2955
|
async advance() {
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2956
|
+
const state = await this.store.state(this.runKey);
|
|
2957
|
+
const frontier = Frontier.compute(this.graph, state);
|
|
2958
|
+
const settled = await Frontier.settleSkips(this.store, this.runKey, frontier.skipped);
|
|
2959
|
+
this.warnForSkipped(settled, state);
|
|
2918
2960
|
return frontier.ready;
|
|
2919
2961
|
}
|
|
2920
2962
|
/**
|
|
@@ -2954,12 +2996,12 @@ var Coordinator = class {
|
|
|
2954
2996
|
await this.store.pause(this.runKey, nodeId, result.error ?? "");
|
|
2955
2997
|
return { nodeId, status: "paused", pause, claimed: true, attempt };
|
|
2956
2998
|
}
|
|
2957
|
-
if (isBoundary(result.error)) {
|
|
2999
|
+
if (isBoundary(result.error) || result.ok) {
|
|
2958
3000
|
await this.store.skip(this.runKey, nodeId);
|
|
2959
3001
|
return {
|
|
2960
3002
|
nodeId,
|
|
2961
3003
|
status: "skipped",
|
|
2962
|
-
error: "
|
|
3004
|
+
error: "the engine found no live inbound edge for this node",
|
|
2963
3005
|
claimed: true,
|
|
2964
3006
|
attempt
|
|
2965
3007
|
};
|
|
@@ -3070,6 +3112,40 @@ var Coordinator = class {
|
|
|
3070
3112
|
if (!row) return this.run;
|
|
3071
3113
|
return this.run.withAttempt(row.attempts, row.firstAttemptAt);
|
|
3072
3114
|
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Send `runFlow`'s undelivered-edge warnings for nodes the frontier skipped.
|
|
3117
|
+
*
|
|
3118
|
+
* The check is `undeliveredEdgeWarnings` — the SAME function `runFlow` calls —
|
|
3119
|
+
* fed the durable equivalent of the engine's own bookkeeping: a port key for
|
|
3120
|
+
* every port a COMPLETED node's claim row stored, in stored order, and the set
|
|
3121
|
+
* of COMPLETED ids. `state` is the snapshot the frontier decided from, and
|
|
3122
|
+
* every predecessor of a skipped node had settled in it, so it holds all a
|
|
3123
|
+
* target's sources.
|
|
3124
|
+
*
|
|
3125
|
+
* Only skipped nodes. A target that RUNS (it had another live inbound edge)
|
|
3126
|
+
* already gets its warning from its own job's replay, whose events carry its
|
|
3127
|
+
* node id and are forwarded — asking here too would send it twice.
|
|
3128
|
+
*/
|
|
3129
|
+
warnForSkipped(skipped, state) {
|
|
3130
|
+
const sink = this.onEvent;
|
|
3131
|
+
if (!sink || skipped.length === 0) return;
|
|
3132
|
+
const portValues = /* @__PURE__ */ new Map();
|
|
3133
|
+
const completed = /* @__PURE__ */ new Set();
|
|
3134
|
+
for (const [nodeId, entry] of Object.entries(state)) {
|
|
3135
|
+
if (entry.status !== NodeRunStatus.COMPLETED) continue;
|
|
3136
|
+
completed.add(nodeId);
|
|
3137
|
+
for (const portId of entry.ports) portValues.set(`${nodeId}:${portId}`, entry.output);
|
|
3138
|
+
}
|
|
3139
|
+
const nodesById = new Map(this.graph.nodes.map((n) => [n.id, n]));
|
|
3140
|
+
for (const nodeId of skipped) {
|
|
3141
|
+
const target = nodesById.get(nodeId);
|
|
3142
|
+
if (!target) continue;
|
|
3143
|
+
const incoming = this.graph.edges.filter((e) => e.target === nodeId);
|
|
3144
|
+
for (const warning of undeliveredEdgeWarnings(target, incoming, portValues, completed, nodesById)) {
|
|
3145
|
+
sink(warning);
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3073
3149
|
async completedOutputs() {
|
|
3074
3150
|
const state = await this.store.state(this.runKey);
|
|
3075
3151
|
const out = {};
|
|
@@ -3101,6 +3177,7 @@ function cryptoRandom() {
|
|
|
3101
3177
|
|
|
3102
3178
|
exports.BOUNDARY = BOUNDARY;
|
|
3103
3179
|
exports.Coordinator = Coordinator;
|
|
3180
|
+
exports.FENCE_PORT = FENCE_PORT;
|
|
3104
3181
|
exports.Frontier = Frontier;
|
|
3105
3182
|
exports.InMemoryClaimStore = InMemoryClaimStore;
|
|
3106
3183
|
exports.NodeRunStatus = NodeRunStatus;
|