@particle-academy/fancy-flow 0.76.0 → 0.77.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-ZKAPSUTL.js → chunk-2VI24UPB.js} +3 -3
- package/dist/{chunk-ZKAPSUTL.js.map → chunk-2VI24UPB.js.map} +1 -1
- package/dist/{chunk-PXVISCSC.js → chunk-ARSOSEJV.js} +4 -4
- package/dist/{chunk-PXVISCSC.js.map → chunk-ARSOSEJV.js.map} +1 -1
- package/dist/{chunk-G4GKDJG5.js → chunk-CQAMEPPI.js} +3 -3
- package/dist/{chunk-G4GKDJG5.js.map → chunk-CQAMEPPI.js.map} +1 -1
- package/dist/{chunk-V2657EQG.js → chunk-EEIWAIXW.js} +45 -5
- package/dist/chunk-EEIWAIXW.js.map +1 -0
- package/dist/{chunk-4KYW5BT4.js → chunk-EGXA4SYJ.js} +4 -4
- package/dist/{chunk-4KYW5BT4.js.map → chunk-EGXA4SYJ.js.map} +1 -1
- package/dist/{chunk-CYVVUKHH.js → chunk-G5RAGPVD.js} +3 -3
- package/dist/{chunk-CYVVUKHH.js.map → chunk-G5RAGPVD.js.map} +1 -1
- package/dist/{chunk-M34S7XUM.js → chunk-T6WIIGSK.js} +3 -3
- package/dist/{chunk-M34S7XUM.js.map → chunk-T6WIIGSK.js.map} +1 -1
- package/dist/durable.cjs +39 -35
- package/dist/durable.cjs.map +1 -1
- package/dist/durable.js +1 -2
- package/dist/durable.js.map +1 -1
- package/dist/engine.cjs +42 -38
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.js +4 -6
- package/dist/engine.js.map +1 -1
- package/dist/index.cjs +42 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +12 -13
- package/dist/index.js.map +1 -1
- package/dist/registry.cjs +42 -38
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +2 -3
- package/dist/runtime.cjs +32 -2
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +3 -3
- package/dist/schema.cjs +32 -2
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +3 -3
- package/dist/screens.cjs +32 -2
- package/dist/screens.cjs.map +1 -1
- package/dist/screens.js +4 -5
- package/dist/screens.js.map +1 -1
- package/dist/ux.cjs +32 -2
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-UEOE6B52.js +0 -39
- package/dist/chunk-UEOE6B52.js.map +0 -1
- package/dist/chunk-V2657EQG.js.map +0 -1
package/dist/durable.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { pauseForHuman, decodePause } from './chunk-
|
|
2
|
-
import { getNodeKind, runFlow, resolveKindId, kindIds, truthy, RunIdentity, undeliveredEdgeWarnings } from './chunk-V2657EQG.js';
|
|
1
|
+
import { getNodeKind, runFlow, resolveKindId, kindIds, pauseForHuman, truthy, RunIdentity, decodePause, undeliveredEdgeWarnings } from './chunk-EEIWAIXW.js';
|
|
3
2
|
import './chunk-2QXKDLGT.js';
|
|
4
3
|
|
|
5
4
|
// src/durable/state.ts
|
package/dist/durable.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/durable/state.ts","../src/durable/frontier.ts","../src/durable/dispatch.ts","../src/durable/replay.ts","../src/durable/retry.ts","../src/durable/human.ts","../src/durable/coordinator.ts"],"names":[],"mappings":"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","file":"durable.js","sourcesContent":["/**\n * What a durable run remembers, and the seam a real database plugs into.\n *\n * A durable run is bookkeeping plus one hard requirement: **the claim is a\n * unique constraint, not a check.** Two workers racing for the same node must\n * produce a no-op, not a double run, and only the storage layer can promise\n * that. So {@link NodeClaimStore} is an interface with exactly the operations a\n * driver needs, and an adapter implements it over `INSERT … ON CONFLICT DO\n * NOTHING` (or its dialect's spelling).\n *\n * {@link InMemoryClaimStore} is the reference implementation and is genuinely\n * useful: it makes the whole per-node driver testable, and it is correct for a\n * single-process durable run.\n *\n * This is a port of `fancy_flow.durable.state`, which is itself the Python twin\n * of `fancy-flow-php`'s `NodeClaims` + `workflow_run_nodes`. Three runtimes,\n * one design — see `.ai/plans/fancy-flow-run-identity-and-steps.md`.\n */\n\n/** Where one node of one run has got to. */\nexport const NodeRunStatus = {\n CLAIMED: \"claimed\",\n COMPLETED: \"completed\",\n SKIPPED: \"skipped\",\n FAILED: \"failed\",\n PAUSED: \"paused\",\n} as const;\n\nexport type NodeRunStatusValue = (typeof NodeRunStatus)[keyof typeof NodeRunStatus];\n\n/**\n * A node the frontier may treat as decided.\n *\n * A FAILED node is settled too — it will never publish, so its successors skip\n * rather than wait forever.\n */\nexport const SETTLED: readonly NodeRunStatusValue[] = [\n NodeRunStatus.COMPLETED,\n NodeRunStatus.SKIPPED,\n NodeRunStatus.FAILED,\n];\n\n/**\n * One node's row.\n *\n * `ports` are the ports the engine's own `node-output` events reported. They\n * are STORED, never recomputed: a second copy of the routing table would agree\n * for a year and then disagree on one branch.\n */\nexport type NodeState = {\n status: NodeRunStatusValue;\n ports: readonly string[];\n output?: unknown;\n error?: string | null;\n owner?: string | null;\n /** 1-based, incremented when an owner re-enters its own claim. */\n attempts: number;\n /** ISO-8601 UTC of the FIRST claim. Never updated — it is the retry clock. */\n firstAttemptAt: string;\n};\n\n/**\n * The persistence a per-node driver needs.\n *\n * Six operations. An adapter over Postgres, SQLite or Redis implements these\n * and nothing else; every rule about WHICH node may run lives in `frontier.ts`,\n * which reads only {@link state}.\n */\nexport interface NodeClaimStore {\n /**\n * Take exclusive ownership of one node of one run.\n *\n * MUST be atomic against concurrent callers, and MUST return `true` for a\n * caller re-entering its OWN claim — that is what lets a job's retry resume\n * instead of deadlocking against the row it wrote itself.\n */\n claim(runKey: string, nodeId: string, owner: string): boolean | Promise<boolean>;\n state(runKey: string): Record<string, NodeState> | Promise<Record<string, NodeState>>;\n complete(\n runKey: string,\n nodeId: string,\n output: unknown,\n ports: readonly string[],\n ): void | Promise<void>;\n /**\n * Settle one node as skipped.\n *\n * Return whether THIS call moved the row to skipped: `false` when the row was\n * already skipped — another caller's frontier made the same decision first.\n * The coordinator emits a skipped node's diagnostics only for a call that\n * settled it, so a warning arrives once however many callers race.\n *\n * Returning nothing is still allowed, for stores written before the boolean,\n * and counts as \"settled now\". Such a store can report a skip twice under a\n * race, and nothing worse.\n */\n skip(runKey: string, nodeId: string): boolean | void | Promise<boolean | void>;\n fail(runKey: string, nodeId: string, error: string): void | Promise<void>;\n pause(runKey: string, nodeId: string, reason: string): void | Promise<void>;\n}\n\n/**\n * A correct, single-process {@link NodeClaimStore}.\n *\n * JavaScript's single-threaded event loop makes the claim atomic for free —\n * there is no interleaving point inside a synchronous method. It is NOT durable\n * across a restart, which is the honest limit: use it for tests, for a CLI run,\n * and for a worker that genuinely owns the whole run.\n */\nexport class InMemoryClaimStore implements NodeClaimStore {\n private readonly runs = new Map<string, Map<string, NodeState>>();\n\n claim(runKey: string, nodeId: string, owner: string): boolean {\n const run = this.run(runKey);\n const existing = run.get(nodeId);\n\n if (!existing) {\n run.set(nodeId, {\n status: NodeRunStatus.CLAIMED,\n ports: [],\n owner,\n attempts: 1,\n firstAttemptAt: new Date().toISOString(),\n });\n return true;\n }\n\n // Re-entering our own claim is how a retry gets back in. Anything else —\n // another owner, or a settled node — is a lost race, and a lost race is a\n // NO-OP rather than a duplicate run.\n const ownClaim =\n (existing.status === NodeRunStatus.CLAIMED || existing.status === NodeRunStatus.PAUSED) &&\n existing.owner === owner;\n\n if (ownClaim) {\n existing.attempts += 1;\n existing.status = NodeRunStatus.CLAIMED;\n return true;\n }\n return false;\n }\n\n state(runKey: string): Record<string, NodeState> {\n const out: Record<string, NodeState> = {};\n for (const [nodeId, entry] of this.run(runKey)) out[nodeId] = { ...entry, ports: [...entry.ports] };\n return out;\n }\n\n complete(runKey: string, nodeId: string, output: unknown, ports: readonly string[]): void {\n const entry = this.entry(runKey, nodeId);\n entry.status = NodeRunStatus.COMPLETED;\n entry.output = output;\n entry.ports = [...ports];\n entry.error = null;\n }\n\n skip(runKey: string, nodeId: string): boolean {\n const entry = this.entry(runKey, nodeId);\n if (entry.status === NodeRunStatus.SKIPPED) return false;\n\n entry.status = NodeRunStatus.SKIPPED;\n entry.ports = [];\n return true;\n }\n\n fail(runKey: string, nodeId: string, error: string): void {\n const entry = this.entry(runKey, nodeId);\n entry.status = NodeRunStatus.FAILED;\n entry.error = error;\n entry.ports = [];\n }\n\n pause(runKey: string, nodeId: string, reason: string): void {\n const entry = this.entry(runKey, nodeId);\n entry.status = NodeRunStatus.PAUSED;\n entry.error = reason;\n entry.ports = [];\n }\n\n /**\n * Drop a paused node's claim so a recorded answer can re-run it.\n *\n * A PAUSED row holds one of the run's dispatch slots (see `selectDispatch`),\n * so this is also what frees the slot: until the row is released, a serial\n * run's `advance()` hands out nothing, the gate included.\n *\n * Not part of the interface: resuming a human gate is the host's decision and\n * its storage's business. Provided here because the in-memory store is also\n * what the tests resume through.\n */\n release(runKey: string, nodeId: string): void {\n this.run(runKey).delete(nodeId);\n }\n\n private run(runKey: string): Map<string, NodeState> {\n let run = this.runs.get(runKey);\n if (!run) {\n run = new Map();\n this.runs.set(runKey, run);\n }\n return run;\n }\n\n /** The row for one node, created as CLAIMED if a driver never claimed it. */\n private entry(runKey: string, nodeId: string): NodeState {\n const run = this.run(runKey);\n let entry = run.get(nodeId);\n if (!entry) {\n entry = {\n status: NodeRunStatus.CLAIMED,\n ports: [],\n attempts: 1,\n firstAttemptAt: new Date().toISOString(),\n };\n run.set(nodeId, entry);\n }\n return entry;\n }\n}\n\nexport function isSettled(status: string | undefined): boolean {\n return status !== undefined && (SETTLED as readonly string[]).includes(status);\n}\n","/**\n * Which nodes can run RIGHT NOW, given what has already settled.\n *\n * ## Why this is not a second engine\n *\n * `runFlow` walks a Kahn topological order and, at each node, runs it when at\n * least one incoming edge is active. That is a total order because one process\n * executes every node. Split the graph across jobs and the same rule has to be\n * asked the other way round — not \"what is next\" but \"what is unblocked\" —\n * which is this module.\n *\n * The rule is the engine's, restated:\n *\n * - every direct predecessor has SETTLED (in topological order the engine has\n * already settled all of them by the time it reaches a node);\n * - and either the node has no incoming edges, or at least one incoming edge is\n * ACTIVE — its source completed and published on that edge's source handle.\n *\n * A node whose predecessors have all settled with no active edge is what the\n * engine reports as `idle/skipped`. Skipping SETTLES it, which can in turn\n * unblock — or skip — its own successors, so the pass repeats until nothing\n * changes. That cascade is how a dead branch collapses without leaving the run\n * stuck.\n *\n * ## The one thing it does NOT decide\n *\n * Which ports a result activated. Those rules (`__port`, `branch`, declared\n * outputs, the kind's ports, the `out` fallback) live in the engine and stay\n * there: this reads the ports back off the `node-output` events the engine\n * emitted when the node ran, stored on the claim row.\n */\n\nimport { getNodeKind, kindIds } from \"../registry/registry\";\nimport type { FlowEdge, FlowGraph } from \"../types\";\nimport { NodeRunStatus, isSettled, type NodeClaimStore, type NodeState } from \"./state\";\n\nexport type FrontierResult = {\n ready: string[];\n skipped: string[];\n};\n\nfunction isNote(type: string | undefined): boolean {\n if (!type) return false;\n if (type === \"note\") return true;\n const kind = getNodeKind(type);\n return kind ? kindIds(kind).includes(\"note\") || kind.category === \"annotation\" : false;\n}\n\nexport const Frontier = {\n compute(graph: FlowGraph, state: Record<string, NodeState>): FrontierResult {\n const incoming = new Map<string, FlowEdge[]>();\n for (const edge of graph.edges) {\n const list = incoming.get(edge.target) ?? [];\n list.push(edge);\n incoming.set(edge.target, list);\n }\n\n // Settled nodes and the ports they lit. A skipped node is settled with NO\n // ports, which is precisely what makes its successors skip too.\n const settled = new Map<string, readonly string[]>();\n const held = new Set<string>();\n for (const [nodeId, entry] of Object.entries(state)) {\n if (entry.status === NodeRunStatus.COMPLETED) settled.set(nodeId, entry.ports);\n else if (isSettled(entry.status)) settled.set(nodeId, []);\n else held.add(nodeId);\n }\n\n const ready: string[] = [];\n const skipped: string[] = [];\n\n let changed = true;\n while (changed) {\n changed = false;\n\n for (const node of graph.nodes) {\n const nodeId = node.id;\n if (settled.has(nodeId) || held.has(nodeId) || ready.includes(nodeId)) continue;\n\n const edges = incoming.get(nodeId) ?? [];\n let blocked = false;\n let active = false;\n\n for (const edge of edges) {\n if (!settled.has(edge.source)) {\n blocked = true;\n break;\n }\n // The engine's port key: an edge with no source handle reads the\n // source's `out` port.\n if (settled.get(edge.source)!.includes(edge.sourceHandle ?? \"out\")) active = true;\n }\n\n if (blocked) continue;\n\n // Reached, but down a branch that never lit.\n if (edges.length > 0 && !active) {\n settled.set(nodeId, []);\n skipped.push(nodeId);\n changed = true;\n continue;\n }\n\n // Annotations and layout are never executed. Settling them here rather\n // than dispatching a job saves a queue round trip per sticky note — and\n // a graph can carry a lot of sticky notes.\n if (isNote(node.type) || getNodeKind(node.type ?? \"\")?.category === \"layout\") {\n settled.set(nodeId, []);\n skipped.push(nodeId);\n changed = true;\n continue;\n }\n\n ready.push(nodeId);\n }\n }\n\n return { ready, skipped };\n },\n\n /** Has every node settled? The run is finished when it has. */\n isComplete(graph: FlowGraph, state: Record<string, NodeState>): boolean {\n return graph.nodes.every((node) => isSettled(state[node.id]?.status));\n },\n\n /**\n * Is any node still held by a worker, or parked for a person?\n *\n * An empty frontier means something different depending on this: with work in\n * flight the run is simply waiting, and whichever job finishes will advance\n * it. With nothing in flight and nodes still unsettled, the graph cannot\n * progress at all — which is a stuck run, and must be reported rather than\n * waited on.\n */\n hasWorkInFlight(state: Record<string, NodeState>): boolean {\n return Object.values(state).some(\n (entry) => entry.status === NodeRunStatus.CLAIMED || entry.status === NodeRunStatus.PAUSED,\n );\n },\n\n /**\n * Persist the skip cascade so the next pass does not recompute it.\n *\n * Resolves to the ids THIS call settled, in cascade order. A node another\n * caller had already settled is left out — its store reported `false` — so a\n * caller acting on \"I skipped this\" acts once per node, not once per racer. A\n * store whose `skip` returns nothing counts every id as settled here.\n */\n async settleSkips(\n store: NodeClaimStore,\n runKey: string,\n skipped: readonly string[],\n ): Promise<string[]> {\n const settled: string[] = [];\n for (const nodeId of skipped) {\n if ((await store.skip(runKey, nodeId)) !== false) settled.push(nodeId);\n }\n return settled;\n },\n};\n","/**\n * How many of ONE run's nodes may be held at once, and which ready nodes go next.\n *\n * ## Serial is the default\n *\n * A queued run hands a node to the queue only once the node before it has\n * settled: one node of a run held at a time, in the graph's own declaration\n * order. Parallel dispatch of a ready frontier is something a host ASKS for,\n * with `maxConcurrent` on the {@link Coordinator}.\n *\n * | `maxConcurrent` | meaning |\n * |---|---|\n * | unset | **1**: serial |\n * | `N >= 1` | up to N of the run's nodes held at once |\n * | {@link UNLIMITED_CONCURRENCY} (`0`) | the whole ready frontier |\n * | anything else | refused, by name |\n *\n * A negative number is refused rather than read as \"unlimited\". Under a serial\n * default, a typo that silently turned a run parallel is the failure to avoid.\n *\n * ## Held means claimed OR paused\n *\n * A node parked on a person keeps its slot. A pause does not park the whole run\n * in this runtime: `advance()` is called whenever any job settles, and without\n * this rule it would hand out the gate's siblings while the person is still\n * deciding.\n *\n * ## Measured against held work, not the batch\n *\n * Two nodes settling at once each trigger an `advance()` on a real queue. A cap\n * applied to one batch would let each of them dispatch its own quota, so the\n * budget is `maxConcurrent - held`, counted off the claim rows.\n *\n * This is the TypeScript member of a three-runtime contract. The\n * `flow/durable-dispatch` conformance suite pins it, and names\n * `fancy-flow-php`'s `DispatchLimit` and the Python runtime's\n * `fancy_flow.durable.select_dispatch` as the other two implementations.\n */\n\nimport { NodeRunStatus, type NodeState } from \"./state\";\n\n/** Dispatch the whole ready frontier. Named so a host never writes a bare `0`. */\nexport const UNLIMITED_CONCURRENCY = 0;\n\n/** One node of a run held at a time: what an unset `maxConcurrent` means. */\nexport const DEFAULT_MAX_CONCURRENT = 1;\n\n/**\n * Refuse a limit that is neither a cap nor {@link UNLIMITED_CONCURRENCY}.\n *\n * Thrown where the limit is SET, not where the first `advance()` trips over it\n * on a worker.\n */\nexport function assertMaxConcurrent(value: unknown): number {\n if (typeof value === \"number\" && Number.isInteger(value) && value >= 0) return value;\n\n throw new RangeError(\n `maxConcurrent must be a positive integer, or UNLIMITED_CONCURRENCY (0) for the whole ready ` +\n `frontier, or unset for serial; got ${describe(value)}.`,\n );\n}\n\n/**\n * The ready nodes that may be dispatched now, in the order given.\n *\n * `ready` comes from `Frontier.compute`, in declaration order, and that order is\n * kept: this slices, it never sorts. `state` must already include any skips the\n * frontier just settled. Skips are never held, so that changes no count, but the\n * state must describe the run as it is after the decision.\n */\nexport function selectDispatch(\n ready: readonly string[],\n state: Record<string, NodeState>,\n maxConcurrent: number,\n): string[] {\n const limit = assertMaxConcurrent(maxConcurrent);\n if (limit === UNLIMITED_CONCURRENCY) return [...ready];\n\n let held = 0;\n for (const entry of Object.values(state)) {\n if (entry.status === NodeRunStatus.CLAIMED || entry.status === NodeRunStatus.PAUSED) held++;\n }\n\n return ready.slice(0, Math.max(0, limit - held));\n}\n\nfunction describe(value: unknown): string {\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"string\") return JSON.stringify(value);\n if (value === null) return \"null\";\n return typeof value;\n}\n","/**\n * Run ONE node of a graph — through the real engine, not around it.\n *\n * ## The problem this solves\n *\n * A per-node driver has to hand a node exactly the inputs it would have\n * received mid-run: the right values, on the right target handles, from the\n * right *active* edges. Those rules are the engine's (`collectInputs`,\n * `activatedPorts`, the merge-after-decision contract, the `out` fallbacks),\n * and they are the reason the three runtimes agree. Re-implementing them here\n * would be a second engine wearing a driver's clothes, and the two would drift.\n *\n * ## What it does instead\n *\n * It replays the graph with `runFlow` untouched:\n *\n * - every node already completed is fed back as `resumeOutputs`, so the engine\n * republishes it on the same ports and routes exactly as it did the first\n * time;\n * - every node EXCEPT the target is bound, through `RunOptions.nodeExecutors`,\n * to a FENCE that runs nothing and publishes only a port no edge reads;\n * - so the engine walks its own topological order, skips its own dead branches,\n * collects the target's inputs its own way, and runs the target.\n *\n * ## Why the fence does not stop the walk\n *\n * It used to abort the run. The target's own inputs never depend on a fenced\n * node -- the frontier dispatches a node only once every source is settled, and\n * settled sources are resumed, not fenced -- but an UNRELATED node can precede\n * the target in topological order. Two siblings dispatched together are exactly\n * that: when `b`'s job started while `a` was still running, the replay aborted\n * at `a`, never reached `b`, and the coordinator read \"the replay ended without\n * running me\" as \"the engine decided I am unreachable\". `b` was recorded\n * skipped, never ran, and the run completed as a success. It did not even take\n * two workers: the frontier lists ready nodes in NODE order and the engine walks\n * EDGE order, so a graph where those disagree about siblings sent in-process\n * `runToCompletion` down the same path.\n *\n * Walking past fences makes that inference honest again: when the replay\n * finishes without an output for the target, it is because the engine found\n * every inbound edge dead.\n *\n * ## Why the fences are not registry entries\n *\n * The registry is one flat object, and a key in it is tried as a node's id AND\n * as its kind. Fencing the node called `host_kind` by writing\n * `executors[\"host_kind\"]` fenced every node of kind `host_kind` too, so a\n * durable run of that graph ran nothing and reported success. And the registry\n * is what `ctx.executors` hands a `subflow` child, so a child node sharing an id\n * with any parent node ran the parent's fence. `nodeExecutors` matches node ids\n * only and is never handed down; the registry reaches the engine untouched.\n *\n * The target's output is `result.outputs[nodeId]`, and the ports it activated\n * arrive as the engine's own `node-output` events. Nothing about routing is\n * recomputed here.\n *\n * ## The cost, stated plainly\n *\n * Replaying the completed prefix is O(nodes) per node, so a run is O(nodes²) in\n * bookkeeping. The republish executes nothing — it re-publishes stored values —\n * so for the graph sizes workflows actually have this is noise next to a single\n * queue round trip. It buys exact fidelity to the engine, which is not\n * negotiable, and one implementation of the routing rules instead of two.\n */\n\nimport { runFlow, type RunResult } from \"../runtime/run-flow\";\nimport type { RunIdentity } from \"../runtime/run-identity\";\nimport type { ExecutorRegistry, FlowGraph, NodeExecutor, RunEvent } from \"../types\";\n\n/**\n * The abort reason a boundary used to report.\n *\n * Nothing aborts with it any more (see \"Why the fence does not stop the walk\");\n * {@link isBoundary} still recognises it so a caller that checks for it keeps\n * working.\n */\nexport const BOUNDARY = \"fancy-flow:node-boundary\";\n\n/**\n * The port a fenced node publishes on. No edge reads it, so everything\n * downstream of a fenced node is dark in the replay -- which never matters to\n * the target, whose sources are all settled.\n */\nexport const FENCE_PORT = \"fancy-flow:fenced\";\n\nexport type ReplayResult = {\n result: RunResult;\n /** node id -> the ports its output activated, from the engine's own events. */\n ports: Record<string, string[]>;\n outputOf(nodeId: string): unknown;\n portsOf(nodeId: string): string[];\n};\n\nexport type ReplayOptions = {\n resumeOutputs?: Record<string, unknown>;\n initialInputs?: Record<string, Record<string, unknown>>;\n onEvent?: (event: RunEvent) => void;\n depth?: number;\n run?: RunIdentity;\n};\n\n/**\n * Replay `graph` up to and through `nodeId`.\n *\n * Pass `nodeId = null` to PROBE: every node is fenced, so nothing executes and\n * the engine reports only what it can determine structurally — a cycle, and\n * the ports each resumed output republishes on.\n */\nexport async function replayUpTo(\n graph: FlowGraph,\n nodeId: string | null,\n executors: ExecutorRegistry,\n options: ReplayOptions = {},\n): Promise<ReplayResult> {\n // The ids a fence actually ran for. A fence executes nothing, so what the\n // engine recorded for these nodes is not an output and is removed below.\n const fencedOff = new Set<string>();\n const fence: NodeExecutor = (ctx) => {\n fencedOff.add(ctx.node.id);\n return { __port: FENCE_PORT, value: null };\n };\n\n const fences: Record<string, NodeExecutor> = {};\n for (const node of graph.nodes) {\n if (node.id !== nodeId) fences[node.id] = fence;\n }\n\n const ports: Record<string, string[]> = {};\n const collect = (event: RunEvent): void => {\n if (event.type === \"node-output\") {\n (ports[event.nodeId] ??= []).push(event.portId);\n }\n options.onEvent?.(event);\n };\n\n const result = await runFlow(graph, executors, collect, {\n initialInputs: options.initialInputs ?? {},\n resumeOutputs: options.resumeOutputs ?? {},\n depth: options.depth ?? 0,\n run: options.run,\n nodeExecutors: fences,\n });\n\n for (const id of fencedOff) {\n delete result.outputs[id];\n delete ports[id];\n }\n\n return {\n result,\n ports,\n outputOf: (id) => result.outputs[id],\n portsOf: (id) => ports[id] ?? [],\n };\n}\n\n/** True when a replay ended because it reached a node it does not own. */\nexport function isBoundary(error: string | null | undefined): boolean {\n return error === BOUNDARY;\n}\n","/**\n * How many times a single node may be attempted.\n *\n * ## Why this cannot be one number\n *\n * A run-wide `tries` setting forces every workflow to pick between two bad\n * answers. At 1, a single flaky LLM or HTTP call takes the whole run down.\n * Above 1, the retry replays from the last checkpoint and everything already\n * done runs again — including the nodes that must not: `git_pr_open` opens a\n * second pull request.\n *\n * Per-node jobs make the question per node, which is where it always belonged.\n * A node declaring `sideEffects: unsafe-to-replay` is pinned to ONE attempt and\n * no backoff. Everything else takes the configured tries, or a per-kind\n * override.\n *\n * Undeclared side effects are treated as the configured default rather than\n * assumed safe: this decides retries, and inventing a safety claim on a node\n * author's behalf is how a retry loop ends up posting the same webhook twice.\n */\n\nimport { getNodeKind, kindIds, resolveKindId } from \"../registry/registry\";\nimport type { FlowNode } from \"../types\";\n\n/** A node that is not safe to run twice. Same vocabulary as the node manifest. */\nexport const UNSAFE_TO_REPLAY = \"unsafe-to-replay\";\n\nexport type RetryPolicyOptions = {\n tries?: number;\n backoffSeconds?: number;\n /** kind id -> tries. Keyed by any spelling; every id is checked. */\n perKind?: Record<string, number>;\n};\n\nexport class RetryPolicy {\n readonly tries: number;\n readonly backoffSeconds: number;\n readonly perKind: Record<string, number>;\n\n constructor(options: RetryPolicyOptions = {}) {\n this.tries = options.tries ?? 1;\n this.backoffSeconds = options.backoffSeconds ?? 0;\n this.perKind = options.perKind ?? {};\n }\n\n triesFor(node: FlowNode): number {\n if (RetryPolicy.isUnsafeToReplay(node)) return 1;\n\n for (const id of idsFor(node)) {\n if (id in this.perKind) return Math.max(1, Math.trunc(this.perKind[id]!));\n }\n\n return Math.max(1, Math.trunc(this.tries));\n }\n\n backoffFor(node: FlowNode): number {\n // Nothing to back off from: the node gets one attempt.\n if (RetryPolicy.isUnsafeToReplay(node)) return 0;\n return Math.max(0, this.backoffSeconds);\n }\n\n static isUnsafeToReplay(node: FlowNode): boolean {\n if (!node.type) return false;\n return getNodeKind(node.type)?.sideEffects === UNSAFE_TO_REPLAY;\n }\n}\n\n/**\n * Every id a per-kind override for this node could be keyed under.\n *\n * Canonical ids are namespaced while a host almost certainly writes the bare\n * one; keying on only the literal string would make the override silently stop\n * applying the day a kind is renamed.\n */\nfunction idsFor(node: FlowNode): string[] {\n if (!node.type) return [];\n const kind = getNodeKind(node.type);\n const canonical = resolveKindId(node.type);\n const ordered = [node.type, ...(canonical ? [canonical] : []), ...(kind ? kindIds(kind) : [])];\n return [...new Set(ordered)];\n}\n","/**\n * Human gates that cannot be walked past — and that hold no worker.\n *\n * ## The directive these implement\n *\n * A node that waits on a person must not occupy a worker, a connection or a\n * process while it waits. So the job for a human gate does its work by\n * **emitting the request** and then **finishing**: the run is persisted as\n * parked, the job returns, the worker takes the next thing off the queue. When\n * the person answers, *that* enqueues the continuation.\n *\n * Mechanically the gate aborts with an encoded pause, which is a *return*, not\n * a block. Nothing in this file sleeps, polls or awaits a person.\n *\n * ## Fail closed, and why\n *\n * A gate pauses because it **is** a human node, not because its input port\n * happens to be empty. This is not a preference; it is a fix. Both peer\n * runtimes once decided whether to pause by reading their own input, so a\n * pre-filled `values` or `approved` value ran the flow straight past the person\n * it was waiting for — silently, with the run reporting success.\n *\n * Restoring the old behaviour is possible, explicit, and per node:\n * `autoAnswerFromInput`. Turn it on for a step that is a form when a human is\n * present and a pass-through when an upstream node already produced the answer.\n * On an approval node, weigh it harder: it means the graph, not a person, can\n * approve.\n *\n * ## The other half of the fix\n *\n * Recording an answer for a node the run is not parked on THROWS rather than\n * queueing a write nobody reads. See {@link Submissions.record}.\n */\n\nimport { pauseForHuman } from \"../registry/pause\";\nimport { truthy } from \"../expressions/expr\";\nimport type { NodeExecutor } from \"../types\";\n\n/** An answer was recorded for a node the run is not waiting on. */\nexport class NotAwaitingHuman extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NotAwaitingHuman\";\n }\n}\n\n/**\n * Answers recorded for human gates, keyed by node id.\n *\n * Deliberately separate from the run's inputs. Keeping them in one bag is\n * precisely what let a pre-filled input satisfy a gate.\n */\nexport class Submissions {\n private readonly answers = new Map<string, unknown>();\n /** The node the run is currently parked on, if any. */\n awaiting: string | null = null;\n\n /**\n * Record an answer for the node the run is parked on.\n *\n * Throws when the run is not waiting on that node. A queued answer for a node\n * that never paused is a write nobody reads — and it looks, from the outside,\n * exactly like a submission that worked.\n */\n record(nodeId: string, value: unknown): void {\n if (this.awaiting !== null && this.awaiting !== nodeId) {\n throw new NotAwaitingHuman(\n `This run is waiting on \"${this.awaiting}\", not \"${nodeId}\". Recording an answer for a ` +\n \"node the run is not parked on would be stored and never read.\",\n );\n }\n if (this.awaiting === null) {\n throw new NotAwaitingHuman(\n `This run is not waiting for anyone, so an answer for \"${nodeId}\" has nothing to resume.`,\n );\n }\n this.answers.set(nodeId, value);\n this.awaiting = null;\n }\n\n answered(nodeId: string): boolean {\n return this.answers.has(nodeId);\n }\n\n answer(nodeId: string): unknown {\n return this.answers.get(nodeId);\n }\n\n park(nodeId: string): void {\n this.awaiting = nodeId;\n }\n}\n\nfunction option(ctx: { node: { data?: unknown } }, key: string, fallback?: unknown): unknown {\n const config = ((ctx.node.data as any)?.config ?? {}) as Record<string, unknown>;\n return config[key] ?? fallback;\n}\n\n/** `user_input` — pauses until a submission for THIS node is recorded. */\nexport function durableUserInput(submissions: Submissions): NodeExecutor {\n return (ctx) => {\n if (submissions.answered(ctx.node.id)) return submissions.answer(ctx.node.id);\n\n if (option(ctx, \"autoAnswerFromInput\") === true) {\n const values = (ctx.inputs as Record<string, unknown>).values;\n if (values !== undefined) return values;\n }\n\n submissions.park(ctx.node.id);\n // Emits the request and RETURNS. A host listening to the feed sends the\n // email; the worker is free the moment this throws.\n return pauseForHuman(ctx, \"input\", {\n title: option(ctx, \"title\", \"Need your input\"),\n fields: option(ctx, \"fields\", []),\n });\n };\n}\n\n/** `human_approval` — pauses until a decision for THIS node is recorded. */\nexport function durableApproval(submissions: Submissions): NodeExecutor {\n return (ctx) => {\n const passthrough = (ctx.inputs as Record<string, unknown>).in ?? ctx.inputs;\n\n if (submissions.answered(ctx.node.id)) {\n return { branch: truthy(submissions.answer(ctx.node.id)) ? \"approved\" : \"denied\", value: passthrough };\n }\n\n if (option(ctx, \"autoAnswerFromInput\") === true) {\n const decision = (ctx.inputs as Record<string, unknown>).approved;\n if (decision !== undefined) {\n return { branch: truthy(decision) ? \"approved\" : \"denied\", value: passthrough };\n }\n }\n\n submissions.park(ctx.node.id);\n return pauseForHuman(ctx, \"approval\", {\n title: option(ctx, \"title\", \"Approve action\"),\n description: option(ctx, \"description\"),\n });\n };\n}\n","/**\n * The per-node driver, with the queue left out.\n *\n * This is the whole of \"how a queued run branches\", minus the transport. It\n * owns two operations and nothing else:\n *\n * `advance()`\n * Ask the frontier what is unblocked, settle the skip cascade, and report the\n * node ids that may be dispatched NOW. A queue adapter dispatches one job per\n * id. By default that is at most one id: a run holds one node at a time, and\n * the next node is handed out only when the one before it settles. See\n * {@link CoordinatorOptions.maxConcurrent}.\n *\n * `runNode()`\n * Claim one node, replay the graph through the real engine fenced to that\n * node, and checkpoint the output plus the ports the ENGINE said it\n * activated.\n *\n * Everything a queue library would add — enqueue, retry scheduling, worker\n * lifecycle — sits outside. That separation is the point: it makes the subtle\n * part (which node may run, and with what inputs) testable in-process, with no\n * broker, and identical under every adapter. **A queue adapter therefore\n * contains no workflow logic at all.** That is the test: if an adapter needs to\n * know what a port is, the seam is in the wrong place.\n *\n * `runToCompletion` drives both in one process. It is a real durable runner,\n * not a toy: with a persistent `NodeClaimStore` it survives a crash exactly as\n * a queued run does, because the crash-resume behaviour lives in the\n * checkpoints rather than in the loop.\n *\n * ## No worker waits on a person\n *\n * A human gate returns `paused`. `runToCompletion` returns immediately when it\n * sees one — it does not spin, sleep or poll. The run is parked in the store,\n * the process is free, and a recorded answer is what starts the next job.\n *\n * A paused node keeps its dispatch slot, so under any finite `maxConcurrent`\n * (serial, by default) a queue adapter's `advance()` hands out nothing\n * alongside a gate while the person decides. Resuming releases the row — see\n * `InMemoryClaimStore.release` — which frees the slot for the gate to run\n * again.\n */\n\nimport { decodePause, type PauseSignal } from \"../registry/pause\";\nimport type { RunResult } from \"../runtime/run-flow\";\nimport { RunIdentity, type RunIdentityJson } from \"../runtime/run-identity\";\nimport { undeliveredEdgeWarnings } from \"../runtime/undelivered-edges\";\nimport type { ExecutorRegistry, FlowGraph, RunEvent } from \"../types\";\nimport { DEFAULT_MAX_CONCURRENT, assertMaxConcurrent, selectDispatch } from \"./dispatch\";\nimport { Frontier } from \"./frontier\";\nimport { isBoundary, replayUpTo } from \"./replay\";\nimport { RetryPolicy } from \"./retry\";\nimport {\n InMemoryClaimStore,\n NodeRunStatus,\n type NodeClaimStore,\n type NodeState,\n} from \"./state\";\n\n/** What happened to one node. */\nexport type NodeOutcome = {\n nodeId: string;\n status: \"not-claimed\" | \"completed\" | \"skipped\" | \"failed\" | \"paused\";\n output?: unknown;\n ports?: string[];\n error?: string;\n pause?: PauseSignal;\n /** False when another worker got there first. A lost race is a NO-OP. */\n claimed: boolean;\n /** 1-based attempt this execution ran as. */\n attempt: number;\n /**\n * `true` when this attempt failed and the policy still allows another.\n *\n * The claim row is left CLAIMED in that case, deliberately: a queue adapter\n * re-dispatches the job with the SAME owner token and the retry re-enters the\n * claim it already holds. Recording FAILED here instead would settle the\n * node, which SKIPS everything downstream — a run reporting a tidy finish\n * having done half its work.\n */\n retryable?: boolean;\n};\n\nexport type DurableRunResult = {\n ok: boolean;\n outputs: Record<string, unknown>;\n error?: string;\n pause?: PauseSignal;\n paused: boolean;\n};\n\nexport type CoordinatorOptions = {\n graph: FlowGraph;\n executors: ExecutorRegistry;\n /**\n * The run's stable identity. A bare string is taken as the run key.\n *\n * Required — not defaulted. A durable run without a stable key cannot key an\n * idempotent write, and minting one per construction would hand a retrying\n * host a different key each time.\n */\n run: string | RunIdentity | RunIdentityJson;\n store?: NodeClaimStore;\n initialInputs?: Record<string, Record<string, unknown>>;\n retry?: RetryPolicy;\n onEvent?: (event: RunEvent) => void;\n /**\n * How many of this run's nodes may be HELD at once. Held means CLAIMED by a\n * worker or PAUSED on a person: a paused gate keeps its slot.\n *\n * **Defaults to `1`: serial.** `advance()` hands out one node, and the next\n * only once that one has settled, in the graph's declaration order.\n *\n * A positive integer raises the cap. `UNLIMITED_CONCURRENCY` (`0`) hands out\n * the whole ready frontier at once, which is what every queued run did before\n * this option existed. A negative, fractional or non-numeric value throws\n * here, at construction.\n */\n maxConcurrent?: number;\n};\n\nexport class Coordinator {\n readonly graph: FlowGraph;\n readonly executors: ExecutorRegistry;\n readonly run: RunIdentity;\n readonly store: NodeClaimStore;\n readonly initialInputs: Record<string, Record<string, unknown>>;\n readonly retry: RetryPolicy;\n /** The dispatch cap `advance()` applies. `0` is unlimited. */\n readonly maxConcurrent: number;\n private readonly onEvent?: (event: RunEvent) => void;\n\n constructor(options: CoordinatorOptions) {\n this.graph = options.graph;\n this.executors = options.executors;\n this.run = RunIdentity.from(options.run);\n this.store = options.store ?? new InMemoryClaimStore();\n this.initialInputs = options.initialInputs ?? {};\n this.retry = options.retry ?? new RetryPolicy();\n this.maxConcurrent = assertMaxConcurrent(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT);\n this.onEvent = options.onEvent;\n }\n\n get runKey(): string {\n return this.run.runKey;\n }\n\n // -- the two operations ----------------------------------------------\n\n /**\n * Which nodes may be dispatched right now.\n *\n * The ready frontier, cut to the run's {@link maxConcurrent} budget: the\n * first `maxConcurrent - held` ready ids in declaration order, where `held`\n * counts CLAIMED and PAUSED rows. Under the serial default that is at most one\n * id, and none while a node is still running or a gate is waiting on a person.\n * An empty result with work held is a throttled run, not a stalled one: the\n * held node's settle is what calls this again.\n *\n * Also settles the skip cascade, because a skip is a decision the frontier\n * just made and a second caller must not make it again.\n *\n * And it is where a skipped node's undelivered-edge warnings are emitted. A\n * skipped node never gets a job, so it never replays to warn for itself: its\n * warning used to be raised inside OTHER jobs' replays, carrying its node id,\n * and {@link forward} filtered it out. Asking at the skip decision — the one\n * place that knows the node will never run — delivers it, and asking only for\n * the nodes this call actually settled delivers it once.\n */\n async advance(): Promise<string[]> {\n const state = await this.store.state(this.runKey);\n const frontier = Frontier.compute(this.graph, state);\n const settled = await Frontier.settleSkips(this.store, this.runKey, frontier.skipped);\n this.warnForSkipped(settled, state);\n\n // Held work is counted against the run AFTER the skips just settled. A skip\n // is never held, so this moves no count today, but the budget must be read\n // off the state the decision produced, not the one it started from.\n const after: Record<string, NodeState> = { ...state };\n for (const nodeId of frontier.skipped) {\n const row: NodeState | undefined = state[nodeId];\n after[nodeId] = row\n ? { ...row, status: NodeRunStatus.SKIPPED, ports: [] }\n : { status: NodeRunStatus.SKIPPED, ports: [], attempts: 0, firstAttemptAt: \"\" };\n }\n\n return selectDispatch(frontier.ready, after, this.maxConcurrent);\n }\n\n /**\n * Claim, execute and checkpoint one node.\n *\n * The claim is taken FIRST. Two workers racing for the same node produce one\n * execution and one no-op, and the loser learns that from the store rather\n * than from a duplicate side effect.\n *\n * `owner` is the token that lets a job's own retry re-enter its claim instead\n * of deadlocking against the row it wrote itself — pass the SAME token across\n * a job's attempts.\n */\n async runNode(nodeId: string, owner?: string): Promise<NodeOutcome> {\n const token = owner ?? cryptoRandom();\n if (!(await this.store.claim(this.runKey, nodeId, token))) {\n return { nodeId, status: \"not-claimed\", claimed: false, attempt: 0 };\n }\n\n const row = (await this.store.state(this.runKey))[nodeId];\n const identity = this.identityFor(row);\n\n const replay = await replayUpTo(this.graph, nodeId, this.executors, {\n resumeOutputs: await this.completedOutputs(),\n initialInputs: this.initialInputs,\n onEvent: this.forward(nodeId),\n run: identity,\n });\n const result = replay.result;\n const attempt = identity.attempt;\n\n if (Object.prototype.hasOwnProperty.call(result.outputs, nodeId)) {\n const output = result.outputs[nodeId];\n const ports = replay.portsOf(nodeId);\n await this.store.complete(this.runKey, nodeId, output, ports);\n return { nodeId, status: \"completed\", output, ports, claimed: true, attempt };\n }\n\n // The node did not produce an output. Three reasons, and they are NOT\n // interchangeable.\n const pause = decodePause(result.error);\n if (pause && pause.nodeId === nodeId) {\n await this.store.pause(this.runKey, nodeId, result.error ?? \"\");\n return { nodeId, status: \"paused\", pause, claimed: true, attempt };\n }\n\n if (isBoundary(result.error) || result.ok) {\n // The replay walks past every fence (see `replay.ts`), so a run that\n // finished without this node's output is the engine deciding the node is\n // unreachable: every inbound edge dead. The frontier normally catches that\n // first; honouring the engine's verdict here too means the two can never\n // disagree about a branch. It must not be recorded as a failure: a FAILED\n // node fails the run.\n //\n // This used to be where a node whose EARLIER SIBLING was still running\n // ended up. The fence aborted the replay at the sibling, and the node was\n // skipped without ever running.\n await this.store.skip(this.runKey, nodeId);\n return {\n nodeId,\n status: \"skipped\",\n error: \"the engine found no live inbound edge for this node\",\n claimed: true,\n attempt,\n };\n }\n\n const error = result.error ?? `node ${nodeId} produced no output and no error`;\n const node = this.graph.nodes.find((n) => n.id === nodeId);\n const tries = node ? this.retry.triesFor(node) : 1;\n\n if (attempt < tries) {\n // Leave the row CLAIMED so the same owner can re-enter it. This is what\n // `fancy-flow-php` does by only marking FAILED from the job's `failed()`\n // hook — the row a worker still holds must not settle mid-retry.\n return { nodeId, status: \"failed\", error, claimed: true, attempt, retryable: true };\n }\n\n await this.store.fail(this.runKey, nodeId, error);\n return { nodeId, status: \"failed\", error, claimed: true, attempt, retryable: false };\n }\n\n // -- an in-process driver over the two ------------------------------\n\n /**\n * Drive the graph here, in this process, one node at a time.\n *\n * It asks `advance()` exactly as a queue adapter does, so it runs nodes in the\n * order a queued run under the same `maxConcurrent` would dispatch them. A run\n * that finishes produces the same outputs under every limit. A run that stops\n * on a pause or a failure can have run a different set of nodes before it\n * stopped, because the order among nodes that are ready together differs.\n *\n * Every checkpoint is written exactly as a queued run writes it, so a crash\n * mid-loop resumes from the same place a crashed worker would.\n *\n * Retries honour {@link RetryPolicy}: a node declaring `unsafe-to-replay`\n * gets one attempt whatever the policy says, and the retry re-enters the same\n * claim with the same owner token — so the step key it derives is unchanged,\n * which is what makes the retry idempotent rather than duplicative.\n */\n async runToCompletion(\n // One pass per batch `advance()` hands out, and a serial batch is one node,\n // so the default allows at least one pass per node. A flat 10,000 would stop\n // a serial run of a larger graph short and report it as unable to progress.\n maxPasses = Math.max(10_000, this.graph.nodes.length + 1),\n ): Promise<DurableRunResult> {\n for (let pass = 0; pass < maxPasses; pass++) {\n const ready = await this.advance();\n if (ready.length === 0) break;\n\n for (const nodeId of ready) {\n const outcome = await this.runNodeWithRetries(nodeId);\n\n if (outcome.status === \"paused\") {\n // A pause parks the RUN, not just the node: continuing would run the\n // human gate's siblings while a person is still deciding. The cohort\n // waits. Nothing here blocks — this RETURNS.\n return {\n ok: false,\n outputs: await this.outputs(),\n pause: outcome.pause,\n paused: true,\n };\n }\n if (outcome.status === \"failed\") {\n return { ok: false, outputs: await this.outputs(), error: outcome.error, paused: false };\n }\n }\n }\n\n const state = await this.store.state(this.runKey);\n if (!Frontier.isComplete(this.graph, state)) {\n if (Frontier.hasWorkInFlight(state)) {\n return {\n ok: false,\n outputs: await this.outputs(),\n error: \"the run is waiting on work held elsewhere\",\n paused: false,\n };\n }\n const unsettled = this.graph.nodes\n .filter((n) => ![\"completed\", \"skipped\", \"failed\"].includes(state[n.id]?.status ?? \"\"))\n .map((n) => n.id);\n return {\n ok: false,\n outputs: await this.outputs(),\n error: `the run cannot progress; unsettled nodes: ${unsettled.join(\", \")}`,\n paused: false,\n };\n }\n\n const failed = Object.values(state)\n .filter((e) => e.status === NodeRunStatus.FAILED)\n .map((e) => e.error ?? \"node failed\");\n\n return {\n ok: failed.length === 0,\n outputs: await this.outputs(),\n error: failed[0],\n paused: false,\n };\n }\n\n /**\n * Checkpointed outputs, in the graph's own node order.\n *\n * Ordered by the graph rather than by completion so two runs of the same\n * workflow produce comparable output maps even when nodes finished in a\n * different order.\n */\n async outputs(): Promise<Record<string, unknown>> {\n const state = await this.store.state(this.runKey);\n const out: Record<string, unknown> = {};\n for (const node of this.graph.nodes) {\n if (state[node.id]?.status === NodeRunStatus.COMPLETED) out[node.id] = state[node.id]!.output;\n }\n return out;\n }\n\n /** The checkpointed run, in the shape a single-process run returns. */\n async asRunResult(): Promise<RunResult> {\n const outcome = await this.runToCompletion();\n return outcome.error === undefined\n ? { ok: outcome.ok, outputs: outcome.outputs }\n : { ok: outcome.ok, outputs: outcome.outputs, error: outcome.error };\n }\n\n // -- internals -------------------------------------------------------\n\n private async runNodeWithRetries(nodeId: string): Promise<NodeOutcome> {\n // One owner token for every attempt of this node — that is what re-enters\n // the claim rather than losing the race to itself, and it is why the step\n // key a retrying node derives is the same one its first attempt sent.\n const owner = cryptoRandom();\n\n let outcome = await this.runNode(nodeId, owner);\n while (outcome.retryable) {\n outcome = await this.runNode(nodeId, owner);\n }\n return outcome;\n }\n\n /**\n * The identity handed to the node about to execute.\n *\n * `attempt` and `firstAttemptAt` come off the CLAIM ROW, so they describe\n * this step rather than the run — which is what makes the retry window check\n * exact instead of conservative.\n */\n private identityFor(row: NodeState | undefined): RunIdentity {\n if (!row) return this.run;\n return this.run.withAttempt(row.attempts, row.firstAttemptAt);\n }\n\n /**\n * Send `runFlow`'s undelivered-edge warnings for nodes the frontier skipped.\n *\n * The check is `undeliveredEdgeWarnings` — the SAME function `runFlow` calls —\n * fed the durable equivalent of the engine's own bookkeeping: a port key for\n * every port a COMPLETED node's claim row stored, in stored order, and the set\n * of COMPLETED ids. `state` is the snapshot the frontier decided from, and\n * every predecessor of a skipped node had settled in it, so it holds all a\n * target's sources.\n *\n * Only skipped nodes. A target that RUNS (it had another live inbound edge)\n * already gets its warning from its own job's replay, whose events carry its\n * node id and are forwarded — asking here too would send it twice.\n */\n private warnForSkipped(skipped: readonly string[], state: Record<string, NodeState>): void {\n const sink = this.onEvent;\n if (!sink || skipped.length === 0) return;\n\n // Only the KEYS are read. The stored output stands in for the published\n // value, which the check never looks at.\n const portValues = new Map<string, unknown>();\n const completed = new Set<string>();\n for (const [nodeId, entry] of Object.entries(state)) {\n if (entry.status !== NodeRunStatus.COMPLETED) continue;\n completed.add(nodeId);\n for (const portId of entry.ports) portValues.set(`${nodeId}:${portId}`, entry.output);\n }\n\n const nodesById = new Map(this.graph.nodes.map((n) => [n.id, n]));\n for (const nodeId of skipped) {\n const target = nodesById.get(nodeId);\n if (!target) continue;\n const incoming = this.graph.edges.filter((e) => e.target === nodeId);\n for (const warning of undeliveredEdgeWarnings(target, incoming, portValues, completed, nodesById)) {\n sink(warning);\n }\n }\n }\n\n private async completedOutputs(): Promise<Record<string, unknown>> {\n const state = await this.store.state(this.runKey);\n const out: Record<string, unknown> = {};\n for (const [nodeId, entry] of Object.entries(state)) {\n if (entry.status === NodeRunStatus.COMPLETED) out[nodeId] = entry.output;\n }\n return out;\n }\n\n /**\n * Forward only the events the target node produced.\n *\n * A replay re-emits the whole completed prefix. Passing that through would\n * show a consumer every node running again on every job — the run feed would\n * report a 20-node workflow as 200 status changes.\n */\n private forward(nodeId: string): ((event: RunEvent) => void) | undefined {\n const sink = this.onEvent;\n if (!sink) return undefined;\n return (event) => {\n const id = \"nodeId\" in event ? event.nodeId : undefined;\n if (id === undefined || id === nodeId) sink(event);\n };\n }\n}\n\nfunction cryptoRandom(): string {\n const g = globalThis as { crypto?: { randomUUID?: () => string } };\n return g.crypto?.randomUUID?.() ?? `own_${Math.random().toString(36).slice(2)}${Date.now()}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/durable/state.ts","../src/durable/frontier.ts","../src/durable/dispatch.ts","../src/durable/replay.ts","../src/durable/retry.ts","../src/durable/human.ts","../src/durable/coordinator.ts"],"names":[],"mappings":"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","file":"durable.js","sourcesContent":["/**\n * What a durable run remembers, and the seam a real database plugs into.\n *\n * A durable run is bookkeeping plus one hard requirement: **the claim is a\n * unique constraint, not a check.** Two workers racing for the same node must\n * produce a no-op, not a double run, and only the storage layer can promise\n * that. So {@link NodeClaimStore} is an interface with exactly the operations a\n * driver needs, and an adapter implements it over `INSERT … ON CONFLICT DO\n * NOTHING` (or its dialect's spelling).\n *\n * {@link InMemoryClaimStore} is the reference implementation and is genuinely\n * useful: it makes the whole per-node driver testable, and it is correct for a\n * single-process durable run.\n *\n * This is a port of `fancy_flow.durable.state`, which is itself the Python twin\n * of `fancy-flow-php`'s `NodeClaims` + `workflow_run_nodes`. Three runtimes,\n * one design — see `.ai/plans/fancy-flow-run-identity-and-steps.md`.\n */\n\n/** Where one node of one run has got to. */\nexport const NodeRunStatus = {\n CLAIMED: \"claimed\",\n COMPLETED: \"completed\",\n SKIPPED: \"skipped\",\n FAILED: \"failed\",\n PAUSED: \"paused\",\n} as const;\n\nexport type NodeRunStatusValue = (typeof NodeRunStatus)[keyof typeof NodeRunStatus];\n\n/**\n * A node the frontier may treat as decided.\n *\n * A FAILED node is settled too — it will never publish, so its successors skip\n * rather than wait forever.\n */\nexport const SETTLED: readonly NodeRunStatusValue[] = [\n NodeRunStatus.COMPLETED,\n NodeRunStatus.SKIPPED,\n NodeRunStatus.FAILED,\n];\n\n/**\n * One node's row.\n *\n * `ports` are the ports the engine's own `node-output` events reported. They\n * are STORED, never recomputed: a second copy of the routing table would agree\n * for a year and then disagree on one branch.\n */\nexport type NodeState = {\n status: NodeRunStatusValue;\n ports: readonly string[];\n output?: unknown;\n error?: string | null;\n owner?: string | null;\n /** 1-based, incremented when an owner re-enters its own claim. */\n attempts: number;\n /** ISO-8601 UTC of the FIRST claim. Never updated — it is the retry clock. */\n firstAttemptAt: string;\n};\n\n/**\n * The persistence a per-node driver needs.\n *\n * Six operations. An adapter over Postgres, SQLite or Redis implements these\n * and nothing else; every rule about WHICH node may run lives in `frontier.ts`,\n * which reads only {@link state}.\n */\nexport interface NodeClaimStore {\n /**\n * Take exclusive ownership of one node of one run.\n *\n * MUST be atomic against concurrent callers, and MUST return `true` for a\n * caller re-entering its OWN claim — that is what lets a job's retry resume\n * instead of deadlocking against the row it wrote itself.\n */\n claim(runKey: string, nodeId: string, owner: string): boolean | Promise<boolean>;\n state(runKey: string): Record<string, NodeState> | Promise<Record<string, NodeState>>;\n complete(\n runKey: string,\n nodeId: string,\n output: unknown,\n ports: readonly string[],\n ): void | Promise<void>;\n /**\n * Settle one node as skipped.\n *\n * Return whether THIS call moved the row to skipped: `false` when the row was\n * already skipped — another caller's frontier made the same decision first.\n * The coordinator emits a skipped node's diagnostics only for a call that\n * settled it, so a warning arrives once however many callers race.\n *\n * Returning nothing is still allowed, for stores written before the boolean,\n * and counts as \"settled now\". Such a store can report a skip twice under a\n * race, and nothing worse.\n */\n skip(runKey: string, nodeId: string): boolean | void | Promise<boolean | void>;\n fail(runKey: string, nodeId: string, error: string): void | Promise<void>;\n pause(runKey: string, nodeId: string, reason: string): void | Promise<void>;\n}\n\n/**\n * A correct, single-process {@link NodeClaimStore}.\n *\n * JavaScript's single-threaded event loop makes the claim atomic for free —\n * there is no interleaving point inside a synchronous method. It is NOT durable\n * across a restart, which is the honest limit: use it for tests, for a CLI run,\n * and for a worker that genuinely owns the whole run.\n */\nexport class InMemoryClaimStore implements NodeClaimStore {\n private readonly runs = new Map<string, Map<string, NodeState>>();\n\n claim(runKey: string, nodeId: string, owner: string): boolean {\n const run = this.run(runKey);\n const existing = run.get(nodeId);\n\n if (!existing) {\n run.set(nodeId, {\n status: NodeRunStatus.CLAIMED,\n ports: [],\n owner,\n attempts: 1,\n firstAttemptAt: new Date().toISOString(),\n });\n return true;\n }\n\n // Re-entering our own claim is how a retry gets back in. Anything else —\n // another owner, or a settled node — is a lost race, and a lost race is a\n // NO-OP rather than a duplicate run.\n const ownClaim =\n (existing.status === NodeRunStatus.CLAIMED || existing.status === NodeRunStatus.PAUSED) &&\n existing.owner === owner;\n\n if (ownClaim) {\n existing.attempts += 1;\n existing.status = NodeRunStatus.CLAIMED;\n return true;\n }\n return false;\n }\n\n state(runKey: string): Record<string, NodeState> {\n const out: Record<string, NodeState> = {};\n for (const [nodeId, entry] of this.run(runKey)) out[nodeId] = { ...entry, ports: [...entry.ports] };\n return out;\n }\n\n complete(runKey: string, nodeId: string, output: unknown, ports: readonly string[]): void {\n const entry = this.entry(runKey, nodeId);\n entry.status = NodeRunStatus.COMPLETED;\n entry.output = output;\n entry.ports = [...ports];\n entry.error = null;\n }\n\n skip(runKey: string, nodeId: string): boolean {\n const entry = this.entry(runKey, nodeId);\n if (entry.status === NodeRunStatus.SKIPPED) return false;\n\n entry.status = NodeRunStatus.SKIPPED;\n entry.ports = [];\n return true;\n }\n\n fail(runKey: string, nodeId: string, error: string): void {\n const entry = this.entry(runKey, nodeId);\n entry.status = NodeRunStatus.FAILED;\n entry.error = error;\n entry.ports = [];\n }\n\n pause(runKey: string, nodeId: string, reason: string): void {\n const entry = this.entry(runKey, nodeId);\n entry.status = NodeRunStatus.PAUSED;\n entry.error = reason;\n entry.ports = [];\n }\n\n /**\n * Drop a paused node's claim so a recorded answer can re-run it.\n *\n * A PAUSED row holds one of the run's dispatch slots (see `selectDispatch`),\n * so this is also what frees the slot: until the row is released, a serial\n * run's `advance()` hands out nothing, the gate included.\n *\n * Not part of the interface: resuming a human gate is the host's decision and\n * its storage's business. Provided here because the in-memory store is also\n * what the tests resume through.\n */\n release(runKey: string, nodeId: string): void {\n this.run(runKey).delete(nodeId);\n }\n\n private run(runKey: string): Map<string, NodeState> {\n let run = this.runs.get(runKey);\n if (!run) {\n run = new Map();\n this.runs.set(runKey, run);\n }\n return run;\n }\n\n /** The row for one node, created as CLAIMED if a driver never claimed it. */\n private entry(runKey: string, nodeId: string): NodeState {\n const run = this.run(runKey);\n let entry = run.get(nodeId);\n if (!entry) {\n entry = {\n status: NodeRunStatus.CLAIMED,\n ports: [],\n attempts: 1,\n firstAttemptAt: new Date().toISOString(),\n };\n run.set(nodeId, entry);\n }\n return entry;\n }\n}\n\nexport function isSettled(status: string | undefined): boolean {\n return status !== undefined && (SETTLED as readonly string[]).includes(status);\n}\n","/**\n * Which nodes can run RIGHT NOW, given what has already settled.\n *\n * ## Why this is not a second engine\n *\n * `runFlow` walks a Kahn topological order and, at each node, runs it when at\n * least one incoming edge is active. That is a total order because one process\n * executes every node. Split the graph across jobs and the same rule has to be\n * asked the other way round — not \"what is next\" but \"what is unblocked\" —\n * which is this module.\n *\n * The rule is the engine's, restated:\n *\n * - every direct predecessor has SETTLED (in topological order the engine has\n * already settled all of them by the time it reaches a node);\n * - and either the node has no incoming edges, or at least one incoming edge is\n * ACTIVE — its source completed and published on that edge's source handle.\n *\n * A node whose predecessors have all settled with no active edge is what the\n * engine reports as `idle/skipped`. Skipping SETTLES it, which can in turn\n * unblock — or skip — its own successors, so the pass repeats until nothing\n * changes. That cascade is how a dead branch collapses without leaving the run\n * stuck.\n *\n * ## The one thing it does NOT decide\n *\n * Which ports a result activated. Those rules (`__port`, `branch`, declared\n * outputs, the kind's ports, the `out` fallback) live in the engine and stay\n * there: this reads the ports back off the `node-output` events the engine\n * emitted when the node ran, stored on the claim row.\n */\n\nimport { getNodeKind, kindIds } from \"../registry/registry\";\nimport type { FlowEdge, FlowGraph } from \"../types\";\nimport { NodeRunStatus, isSettled, type NodeClaimStore, type NodeState } from \"./state\";\n\nexport type FrontierResult = {\n ready: string[];\n skipped: string[];\n};\n\nfunction isNote(type: string | undefined): boolean {\n if (!type) return false;\n if (type === \"note\") return true;\n const kind = getNodeKind(type);\n return kind ? kindIds(kind).includes(\"note\") || kind.category === \"annotation\" : false;\n}\n\nexport const Frontier = {\n compute(graph: FlowGraph, state: Record<string, NodeState>): FrontierResult {\n const incoming = new Map<string, FlowEdge[]>();\n for (const edge of graph.edges) {\n const list = incoming.get(edge.target) ?? [];\n list.push(edge);\n incoming.set(edge.target, list);\n }\n\n // Settled nodes and the ports they lit. A skipped node is settled with NO\n // ports, which is precisely what makes its successors skip too.\n const settled = new Map<string, readonly string[]>();\n const held = new Set<string>();\n for (const [nodeId, entry] of Object.entries(state)) {\n if (entry.status === NodeRunStatus.COMPLETED) settled.set(nodeId, entry.ports);\n else if (isSettled(entry.status)) settled.set(nodeId, []);\n else held.add(nodeId);\n }\n\n const ready: string[] = [];\n const skipped: string[] = [];\n\n let changed = true;\n while (changed) {\n changed = false;\n\n for (const node of graph.nodes) {\n const nodeId = node.id;\n if (settled.has(nodeId) || held.has(nodeId) || ready.includes(nodeId)) continue;\n\n const edges = incoming.get(nodeId) ?? [];\n let blocked = false;\n let active = false;\n\n for (const edge of edges) {\n if (!settled.has(edge.source)) {\n blocked = true;\n break;\n }\n // The engine's port key: an edge with no source handle reads the\n // source's `out` port.\n if (settled.get(edge.source)!.includes(edge.sourceHandle ?? \"out\")) active = true;\n }\n\n if (blocked) continue;\n\n // Reached, but down a branch that never lit.\n if (edges.length > 0 && !active) {\n settled.set(nodeId, []);\n skipped.push(nodeId);\n changed = true;\n continue;\n }\n\n // Annotations and layout are never executed. Settling them here rather\n // than dispatching a job saves a queue round trip per sticky note — and\n // a graph can carry a lot of sticky notes.\n if (isNote(node.type) || getNodeKind(node.type ?? \"\")?.category === \"layout\") {\n settled.set(nodeId, []);\n skipped.push(nodeId);\n changed = true;\n continue;\n }\n\n ready.push(nodeId);\n }\n }\n\n return { ready, skipped };\n },\n\n /** Has every node settled? The run is finished when it has. */\n isComplete(graph: FlowGraph, state: Record<string, NodeState>): boolean {\n return graph.nodes.every((node) => isSettled(state[node.id]?.status));\n },\n\n /**\n * Is any node still held by a worker, or parked for a person?\n *\n * An empty frontier means something different depending on this: with work in\n * flight the run is simply waiting, and whichever job finishes will advance\n * it. With nothing in flight and nodes still unsettled, the graph cannot\n * progress at all — which is a stuck run, and must be reported rather than\n * waited on.\n */\n hasWorkInFlight(state: Record<string, NodeState>): boolean {\n return Object.values(state).some(\n (entry) => entry.status === NodeRunStatus.CLAIMED || entry.status === NodeRunStatus.PAUSED,\n );\n },\n\n /**\n * Persist the skip cascade so the next pass does not recompute it.\n *\n * Resolves to the ids THIS call settled, in cascade order. A node another\n * caller had already settled is left out — its store reported `false` — so a\n * caller acting on \"I skipped this\" acts once per node, not once per racer. A\n * store whose `skip` returns nothing counts every id as settled here.\n */\n async settleSkips(\n store: NodeClaimStore,\n runKey: string,\n skipped: readonly string[],\n ): Promise<string[]> {\n const settled: string[] = [];\n for (const nodeId of skipped) {\n if ((await store.skip(runKey, nodeId)) !== false) settled.push(nodeId);\n }\n return settled;\n },\n};\n","/**\n * How many of ONE run's nodes may be held at once, and which ready nodes go next.\n *\n * ## Serial is the default\n *\n * A queued run hands a node to the queue only once the node before it has\n * settled: one node of a run held at a time, in the graph's own declaration\n * order. Parallel dispatch of a ready frontier is something a host ASKS for,\n * with `maxConcurrent` on the {@link Coordinator}.\n *\n * | `maxConcurrent` | meaning |\n * |---|---|\n * | unset | **1**: serial |\n * | `N >= 1` | up to N of the run's nodes held at once |\n * | {@link UNLIMITED_CONCURRENCY} (`0`) | the whole ready frontier |\n * | anything else | refused, by name |\n *\n * A negative number is refused rather than read as \"unlimited\". Under a serial\n * default, a typo that silently turned a run parallel is the failure to avoid.\n *\n * ## Held means claimed OR paused\n *\n * A node parked on a person keeps its slot. A pause does not park the whole run\n * in this runtime: `advance()` is called whenever any job settles, and without\n * this rule it would hand out the gate's siblings while the person is still\n * deciding.\n *\n * ## Measured against held work, not the batch\n *\n * Two nodes settling at once each trigger an `advance()` on a real queue. A cap\n * applied to one batch would let each of them dispatch its own quota, so the\n * budget is `maxConcurrent - held`, counted off the claim rows.\n *\n * This is the TypeScript member of a three-runtime contract. The\n * `flow/durable-dispatch` conformance suite pins it, and names\n * `fancy-flow-php`'s `DispatchLimit` and the Python runtime's\n * `fancy_flow.durable.select_dispatch` as the other two implementations.\n */\n\nimport { NodeRunStatus, type NodeState } from \"./state\";\n\n/** Dispatch the whole ready frontier. Named so a host never writes a bare `0`. */\nexport const UNLIMITED_CONCURRENCY = 0;\n\n/** One node of a run held at a time: what an unset `maxConcurrent` means. */\nexport const DEFAULT_MAX_CONCURRENT = 1;\n\n/**\n * Refuse a limit that is neither a cap nor {@link UNLIMITED_CONCURRENCY}.\n *\n * Thrown where the limit is SET, not where the first `advance()` trips over it\n * on a worker.\n */\nexport function assertMaxConcurrent(value: unknown): number {\n if (typeof value === \"number\" && Number.isInteger(value) && value >= 0) return value;\n\n throw new RangeError(\n `maxConcurrent must be a positive integer, or UNLIMITED_CONCURRENCY (0) for the whole ready ` +\n `frontier, or unset for serial; got ${describe(value)}.`,\n );\n}\n\n/**\n * The ready nodes that may be dispatched now, in the order given.\n *\n * `ready` comes from `Frontier.compute`, in declaration order, and that order is\n * kept: this slices, it never sorts. `state` must already include any skips the\n * frontier just settled. Skips are never held, so that changes no count, but the\n * state must describe the run as it is after the decision.\n */\nexport function selectDispatch(\n ready: readonly string[],\n state: Record<string, NodeState>,\n maxConcurrent: number,\n): string[] {\n const limit = assertMaxConcurrent(maxConcurrent);\n if (limit === UNLIMITED_CONCURRENCY) return [...ready];\n\n let held = 0;\n for (const entry of Object.values(state)) {\n if (entry.status === NodeRunStatus.CLAIMED || entry.status === NodeRunStatus.PAUSED) held++;\n }\n\n return ready.slice(0, Math.max(0, limit - held));\n}\n\nfunction describe(value: unknown): string {\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"string\") return JSON.stringify(value);\n if (value === null) return \"null\";\n return typeof value;\n}\n","/**\n * Run ONE node of a graph — through the real engine, not around it.\n *\n * ## The problem this solves\n *\n * A per-node driver has to hand a node exactly the inputs it would have\n * received mid-run: the right values, on the right target handles, from the\n * right *active* edges. Those rules are the engine's (`collectInputs`,\n * `activatedPorts`, the merge-after-decision contract, the `out` fallbacks),\n * and they are the reason the three runtimes agree. Re-implementing them here\n * would be a second engine wearing a driver's clothes, and the two would drift.\n *\n * ## What it does instead\n *\n * It replays the graph with `runFlow` untouched:\n *\n * - every node already completed is fed back as `resumeOutputs`, so the engine\n * republishes it on the same ports and routes exactly as it did the first\n * time;\n * - every node EXCEPT the target is bound, through `RunOptions.nodeExecutors`,\n * to a FENCE that runs nothing and publishes only a port no edge reads;\n * - so the engine walks its own topological order, skips its own dead branches,\n * collects the target's inputs its own way, and runs the target.\n *\n * ## Why the fence does not stop the walk\n *\n * It used to abort the run. The target's own inputs never depend on a fenced\n * node -- the frontier dispatches a node only once every source is settled, and\n * settled sources are resumed, not fenced -- but an UNRELATED node can precede\n * the target in topological order. Two siblings dispatched together are exactly\n * that: when `b`'s job started while `a` was still running, the replay aborted\n * at `a`, never reached `b`, and the coordinator read \"the replay ended without\n * running me\" as \"the engine decided I am unreachable\". `b` was recorded\n * skipped, never ran, and the run completed as a success. It did not even take\n * two workers: the frontier lists ready nodes in NODE order and the engine walks\n * EDGE order, so a graph where those disagree about siblings sent in-process\n * `runToCompletion` down the same path.\n *\n * Walking past fences makes that inference honest again: when the replay\n * finishes without an output for the target, it is because the engine found\n * every inbound edge dead.\n *\n * ## Why the fences are not registry entries\n *\n * The registry is one flat object, and a key in it is tried as a node's id AND\n * as its kind. Fencing the node called `host_kind` by writing\n * `executors[\"host_kind\"]` fenced every node of kind `host_kind` too, so a\n * durable run of that graph ran nothing and reported success. And the registry\n * is what `ctx.executors` hands a `subflow` child, so a child node sharing an id\n * with any parent node ran the parent's fence. `nodeExecutors` matches node ids\n * only and is never handed down; the registry reaches the engine untouched.\n *\n * The target's output is `result.outputs[nodeId]`, and the ports it activated\n * arrive as the engine's own `node-output` events. Nothing about routing is\n * recomputed here.\n *\n * ## The cost, stated plainly\n *\n * Replaying the completed prefix is O(nodes) per node, so a run is O(nodes²) in\n * bookkeeping. The republish executes nothing — it re-publishes stored values —\n * so for the graph sizes workflows actually have this is noise next to a single\n * queue round trip. It buys exact fidelity to the engine, which is not\n * negotiable, and one implementation of the routing rules instead of two.\n */\n\nimport { runFlow, type RunResult } from \"../runtime/run-flow\";\nimport type { RunIdentity } from \"../runtime/run-identity\";\nimport type { ExecutorRegistry, FlowGraph, NodeExecutor, RunEvent } from \"../types\";\n\n/**\n * The abort reason a boundary used to report.\n *\n * Nothing aborts with it any more (see \"Why the fence does not stop the walk\");\n * {@link isBoundary} still recognises it so a caller that checks for it keeps\n * working.\n */\nexport const BOUNDARY = \"fancy-flow:node-boundary\";\n\n/**\n * The port a fenced node publishes on. No edge reads it, so everything\n * downstream of a fenced node is dark in the replay -- which never matters to\n * the target, whose sources are all settled.\n */\nexport const FENCE_PORT = \"fancy-flow:fenced\";\n\nexport type ReplayResult = {\n result: RunResult;\n /** node id -> the ports its output activated, from the engine's own events. */\n ports: Record<string, string[]>;\n outputOf(nodeId: string): unknown;\n portsOf(nodeId: string): string[];\n};\n\nexport type ReplayOptions = {\n resumeOutputs?: Record<string, unknown>;\n initialInputs?: Record<string, Record<string, unknown>>;\n onEvent?: (event: RunEvent) => void;\n depth?: number;\n run?: RunIdentity;\n};\n\n/**\n * Replay `graph` up to and through `nodeId`.\n *\n * Pass `nodeId = null` to PROBE: every node is fenced, so nothing executes and\n * the engine reports only what it can determine structurally — a cycle, and\n * the ports each resumed output republishes on.\n */\nexport async function replayUpTo(\n graph: FlowGraph,\n nodeId: string | null,\n executors: ExecutorRegistry,\n options: ReplayOptions = {},\n): Promise<ReplayResult> {\n // The ids a fence actually ran for. A fence executes nothing, so what the\n // engine recorded for these nodes is not an output and is removed below.\n const fencedOff = new Set<string>();\n const fence: NodeExecutor = (ctx) => {\n fencedOff.add(ctx.node.id);\n return { __port: FENCE_PORT, value: null };\n };\n\n const fences: Record<string, NodeExecutor> = {};\n for (const node of graph.nodes) {\n if (node.id !== nodeId) fences[node.id] = fence;\n }\n\n const ports: Record<string, string[]> = {};\n const collect = (event: RunEvent): void => {\n if (event.type === \"node-output\") {\n (ports[event.nodeId] ??= []).push(event.portId);\n }\n options.onEvent?.(event);\n };\n\n const result = await runFlow(graph, executors, collect, {\n initialInputs: options.initialInputs ?? {},\n resumeOutputs: options.resumeOutputs ?? {},\n depth: options.depth ?? 0,\n run: options.run,\n nodeExecutors: fences,\n });\n\n for (const id of fencedOff) {\n delete result.outputs[id];\n delete ports[id];\n }\n\n return {\n result,\n ports,\n outputOf: (id) => result.outputs[id],\n portsOf: (id) => ports[id] ?? [],\n };\n}\n\n/** True when a replay ended because it reached a node it does not own. */\nexport function isBoundary(error: string | null | undefined): boolean {\n return error === BOUNDARY;\n}\n","/**\n * How many times a single node may be attempted.\n *\n * ## Why this cannot be one number\n *\n * A run-wide `tries` setting forces every workflow to pick between two bad\n * answers. At 1, a single flaky LLM or HTTP call takes the whole run down.\n * Above 1, the retry replays from the last checkpoint and everything already\n * done runs again — including the nodes that must not: `git_pr_open` opens a\n * second pull request.\n *\n * Per-node jobs make the question per node, which is where it always belonged.\n * A node declaring `sideEffects: unsafe-to-replay` is pinned to ONE attempt and\n * no backoff. Everything else takes the configured tries, or a per-kind\n * override.\n *\n * Undeclared side effects are treated as the configured default rather than\n * assumed safe: this decides retries, and inventing a safety claim on a node\n * author's behalf is how a retry loop ends up posting the same webhook twice.\n */\n\nimport { getNodeKind, kindIds, resolveKindId } from \"../registry/registry\";\nimport type { FlowNode } from \"../types\";\n\n/** A node that is not safe to run twice. Same vocabulary as the node manifest. */\nexport const UNSAFE_TO_REPLAY = \"unsafe-to-replay\";\n\nexport type RetryPolicyOptions = {\n tries?: number;\n backoffSeconds?: number;\n /** kind id -> tries. Keyed by any spelling; every id is checked. */\n perKind?: Record<string, number>;\n};\n\nexport class RetryPolicy {\n readonly tries: number;\n readonly backoffSeconds: number;\n readonly perKind: Record<string, number>;\n\n constructor(options: RetryPolicyOptions = {}) {\n this.tries = options.tries ?? 1;\n this.backoffSeconds = options.backoffSeconds ?? 0;\n this.perKind = options.perKind ?? {};\n }\n\n triesFor(node: FlowNode): number {\n if (RetryPolicy.isUnsafeToReplay(node)) return 1;\n\n for (const id of idsFor(node)) {\n if (id in this.perKind) return Math.max(1, Math.trunc(this.perKind[id]!));\n }\n\n return Math.max(1, Math.trunc(this.tries));\n }\n\n backoffFor(node: FlowNode): number {\n // Nothing to back off from: the node gets one attempt.\n if (RetryPolicy.isUnsafeToReplay(node)) return 0;\n return Math.max(0, this.backoffSeconds);\n }\n\n static isUnsafeToReplay(node: FlowNode): boolean {\n if (!node.type) return false;\n return getNodeKind(node.type)?.sideEffects === UNSAFE_TO_REPLAY;\n }\n}\n\n/**\n * Every id a per-kind override for this node could be keyed under.\n *\n * Canonical ids are namespaced while a host almost certainly writes the bare\n * one; keying on only the literal string would make the override silently stop\n * applying the day a kind is renamed.\n */\nfunction idsFor(node: FlowNode): string[] {\n if (!node.type) return [];\n const kind = getNodeKind(node.type);\n const canonical = resolveKindId(node.type);\n const ordered = [node.type, ...(canonical ? [canonical] : []), ...(kind ? kindIds(kind) : [])];\n return [...new Set(ordered)];\n}\n","/**\n * Human gates that cannot be walked past — and that hold no worker.\n *\n * ## The directive these implement\n *\n * A node that waits on a person must not occupy a worker, a connection or a\n * process while it waits. So the job for a human gate does its work by\n * **emitting the request** and then **finishing**: the run is persisted as\n * parked, the job returns, the worker takes the next thing off the queue. When\n * the person answers, *that* enqueues the continuation.\n *\n * Mechanically the gate aborts with an encoded pause, which is a *return*, not\n * a block. Nothing in this file sleeps, polls or awaits a person.\n *\n * ## Fail closed, and why\n *\n * A gate pauses because it **is** a human node, not because its input port\n * happens to be empty. This is not a preference; it is a fix. Both peer\n * runtimes once decided whether to pause by reading their own input, so a\n * pre-filled `values` or `approved` value ran the flow straight past the person\n * it was waiting for — silently, with the run reporting success.\n *\n * Restoring the old behaviour is possible, explicit, and per node:\n * `autoAnswerFromInput`. Turn it on for a step that is a form when a human is\n * present and a pass-through when an upstream node already produced the answer.\n * On an approval node, weigh it harder: it means the graph, not a person, can\n * approve.\n *\n * ## The other half of the fix\n *\n * Recording an answer for a node the run is not parked on THROWS rather than\n * queueing a write nobody reads. See {@link Submissions.record}.\n */\n\nimport { pauseForHuman } from \"../registry/pause\";\nimport { truthy } from \"../expressions/expr\";\nimport type { NodeExecutor } from \"../types\";\n\n/** An answer was recorded for a node the run is not waiting on. */\nexport class NotAwaitingHuman extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NotAwaitingHuman\";\n }\n}\n\n/**\n * Answers recorded for human gates, keyed by node id.\n *\n * Deliberately separate from the run's inputs. Keeping them in one bag is\n * precisely what let a pre-filled input satisfy a gate.\n */\nexport class Submissions {\n private readonly answers = new Map<string, unknown>();\n /** The node the run is currently parked on, if any. */\n awaiting: string | null = null;\n\n /**\n * Record an answer for the node the run is parked on.\n *\n * Throws when the run is not waiting on that node. A queued answer for a node\n * that never paused is a write nobody reads — and it looks, from the outside,\n * exactly like a submission that worked.\n */\n record(nodeId: string, value: unknown): void {\n if (this.awaiting !== null && this.awaiting !== nodeId) {\n throw new NotAwaitingHuman(\n `This run is waiting on \"${this.awaiting}\", not \"${nodeId}\". Recording an answer for a ` +\n \"node the run is not parked on would be stored and never read.\",\n );\n }\n if (this.awaiting === null) {\n throw new NotAwaitingHuman(\n `This run is not waiting for anyone, so an answer for \"${nodeId}\" has nothing to resume.`,\n );\n }\n this.answers.set(nodeId, value);\n this.awaiting = null;\n }\n\n answered(nodeId: string): boolean {\n return this.answers.has(nodeId);\n }\n\n answer(nodeId: string): unknown {\n return this.answers.get(nodeId);\n }\n\n park(nodeId: string): void {\n this.awaiting = nodeId;\n }\n}\n\nfunction option(ctx: { node: { data?: unknown } }, key: string, fallback?: unknown): unknown {\n const config = ((ctx.node.data as any)?.config ?? {}) as Record<string, unknown>;\n return config[key] ?? fallback;\n}\n\n/** `user_input` — pauses until a submission for THIS node is recorded. */\nexport function durableUserInput(submissions: Submissions): NodeExecutor {\n return (ctx) => {\n if (submissions.answered(ctx.node.id)) return submissions.answer(ctx.node.id);\n\n if (option(ctx, \"autoAnswerFromInput\") === true) {\n const values = (ctx.inputs as Record<string, unknown>).values;\n if (values !== undefined) return values;\n }\n\n submissions.park(ctx.node.id);\n // Emits the request and RETURNS. A host listening to the feed sends the\n // email; the worker is free the moment this throws.\n return pauseForHuman(ctx, \"input\", {\n title: option(ctx, \"title\", \"Need your input\"),\n fields: option(ctx, \"fields\", []),\n });\n };\n}\n\n/** `human_approval` — pauses until a decision for THIS node is recorded. */\nexport function durableApproval(submissions: Submissions): NodeExecutor {\n return (ctx) => {\n const passthrough = (ctx.inputs as Record<string, unknown>).in ?? ctx.inputs;\n\n if (submissions.answered(ctx.node.id)) {\n return { branch: truthy(submissions.answer(ctx.node.id)) ? \"approved\" : \"denied\", value: passthrough };\n }\n\n if (option(ctx, \"autoAnswerFromInput\") === true) {\n const decision = (ctx.inputs as Record<string, unknown>).approved;\n if (decision !== undefined) {\n return { branch: truthy(decision) ? \"approved\" : \"denied\", value: passthrough };\n }\n }\n\n submissions.park(ctx.node.id);\n return pauseForHuman(ctx, \"approval\", {\n title: option(ctx, \"title\", \"Approve action\"),\n description: option(ctx, \"description\"),\n });\n };\n}\n","/**\n * The per-node driver, with the queue left out.\n *\n * This is the whole of \"how a queued run branches\", minus the transport. It\n * owns two operations and nothing else:\n *\n * `advance()`\n * Ask the frontier what is unblocked, settle the skip cascade, and report the\n * node ids that may be dispatched NOW. A queue adapter dispatches one job per\n * id. By default that is at most one id: a run holds one node at a time, and\n * the next node is handed out only when the one before it settles. See\n * {@link CoordinatorOptions.maxConcurrent}.\n *\n * `runNode()`\n * Claim one node, replay the graph through the real engine fenced to that\n * node, and checkpoint the output plus the ports the ENGINE said it\n * activated.\n *\n * Everything a queue library would add — enqueue, retry scheduling, worker\n * lifecycle — sits outside. That separation is the point: it makes the subtle\n * part (which node may run, and with what inputs) testable in-process, with no\n * broker, and identical under every adapter. **A queue adapter therefore\n * contains no workflow logic at all.** That is the test: if an adapter needs to\n * know what a port is, the seam is in the wrong place.\n *\n * `runToCompletion` drives both in one process. It is a real durable runner,\n * not a toy: with a persistent `NodeClaimStore` it survives a crash exactly as\n * a queued run does, because the crash-resume behaviour lives in the\n * checkpoints rather than in the loop.\n *\n * ## No worker waits on a person\n *\n * A human gate returns `paused`. `runToCompletion` returns immediately when it\n * sees one — it does not spin, sleep or poll. The run is parked in the store,\n * the process is free, and a recorded answer is what starts the next job.\n *\n * A paused node keeps its dispatch slot, so under any finite `maxConcurrent`\n * (serial, by default) a queue adapter's `advance()` hands out nothing\n * alongside a gate while the person decides. Resuming releases the row — see\n * `InMemoryClaimStore.release` — which frees the slot for the gate to run\n * again.\n */\n\nimport { decodePause, type PauseSignal } from \"../registry/pause\";\nimport type { RunResult } from \"../runtime/run-flow\";\nimport { RunIdentity, type RunIdentityJson } from \"../runtime/run-identity\";\nimport { undeliveredEdgeWarnings } from \"../runtime/undelivered-edges\";\nimport type { ExecutorRegistry, FlowGraph, RunEvent } from \"../types\";\nimport { DEFAULT_MAX_CONCURRENT, assertMaxConcurrent, selectDispatch } from \"./dispatch\";\nimport { Frontier } from \"./frontier\";\nimport { isBoundary, replayUpTo } from \"./replay\";\nimport { RetryPolicy } from \"./retry\";\nimport {\n InMemoryClaimStore,\n NodeRunStatus,\n type NodeClaimStore,\n type NodeState,\n} from \"./state\";\n\n/** What happened to one node. */\nexport type NodeOutcome = {\n nodeId: string;\n status: \"not-claimed\" | \"completed\" | \"skipped\" | \"failed\" | \"paused\";\n output?: unknown;\n ports?: string[];\n error?: string;\n pause?: PauseSignal;\n /** False when another worker got there first. A lost race is a NO-OP. */\n claimed: boolean;\n /** 1-based attempt this execution ran as. */\n attempt: number;\n /**\n * `true` when this attempt failed and the policy still allows another.\n *\n * The claim row is left CLAIMED in that case, deliberately: a queue adapter\n * re-dispatches the job with the SAME owner token and the retry re-enters the\n * claim it already holds. Recording FAILED here instead would settle the\n * node, which SKIPS everything downstream — a run reporting a tidy finish\n * having done half its work.\n */\n retryable?: boolean;\n};\n\nexport type DurableRunResult = {\n ok: boolean;\n outputs: Record<string, unknown>;\n error?: string;\n pause?: PauseSignal;\n paused: boolean;\n};\n\nexport type CoordinatorOptions = {\n graph: FlowGraph;\n executors: ExecutorRegistry;\n /**\n * The run's stable identity. A bare string is taken as the run key.\n *\n * Required — not defaulted. A durable run without a stable key cannot key an\n * idempotent write, and minting one per construction would hand a retrying\n * host a different key each time.\n */\n run: string | RunIdentity | RunIdentityJson;\n store?: NodeClaimStore;\n initialInputs?: Record<string, Record<string, unknown>>;\n retry?: RetryPolicy;\n onEvent?: (event: RunEvent) => void;\n /**\n * How many of this run's nodes may be HELD at once. Held means CLAIMED by a\n * worker or PAUSED on a person: a paused gate keeps its slot.\n *\n * **Defaults to `1`: serial.** `advance()` hands out one node, and the next\n * only once that one has settled, in the graph's declaration order.\n *\n * A positive integer raises the cap. `UNLIMITED_CONCURRENCY` (`0`) hands out\n * the whole ready frontier at once, which is what every queued run did before\n * this option existed. A negative, fractional or non-numeric value throws\n * here, at construction.\n */\n maxConcurrent?: number;\n};\n\nexport class Coordinator {\n readonly graph: FlowGraph;\n readonly executors: ExecutorRegistry;\n readonly run: RunIdentity;\n readonly store: NodeClaimStore;\n readonly initialInputs: Record<string, Record<string, unknown>>;\n readonly retry: RetryPolicy;\n /** The dispatch cap `advance()` applies. `0` is unlimited. */\n readonly maxConcurrent: number;\n private readonly onEvent?: (event: RunEvent) => void;\n\n constructor(options: CoordinatorOptions) {\n this.graph = options.graph;\n this.executors = options.executors;\n this.run = RunIdentity.from(options.run);\n this.store = options.store ?? new InMemoryClaimStore();\n this.initialInputs = options.initialInputs ?? {};\n this.retry = options.retry ?? new RetryPolicy();\n this.maxConcurrent = assertMaxConcurrent(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT);\n this.onEvent = options.onEvent;\n }\n\n get runKey(): string {\n return this.run.runKey;\n }\n\n // -- the two operations ----------------------------------------------\n\n /**\n * Which nodes may be dispatched right now.\n *\n * The ready frontier, cut to the run's {@link maxConcurrent} budget: the\n * first `maxConcurrent - held` ready ids in declaration order, where `held`\n * counts CLAIMED and PAUSED rows. Under the serial default that is at most one\n * id, and none while a node is still running or a gate is waiting on a person.\n * An empty result with work held is a throttled run, not a stalled one: the\n * held node's settle is what calls this again.\n *\n * Also settles the skip cascade, because a skip is a decision the frontier\n * just made and a second caller must not make it again.\n *\n * And it is where a skipped node's undelivered-edge warnings are emitted. A\n * skipped node never gets a job, so it never replays to warn for itself: its\n * warning used to be raised inside OTHER jobs' replays, carrying its node id,\n * and {@link forward} filtered it out. Asking at the skip decision — the one\n * place that knows the node will never run — delivers it, and asking only for\n * the nodes this call actually settled delivers it once.\n */\n async advance(): Promise<string[]> {\n const state = await this.store.state(this.runKey);\n const frontier = Frontier.compute(this.graph, state);\n const settled = await Frontier.settleSkips(this.store, this.runKey, frontier.skipped);\n this.warnForSkipped(settled, state);\n\n // Held work is counted against the run AFTER the skips just settled. A skip\n // is never held, so this moves no count today, but the budget must be read\n // off the state the decision produced, not the one it started from.\n const after: Record<string, NodeState> = { ...state };\n for (const nodeId of frontier.skipped) {\n const row: NodeState | undefined = state[nodeId];\n after[nodeId] = row\n ? { ...row, status: NodeRunStatus.SKIPPED, ports: [] }\n : { status: NodeRunStatus.SKIPPED, ports: [], attempts: 0, firstAttemptAt: \"\" };\n }\n\n return selectDispatch(frontier.ready, after, this.maxConcurrent);\n }\n\n /**\n * Claim, execute and checkpoint one node.\n *\n * The claim is taken FIRST. Two workers racing for the same node produce one\n * execution and one no-op, and the loser learns that from the store rather\n * than from a duplicate side effect.\n *\n * `owner` is the token that lets a job's own retry re-enter its claim instead\n * of deadlocking against the row it wrote itself — pass the SAME token across\n * a job's attempts.\n */\n async runNode(nodeId: string, owner?: string): Promise<NodeOutcome> {\n const token = owner ?? cryptoRandom();\n if (!(await this.store.claim(this.runKey, nodeId, token))) {\n return { nodeId, status: \"not-claimed\", claimed: false, attempt: 0 };\n }\n\n const row = (await this.store.state(this.runKey))[nodeId];\n const identity = this.identityFor(row);\n\n const replay = await replayUpTo(this.graph, nodeId, this.executors, {\n resumeOutputs: await this.completedOutputs(),\n initialInputs: this.initialInputs,\n onEvent: this.forward(nodeId),\n run: identity,\n });\n const result = replay.result;\n const attempt = identity.attempt;\n\n if (Object.prototype.hasOwnProperty.call(result.outputs, nodeId)) {\n const output = result.outputs[nodeId];\n const ports = replay.portsOf(nodeId);\n await this.store.complete(this.runKey, nodeId, output, ports);\n return { nodeId, status: \"completed\", output, ports, claimed: true, attempt };\n }\n\n // The node did not produce an output. Three reasons, and they are NOT\n // interchangeable.\n const pause = decodePause(result.error);\n if (pause && pause.nodeId === nodeId) {\n await this.store.pause(this.runKey, nodeId, result.error ?? \"\");\n return { nodeId, status: \"paused\", pause, claimed: true, attempt };\n }\n\n if (isBoundary(result.error) || result.ok) {\n // The replay walks past every fence (see `replay.ts`), so a run that\n // finished without this node's output is the engine deciding the node is\n // unreachable: every inbound edge dead. The frontier normally catches that\n // first; honouring the engine's verdict here too means the two can never\n // disagree about a branch. It must not be recorded as a failure: a FAILED\n // node fails the run.\n //\n // This used to be where a node whose EARLIER SIBLING was still running\n // ended up. The fence aborted the replay at the sibling, and the node was\n // skipped without ever running.\n await this.store.skip(this.runKey, nodeId);\n return {\n nodeId,\n status: \"skipped\",\n error: \"the engine found no live inbound edge for this node\",\n claimed: true,\n attempt,\n };\n }\n\n const error = result.error ?? `node ${nodeId} produced no output and no error`;\n const node = this.graph.nodes.find((n) => n.id === nodeId);\n const tries = node ? this.retry.triesFor(node) : 1;\n\n if (attempt < tries) {\n // Leave the row CLAIMED so the same owner can re-enter it. This is what\n // `fancy-flow-php` does by only marking FAILED from the job's `failed()`\n // hook — the row a worker still holds must not settle mid-retry.\n return { nodeId, status: \"failed\", error, claimed: true, attempt, retryable: true };\n }\n\n await this.store.fail(this.runKey, nodeId, error);\n return { nodeId, status: \"failed\", error, claimed: true, attempt, retryable: false };\n }\n\n // -- an in-process driver over the two ------------------------------\n\n /**\n * Drive the graph here, in this process, one node at a time.\n *\n * It asks `advance()` exactly as a queue adapter does, so it runs nodes in the\n * order a queued run under the same `maxConcurrent` would dispatch them. A run\n * that finishes produces the same outputs under every limit. A run that stops\n * on a pause or a failure can have run a different set of nodes before it\n * stopped, because the order among nodes that are ready together differs.\n *\n * Every checkpoint is written exactly as a queued run writes it, so a crash\n * mid-loop resumes from the same place a crashed worker would.\n *\n * Retries honour {@link RetryPolicy}: a node declaring `unsafe-to-replay`\n * gets one attempt whatever the policy says, and the retry re-enters the same\n * claim with the same owner token — so the step key it derives is unchanged,\n * which is what makes the retry idempotent rather than duplicative.\n */\n async runToCompletion(\n // One pass per batch `advance()` hands out, and a serial batch is one node,\n // so the default allows at least one pass per node. A flat 10,000 would stop\n // a serial run of a larger graph short and report it as unable to progress.\n maxPasses = Math.max(10_000, this.graph.nodes.length + 1),\n ): Promise<DurableRunResult> {\n for (let pass = 0; pass < maxPasses; pass++) {\n const ready = await this.advance();\n if (ready.length === 0) break;\n\n for (const nodeId of ready) {\n const outcome = await this.runNodeWithRetries(nodeId);\n\n if (outcome.status === \"paused\") {\n // A pause parks the RUN, not just the node: continuing would run the\n // human gate's siblings while a person is still deciding. The cohort\n // waits. Nothing here blocks — this RETURNS.\n return {\n ok: false,\n outputs: await this.outputs(),\n pause: outcome.pause,\n paused: true,\n };\n }\n if (outcome.status === \"failed\") {\n return { ok: false, outputs: await this.outputs(), error: outcome.error, paused: false };\n }\n }\n }\n\n const state = await this.store.state(this.runKey);\n if (!Frontier.isComplete(this.graph, state)) {\n if (Frontier.hasWorkInFlight(state)) {\n return {\n ok: false,\n outputs: await this.outputs(),\n error: \"the run is waiting on work held elsewhere\",\n paused: false,\n };\n }\n const unsettled = this.graph.nodes\n .filter((n) => ![\"completed\", \"skipped\", \"failed\"].includes(state[n.id]?.status ?? \"\"))\n .map((n) => n.id);\n return {\n ok: false,\n outputs: await this.outputs(),\n error: `the run cannot progress; unsettled nodes: ${unsettled.join(\", \")}`,\n paused: false,\n };\n }\n\n const failed = Object.values(state)\n .filter((e) => e.status === NodeRunStatus.FAILED)\n .map((e) => e.error ?? \"node failed\");\n\n return {\n ok: failed.length === 0,\n outputs: await this.outputs(),\n error: failed[0],\n paused: false,\n };\n }\n\n /**\n * Checkpointed outputs, in the graph's own node order.\n *\n * Ordered by the graph rather than by completion so two runs of the same\n * workflow produce comparable output maps even when nodes finished in a\n * different order.\n */\n async outputs(): Promise<Record<string, unknown>> {\n const state = await this.store.state(this.runKey);\n const out: Record<string, unknown> = {};\n for (const node of this.graph.nodes) {\n if (state[node.id]?.status === NodeRunStatus.COMPLETED) out[node.id] = state[node.id]!.output;\n }\n return out;\n }\n\n /** The checkpointed run, in the shape a single-process run returns. */\n async asRunResult(): Promise<RunResult> {\n const outcome = await this.runToCompletion();\n return outcome.error === undefined\n ? { ok: outcome.ok, outputs: outcome.outputs }\n : { ok: outcome.ok, outputs: outcome.outputs, error: outcome.error };\n }\n\n // -- internals -------------------------------------------------------\n\n private async runNodeWithRetries(nodeId: string): Promise<NodeOutcome> {\n // One owner token for every attempt of this node — that is what re-enters\n // the claim rather than losing the race to itself, and it is why the step\n // key a retrying node derives is the same one its first attempt sent.\n const owner = cryptoRandom();\n\n let outcome = await this.runNode(nodeId, owner);\n while (outcome.retryable) {\n outcome = await this.runNode(nodeId, owner);\n }\n return outcome;\n }\n\n /**\n * The identity handed to the node about to execute.\n *\n * `attempt` and `firstAttemptAt` come off the CLAIM ROW, so they describe\n * this step rather than the run — which is what makes the retry window check\n * exact instead of conservative.\n */\n private identityFor(row: NodeState | undefined): RunIdentity {\n if (!row) return this.run;\n return this.run.withAttempt(row.attempts, row.firstAttemptAt);\n }\n\n /**\n * Send `runFlow`'s undelivered-edge warnings for nodes the frontier skipped.\n *\n * The check is `undeliveredEdgeWarnings` — the SAME function `runFlow` calls —\n * fed the durable equivalent of the engine's own bookkeeping: a port key for\n * every port a COMPLETED node's claim row stored, in stored order, and the set\n * of COMPLETED ids. `state` is the snapshot the frontier decided from, and\n * every predecessor of a skipped node had settled in it, so it holds all a\n * target's sources.\n *\n * Only skipped nodes. A target that RUNS (it had another live inbound edge)\n * already gets its warning from its own job's replay, whose events carry its\n * node id and are forwarded — asking here too would send it twice.\n */\n private warnForSkipped(skipped: readonly string[], state: Record<string, NodeState>): void {\n const sink = this.onEvent;\n if (!sink || skipped.length === 0) return;\n\n // Only the KEYS are read. The stored output stands in for the published\n // value, which the check never looks at.\n const portValues = new Map<string, unknown>();\n const completed = new Set<string>();\n for (const [nodeId, entry] of Object.entries(state)) {\n if (entry.status !== NodeRunStatus.COMPLETED) continue;\n completed.add(nodeId);\n for (const portId of entry.ports) portValues.set(`${nodeId}:${portId}`, entry.output);\n }\n\n const nodesById = new Map(this.graph.nodes.map((n) => [n.id, n]));\n for (const nodeId of skipped) {\n const target = nodesById.get(nodeId);\n if (!target) continue;\n const incoming = this.graph.edges.filter((e) => e.target === nodeId);\n for (const warning of undeliveredEdgeWarnings(target, incoming, portValues, completed, nodesById)) {\n sink(warning);\n }\n }\n }\n\n private async completedOutputs(): Promise<Record<string, unknown>> {\n const state = await this.store.state(this.runKey);\n const out: Record<string, unknown> = {};\n for (const [nodeId, entry] of Object.entries(state)) {\n if (entry.status === NodeRunStatus.COMPLETED) out[nodeId] = entry.output;\n }\n return out;\n }\n\n /**\n * Forward only the events the target node produced.\n *\n * A replay re-emits the whole completed prefix. Passing that through would\n * show a consumer every node running again on every job — the run feed would\n * report a 20-node workflow as 200 status changes.\n */\n private forward(nodeId: string): ((event: RunEvent) => void) | undefined {\n const sink = this.onEvent;\n if (!sink) return undefined;\n return (event) => {\n const id = \"nodeId\" in event ? event.nodeId : undefined;\n if (id === undefined || id === nodeId) sink(event);\n };\n }\n}\n\nfunction cryptoRandom(): string {\n const g = globalThis as { crypto?: { randomUUID?: () => string } };\n return g.crypto?.randomUUID?.() ?? `own_${Math.random().toString(36).slice(2)}${Date.now()}`;\n}\n"]}
|
package/dist/engine.cjs
CHANGED
|
@@ -126,6 +126,42 @@ var llmRouterExecutor = async (ctx) => {
|
|
|
126
126
|
return { __port: port, value: { route: port, reason, input: ctx.inputs } };
|
|
127
127
|
};
|
|
128
128
|
|
|
129
|
+
// src/registry/pause.ts
|
|
130
|
+
var PAUSE_PREFIX = "fancy-flow:pause:";
|
|
131
|
+
var LEGACY_PAUSE_PREFIXES = [
|
|
132
|
+
["awaiting-approval:", "approval"],
|
|
133
|
+
["awaiting-input:", "input"]
|
|
134
|
+
];
|
|
135
|
+
function encodePause(signal) {
|
|
136
|
+
const { nodeId, awaiting, detail } = signal;
|
|
137
|
+
return PAUSE_PREFIX + JSON.stringify(detail === void 0 ? { nodeId, awaiting } : { nodeId, awaiting, detail });
|
|
138
|
+
}
|
|
139
|
+
function decodePause(reason) {
|
|
140
|
+
if (typeof reason !== "string") return null;
|
|
141
|
+
if (reason.startsWith(PAUSE_PREFIX)) {
|
|
142
|
+
const body = reason.slice(PAUSE_PREFIX.length);
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(body);
|
|
145
|
+
if (typeof parsed?.nodeId !== "string" || typeof parsed?.awaiting !== "string") return null;
|
|
146
|
+
return "detail" in parsed ? { nodeId: parsed.nodeId, awaiting: parsed.awaiting, detail: parsed.detail } : { nodeId: parsed.nodeId, awaiting: parsed.awaiting };
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const [prefix, awaiting] of LEGACY_PAUSE_PREFIXES) {
|
|
152
|
+
if (reason.startsWith(prefix)) {
|
|
153
|
+
return { nodeId: reason.slice(prefix.length), awaiting };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
function isPause(reason) {
|
|
159
|
+
return decodePause(reason) !== null;
|
|
160
|
+
}
|
|
161
|
+
function pauseForHuman(ctx, awaiting, detail) {
|
|
162
|
+
return ctx.abort(encodePause({ nodeId: ctx.node.id, awaiting, detail }));
|
|
163
|
+
}
|
|
164
|
+
|
|
129
165
|
// src/registry/subflow.ts
|
|
130
166
|
var DEFAULT_MAX_DEPTH = 8;
|
|
131
167
|
function subflowMode(config) {
|
|
@@ -207,7 +243,11 @@ var subflowExecutor = async (ctx) => {
|
|
|
207
243
|
}
|
|
208
244
|
);
|
|
209
245
|
if (!result.ok) {
|
|
210
|
-
|
|
246
|
+
const reason = result.error ?? "unknown error";
|
|
247
|
+
if (decodePause(reason)) {
|
|
248
|
+
ctx.abort(reason);
|
|
249
|
+
}
|
|
250
|
+
ctx.abort(`subflow "${ref}" failed: ${reason}`);
|
|
211
251
|
}
|
|
212
252
|
if (mode === "stream") {
|
|
213
253
|
return { __port: "stream", value: result.outputs };
|
|
@@ -1025,7 +1065,7 @@ var KINDS = [
|
|
|
1025
1065
|
executor: forEachExecutor,
|
|
1026
1066
|
category: "logic",
|
|
1027
1067
|
label: "For Each",
|
|
1028
|
-
description: "
|
|
1068
|
+
description: "Publishes the resolved list and its size on BOTH `item` and `done`. Fan-out as DATA, not as jobs -- nothing runs per item.",
|
|
1029
1069
|
icon: "\u21BB",
|
|
1030
1070
|
inputs: [{ id: "in" }],
|
|
1031
1071
|
outputs: [{ id: "item", label: "item" }, { id: "done", label: "done" }],
|
|
@@ -2777,42 +2817,6 @@ function errorText(error) {
|
|
|
2777
2817
|
return error instanceof Error ? error.message : String(error);
|
|
2778
2818
|
}
|
|
2779
2819
|
|
|
2780
|
-
// src/registry/pause.ts
|
|
2781
|
-
var PAUSE_PREFIX = "fancy-flow:pause:";
|
|
2782
|
-
var LEGACY_PAUSE_PREFIXES = [
|
|
2783
|
-
["awaiting-approval:", "approval"],
|
|
2784
|
-
["awaiting-input:", "input"]
|
|
2785
|
-
];
|
|
2786
|
-
function encodePause(signal) {
|
|
2787
|
-
const { nodeId, awaiting, detail } = signal;
|
|
2788
|
-
return PAUSE_PREFIX + JSON.stringify(detail === void 0 ? { nodeId, awaiting } : { nodeId, awaiting, detail });
|
|
2789
|
-
}
|
|
2790
|
-
function decodePause(reason) {
|
|
2791
|
-
if (typeof reason !== "string") return null;
|
|
2792
|
-
if (reason.startsWith(PAUSE_PREFIX)) {
|
|
2793
|
-
const body = reason.slice(PAUSE_PREFIX.length);
|
|
2794
|
-
try {
|
|
2795
|
-
const parsed = JSON.parse(body);
|
|
2796
|
-
if (typeof parsed?.nodeId !== "string" || typeof parsed?.awaiting !== "string") return null;
|
|
2797
|
-
return "detail" in parsed ? { nodeId: parsed.nodeId, awaiting: parsed.awaiting, detail: parsed.detail } : { nodeId: parsed.nodeId, awaiting: parsed.awaiting };
|
|
2798
|
-
} catch {
|
|
2799
|
-
return null;
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
for (const [prefix, awaiting] of LEGACY_PAUSE_PREFIXES) {
|
|
2803
|
-
if (reason.startsWith(prefix)) {
|
|
2804
|
-
return { nodeId: reason.slice(prefix.length), awaiting };
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
return null;
|
|
2808
|
-
}
|
|
2809
|
-
function isPause(reason) {
|
|
2810
|
-
return decodePause(reason) !== null;
|
|
2811
|
-
}
|
|
2812
|
-
function pauseForHuman(ctx, awaiting, detail) {
|
|
2813
|
-
return ctx.abort(encodePause({ nodeId: ctx.node.id, awaiting, detail }));
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
2820
|
// src/marketplace/manifest.ts
|
|
2817
2821
|
var NODE_MANIFEST_SCHEMA_VERSION = 1;
|
|
2818
2822
|
var FIRST_PARTY_SCOPE = "@particle-academy/";
|