@particle-academy/fancy-flow 0.72.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/{chunk-57QTX3S3.js → chunk-3RXQGYGH.js} +3 -3
- package/dist/{chunk-57QTX3S3.js.map → chunk-3RXQGYGH.js.map} +1 -1
- package/dist/{chunk-RQP666FB.js → chunk-7DJ56PUA.js} +4 -4
- package/dist/{chunk-RQP666FB.js.map → chunk-7DJ56PUA.js.map} +1 -1
- package/dist/{chunk-C5K2DSZB.js → chunk-EX34GCKK.js} +7 -5
- package/dist/chunk-EX34GCKK.js.map +1 -0
- package/dist/{chunk-ZEDBF2V6.js → chunk-GQ5LEA7Q.js} +3 -3
- package/dist/{chunk-ZEDBF2V6.js.map → chunk-GQ5LEA7Q.js.map} +1 -1
- package/dist/{chunk-E3U3MBKI.js → chunk-K7KO3UJ3.js} +3 -3
- package/dist/{chunk-E3U3MBKI.js.map → chunk-K7KO3UJ3.js.map} +1 -1
- package/dist/{chunk-R3GQ72WA.js → chunk-LYYQO7EY.js} +4 -4
- package/dist/{chunk-R3GQ72WA.js.map → chunk-LYYQO7EY.js.map} +1 -1
- package/dist/{chunk-6TC44VCY.js → chunk-VAF2SWTC.js} +3 -3
- package/dist/{chunk-6TC44VCY.js.map → chunk-VAF2SWTC.js.map} +1 -1
- package/dist/durable/index.d.cts +138 -13
- package/dist/durable/index.d.ts +138 -13
- package/dist/durable.cjs +75 -13
- package/dist/durable.cjs.map +1 -1
- package/dist/durable.js +69 -12
- package/dist/durable.js.map +1 -1
- package/dist/engine.cjs +5 -3
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.cts +2 -2
- package/dist/engine.d.ts +2 -2
- package/dist/engine.js +4 -4
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +12 -12
- package/dist/registry.cjs +5 -3
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +2 -2
- package/dist/{run-cohort-DL9JzzPU.d.cts → run-cohort-DNpPa0mq.d.cts} +1 -1
- package/dist/{run-cohort-CjUBWg6X.d.ts → run-cohort-rgT_FX3V.d.ts} +1 -1
- package/dist/{run-flow-CxEGBOxd.d.ts → run-flow-BLP9rXfO.d.ts} +22 -1
- package/dist/{run-flow-B_8hgO5_.d.cts → run-flow-pW0PllaZ.d.cts} +22 -1
- package/dist/runtime/index.d.cts +3 -3
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime.cjs +5 -3
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +3 -3
- package/dist/schema.cjs +5 -3
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +3 -3
- package/dist/screens.cjs +5 -3
- package/dist/screens.cjs.map +1 -1
- package/dist/screens.js +4 -4
- package/dist/ux.cjs +5 -3
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-C5K2DSZB.js.map +0 -1
package/README.md
CHANGED
|
@@ -633,6 +633,28 @@ registerNodeKind({
|
|
|
633
633
|
`awaiting-input:` prefixes, so runs that parked under an older version still
|
|
634
634
|
resume.
|
|
635
635
|
|
|
636
|
+
### Queued runs: one node at a time
|
|
637
|
+
|
|
638
|
+
`@particle-academy/fancy-flow/durable` splits a run into one queue job per node.
|
|
639
|
+
`Coordinator.advance()` says what to dispatch, and `runNode()` claims, executes
|
|
640
|
+
and checkpoints one node.
|
|
641
|
+
|
|
642
|
+
**Serial is the default.** `advance()` hands out one node, and the next only
|
|
643
|
+
once that node has settled, in the graph's declaration order. A node paused for
|
|
644
|
+
a person keeps its slot, so nothing else goes out while they decide. To dispatch
|
|
645
|
+
more at once, pass `maxConcurrent`:
|
|
646
|
+
|
|
647
|
+
```ts
|
|
648
|
+
import { Coordinator, UNLIMITED_CONCURRENCY } from "@particle-academy/fancy-flow/durable";
|
|
649
|
+
|
|
650
|
+
new Coordinator({ graph, executors, run, store }); // serial
|
|
651
|
+
new Coordinator({ graph, executors, run, store, maxConcurrent: 4 }); // up to 4 held
|
|
652
|
+
new Coordinator({ graph, executors, run, store, maxConcurrent: UNLIMITED_CONCURRENCY }); // whole frontier
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
The selection is pinned by the shared `flow/durable-dispatch` conformance suite,
|
|
656
|
+
which specifies the same behaviour for the PHP and Python runtimes.
|
|
657
|
+
|
|
636
658
|
## Status
|
|
637
659
|
|
|
638
660
|
Shipping. Since this list was last written, all of the following landed:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RichInputPreview } from './chunk-F5RPRB7A.js';
|
|
2
2
|
import { NodeResizer, NodeToolbar, Handle, Position } from './chunk-OWENS2H5.js';
|
|
3
|
-
import { BUILTIN_KIND_DATA, getNodeKind, categoryAccent, resolveNodePorts, nodeConfig, ensureBuiltinKinds, registerBuiltinKindInternal, listNodeKinds, kindIds } from './chunk-
|
|
3
|
+
import { BUILTIN_KIND_DATA, getNodeKind, categoryAccent, resolveNodePorts, nodeConfig, ensureBuiltinKinds, registerBuiltinKindInternal, listNodeKinds, kindIds } from './chunk-EX34GCKK.js';
|
|
4
4
|
import { memo, createContext, useMemo, useState, useRef, useEffect, createElement, useContext } from 'react';
|
|
5
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
@@ -315,5 +315,5 @@ function buildNodeTypes() {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
export { ANY_PORT_TYPE, BUILTIN_KINDS, FlowEditorProvider, LaneNode, NoteNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds, useFlowEditor, useFlowEditorOptional };
|
|
318
|
-
//# sourceMappingURL=chunk-
|
|
319
|
-
//# sourceMappingURL=chunk-
|
|
318
|
+
//# sourceMappingURL=chunk-3RXQGYGH.js.map
|
|
319
|
+
//# sourceMappingURL=chunk-3RXQGYGH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/registry/connection.ts","../src/registry/RegistryNode.tsx","../src/components/nodes/LaneNode.tsx","../src/components/FlowEditor/api.ts","../src/components/nodes/NoteNode.tsx","../src/registry/builtin.ts","../src/registry/index.ts"],"names":["jsxs","jsx","memo"],"mappings":"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","file":"chunk-57QTX3S3.js","sourcesContent":["import type { Connection, Edge } from \"@xyflow/react\";\nimport type { FlowNode, PortDescriptor } from \"../types\";\nimport { getNodeKind } from \"./registry\";\nimport { resolveNodePorts } from \"./ports\";\n\n/**\n * Connection validation — the single rule that decides whether an edge between\n * two ports is allowed. Lives here (React-free, registry-adjacent) so BOTH the\n * canvas (`<FlowCanvas isValidConnection>`) and the agent bridge's\n * `flow_connect` tool call the same function: a connection the canvas refuses\n * is a connection an agent cannot sneak past, and vice versa — no drift.\n *\n * `PortDescriptor.type` has always documented itself as being \"for hosts that\n * want to validate connections\", but nothing consumed it. This wires it up.\n */\n\n/**\n * Wildcard port type — matches any other type. A port that declares no `type`\n * is treated as a wildcard too, so existing flows (whose ports are untyped)\n * validate exactly as before.\n */\nexport const ANY_PORT_TYPE = \"any\";\n\n/** Decides whether a source-output port may feed a target-input port. */\nexport type PortCompatibility = (\n source: PortDescriptor | undefined,\n target: PortDescriptor | undefined,\n) => boolean;\n\n/**\n * Default compatibility rule: allow the connection UNLESS both ports declare a\n * concrete, differing type. An absent type or the `any` wildcard matches\n * anything — so typed ports get enforced while untyped ports stay permissive,\n * making this a safe default that never breaks an existing untyped graph.\n */\nexport const defaultPortCompatibility: PortCompatibility = (source, target) => {\n const s = source?.type;\n const t = target?.type;\n if (!s || !t) return true;\n if (s === ANY_PORT_TYPE || t === ANY_PORT_TYPE) return true;\n return s === t;\n};\n\nexport type ConnectionValidatorOptions = {\n /** Override the type-compatibility rule. Defaults to {@link defaultPortCompatibility}. */\n compatible?: PortCompatibility;\n /**\n * Allow a node's own output to feed its own input (a self-loop). Default\n * false — self-connections are almost always an accident, and the rare flow\n * that wants one can opt in.\n */\n allowSelfConnection?: boolean;\n};\n\n/** Resolve a node's effective ports for one side, going through the registry. */\nfunction portsFor(node: FlowNode, side: \"inputs\" | \"outputs\"): PortDescriptor[] {\n const kind = getNodeKind((node.data as any)?.kind ?? node.type);\n const resolved = resolveNodePorts(node, kind ?? undefined);\n return resolved[side] ?? [];\n}\n\n/**\n * Resolve which port a handle id refers to. When a side has a single port and\n * the handle id is absent (React Flow omits it for a lone default handle), use\n * that port; otherwise match by id.\n */\nfunction findPort(\n ports: PortDescriptor[],\n handleId: string | null | undefined,\n): PortDescriptor | undefined {\n if (handleId == null) return ports.length === 1 ? ports[0] : undefined;\n return ports.find((p) => p.id === handleId);\n}\n\n/**\n * Build an `isValidConnection` predicate that enforces port-type compatibility\n * against a live node list. Pass it straight to `<FlowCanvas isValidConnection>`\n * or reuse it to gate an agent's `flow_connect`.\n *\n * `getNodes` is called on every check so the validator always sees the current\n * graph (wire it to a ref/state getter, not a snapshot).\n *\n * A connection is rejected when: an endpoint is missing; it is a self-loop and\n * `allowSelfConnection` is off; either node is unknown; or the resolved ports\n * are type-incompatible. Nodes/ports with no declared type validate as before.\n */\nexport function createConnectionValidator(\n getNodes: () => FlowNode[],\n options: ConnectionValidatorOptions = {},\n): (connection: Connection | Edge) => boolean {\n const compatible = options.compatible ?? defaultPortCompatibility;\n return (connection) => {\n const { source, target, sourceHandle, targetHandle } = connection;\n if (!source || !target) return false;\n if (!options.allowSelfConnection && source === target) return false;\n\n const nodes = getNodes();\n const src = nodes.find((n) => n.id === source);\n const tgt = nodes.find((n) => n.id === target);\n if (!src || !tgt) return false;\n\n const outPort = findPort(portsFor(src, \"outputs\"), sourceHandle);\n const inPort = findPort(portsFor(tgt, \"inputs\"), targetHandle);\n return compatible(outPort, inPort);\n };\n}\n","import { memo, useMemo } from \"react\";\nimport { Handle, NodeResizer, NodeToolbar, Position, type NodeProps } from \"@xyflow/react\";\nimport type { FlowNode, NodeRunStatus, PortDescriptor } from \"../types\";\nimport { categoryAccent, getNodeKind } from \"./registry\";\nimport { nodeConfig, resolveNodePorts } from \"./ports\";\n\n/**\n * RegistryNode — generic node renderer that looks up the node's kind in\n * the registry and applies its `accent`/`label`/`icon`/`renderBody`. Used\n * as the xyflow node component for every registered kind.\n */\nfunction RegistryNodeInner(props: NodeProps<FlowNode>) {\n const kindName = (props.data as any).kind ?? props.type;\n const kind = useMemo(() => getNodeKind(kindName), [kindName]);\n\n if (!kind) {\n return (\n <div className=\"ff-node ff-node--unknown\">\n <div className=\"ff-node__header\" style={{ background: \"#71717a\" }}>\n <span className=\"ff-node__tag\">UNKNOWN</span>\n <span className=\"ff-node__label\">{kindName}</span>\n </div>\n <p className=\"ff-node__desc\">No registered kind for \"{kindName}\".</p>\n </div>\n );\n }\n\n const data = props.data;\n const status: NodeRunStatus = data.status ?? \"idle\";\n const accent = kind.accent ?? categoryAccent(kind.category);\n const resolved = resolveNodePorts(props, kind);\n const inputs: PortDescriptor[] = resolved.inputs ?? defaultInputs(kind.category);\n const outputs: PortDescriptor[] = resolved.outputs ?? defaultOutputs(kind.category);\n const config = nodeConfig(props);\n const label = data.label ?? kind.label;\n\n return (\n <div\n className={[\n \"ff-node\",\n `ff-node--status-${status}`,\n `ff-node--cat-${kind.category}`,\n props.selected ? \"ff-node--selected\" : \"\",\n kind.resizable ? \"ff-node--resizable\" : \"\",\n ].filter(Boolean).join(\" \")}\n style={{ borderColor: props.selected ? accent : undefined }}\n >\n {kind.resizable && (\n <NodeResizer\n isVisible={props.selected ?? false}\n color={accent}\n {...(typeof kind.resizable === \"object\" ? kind.resizable : {})}\n />\n )}\n {kind.toolbar && (\n <NodeToolbar isVisible={props.selected ?? false}>\n {kind.toolbar({ nodeId: props.id, config: config as any, selected: props.selected ?? false })}\n </NodeToolbar>\n )}\n <header className=\"ff-node__header\" style={{ background: accent }}>\n {kind.icon && <span className=\"ff-node__icon\" aria-hidden>{kind.icon}</span>}\n <span className=\"ff-node__tag\">{kind.label.toUpperCase()}</span>\n <span className=\"ff-node__label\">{label}</span>\n {status !== \"idle\" && <span className={`ff-node__dot ff-node__dot--${status}`} aria-label={`status ${status}`} />}\n </header>\n\n {data.description && <p className=\"ff-node__desc\">{data.description}</p>}\n\n <div className=\"ff-node__body\">\n {kind.renderBody\n ? kind.renderBody({ nodeId: props.id, config: config as any, selected: props.selected ?? false })\n : <DefaultBody config={config} kind={kind.configSchema} />}\n </div>\n\n {data.statusText && <p className=\"ff-node__status-text\">{data.statusText}</p>}\n\n {(data as any).output !== undefined && (\n <p className=\"ff-node__output\" title=\"Latest output\">→ {previewValue((data as any).output)}</p>\n )}\n\n {inputs.map((p, i) => (\n <Handle\n key={p.id}\n type=\"target\"\n position={Position.Left}\n id={p.id}\n style={portStyle(i, inputs.length)}\n title={p.label ?? p.id}\n />\n ))}\n {outputs.map((p, i) => (\n <Handle\n key={p.id}\n type=\"source\"\n position={Position.Right}\n id={p.id}\n style={portStyle(i, outputs.length)}\n title={p.label ?? p.id}\n />\n ))}\n </div>\n );\n}\n\nexport const RegistryNode = memo(RegistryNodeInner);\n\nfunction defaultInputs(category: string): PortDescriptor[] {\n return category === \"trigger\" ? [] : [{ id: \"in\" }];\n}\nfunction defaultOutputs(category: string): PortDescriptor[] {\n return category === \"output\" ? [] : [{ id: \"out\" }];\n}\n\nfunction portStyle(i: number, total: number): React.CSSProperties {\n if (total <= 1) return {};\n const slot = (100 / (total + 1)) * (i + 1);\n return { top: `${slot}%` };\n}\n\n/**\n * DefaultBody — compact summary of the config values for nodes that\n * don't provide a custom renderBody. Skips fields that look empty.\n */\nfunction DefaultBody({ config, kind }: { config: Record<string, unknown>; kind?: import(\"./types\").ConfigField[] }) {\n const fields = kind ?? [];\n // A kind with no config schema has nothing to configure — don't nag with a\n // \"configure in the panel\" prompt (that's only for a kind that HAS fields but\n // none are filled in yet). Triggers, Output, etc. render as just their header.\n if (fields.length === 0) return null;\n const visible = fields\n .map((f) => ({ field: f, value: config[f.key] }))\n .filter(({ value }) => value !== undefined && value !== \"\" && value !== null);\n if (visible.length === 0) {\n return <div className=\"ff-node__body-empty\">— configure in the panel</div>;\n }\n return (\n <ul className=\"ff-node__summary\">\n {visible.slice(0, 4).map(({ field, value }) => (\n <li key={field.key}>\n <span className=\"ff-node__summary-key\">{field.label}:</span>\n <span className=\"ff-node__summary-value\">{previewValue(value)}</span>\n </li>\n ))}\n {visible.length > 4 && <li className=\"ff-node__summary-more\">+ {visible.length - 4} more</li>}\n </ul>\n );\n}\n\nconst truncate = (s: string, n = 30): string => (s.length > n ? s.slice(0, n - 1) + \"…\" : s);\n\n/** A short, human name for one item in a repeater/list value. */\nfunction itemLabel(item: unknown): string {\n if (item && typeof item === \"object\") {\n const o = item as Record<string, unknown>;\n const name = o.label ?? o.name ?? o.key ?? o.title ?? o.id;\n if (typeof name === \"string\" && name) return name;\n if (typeof name === \"number\") return String(name);\n return \"item\";\n }\n return String(item ?? \"\");\n}\n\n/**\n * A node card's job is to read at a glance, not to be a data dump — so a value\n * is summarised, never `JSON.stringify`d. An array becomes its item names (or a\n * count), an object becomes a field count. Raw JSON on a card was the specific\n * thing to kill: `Fields: [{\"key\":\"answer\",…}]` reads as noise.\n */\nexport function previewValue(v: unknown): string {\n if (v === null || v === undefined) return \"\";\n if (typeof v === \"string\") return truncate(v);\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n if (Array.isArray(v)) {\n if (v.length === 0) return \"none\";\n const names = v.slice(0, 3).map(itemLabel).filter(Boolean).join(\", \");\n const rest = v.length > 3 ? `, +${v.length - 3}` : \"\";\n return names ? truncate(names + rest) : `${v.length} item${v.length === 1 ? \"\" : \"s\"}`;\n }\n if (typeof v === \"object\") {\n const keys = Object.keys(v as object);\n return keys.length === 0 ? \"empty\" : `${keys.length} field${keys.length === 1 ? \"\" : \"s\"}`;\n }\n return \"…\";\n}\n","import { memo } from \"react\";\nimport { NodeResizer, type NodeProps } from \"@xyflow/react\";\nimport type { FlowNode } from \"../../types\";\nimport { categoryAccent } from \"../../registry/registry\";\n\n/**\n * LaneNode — a resizable swimlane band. A portless container: child nodes are\n * parented into it (`parentId` + `extent:'parent'`) and render on top, so this\n * node is just the titled background. Wired as the `@particle-academy/lane`\n * kind's `component`, so `buildNodeTypes` uses it instead of the default card.\n */\nfunction LaneNodeInner(props: NodeProps<FlowNode>) {\n const data = props.data as any;\n const config = data.config ?? {};\n const title: string = config.title ?? data.label ?? \"Lane\";\n const orientation: string = config.orientation ?? \"horizontal\";\n const accent: string = config.color ?? data.color ?? categoryAccent(\"layout\");\n return (\n <div\n className={[\"ff-lane\", `ff-lane--${orientation}`, props.selected ? \"ff-lane--selected\" : \"\"]\n .filter(Boolean)\n .join(\" \")}\n style={{ borderColor: props.selected ? accent : undefined }}\n >\n <NodeResizer isVisible={props.selected ?? false} color={accent} minWidth={160} minHeight={72} />\n <div className=\"ff-lane__header\" style={{ background: accent }}>\n <span className=\"ff-lane__title\">{title}</span>\n </div>\n </div>\n );\n}\n\nexport const LaneNode = memo(LaneNodeInner);\n","import { createContext, useContext } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Edge } from \"@xyflow/react\";\nimport type { FlowGraph, FlowNode, NodeRunStatus } from \"../../types\";\nimport type { WorkflowSchema } from \"../../schema\";\nimport type { AlignEdge } from \"./graph-ops\";\nimport type { AutoLayoutOptions } from \"../../layout\";\n\n/**\n * Everything the editor knows and can do, handed to every extension point.\n *\n * This is the seam that makes `<FlowEditor>` composable instead of fixed: it is\n * passed to custom actions and slots, returned from `useFlowEditor()` inside\n * any child, and exposed on the editor `ref` so a host can drive the editor\n * imperatively (or an MCP bridge can drive it for an agent).\n */\nexport type FlowEditorApi = {\n // ── State ────────────────────────────────────────────────────────────\n /** The current graph. */\n graph: FlowGraph;\n nodes: FlowNode[];\n edges: Edge[];\n /** Currently selected node id, or null. */\n selectedId: string | null;\n /** Currently selected node, or null. */\n selected: FlowNode | null;\n /** Currently selected edge id, or null. Independent of node selection. */\n selectedEdgeId: string | null;\n /** Currently selected edge, or null. */\n selectedEdge: Edge | null;\n /** All multi-selected node ids (xyflow box / shift selection). */\n selectedIds: string[];\n /** All multi-selected nodes. */\n selectedNodes: FlowNode[];\n /** True while a run is in flight. */\n running: boolean;\n /** Per-node run status, keyed by node id. */\n statuses: Record<string, NodeRunStatus>;\n\n // ── Selection ────────────────────────────────────────────────────────\n select: (id: string | null) => void;\n /** Select an edge (the connection between two nodes), or clear with null. */\n selectEdge: (id: string | null) => void;\n\n // ── Graph mutation ───────────────────────────────────────────────────\n /** Add a node of `kind` at an optional flow position. Returns the new id. */\n addNode: (kind: string, position?: { x: number; y: number }) => string | null;\n /** Replace one node (matched by id). */\n updateNode: (node: FlowNode) => void;\n /** Delete nodes by id, pruning every edge attached to them. */\n deleteNodes: (ids: string[]) => void;\n /** Delete the current selection (no-op when nothing is selected). */\n deleteSelected: () => void;\n /** Delete edges by id — i.e. break the connections between nodes. */\n deleteEdges: (ids: string[]) => void;\n /** Delete the selected edge (no-op when no edge is selected). */\n deleteSelectedEdge: () => void;\n /** Label a connection, or clear the label by passing undefined/\"\". */\n setEdgeLabel: (id: string, label: string | undefined) => void;\n /** Copy a node (offset slightly) and select the copy. Returns the new id. */\n duplicateNode: (id: string) => string | null;\n /** Replace the whole graph. */\n setGraph: (graph: FlowGraph) => void;\n\n // ── Bulk (multi-selection) ───────────────────────────────────────────\n /** Duplicate the current multi-selection, preserving edges between them. */\n duplicateSelected: () => void;\n /** Align the multi-selection to a shared edge/center of its bounding box. */\n alignSelected: (edge: AlignEdge) => void;\n /** Evenly distribute the multi-selection's gaps along an axis (needs 3+). */\n distributeSelected: (axis: \"h\" | \"v\") => void;\n\n // ── Swimlanes ────────────────────────────────────────────────────────\n /** Add a swimlane, stacked with existing lanes. Returns the new lane id. */\n addLane: (orientation?: \"horizontal\" | \"vertical\", title?: string) => string | null;\n /** Put a node inside a lane (sets parentId + a lane-relative position). */\n assignToLane: (nodeId: string, laneId: string) => void;\n /** Remove a node from its lane (restores its absolute position). */\n removeFromLane: (nodeId: string) => void;\n /** Auto-arrange the graph (or a lane's children) into a tidy DAG layout. */\n autoLayout: (options?: AutoLayoutOptions) => void;\n /** Tidy just one lane's children. */\n tidyLane: (laneId: string) => void;\n\n // ── Clipboard ────────────────────────────────────────────────────────\n /** Copy the current selection to the editor clipboard. */\n copy: () => void;\n /** Copy then delete the current selection. */\n cut: () => void;\n /** Paste the clipboard (optionally at a flow position) and select the result. */\n paste: (at?: { x: number; y: number }) => void;\n\n // ── Run ──────────────────────────────────────────────────────────────\n run: () => void;\n cancel: () => void;\n reset: () => void;\n\n // ── Workflow I/O ─────────────────────────────────────────────────────\n /** The current graph as a WorkflowSchema (what `export` downloads). */\n toWorkflow: () => WorkflowSchema;\n /** Download the workflow as JSON. */\n exportWorkflow: () => void;\n /** Open a file picker and load a workflow. */\n importWorkflow: () => void;\n\n // ── Viewport ─────────────────────────────────────────────────────────\n fitView: () => void;\n\n // ── History ──\n /** Undo the last committing edit (add / delete / connect / config / drag /\n * import). Transient interactions (a drag in progress, selection) are not\n * their own steps. */\n undo: () => void;\n /** Redo the last undone edit. */\n redo: () => void;\n /** True when there is an edit to undo. */\n canUndo: boolean;\n /** True when there is an undone edit to redo. */\n canRedo: boolean;\n};\n\n/**\n * A declarative toolbar button. JSON-friendly on purpose — a host (or an\n * agent) can describe editor affordances as data rather than JSX.\n */\nexport type FlowEditorAction = {\n /** Stable id — also the `data-action` handle, so agents never guess DOM. */\n id: string;\n label: ReactNode;\n /** Native tooltip. */\n title?: string;\n /** Where it sits relative to the built-in buttons. Default \"end\". */\n placement?: \"start\" | \"end\";\n /** Disable when this returns true. Re-evaluated on every render. */\n disabled?: (api: FlowEditorApi) => boolean;\n /** Hide entirely when this returns false. */\n visible?: (api: FlowEditorApi) => boolean;\n /** Only enabled when a node is selected. Sugar over `disabled`. */\n requiresSelection?: boolean;\n onSelect: (api: FlowEditorApi) => void;\n};\n\n/** Which built-in toolbar affordances to render. All default to true. */\nexport type FlowEditorBuiltins = {\n run?: boolean;\n delete?: boolean;\n /** Undo/redo toolbar buttons. Default true. */\n history?: boolean;\n /** \"Add lane\" toolbar button. Default true. */\n addLane?: boolean;\n /** \"Tidy\" (auto-layout) toolbar button. Default true. */\n autoLayout?: boolean;\n /** Right-click a node for Delete / Duplicate. Default true. */\n contextMenu?: boolean;\n /** Right-click a connection for Label / Delete. Default true. */\n edgeContextMenu?: boolean;\n export?: boolean;\n import?: boolean;\n count?: boolean;\n};\n\n/** Replaceable regions. Each receives the editor API. */\nexport type FlowEditorSlots = {\n /** Replace the ENTIRE toolbar (built-ins and actions are not rendered). */\n toolbar?: (api: FlowEditorApi) => ReactNode;\n /** Replace the left palette. */\n palette?: (api: FlowEditorApi) => ReactNode;\n /** Replace the right config panel. */\n panel?: (api: FlowEditorApi) => ReactNode;\n /** Appended inside the config panel, under the fields — per-node actions. */\n panelFooter?: (api: FlowEditorApi) => ReactNode;\n /** Replace the run feed. */\n feed?: (api: FlowEditorApi) => ReactNode;\n /** Rendered over the canvas when the graph is empty. */\n empty?: (api: FlowEditorApi) => ReactNode;\n /** Replace the node right-click menu. Receives the right-clicked node id;\n * call `close` when an item is chosen. */\n contextMenu?: (api: FlowEditorApi, nodeId: string, close: () => void) => ReactNode;\n /** Replace the connection right-click menu. Receives the right-clicked edge\n * id; call `close` when an item is chosen. */\n edgeContextMenu?: (api: FlowEditorApi, edgeId: string, close: () => void) => ReactNode;\n};\n\nconst FlowEditorContext = createContext<FlowEditorApi | null>(null);\n\nexport const FlowEditorProvider = FlowEditorContext.Provider;\n\n/**\n * Read the editor API from any child of `<FlowEditor>`. Throws outside one, so\n * a misplaced custom control fails loudly instead of silently doing nothing.\n */\nexport function useFlowEditor(): FlowEditorApi {\n const api = useContext(FlowEditorContext);\n if (api === null) {\n throw new Error(\"useFlowEditor() must be called inside <FlowEditor>.\");\n }\n return api;\n}\n\n/** Non-throwing variant, for components that may render outside an editor. */\nexport function useFlowEditorOptional(): FlowEditorApi | null {\n return useContext(FlowEditorContext);\n}\n","import { memo, useEffect, useRef, useState } from \"react\";\nimport { NodeResizer, type NodeProps } from \"@xyflow/react\";\nimport type { FlowNode } from \"../../types\";\nimport { useFlowEditorOptional } from \"../FlowEditor/api\";\n\n/**\n * NoteNode — a sticky-note annotation. A portless, visual-only node: nothing\n * wires to it and the engine skips it entirely, so a note NEVER reaches a\n * runner — its text lives in the document purely for people reading the canvas\n * and for editor / agent (MCP) tools. Wired as the `@particle-academy/note`\n * kind's `component`, so `buildNodeTypes` uses it instead of the default card;\n * also the legacy `defaultNodeTypes.note` renderer.\n *\n * Inside a `<FlowEditor>` the note is editable in place — double-click to type;\n * commits flow through `api.updateNode`, so each is one undoable step. In a\n * read-only viewer (no editor context) it renders as static text. Text is read\n * from `config.text` (canonical), falling back to the legacy `data.body` shape.\n */\n\ntype NoteColor = \"amber\" | \"sky\" | \"violet\" | \"emerald\" | \"rose\" | \"slate\";\n\n/** Soft sticky-note fills that read in both light and dark. */\nconst NOTE_COLORS: Record<NoteColor, { bg: string; border: string; title: string }> = {\n amber: { bg: \"rgba(234, 179, 8, 0.14)\", border: \"rgba(234, 179, 8, 0.55)\", title: \"#a16207\" },\n sky: { bg: \"rgba(14, 165, 233, 0.12)\", border: \"rgba(14, 165, 233, 0.5)\", title: \"#0369a1\" },\n violet: { bg: \"rgba(139, 92, 246, 0.13)\", border: \"rgba(139, 92, 246, 0.5)\", title: \"#6d28d9\" },\n emerald: { bg: \"rgba(16, 185, 129, 0.13)\", border: \"rgba(16, 185, 129, 0.5)\", title: \"#047857\" },\n rose: { bg: \"rgba(244, 63, 94, 0.12)\", border: \"rgba(244, 63, 94, 0.5)\", title: \"#be123c\" },\n slate: { bg: \"rgba(100, 116, 139, 0.12)\", border: \"rgba(100, 116, 139, 0.5)\", title: \"#475569\" },\n};\n\nfunction coerceColor(v: unknown): NoteColor {\n return typeof v === \"string\" && v in NOTE_COLORS ? (v as NoteColor) : \"amber\";\n}\n\nfunction NoteNodeInner(props: NodeProps<FlowNode>) {\n const data = props.data as any;\n const config = (data.config ?? {}) as Record<string, unknown>;\n const api = useFlowEditorOptional();\n\n const hasConfigText = typeof config.text === \"string\" && config.text.length > 0;\n // config.text is canonical; legacy notes carried their text in `data.body`.\n const text = hasConfigText\n ? String(config.text)\n : typeof data.body === \"string\"\n ? data.body\n : typeof data.text === \"string\"\n ? data.text\n : \"\";\n // config.title is canonical; a legacy note used its `data.label` as the title.\n const title =\n (typeof config.title === \"string\" && config.title) ||\n (!hasConfigText && typeof data.body === \"string\" && typeof data.label === \"string\" ? data.label : \"\");\n const skin = NOTE_COLORS[coerceColor(config.color)];\n\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(text);\n const areaRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n if (editing && areaRef.current) {\n const el = areaRef.current;\n el.focus();\n el.setSelectionRange(el.value.length, el.value.length);\n }\n }, [editing]);\n\n const commit = () => {\n setEditing(false);\n if (!api || draft === text) return;\n const current = api.nodes.find((n) => n.id === props.id);\n if (!current) return;\n api.updateNode({\n ...current,\n data: { ...(current.data as any), config: { ...config, text: draft } },\n } as FlowNode);\n };\n\n return (\n <div\n className={[\"ff-note\", props.selected ? \"ff-note--selected\" : \"\"].filter(Boolean).join(\" \")}\n style={{ background: skin.bg, borderColor: skin.border }}\n onDoubleClick={api ? () => { setDraft(text); setEditing(true); } : undefined}\n >\n <NodeResizer isVisible={props.selected ?? false} color={skin.border} minWidth={140} minHeight={80} />\n {title && <div className=\"ff-note__title\" style={{ color: skin.title }}>{title}</div>}\n {editing ? (\n <textarea\n ref={areaRef}\n className=\"ff-note__input nodrag nowheel\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commit}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") setEditing(false);\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) commit();\n }}\n />\n ) : (\n <div className=\"ff-note__body\">\n {text || <span className=\"ff-note__placeholder\">{api ? \"Double-click to write a note…\" : \"Note\"}</span>}\n </div>\n )}\n </div>\n );\n}\n\nexport const NoteNode = memo(NoteNodeInner);\n","import { createElement } from \"react\";\nimport { ensureBuiltinKinds, registerBuiltinKindInternal } from \"./registry\";\nimport { RichInputPreview } from \"./rich-input\";\nimport { LaneNode } from \"../components/nodes/LaneNode\";\nimport { NoteNode } from \"../components/nodes/NoteNode\";\nimport { BUILTIN_KIND_DATA } from \"./builtin-kinds\";\nimport type { NodeKindDefinition } from \"./types\";\n\n/**\n * The built-in kit WITH its React renderers — the editor's door into it.\n *\n * ## Why this file is a decorator rather than the table\n *\n * `/engine` promised to be React-free and was not (#11). The exact edge was\n * `engine.ts → registry/registry.ts → registry/builtin.ts → react`, and it was\n * a REGRESSION of a fix whose comment is still in `engine.ts`: that file\n * imports from the module rather than the barrel precisely to avoid this, and\n * `registry.ts` later gained a `builtin.ts` import so the registry\n * self-populates. Both changes were right. Nothing tested the property, so the\n * second silently undid the first.\n *\n * The surface turned out to be four lines across three kinds in a 1,064-line\n * file, so the table moved to `builtin-kinds.ts` (React-free, what `registry.ts`\n * imports) and only the renderers stayed here.\n *\n * ## Where this gets applied, and why THERE\n *\n * `src/index.ts` calls `registerBuiltinKinds()` at module scope. That is not an\n * arbitrary choice: `package.json`'s `sideEffects` lists `./dist/index.js` and\n * `./dist/index.cjs` and NOT `./dist/registry.js`, so a consumer's bundler is\n * entitled to drop a top-level statement in the registry chunk and obliged to\n * keep one in the root barrel. Putting it anywhere else would work until\n * somebody's tree-shaker disagreed, and the symptom would be lanes quietly\n * rendering as ordinary cards.\n *\n * A consumer who never imports the React root still gets every kind — schema,\n * ports, executors — because `registry.ts` self-populates from the data\n * module. They simply get no renderers, which is correct: a queue worker has\n * no DOM to render into.\n *\n * ## Host and marketplace kinds are untouched\n *\n * Both arrive at runtime through `registerNodeKind` carrying their own\n * `component` / `renderBody`, and never pass through here. This split is\n * first-party-only by construction and adds no rule a third-party kind has to\n * follow.\n */\n\n/** The renderers, by canonical kind name. */\nconst RENDERERS: Record<string, Partial<NodeKindDefinition>> = {\n \"@particle-academy/lane\": { component: LaneNode },\n \"@particle-academy/terminal_lane\": { component: LaneNode },\n \"@particle-academy/note\": { component: NoteNode },\n \"@particle-academy/rich_user_input\": {\n // Previews the authored page inside a FauxClient frame, so the canvas shows\n // what the person hitting this step will actually see.\n renderBody: (ctx) =>\n createElement(RichInputPreview, { config: (ctx.config ?? {}) as Record<string, unknown> }),\n },\n};\n\n/**\n * Every builtin, with renderers attached to the four that have one.\n *\n * A kind with no renderer is passed through by identity rather than copied, so\n * `BUILTIN_KINDS` and `BUILTIN_KIND_DATA` share objects wherever there is\n * nothing to add.\n */\nexport const BUILTIN_KINDS: NodeKindDefinition[] = BUILTIN_KIND_DATA.map((kind) => {\n const renderer = RENDERERS[kind.name];\n return renderer ? { ...kind, ...renderer } : kind;\n});\n\n/**\n * Register every built-in kind, renderers included.\n *\n * Idempotent, and safe to call after `registry.ts` has already self-populated\n * the plain table: it goes through `registerBuiltinKindInternal`, which\n * replaces a BUILTIN by name, so this UPGRADES the four kinds in place rather\n * than duplicating anything.\n *\n * It does NOT replace a name a host has claimed with `registerNodeKind`. That\n * is the whole point of 0.66.2 — a consumer's replacement of a builtin used to\n * be silently reverted every time this ran, and since the root barrel calls it\n * as an import side effect, whether that happened depended on bundler ordering.\n *\n * The distinction matters to anyone reading this to decide whether calling it\n * is safe: safe against builtins, deliberately powerless against a host.\n */\nexport function registerBuiltinKinds(): void {\n // Lay the React-free table down FIRST, through the same lazy path every other\n // entry point uses. Without this the flag is still unset, so the next read\n // anywhere — `getNodeKind`, the palette, a viewer — triggers\n // `ensureBuiltinKinds()` and re-registers the plain table OVER the decorated\n // kinds, silently dropping the four renderers this function exists to attach.\n ensureBuiltinKinds();\n\n for (const kind of BUILTIN_KINDS) registerBuiltinKindInternal(kind);\n}\n\n// Everything else the table exports keeps its old import path, so no consumer\n// of `./builtin` has to change.\nexport { BUILTIN_KIND_DATA, registerBuiltinKindData } from \"./builtin-kinds\";\n","export type {\n ConfigField,\n TextConfigField,\n TextareaConfigField,\n NumberConfigField,\n SelectConfigField,\n SwitchConfigField,\n JsonConfigField,\n ExpressionConfigField,\n CredentialConfigField,\n RepeaterConfigField,\n RepeaterRowField,\n KeyValueConfigField,\n DocumentConfigField,\n NodeCategory,\n NodeKindDefinition,\n EmitsRelation,\n PortSpec,\n RenderBodyContext,\n} from \"./types\";\n\nexport { resolvePortSpec, resolveNodePorts, nodeConfig } from \"./ports\";\n\nexport {\n createConnectionValidator,\n defaultPortCompatibility,\n ANY_PORT_TYPE,\n type PortCompatibility,\n type ConnectionValidatorOptions,\n} from \"./connection\";\n\n/** Host capabilities — core declares the contract, the host supplies the impl. */\nexport {\n registerLlmClient,\n getLlmClient,\n registerWorkflowResolver,\n getWorkflowResolver,\n registerTerminalHost,\n getTerminalHost,\n capabilityStatus,\n type LlmClient,\n type LlmRoute,\n type LlmRouteRequest,\n type LlmRouteChoice,\n type WorkflowResolver,\n type WorkflowResolution,\n type WorkflowResolutionFailure,\n type TerminalHost,\n type TerminalSession,\n type TerminalSessionSpec,\n type TerminalExit,\n type CapabilityId,\n isResolutionFailure,\n} from \"./capabilities\";\n\n/**\n * The human-pause contract — a run waiting for a person, not a failure.\n * `decodePause` is the one function a durable runner needs.\n */\nexport {\n pauseForHuman,\n encodePause,\n decodePause,\n isPause,\n PAUSE_PREFIX,\n LEGACY_PAUSE_PREFIXES,\n type PauseAwaiting,\n type PauseSignal,\n} from \"./pause\";\n\nexport { subflowExecutor, subflowPorts, subflowMode, DEFAULT_MAX_DEPTH, type SubflowMode } from \"./subflow\";\nexport { llmRouterExecutor, declaredRoutes, resolveFallbackPort } from \"./llm-router\";\n/** @deprecated Renamed to `llmRouterExecutor` — the id and label now match. */\nexport { llmRouterExecutor as llmBranchExecutor } from \"./llm-router\";\n\nexport {\n registerRichInputAdapter,\n getRichInputAdapter,\n isRichInputEnabled,\n onRichInputAdapterChanged,\n RichInputPreview,\n type RichInputAdapter,\n} from \"./rich-input\";\n\nexport {\n registerNodeKind,\n unregisterNodeKind,\n applyKindSchemaOverlay,\n clearKindSchemaOverlays,\n type KindSchemaOverlay,\n type KindSchemaOverlayResult,\n overrideNodeKind,\n clearNodeKindOverrides,\n type NodeKindPresentation,\n getNodeKind,\n resolveKindId,\n kindIds,\n listNodeKinds,\n onNodeKindsChanged,\n defaultConfigFor,\n validateConfig,\n categoryAccent,\n} from \"./registry\";\n\nexport { RegistryNode } from \"./RegistryNode\";\nexport { registerBuiltinKinds, BUILTIN_KINDS } from \"./builtin\";\nexport { ensureBuiltinKinds } from \"./registry\";\n\nimport type { NodeTypes } from \"@xyflow/react\";\nimport { RegistryNode } from \"./RegistryNode\";\nimport { kindIds, listNodeKinds } from \"./registry\";\n\n/**\n * Build an xyflow `nodeTypes` map from the registry — every registered\n * kind gets `RegistryNode` as its renderer. Refresh manually via\n * `useNodeTypes()` (a hook that subscribes to registry changes).\n */\nexport function buildNodeTypes(): NodeTypes {\n const map: NodeTypes = {};\n for (const k of listNodeKinds()) {\n // A kind may bring its own full renderer (lanes/containers); otherwise the\n // default card. Key on every id the kind answers to, not just the canonical\n // one — xyflow looks the renderer up by `node.type` BEFORE RegistryNode gets\n // a chance to resolve aliases, so a graph carrying a pre-namespace type would\n // otherwise fall through to the unknown-node placeholder.\n const renderer = k.component ?? RegistryNode;\n for (const id of kindIds(k)) map[id] = renderer;\n }\n return map;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/registry/connection.ts","../src/registry/RegistryNode.tsx","../src/components/nodes/LaneNode.tsx","../src/components/FlowEditor/api.ts","../src/components/nodes/NoteNode.tsx","../src/registry/builtin.ts","../src/registry/index.ts"],"names":["jsxs","jsx","memo"],"mappings":"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","file":"chunk-3RXQGYGH.js","sourcesContent":["import type { Connection, Edge } from \"@xyflow/react\";\nimport type { FlowNode, PortDescriptor } from \"../types\";\nimport { getNodeKind } from \"./registry\";\nimport { resolveNodePorts } from \"./ports\";\n\n/**\n * Connection validation — the single rule that decides whether an edge between\n * two ports is allowed. Lives here (React-free, registry-adjacent) so BOTH the\n * canvas (`<FlowCanvas isValidConnection>`) and the agent bridge's\n * `flow_connect` tool call the same function: a connection the canvas refuses\n * is a connection an agent cannot sneak past, and vice versa — no drift.\n *\n * `PortDescriptor.type` has always documented itself as being \"for hosts that\n * want to validate connections\", but nothing consumed it. This wires it up.\n */\n\n/**\n * Wildcard port type — matches any other type. A port that declares no `type`\n * is treated as a wildcard too, so existing flows (whose ports are untyped)\n * validate exactly as before.\n */\nexport const ANY_PORT_TYPE = \"any\";\n\n/** Decides whether a source-output port may feed a target-input port. */\nexport type PortCompatibility = (\n source: PortDescriptor | undefined,\n target: PortDescriptor | undefined,\n) => boolean;\n\n/**\n * Default compatibility rule: allow the connection UNLESS both ports declare a\n * concrete, differing type. An absent type or the `any` wildcard matches\n * anything — so typed ports get enforced while untyped ports stay permissive,\n * making this a safe default that never breaks an existing untyped graph.\n */\nexport const defaultPortCompatibility: PortCompatibility = (source, target) => {\n const s = source?.type;\n const t = target?.type;\n if (!s || !t) return true;\n if (s === ANY_PORT_TYPE || t === ANY_PORT_TYPE) return true;\n return s === t;\n};\n\nexport type ConnectionValidatorOptions = {\n /** Override the type-compatibility rule. Defaults to {@link defaultPortCompatibility}. */\n compatible?: PortCompatibility;\n /**\n * Allow a node's own output to feed its own input (a self-loop). Default\n * false — self-connections are almost always an accident, and the rare flow\n * that wants one can opt in.\n */\n allowSelfConnection?: boolean;\n};\n\n/** Resolve a node's effective ports for one side, going through the registry. */\nfunction portsFor(node: FlowNode, side: \"inputs\" | \"outputs\"): PortDescriptor[] {\n const kind = getNodeKind((node.data as any)?.kind ?? node.type);\n const resolved = resolveNodePorts(node, kind ?? undefined);\n return resolved[side] ?? [];\n}\n\n/**\n * Resolve which port a handle id refers to. When a side has a single port and\n * the handle id is absent (React Flow omits it for a lone default handle), use\n * that port; otherwise match by id.\n */\nfunction findPort(\n ports: PortDescriptor[],\n handleId: string | null | undefined,\n): PortDescriptor | undefined {\n if (handleId == null) return ports.length === 1 ? ports[0] : undefined;\n return ports.find((p) => p.id === handleId);\n}\n\n/**\n * Build an `isValidConnection` predicate that enforces port-type compatibility\n * against a live node list. Pass it straight to `<FlowCanvas isValidConnection>`\n * or reuse it to gate an agent's `flow_connect`.\n *\n * `getNodes` is called on every check so the validator always sees the current\n * graph (wire it to a ref/state getter, not a snapshot).\n *\n * A connection is rejected when: an endpoint is missing; it is a self-loop and\n * `allowSelfConnection` is off; either node is unknown; or the resolved ports\n * are type-incompatible. Nodes/ports with no declared type validate as before.\n */\nexport function createConnectionValidator(\n getNodes: () => FlowNode[],\n options: ConnectionValidatorOptions = {},\n): (connection: Connection | Edge) => boolean {\n const compatible = options.compatible ?? defaultPortCompatibility;\n return (connection) => {\n const { source, target, sourceHandle, targetHandle } = connection;\n if (!source || !target) return false;\n if (!options.allowSelfConnection && source === target) return false;\n\n const nodes = getNodes();\n const src = nodes.find((n) => n.id === source);\n const tgt = nodes.find((n) => n.id === target);\n if (!src || !tgt) return false;\n\n const outPort = findPort(portsFor(src, \"outputs\"), sourceHandle);\n const inPort = findPort(portsFor(tgt, \"inputs\"), targetHandle);\n return compatible(outPort, inPort);\n };\n}\n","import { memo, useMemo } from \"react\";\nimport { Handle, NodeResizer, NodeToolbar, Position, type NodeProps } from \"@xyflow/react\";\nimport type { FlowNode, NodeRunStatus, PortDescriptor } from \"../types\";\nimport { categoryAccent, getNodeKind } from \"./registry\";\nimport { nodeConfig, resolveNodePorts } from \"./ports\";\n\n/**\n * RegistryNode — generic node renderer that looks up the node's kind in\n * the registry and applies its `accent`/`label`/`icon`/`renderBody`. Used\n * as the xyflow node component for every registered kind.\n */\nfunction RegistryNodeInner(props: NodeProps<FlowNode>) {\n const kindName = (props.data as any).kind ?? props.type;\n const kind = useMemo(() => getNodeKind(kindName), [kindName]);\n\n if (!kind) {\n return (\n <div className=\"ff-node ff-node--unknown\">\n <div className=\"ff-node__header\" style={{ background: \"#71717a\" }}>\n <span className=\"ff-node__tag\">UNKNOWN</span>\n <span className=\"ff-node__label\">{kindName}</span>\n </div>\n <p className=\"ff-node__desc\">No registered kind for \"{kindName}\".</p>\n </div>\n );\n }\n\n const data = props.data;\n const status: NodeRunStatus = data.status ?? \"idle\";\n const accent = kind.accent ?? categoryAccent(kind.category);\n const resolved = resolveNodePorts(props, kind);\n const inputs: PortDescriptor[] = resolved.inputs ?? defaultInputs(kind.category);\n const outputs: PortDescriptor[] = resolved.outputs ?? defaultOutputs(kind.category);\n const config = nodeConfig(props);\n const label = data.label ?? kind.label;\n\n return (\n <div\n className={[\n \"ff-node\",\n `ff-node--status-${status}`,\n `ff-node--cat-${kind.category}`,\n props.selected ? \"ff-node--selected\" : \"\",\n kind.resizable ? \"ff-node--resizable\" : \"\",\n ].filter(Boolean).join(\" \")}\n style={{ borderColor: props.selected ? accent : undefined }}\n >\n {kind.resizable && (\n <NodeResizer\n isVisible={props.selected ?? false}\n color={accent}\n {...(typeof kind.resizable === \"object\" ? kind.resizable : {})}\n />\n )}\n {kind.toolbar && (\n <NodeToolbar isVisible={props.selected ?? false}>\n {kind.toolbar({ nodeId: props.id, config: config as any, selected: props.selected ?? false })}\n </NodeToolbar>\n )}\n <header className=\"ff-node__header\" style={{ background: accent }}>\n {kind.icon && <span className=\"ff-node__icon\" aria-hidden>{kind.icon}</span>}\n <span className=\"ff-node__tag\">{kind.label.toUpperCase()}</span>\n <span className=\"ff-node__label\">{label}</span>\n {status !== \"idle\" && <span className={`ff-node__dot ff-node__dot--${status}`} aria-label={`status ${status}`} />}\n </header>\n\n {data.description && <p className=\"ff-node__desc\">{data.description}</p>}\n\n <div className=\"ff-node__body\">\n {kind.renderBody\n ? kind.renderBody({ nodeId: props.id, config: config as any, selected: props.selected ?? false })\n : <DefaultBody config={config} kind={kind.configSchema} />}\n </div>\n\n {data.statusText && <p className=\"ff-node__status-text\">{data.statusText}</p>}\n\n {(data as any).output !== undefined && (\n <p className=\"ff-node__output\" title=\"Latest output\">→ {previewValue((data as any).output)}</p>\n )}\n\n {inputs.map((p, i) => (\n <Handle\n key={p.id}\n type=\"target\"\n position={Position.Left}\n id={p.id}\n style={portStyle(i, inputs.length)}\n title={p.label ?? p.id}\n />\n ))}\n {outputs.map((p, i) => (\n <Handle\n key={p.id}\n type=\"source\"\n position={Position.Right}\n id={p.id}\n style={portStyle(i, outputs.length)}\n title={p.label ?? p.id}\n />\n ))}\n </div>\n );\n}\n\nexport const RegistryNode = memo(RegistryNodeInner);\n\nfunction defaultInputs(category: string): PortDescriptor[] {\n return category === \"trigger\" ? [] : [{ id: \"in\" }];\n}\nfunction defaultOutputs(category: string): PortDescriptor[] {\n return category === \"output\" ? [] : [{ id: \"out\" }];\n}\n\nfunction portStyle(i: number, total: number): React.CSSProperties {\n if (total <= 1) return {};\n const slot = (100 / (total + 1)) * (i + 1);\n return { top: `${slot}%` };\n}\n\n/**\n * DefaultBody — compact summary of the config values for nodes that\n * don't provide a custom renderBody. Skips fields that look empty.\n */\nfunction DefaultBody({ config, kind }: { config: Record<string, unknown>; kind?: import(\"./types\").ConfigField[] }) {\n const fields = kind ?? [];\n // A kind with no config schema has nothing to configure — don't nag with a\n // \"configure in the panel\" prompt (that's only for a kind that HAS fields but\n // none are filled in yet). Triggers, Output, etc. render as just their header.\n if (fields.length === 0) return null;\n const visible = fields\n .map((f) => ({ field: f, value: config[f.key] }))\n .filter(({ value }) => value !== undefined && value !== \"\" && value !== null);\n if (visible.length === 0) {\n return <div className=\"ff-node__body-empty\">— configure in the panel</div>;\n }\n return (\n <ul className=\"ff-node__summary\">\n {visible.slice(0, 4).map(({ field, value }) => (\n <li key={field.key}>\n <span className=\"ff-node__summary-key\">{field.label}:</span>\n <span className=\"ff-node__summary-value\">{previewValue(value)}</span>\n </li>\n ))}\n {visible.length > 4 && <li className=\"ff-node__summary-more\">+ {visible.length - 4} more</li>}\n </ul>\n );\n}\n\nconst truncate = (s: string, n = 30): string => (s.length > n ? s.slice(0, n - 1) + \"…\" : s);\n\n/** A short, human name for one item in a repeater/list value. */\nfunction itemLabel(item: unknown): string {\n if (item && typeof item === \"object\") {\n const o = item as Record<string, unknown>;\n const name = o.label ?? o.name ?? o.key ?? o.title ?? o.id;\n if (typeof name === \"string\" && name) return name;\n if (typeof name === \"number\") return String(name);\n return \"item\";\n }\n return String(item ?? \"\");\n}\n\n/**\n * A node card's job is to read at a glance, not to be a data dump — so a value\n * is summarised, never `JSON.stringify`d. An array becomes its item names (or a\n * count), an object becomes a field count. Raw JSON on a card was the specific\n * thing to kill: `Fields: [{\"key\":\"answer\",…}]` reads as noise.\n */\nexport function previewValue(v: unknown): string {\n if (v === null || v === undefined) return \"\";\n if (typeof v === \"string\") return truncate(v);\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n if (Array.isArray(v)) {\n if (v.length === 0) return \"none\";\n const names = v.slice(0, 3).map(itemLabel).filter(Boolean).join(\", \");\n const rest = v.length > 3 ? `, +${v.length - 3}` : \"\";\n return names ? truncate(names + rest) : `${v.length} item${v.length === 1 ? \"\" : \"s\"}`;\n }\n if (typeof v === \"object\") {\n const keys = Object.keys(v as object);\n return keys.length === 0 ? \"empty\" : `${keys.length} field${keys.length === 1 ? \"\" : \"s\"}`;\n }\n return \"…\";\n}\n","import { memo } from \"react\";\nimport { NodeResizer, type NodeProps } from \"@xyflow/react\";\nimport type { FlowNode } from \"../../types\";\nimport { categoryAccent } from \"../../registry/registry\";\n\n/**\n * LaneNode — a resizable swimlane band. A portless container: child nodes are\n * parented into it (`parentId` + `extent:'parent'`) and render on top, so this\n * node is just the titled background. Wired as the `@particle-academy/lane`\n * kind's `component`, so `buildNodeTypes` uses it instead of the default card.\n */\nfunction LaneNodeInner(props: NodeProps<FlowNode>) {\n const data = props.data as any;\n const config = data.config ?? {};\n const title: string = config.title ?? data.label ?? \"Lane\";\n const orientation: string = config.orientation ?? \"horizontal\";\n const accent: string = config.color ?? data.color ?? categoryAccent(\"layout\");\n return (\n <div\n className={[\"ff-lane\", `ff-lane--${orientation}`, props.selected ? \"ff-lane--selected\" : \"\"]\n .filter(Boolean)\n .join(\" \")}\n style={{ borderColor: props.selected ? accent : undefined }}\n >\n <NodeResizer isVisible={props.selected ?? false} color={accent} minWidth={160} minHeight={72} />\n <div className=\"ff-lane__header\" style={{ background: accent }}>\n <span className=\"ff-lane__title\">{title}</span>\n </div>\n </div>\n );\n}\n\nexport const LaneNode = memo(LaneNodeInner);\n","import { createContext, useContext } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Edge } from \"@xyflow/react\";\nimport type { FlowGraph, FlowNode, NodeRunStatus } from \"../../types\";\nimport type { WorkflowSchema } from \"../../schema\";\nimport type { AlignEdge } from \"./graph-ops\";\nimport type { AutoLayoutOptions } from \"../../layout\";\n\n/**\n * Everything the editor knows and can do, handed to every extension point.\n *\n * This is the seam that makes `<FlowEditor>` composable instead of fixed: it is\n * passed to custom actions and slots, returned from `useFlowEditor()` inside\n * any child, and exposed on the editor `ref` so a host can drive the editor\n * imperatively (or an MCP bridge can drive it for an agent).\n */\nexport type FlowEditorApi = {\n // ── State ────────────────────────────────────────────────────────────\n /** The current graph. */\n graph: FlowGraph;\n nodes: FlowNode[];\n edges: Edge[];\n /** Currently selected node id, or null. */\n selectedId: string | null;\n /** Currently selected node, or null. */\n selected: FlowNode | null;\n /** Currently selected edge id, or null. Independent of node selection. */\n selectedEdgeId: string | null;\n /** Currently selected edge, or null. */\n selectedEdge: Edge | null;\n /** All multi-selected node ids (xyflow box / shift selection). */\n selectedIds: string[];\n /** All multi-selected nodes. */\n selectedNodes: FlowNode[];\n /** True while a run is in flight. */\n running: boolean;\n /** Per-node run status, keyed by node id. */\n statuses: Record<string, NodeRunStatus>;\n\n // ── Selection ────────────────────────────────────────────────────────\n select: (id: string | null) => void;\n /** Select an edge (the connection between two nodes), or clear with null. */\n selectEdge: (id: string | null) => void;\n\n // ── Graph mutation ───────────────────────────────────────────────────\n /** Add a node of `kind` at an optional flow position. Returns the new id. */\n addNode: (kind: string, position?: { x: number; y: number }) => string | null;\n /** Replace one node (matched by id). */\n updateNode: (node: FlowNode) => void;\n /** Delete nodes by id, pruning every edge attached to them. */\n deleteNodes: (ids: string[]) => void;\n /** Delete the current selection (no-op when nothing is selected). */\n deleteSelected: () => void;\n /** Delete edges by id — i.e. break the connections between nodes. */\n deleteEdges: (ids: string[]) => void;\n /** Delete the selected edge (no-op when no edge is selected). */\n deleteSelectedEdge: () => void;\n /** Label a connection, or clear the label by passing undefined/\"\". */\n setEdgeLabel: (id: string, label: string | undefined) => void;\n /** Copy a node (offset slightly) and select the copy. Returns the new id. */\n duplicateNode: (id: string) => string | null;\n /** Replace the whole graph. */\n setGraph: (graph: FlowGraph) => void;\n\n // ── Bulk (multi-selection) ───────────────────────────────────────────\n /** Duplicate the current multi-selection, preserving edges between them. */\n duplicateSelected: () => void;\n /** Align the multi-selection to a shared edge/center of its bounding box. */\n alignSelected: (edge: AlignEdge) => void;\n /** Evenly distribute the multi-selection's gaps along an axis (needs 3+). */\n distributeSelected: (axis: \"h\" | \"v\") => void;\n\n // ── Swimlanes ────────────────────────────────────────────────────────\n /** Add a swimlane, stacked with existing lanes. Returns the new lane id. */\n addLane: (orientation?: \"horizontal\" | \"vertical\", title?: string) => string | null;\n /** Put a node inside a lane (sets parentId + a lane-relative position). */\n assignToLane: (nodeId: string, laneId: string) => void;\n /** Remove a node from its lane (restores its absolute position). */\n removeFromLane: (nodeId: string) => void;\n /** Auto-arrange the graph (or a lane's children) into a tidy DAG layout. */\n autoLayout: (options?: AutoLayoutOptions) => void;\n /** Tidy just one lane's children. */\n tidyLane: (laneId: string) => void;\n\n // ── Clipboard ────────────────────────────────────────────────────────\n /** Copy the current selection to the editor clipboard. */\n copy: () => void;\n /** Copy then delete the current selection. */\n cut: () => void;\n /** Paste the clipboard (optionally at a flow position) and select the result. */\n paste: (at?: { x: number; y: number }) => void;\n\n // ── Run ──────────────────────────────────────────────────────────────\n run: () => void;\n cancel: () => void;\n reset: () => void;\n\n // ── Workflow I/O ─────────────────────────────────────────────────────\n /** The current graph as a WorkflowSchema (what `export` downloads). */\n toWorkflow: () => WorkflowSchema;\n /** Download the workflow as JSON. */\n exportWorkflow: () => void;\n /** Open a file picker and load a workflow. */\n importWorkflow: () => void;\n\n // ── Viewport ─────────────────────────────────────────────────────────\n fitView: () => void;\n\n // ── History ──\n /** Undo the last committing edit (add / delete / connect / config / drag /\n * import). Transient interactions (a drag in progress, selection) are not\n * their own steps. */\n undo: () => void;\n /** Redo the last undone edit. */\n redo: () => void;\n /** True when there is an edit to undo. */\n canUndo: boolean;\n /** True when there is an undone edit to redo. */\n canRedo: boolean;\n};\n\n/**\n * A declarative toolbar button. JSON-friendly on purpose — a host (or an\n * agent) can describe editor affordances as data rather than JSX.\n */\nexport type FlowEditorAction = {\n /** Stable id — also the `data-action` handle, so agents never guess DOM. */\n id: string;\n label: ReactNode;\n /** Native tooltip. */\n title?: string;\n /** Where it sits relative to the built-in buttons. Default \"end\". */\n placement?: \"start\" | \"end\";\n /** Disable when this returns true. Re-evaluated on every render. */\n disabled?: (api: FlowEditorApi) => boolean;\n /** Hide entirely when this returns false. */\n visible?: (api: FlowEditorApi) => boolean;\n /** Only enabled when a node is selected. Sugar over `disabled`. */\n requiresSelection?: boolean;\n onSelect: (api: FlowEditorApi) => void;\n};\n\n/** Which built-in toolbar affordances to render. All default to true. */\nexport type FlowEditorBuiltins = {\n run?: boolean;\n delete?: boolean;\n /** Undo/redo toolbar buttons. Default true. */\n history?: boolean;\n /** \"Add lane\" toolbar button. Default true. */\n addLane?: boolean;\n /** \"Tidy\" (auto-layout) toolbar button. Default true. */\n autoLayout?: boolean;\n /** Right-click a node for Delete / Duplicate. Default true. */\n contextMenu?: boolean;\n /** Right-click a connection for Label / Delete. Default true. */\n edgeContextMenu?: boolean;\n export?: boolean;\n import?: boolean;\n count?: boolean;\n};\n\n/** Replaceable regions. Each receives the editor API. */\nexport type FlowEditorSlots = {\n /** Replace the ENTIRE toolbar (built-ins and actions are not rendered). */\n toolbar?: (api: FlowEditorApi) => ReactNode;\n /** Replace the left palette. */\n palette?: (api: FlowEditorApi) => ReactNode;\n /** Replace the right config panel. */\n panel?: (api: FlowEditorApi) => ReactNode;\n /** Appended inside the config panel, under the fields — per-node actions. */\n panelFooter?: (api: FlowEditorApi) => ReactNode;\n /** Replace the run feed. */\n feed?: (api: FlowEditorApi) => ReactNode;\n /** Rendered over the canvas when the graph is empty. */\n empty?: (api: FlowEditorApi) => ReactNode;\n /** Replace the node right-click menu. Receives the right-clicked node id;\n * call `close` when an item is chosen. */\n contextMenu?: (api: FlowEditorApi, nodeId: string, close: () => void) => ReactNode;\n /** Replace the connection right-click menu. Receives the right-clicked edge\n * id; call `close` when an item is chosen. */\n edgeContextMenu?: (api: FlowEditorApi, edgeId: string, close: () => void) => ReactNode;\n};\n\nconst FlowEditorContext = createContext<FlowEditorApi | null>(null);\n\nexport const FlowEditorProvider = FlowEditorContext.Provider;\n\n/**\n * Read the editor API from any child of `<FlowEditor>`. Throws outside one, so\n * a misplaced custom control fails loudly instead of silently doing nothing.\n */\nexport function useFlowEditor(): FlowEditorApi {\n const api = useContext(FlowEditorContext);\n if (api === null) {\n throw new Error(\"useFlowEditor() must be called inside <FlowEditor>.\");\n }\n return api;\n}\n\n/** Non-throwing variant, for components that may render outside an editor. */\nexport function useFlowEditorOptional(): FlowEditorApi | null {\n return useContext(FlowEditorContext);\n}\n","import { memo, useEffect, useRef, useState } from \"react\";\nimport { NodeResizer, type NodeProps } from \"@xyflow/react\";\nimport type { FlowNode } from \"../../types\";\nimport { useFlowEditorOptional } from \"../FlowEditor/api\";\n\n/**\n * NoteNode — a sticky-note annotation. A portless, visual-only node: nothing\n * wires to it and the engine skips it entirely, so a note NEVER reaches a\n * runner — its text lives in the document purely for people reading the canvas\n * and for editor / agent (MCP) tools. Wired as the `@particle-academy/note`\n * kind's `component`, so `buildNodeTypes` uses it instead of the default card;\n * also the legacy `defaultNodeTypes.note` renderer.\n *\n * Inside a `<FlowEditor>` the note is editable in place — double-click to type;\n * commits flow through `api.updateNode`, so each is one undoable step. In a\n * read-only viewer (no editor context) it renders as static text. Text is read\n * from `config.text` (canonical), falling back to the legacy `data.body` shape.\n */\n\ntype NoteColor = \"amber\" | \"sky\" | \"violet\" | \"emerald\" | \"rose\" | \"slate\";\n\n/** Soft sticky-note fills that read in both light and dark. */\nconst NOTE_COLORS: Record<NoteColor, { bg: string; border: string; title: string }> = {\n amber: { bg: \"rgba(234, 179, 8, 0.14)\", border: \"rgba(234, 179, 8, 0.55)\", title: \"#a16207\" },\n sky: { bg: \"rgba(14, 165, 233, 0.12)\", border: \"rgba(14, 165, 233, 0.5)\", title: \"#0369a1\" },\n violet: { bg: \"rgba(139, 92, 246, 0.13)\", border: \"rgba(139, 92, 246, 0.5)\", title: \"#6d28d9\" },\n emerald: { bg: \"rgba(16, 185, 129, 0.13)\", border: \"rgba(16, 185, 129, 0.5)\", title: \"#047857\" },\n rose: { bg: \"rgba(244, 63, 94, 0.12)\", border: \"rgba(244, 63, 94, 0.5)\", title: \"#be123c\" },\n slate: { bg: \"rgba(100, 116, 139, 0.12)\", border: \"rgba(100, 116, 139, 0.5)\", title: \"#475569\" },\n};\n\nfunction coerceColor(v: unknown): NoteColor {\n return typeof v === \"string\" && v in NOTE_COLORS ? (v as NoteColor) : \"amber\";\n}\n\nfunction NoteNodeInner(props: NodeProps<FlowNode>) {\n const data = props.data as any;\n const config = (data.config ?? {}) as Record<string, unknown>;\n const api = useFlowEditorOptional();\n\n const hasConfigText = typeof config.text === \"string\" && config.text.length > 0;\n // config.text is canonical; legacy notes carried their text in `data.body`.\n const text = hasConfigText\n ? String(config.text)\n : typeof data.body === \"string\"\n ? data.body\n : typeof data.text === \"string\"\n ? data.text\n : \"\";\n // config.title is canonical; a legacy note used its `data.label` as the title.\n const title =\n (typeof config.title === \"string\" && config.title) ||\n (!hasConfigText && typeof data.body === \"string\" && typeof data.label === \"string\" ? data.label : \"\");\n const skin = NOTE_COLORS[coerceColor(config.color)];\n\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(text);\n const areaRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n if (editing && areaRef.current) {\n const el = areaRef.current;\n el.focus();\n el.setSelectionRange(el.value.length, el.value.length);\n }\n }, [editing]);\n\n const commit = () => {\n setEditing(false);\n if (!api || draft === text) return;\n const current = api.nodes.find((n) => n.id === props.id);\n if (!current) return;\n api.updateNode({\n ...current,\n data: { ...(current.data as any), config: { ...config, text: draft } },\n } as FlowNode);\n };\n\n return (\n <div\n className={[\"ff-note\", props.selected ? \"ff-note--selected\" : \"\"].filter(Boolean).join(\" \")}\n style={{ background: skin.bg, borderColor: skin.border }}\n onDoubleClick={api ? () => { setDraft(text); setEditing(true); } : undefined}\n >\n <NodeResizer isVisible={props.selected ?? false} color={skin.border} minWidth={140} minHeight={80} />\n {title && <div className=\"ff-note__title\" style={{ color: skin.title }}>{title}</div>}\n {editing ? (\n <textarea\n ref={areaRef}\n className=\"ff-note__input nodrag nowheel\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commit}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") setEditing(false);\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) commit();\n }}\n />\n ) : (\n <div className=\"ff-note__body\">\n {text || <span className=\"ff-note__placeholder\">{api ? \"Double-click to write a note…\" : \"Note\"}</span>}\n </div>\n )}\n </div>\n );\n}\n\nexport const NoteNode = memo(NoteNodeInner);\n","import { createElement } from \"react\";\nimport { ensureBuiltinKinds, registerBuiltinKindInternal } from \"./registry\";\nimport { RichInputPreview } from \"./rich-input\";\nimport { LaneNode } from \"../components/nodes/LaneNode\";\nimport { NoteNode } from \"../components/nodes/NoteNode\";\nimport { BUILTIN_KIND_DATA } from \"./builtin-kinds\";\nimport type { NodeKindDefinition } from \"./types\";\n\n/**\n * The built-in kit WITH its React renderers — the editor's door into it.\n *\n * ## Why this file is a decorator rather than the table\n *\n * `/engine` promised to be React-free and was not (#11). The exact edge was\n * `engine.ts → registry/registry.ts → registry/builtin.ts → react`, and it was\n * a REGRESSION of a fix whose comment is still in `engine.ts`: that file\n * imports from the module rather than the barrel precisely to avoid this, and\n * `registry.ts` later gained a `builtin.ts` import so the registry\n * self-populates. Both changes were right. Nothing tested the property, so the\n * second silently undid the first.\n *\n * The surface turned out to be four lines across three kinds in a 1,064-line\n * file, so the table moved to `builtin-kinds.ts` (React-free, what `registry.ts`\n * imports) and only the renderers stayed here.\n *\n * ## Where this gets applied, and why THERE\n *\n * `src/index.ts` calls `registerBuiltinKinds()` at module scope. That is not an\n * arbitrary choice: `package.json`'s `sideEffects` lists `./dist/index.js` and\n * `./dist/index.cjs` and NOT `./dist/registry.js`, so a consumer's bundler is\n * entitled to drop a top-level statement in the registry chunk and obliged to\n * keep one in the root barrel. Putting it anywhere else would work until\n * somebody's tree-shaker disagreed, and the symptom would be lanes quietly\n * rendering as ordinary cards.\n *\n * A consumer who never imports the React root still gets every kind — schema,\n * ports, executors — because `registry.ts` self-populates from the data\n * module. They simply get no renderers, which is correct: a queue worker has\n * no DOM to render into.\n *\n * ## Host and marketplace kinds are untouched\n *\n * Both arrive at runtime through `registerNodeKind` carrying their own\n * `component` / `renderBody`, and never pass through here. This split is\n * first-party-only by construction and adds no rule a third-party kind has to\n * follow.\n */\n\n/** The renderers, by canonical kind name. */\nconst RENDERERS: Record<string, Partial<NodeKindDefinition>> = {\n \"@particle-academy/lane\": { component: LaneNode },\n \"@particle-academy/terminal_lane\": { component: LaneNode },\n \"@particle-academy/note\": { component: NoteNode },\n \"@particle-academy/rich_user_input\": {\n // Previews the authored page inside a FauxClient frame, so the canvas shows\n // what the person hitting this step will actually see.\n renderBody: (ctx) =>\n createElement(RichInputPreview, { config: (ctx.config ?? {}) as Record<string, unknown> }),\n },\n};\n\n/**\n * Every builtin, with renderers attached to the four that have one.\n *\n * A kind with no renderer is passed through by identity rather than copied, so\n * `BUILTIN_KINDS` and `BUILTIN_KIND_DATA` share objects wherever there is\n * nothing to add.\n */\nexport const BUILTIN_KINDS: NodeKindDefinition[] = BUILTIN_KIND_DATA.map((kind) => {\n const renderer = RENDERERS[kind.name];\n return renderer ? { ...kind, ...renderer } : kind;\n});\n\n/**\n * Register every built-in kind, renderers included.\n *\n * Idempotent, and safe to call after `registry.ts` has already self-populated\n * the plain table: it goes through `registerBuiltinKindInternal`, which\n * replaces a BUILTIN by name, so this UPGRADES the four kinds in place rather\n * than duplicating anything.\n *\n * It does NOT replace a name a host has claimed with `registerNodeKind`. That\n * is the whole point of 0.66.2 — a consumer's replacement of a builtin used to\n * be silently reverted every time this ran, and since the root barrel calls it\n * as an import side effect, whether that happened depended on bundler ordering.\n *\n * The distinction matters to anyone reading this to decide whether calling it\n * is safe: safe against builtins, deliberately powerless against a host.\n */\nexport function registerBuiltinKinds(): void {\n // Lay the React-free table down FIRST, through the same lazy path every other\n // entry point uses. Without this the flag is still unset, so the next read\n // anywhere — `getNodeKind`, the palette, a viewer — triggers\n // `ensureBuiltinKinds()` and re-registers the plain table OVER the decorated\n // kinds, silently dropping the four renderers this function exists to attach.\n ensureBuiltinKinds();\n\n for (const kind of BUILTIN_KINDS) registerBuiltinKindInternal(kind);\n}\n\n// Everything else the table exports keeps its old import path, so no consumer\n// of `./builtin` has to change.\nexport { BUILTIN_KIND_DATA, registerBuiltinKindData } from \"./builtin-kinds\";\n","export type {\n ConfigField,\n TextConfigField,\n TextareaConfigField,\n NumberConfigField,\n SelectConfigField,\n SwitchConfigField,\n JsonConfigField,\n ExpressionConfigField,\n CredentialConfigField,\n RepeaterConfigField,\n RepeaterRowField,\n KeyValueConfigField,\n DocumentConfigField,\n NodeCategory,\n NodeKindDefinition,\n EmitsRelation,\n PortSpec,\n RenderBodyContext,\n} from \"./types\";\n\nexport { resolvePortSpec, resolveNodePorts, nodeConfig } from \"./ports\";\n\nexport {\n createConnectionValidator,\n defaultPortCompatibility,\n ANY_PORT_TYPE,\n type PortCompatibility,\n type ConnectionValidatorOptions,\n} from \"./connection\";\n\n/** Host capabilities — core declares the contract, the host supplies the impl. */\nexport {\n registerLlmClient,\n getLlmClient,\n registerWorkflowResolver,\n getWorkflowResolver,\n registerTerminalHost,\n getTerminalHost,\n capabilityStatus,\n type LlmClient,\n type LlmRoute,\n type LlmRouteRequest,\n type LlmRouteChoice,\n type WorkflowResolver,\n type WorkflowResolution,\n type WorkflowResolutionFailure,\n type TerminalHost,\n type TerminalSession,\n type TerminalSessionSpec,\n type TerminalExit,\n type CapabilityId,\n isResolutionFailure,\n} from \"./capabilities\";\n\n/**\n * The human-pause contract — a run waiting for a person, not a failure.\n * `decodePause` is the one function a durable runner needs.\n */\nexport {\n pauseForHuman,\n encodePause,\n decodePause,\n isPause,\n PAUSE_PREFIX,\n LEGACY_PAUSE_PREFIXES,\n type PauseAwaiting,\n type PauseSignal,\n} from \"./pause\";\n\nexport { subflowExecutor, subflowPorts, subflowMode, DEFAULT_MAX_DEPTH, type SubflowMode } from \"./subflow\";\nexport { llmRouterExecutor, declaredRoutes, resolveFallbackPort } from \"./llm-router\";\n/** @deprecated Renamed to `llmRouterExecutor` — the id and label now match. */\nexport { llmRouterExecutor as llmBranchExecutor } from \"./llm-router\";\n\nexport {\n registerRichInputAdapter,\n getRichInputAdapter,\n isRichInputEnabled,\n onRichInputAdapterChanged,\n RichInputPreview,\n type RichInputAdapter,\n} from \"./rich-input\";\n\nexport {\n registerNodeKind,\n unregisterNodeKind,\n applyKindSchemaOverlay,\n clearKindSchemaOverlays,\n type KindSchemaOverlay,\n type KindSchemaOverlayResult,\n overrideNodeKind,\n clearNodeKindOverrides,\n type NodeKindPresentation,\n getNodeKind,\n resolveKindId,\n kindIds,\n listNodeKinds,\n onNodeKindsChanged,\n defaultConfigFor,\n validateConfig,\n categoryAccent,\n} from \"./registry\";\n\nexport { RegistryNode } from \"./RegistryNode\";\nexport { registerBuiltinKinds, BUILTIN_KINDS } from \"./builtin\";\nexport { ensureBuiltinKinds } from \"./registry\";\n\nimport type { NodeTypes } from \"@xyflow/react\";\nimport { RegistryNode } from \"./RegistryNode\";\nimport { kindIds, listNodeKinds } from \"./registry\";\n\n/**\n * Build an xyflow `nodeTypes` map from the registry — every registered\n * kind gets `RegistryNode` as its renderer. Refresh manually via\n * `useNodeTypes()` (a hook that subscribes to registry changes).\n */\nexport function buildNodeTypes(): NodeTypes {\n const map: NodeTypes = {};\n for (const k of listNodeKinds()) {\n // A kind may bring its own full renderer (lanes/containers); otherwise the\n // default card. Key on every id the kind answers to, not just the canonical\n // one — xyflow looks the renderer up by `node.type` BEFORE RegistryNode gets\n // a chance to resolve aliases, so a graph carrying a pre-namespace type would\n // otherwise fall through to the unknown-node placeholder.\n const renderer = k.component ?? RegistryNode;\n for (const id of kindIds(k)) map[id] = renderer;\n }\n return map;\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { checkGraphConnectivity } from './chunk-
|
|
2
|
-
import { resolveNodePorts, getNodeKind, defaultConfigFor, validateConfig } from './chunk-
|
|
1
|
+
import { checkGraphConnectivity } from './chunk-VAF2SWTC.js';
|
|
2
|
+
import { resolveNodePorts, getNodeKind, defaultConfigFor, validateConfig } from './chunk-EX34GCKK.js';
|
|
3
3
|
|
|
4
4
|
// src/schema/workflow-schema.ts
|
|
5
5
|
var WORKFLOW_SCHEMA_VERSION = 1;
|
|
@@ -168,5 +168,5 @@ function workflowToBlob(schema) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob };
|
|
171
|
-
//# sourceMappingURL=chunk-
|
|
172
|
-
//# sourceMappingURL=chunk-
|
|
171
|
+
//# sourceMappingURL=chunk-7DJ56PUA.js.map
|
|
172
|
+
//# sourceMappingURL=chunk-7DJ56PUA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schema/workflow-schema.ts"],"names":[],"mappings":"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","file":"chunk-RQP666FB.js","sourcesContent":["import type { FlowEdge, FlowGraph, FlowNode, PortDescriptor, WorkflowInput } from \"../types\";\nimport { checkGraphConnectivity } from \"../analysis/graph-connectivity\";\nimport { defaultConfigFor, getNodeKind, validateConfig } from \"../registry/registry\";\nimport { resolveNodePorts } from \"../registry/ports\";\n\n/** Schema version. Bump on breaking shape changes; add migrations as needed. */\nexport const WORKFLOW_SCHEMA_VERSION = 1 as const;\nexport const WORKFLOW_SCHEMA_URL = \"https://particle.academy/schemas/workflow/v1.json\";\n\nexport type WorkflowSchema = {\n $schema: typeof WORKFLOW_SCHEMA_URL;\n version: typeof WORKFLOW_SCHEMA_VERSION;\n metadata?: WorkflowMetadata;\n /**\n * What this workflow accepts at run start, passed BY NAME.\n *\n * Lives beside `graph` rather than inside it because it describes the\n * workflow's CONTRACT, not its topology: a caller reads this to know what to\n * pass, and reading it should not mean walking nodes looking for a trigger.\n *\n * Omitted entirely when a workflow takes none, so every existing saved\n * document is unchanged byte for byte.\n */\n inputs?: WorkflowInput[];\n graph: {\n nodes: WorkflowSchemaNode[];\n edges: WorkflowSchemaEdge[];\n };\n view?: {\n viewport?: { x: number; y: number; zoom: number };\n };\n};\n\nexport type WorkflowMetadata = {\n id?: string;\n name?: string;\n description?: string;\n createdAt?: number;\n updatedAt?: number;\n author?: string;\n tags?: string[];\n};\n\nexport type WorkflowSchemaNode = {\n id: string;\n /** Registry kind name (e.g. \"memory_store\"). */\n kind: string;\n position: { x: number; y: number };\n label?: string;\n description?: string;\n /**\n * Announced to a person just before / just after this node runs. Omitted\n * entirely when unset, so a graph of ordinary plumbing nodes does not carry\n * a pair of empty keys per node and every diff of a saved graph stays\n * readable.\n */\n startingMsg?: string;\n stoppingMsg?: string;\n config?: Record<string, unknown>;\n /**\n * Resolved ports, written on export.\n *\n * A kind may derive its ports from config (`switch_case` cases,\n * `llm_branch` routes), and that derivation is a JavaScript function — a\n * runtime in another language cannot execute it. Without the resolved ports\n * in the document, the PHP twin sees no declared outputs and falls back to a\n * single `out`, so every branch edge in an exported flow silently stops\n * firing. Serializing them keeps the schema self-describing and preserves\n * the cross-runtime guarantee: same JSON in, same routing out.\n *\n * Optional and additive — a hand-written schema may omit them, and each\n * runtime then falls back to its own kind registry.\n */\n inputs?: PortDescriptor[];\n outputs?: PortDescriptor[];\n /**\n * Node grouping and visual layout — additive + optional. `parentId`/`extent`\n * say which container a node sits in (swimlanes); `width`/`height` carry an\n * explicit (resized) size; `style` carries inline presentation.\n *\n * **`parentId` is NOT decoration, and a runtime that drops it is wrong.** It\n * decides which terminal a node talks to — a terminal lane owns one session\n * and membership is this field, deliberately, so there is no second copy of\n * the grouping in each node's config to drift from what the canvas shows.\n *\n * This comment used to say these fields exist \"purely for the canvas\", so a\n * runtime that only walks edges and ports could ignore all of them. The\n * Python twin read that literally and carried none of them: a graph loaded\n * there and saved back lost every grouping a person had drawn, silently. The\n * sentence was true when it was written and became false without being\n * revisited, which is the more common way a comment does harm.\n *\n * An older reader that does not know these fields still drops them — that is\n * forward-compatibility, not permission.\n */\n parentId?: string;\n extent?: \"parent\" | [[number, number], [number, number]];\n width?: number;\n height?: number;\n style?: Record<string, unknown>;\n};\n\nexport type WorkflowSchemaEdge = {\n id: string;\n source: string;\n target: string;\n sourceHandle?: string;\n targetHandle?: string;\n label?: string;\n};\n\nexport type ImportIssue = {\n level: \"error\" | \"warning\";\n nodeId?: string;\n edgeId?: string;\n message: string;\n};\n\nexport type ImportResult = {\n graph: FlowGraph;\n issues: ImportIssue[];\n /** True when the import produced a usable graph (errors may have been\n * rewritten to warnings via `lenient: true`, except an unsupported schema\n * version, which is always an error). */\n ok: boolean;\n};\n\n/** Snapshot the in-memory graph as a portable WorkflowSchema. */\nexport function exportWorkflow(\n graph: FlowGraph,\n metadata?: WorkflowMetadata,\n view?: WorkflowSchema[\"view\"],\n): WorkflowSchema {\n return {\n $schema: WORKFLOW_SCHEMA_URL,\n version: WORKFLOW_SCHEMA_VERSION,\n metadata: metadata ? { ...metadata, updatedAt: Date.now() } : undefined,\n // Round-trips, or a saved workflow silently forgets its own contract and\n // every caller's props become \"unknown input\" on the next load.\n ...(graph.inputs && graph.inputs.length > 0 ? { inputs: graph.inputs } : {}),\n graph: {\n nodes: graph.nodes.map(toSchemaNode),\n edges: graph.edges.map(toSchemaEdge),\n },\n view,\n };\n}\n\nfunction toSchemaNode(n: FlowNode): WorkflowSchemaNode {\n const data: any = n.data ?? {};\n const kindName = data.kind ?? n.type ?? \"custom\";\n // Resolve through the same helper the canvas and runtime use, so a\n // config-driven kind writes its ACTUAL ports into the document instead of\n // leaving another language's runtime to guess at them.\n const ports = resolveNodePorts(n, getNodeKind(kindName) ?? undefined);\n const node = n as any;\n return {\n id: n.id,\n kind: kindName,\n position: { x: n.position.x, y: n.position.y },\n label: data.label,\n description: data.description,\n ...(typeof data.startingMsg === \"string\" && data.startingMsg !== \"\" ? { startingMsg: data.startingMsg } : {}),\n ...(typeof data.stoppingMsg === \"string\" && data.stoppingMsg !== \"\" ? { stoppingMsg: data.stoppingMsg } : {}),\n config: data.config,\n inputs: ports.inputs,\n outputs: ports.outputs,\n // Visual layout — only when explicitly set (never persist auto-`measured`\n // dimensions, which are derived, not authored).\n ...(node.parentId ? { parentId: node.parentId } : {}),\n ...(node.extent ? { extent: node.extent } : {}),\n ...(typeof node.width === \"number\" ? { width: node.width } : {}),\n ...(typeof node.height === \"number\" ? { height: node.height } : {}),\n ...(node.style ? { style: node.style } : {}),\n };\n}\n\nfunction toSchemaEdge(e: FlowEdge): WorkflowSchemaEdge {\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle ?? undefined,\n targetHandle: e.targetHandle ?? undefined,\n label: typeof e.label === \"string\" ? e.label : undefined,\n };\n}\n\nexport type ImportOptions = {\n /** When true, unknown kinds become warnings + a \"custom\" placeholder\n * instead of errors. Default false. It never softens the schema version:\n * a document that is not `version: 1` (after migration) is refused either\n * way, because no runtime can honour a format it does not know. */\n lenient?: boolean;\n};\n\n/** A migration step: takes a version-N document to version N+1. */\nexport type MigrationStep = (schema: Record<string, unknown>) => Record<string, unknown>;\n\n/**\n * Every migration step, keyed by the version it upgrades FROM.\n *\n * Empty today because v1 is current — when a BREAKING bump lands, add the step\n * here and every stored document upgrades on read, in this runtime and its\n * PHP and Python twins.\n */\nexport const MIGRATIONS: Record<number, MigrationStep> = {};\n\n/**\n * Migrate a raw schema object up to the current version, as far as it can go.\n *\n * Additive changes need no migration — an older document simply lacks the newer\n * optional fields. This is the seam for a future BREAKING bump, and\n * `importWorkflow` runs it before validating the version.\n *\n * ## The three rules, each with a reason\n *\n * - **A PAST version migrates forward**, step by step, to the current one.\n * - **A FUTURE version is left ALONE.** We cannot know what a later schema\n * means, and migrating downward would be guessing. Untouched hands it to the\n * version check, which reports it honestly.\n * - **A GAP in the table is left alone too.** A missing step is not a licence\n * to guess.\n *\n * ## Why `steps` is a parameter\n *\n * With only v1 in existence there is no old document to migrate, so a seam\n * tested against the built-in (empty) table is a check that CANNOT fail — it\n * would pass identically against a function that returned its input untouched,\n * which is what this was until the twins needed the same seam.\n *\n * The PHP and Python runtimes carry the identical shape. That mattered more\n * than it sounds: they had no migration at all and errored on any version\n * mismatch, so a v2 bump would have made every stored Op unreadable on exactly\n * the runtimes where durable runs resume.\n */\nexport function migrateSchema(\n schema: unknown,\n steps: Record<number, MigrationStep> = MIGRATIONS,\n): unknown {\n if (!schema || typeof schema !== \"object\") return schema;\n\n const doc = schema as Record<string, unknown>;\n let version = doc.version;\n\n if (typeof version !== \"number\" || !Number.isInteger(version) || version >= WORKFLOW_SCHEMA_VERSION) {\n return schema;\n }\n\n let out = doc;\n while (version < WORKFLOW_SCHEMA_VERSION) {\n const step = steps[version];\n if (!step) return out;\n\n out = step(out);\n version += 1;\n out.version = version;\n }\n\n return out;\n}\n\n/**\n * Hydrate a schema into runtime FlowGraph + validate kinds/configs against\n * the registry. Reports issues for unknown kinds, missing required config,\n * and dangling edges.\n */\nexport function importWorkflow(schema: unknown, options: ImportOptions = {}): ImportResult {\n const issues: ImportIssue[] = [];\n const lenient = options.lenient === true;\n schema = migrateSchema(schema);\n\n if (!schema || typeof schema !== \"object\") {\n return { ok: false, graph: { nodes: [], edges: [] }, issues: [{ level: \"error\", message: \"Schema is not an object.\" }] };\n }\n const s = schema as Partial<WorkflowSchema>;\n // NEVER softened by `lenient`. That flag is about unknown VOCABULARY (a kind\n // this host has not registered); a version is the format itself, and a\n // runtime cannot honour a format it does not know. It used to become a\n // warning, and fancy-flow-php imports leniently on every run(), so one\n // versionless document ran in Laravel and was refused by a default (strict)\n // import here. Past versions were migrated above, so anything still unequal\n // is refused.\n if (s.version !== WORKFLOW_SCHEMA_VERSION) {\n issues.push({\n level: \"error\",\n message: `Unsupported workflow schema version: ${s.version} (expected ${WORKFLOW_SCHEMA_VERSION})`,\n });\n return { ok: false, graph: { nodes: [], edges: [] }, issues };\n }\n\n const rawNodes = s.graph?.nodes ?? [];\n const rawEdges = s.graph?.edges ?? [];\n\n const nodes: FlowNode[] = rawNodes.map((n) => {\n const kind = getNodeKind(n.kind);\n if (!kind) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n nodeId: n.id,\n message: `Unknown kind \"${n.kind}\" — register it before importing.`,\n });\n }\n const config = n.config ?? (kind ? defaultConfigFor(kind) : {});\n if (kind) {\n for (const iss of validateConfig(kind, config)) {\n issues.push({ level: \"warning\", nodeId: n.id, message: `${iss.key}: ${iss.message}` });\n }\n }\n // Canonicalise on the way in: a document may carry a pre-namespace id, and\n // rewriting it here means the graph converges on the canonical id the next\n // time it is saved, rather than carrying the ambiguous name forever.\n const kindId = kind?.name ?? n.kind;\n return {\n id: n.id,\n type: kindId,\n position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },\n // Rehydrate visual layout onto the node's top level (where xyflow reads it).\n ...(n.parentId ? { parentId: n.parentId } : {}),\n ...(n.extent ? { extent: n.extent } : {}),\n ...(typeof n.width === \"number\" ? { width: n.width } : {}),\n ...(typeof n.height === \"number\" ? { height: n.height } : {}),\n ...(n.style ? { style: n.style } : {}),\n data: {\n kind: kindId,\n label: n.label ?? kind?.label ?? n.kind,\n description: n.description,\n ...(n.startingMsg ? { startingMsg: n.startingMsg } : {}),\n ...(n.stoppingMsg ? { stoppingMsg: n.stoppingMsg } : {}),\n config,\n // Carry serialized ports back onto the node so a round-trip is stable\n // and an unknown kind still routes the way the document described.\n ...(n.inputs ? { inputs: n.inputs } : {}),\n ...(n.outputs ? { outputs: n.outputs } : {}),\n } as any,\n };\n });\n\n const nodeIds = new Set(nodes.map((n) => n.id));\n const edges: FlowEdge[] = rawEdges\n .map((e) => {\n if (!nodeIds.has(e.source)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge source \"${e.source}\" not found.` });\n return null;\n }\n if (!nodeIds.has(e.target)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge target \"${e.target}\" not found.` });\n return null;\n }\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle,\n targetHandle: e.targetHandle,\n label: e.label,\n } as FlowEdge;\n })\n .filter((e): e is FlowEdge => e !== null);\n\n // WIRING, not merely dataflow: a node no edge reaches and that reaches no\n // edge, and an edge reading from a node that publishes nothing.\n //\n // Deliberately AFTER the edge loop, so it sees the same edges the engine will\n // -- a dangling edge is dropped with a warning above, and running this first\n // would let a dropped edge count as a connection.\n //\n // Deliberately NOT gated on `lenient`. That flag is about unknown VOCABULARY\n // (a kind this host has not registered), never about wiring.\n issues.push(...checkGraphConnectivity({ nodes, edges }));\n\n const ok = issues.every((i) => i.level !== \"error\");\n // The declaration comes back with the graph. Dropping it on import would be\n // the worse half of losing it on export: the document still says what it\n // accepts, the loaded graph does not, and every prop a caller passes becomes\n // \"unknown input\" — an error pointing at the caller for a bug in the loader.\n const declaredInputs = Array.isArray(s.inputs)\n ? (s.inputs as WorkflowInput[]).filter(\n (input): input is WorkflowInput =>\n input !== null && typeof input === \"object\" && typeof (input as WorkflowInput).name === \"string\",\n )\n : undefined;\n\n return {\n ok,\n graph: {\n nodes,\n edges,\n ...(declaredInputs && declaredInputs.length > 0 ? { inputs: declaredInputs } : {}),\n },\n issues,\n };\n}\n\n/** Convenience: serialize a schema as a downloadable JSON Blob. */\nexport function workflowToBlob(schema: WorkflowSchema): Blob {\n return new Blob([JSON.stringify(schema, null, 2)], { type: \"application/json\" });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/schema/workflow-schema.ts"],"names":[],"mappings":";;;;AAMO,IAAM,uBAAA,GAA0B;AAChC,IAAM,mBAAA,GAAsB;AAyH5B,SAAS,cAAA,CACd,KAAA,EACA,QAAA,EACA,IAAA,EACgB;AAChB,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,mBAAA;AAAA,IACT,OAAA,EAAS,uBAAA;AAAA,IACT,QAAA,EAAU,WAAW,EAAE,GAAG,UAAU,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,EAAE,GAAI,MAAA;AAAA;AAAA;AAAA,IAG9D,GAAI,KAAA,CAAM,MAAA,IAAU,KAAA,CAAM,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,EAAE,MAAA,EAAQ,KAAA,CAAM,MAAA,EAAO,GAAI,EAAC;AAAA,IAC1E,KAAA,EAAO;AAAA,MACL,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,YAAY,CAAA;AAAA,MACnC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,YAAY;AAAA,KACrC;AAAA,IACA;AAAA,GACF;AACF;AAEA,SAAS,aAAa,CAAA,EAAiC;AACrD,EAAA,MAAM,IAAA,GAAY,CAAA,CAAE,IAAA,IAAQ,EAAC;AAC7B,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,IAAQ,CAAA,CAAE,IAAA,IAAQ,QAAA;AAIxC,EAAA,MAAM,QAAQ,gBAAA,CAAiB,CAAA,EAAG,WAAA,CAAY,QAAQ,KAAK,MAAS,CAAA;AACpE,EAAA,MAAM,IAAA,GAAO,CAAA;AACb,EAAA,OAAO;AAAA,IACL,IAAI,CAAA,CAAE,EAAA;AAAA,IACN,IAAA,EAAM,QAAA;AAAA,IACN,QAAA,EAAU,EAAE,CAAA,EAAG,CAAA,CAAE,SAAS,CAAA,EAAG,CAAA,EAAG,CAAA,CAAE,QAAA,CAAS,CAAA,EAAE;AAAA,IAC7C,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,aAAa,IAAA,CAAK,WAAA;AAAA,IAClB,GAAI,OAAO,IAAA,CAAK,WAAA,KAAgB,QAAA,IAAY,IAAA,CAAK,WAAA,KAAgB,EAAA,GAAK,EAAE,WAAA,EAAa,IAAA,CAAK,WAAA,KAAgB,EAAC;AAAA,IAC3G,GAAI,OAAO,IAAA,CAAK,WAAA,KAAgB,QAAA,IAAY,IAAA,CAAK,WAAA,KAAgB,EAAA,GAAK,EAAE,WAAA,EAAa,IAAA,CAAK,WAAA,KAAgB,EAAC;AAAA,IAC3G,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,SAAS,KAAA,CAAM,OAAA;AAAA;AAAA;AAAA,IAGf,GAAI,KAAK,QAAA,GAAW,EAAE,UAAU,IAAA,CAAK,QAAA,KAAa,EAAC;AAAA,IACnD,GAAI,KAAK,MAAA,GAAS,EAAE,QAAQ,IAAA,CAAK,MAAA,KAAW,EAAC;AAAA,IAC7C,GAAI,OAAO,IAAA,CAAK,KAAA,KAAU,QAAA,GAAW,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA,EAAM,GAAI,EAAC;AAAA,IAC9D,GAAI,OAAO,IAAA,CAAK,MAAA,KAAW,QAAA,GAAW,EAAE,MAAA,EAAQ,IAAA,CAAK,MAAA,EAAO,GAAI,EAAC;AAAA,IACjE,GAAI,KAAK,KAAA,GAAQ,EAAE,OAAO,IAAA,CAAK,KAAA,KAAU;AAAC,GAC5C;AACF;AAEA,SAAS,aAAa,CAAA,EAAiC;AACrD,EAAA,OAAO;AAAA,IACL,IAAI,CAAA,CAAE,EAAA;AAAA,IACN,QAAQ,CAAA,CAAE,MAAA;AAAA,IACV,QAAQ,CAAA,CAAE,MAAA;AAAA,IACV,YAAA,EAAc,EAAE,YAAA,IAAgB,MAAA;AAAA,IAChC,YAAA,EAAc,EAAE,YAAA,IAAgB,MAAA;AAAA,IAChC,OAAO,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,GAAW,EAAE,KAAA,GAAQ;AAAA,GACjD;AACF;AAoBO,IAAM,aAA4C,EAAC;AA8BnD,SAAS,aAAA,CACd,MAAA,EACA,KAAA,GAAuC,UAAA,EAC9B;AACT,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,UAAU,OAAO,MAAA;AAElD,EAAA,MAAM,GAAA,GAAM,MAAA;AACZ,EAAA,IAAI,UAAU,GAAA,CAAI,OAAA;AAElB,EAAA,IAAI,OAAO,YAAY,QAAA,IAAY,CAAC,OAAO,SAAA,CAAU,OAAO,CAAA,IAAK,OAAA,IAAW,uBAAA,EAAyB;AACnG,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI,GAAA,GAAM,GAAA;AACV,EAAA,OAAO,UAAU,uBAAA,EAAyB;AACxC,IAAA,MAAM,IAAA,GAAO,MAAM,OAAO,CAAA;AAC1B,IAAA,IAAI,CAAC,MAAM,OAAO,GAAA;AAElB,IAAA,GAAA,GAAM,KAAK,GAAG,CAAA;AACd,IAAA,OAAA,IAAW,CAAA;AACX,IAAA,GAAA,CAAI,OAAA,GAAU,OAAA;AAAA,EAChB;AAEA,EAAA,OAAO,GAAA;AACT;AAOO,SAAS,cAAA,CAAe,MAAA,EAAiB,OAAA,GAAyB,EAAC,EAAiB;AACzF,EAAA,MAAM,SAAwB,EAAC;AAC/B,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,KAAY,IAAA;AACpC,EAAA,MAAA,GAAS,cAAc,MAAM,CAAA;AAE7B,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,EAAU;AACzC,IAAA,OAAO,EAAE,IAAI,KAAA,EAAO,KAAA,EAAO,EAAE,KAAA,EAAO,IAAI,KAAA,EAAO,IAAG,EAAG,MAAA,EAAQ,CAAC,EAAE,KAAA,EAAO,SAAS,OAAA,EAAS,0BAAA,EAA4B,CAAA,EAAE;AAAA,EACzH;AACA,EAAA,MAAM,CAAA,GAAI,MAAA;AAQV,EAAA,IAAI,CAAA,CAAE,YAAY,uBAAA,EAAyB;AACzC,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,KAAA,EAAO,OAAA;AAAA,MACP,OAAA,EAAS,CAAA,qCAAA,EAAwC,CAAA,CAAE,OAAO,cAAc,uBAAuB,CAAA,CAAA;AAAA,KAChG,CAAA;AACD,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,EAAE,KAAA,EAAO,EAAC,EAAG,KAAA,EAAO,EAAC,EAAE,EAAG,MAAA,EAAO;AAAA,EAC9D;AAEA,EAAA,MAAM,QAAA,GAAW,CAAA,CAAE,KAAA,EAAO,KAAA,IAAS,EAAC;AACpC,EAAA,MAAM,QAAA,GAAW,CAAA,CAAE,KAAA,EAAO,KAAA,IAAS,EAAC;AAEpC,EAAA,MAAM,KAAA,GAAoB,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,KAAM;AAC5C,IAAA,MAAM,IAAA,GAAO,WAAA,CAAY,CAAA,CAAE,IAAI,CAAA;AAC/B,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,KAAA,EAAO,UAAU,SAAA,GAAY,OAAA;AAAA,QAC7B,QAAQ,CAAA,CAAE,EAAA;AAAA,QACV,OAAA,EAAS,CAAA,cAAA,EAAiB,CAAA,CAAE,IAAI,CAAA,sCAAA;AAAA,OACjC,CAAA;AAAA,IACH;AACA,IAAA,MAAM,SAAS,CAAA,CAAE,MAAA,KAAW,OAAO,gBAAA,CAAiB,IAAI,IAAI,EAAC,CAAA;AAC7D,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,KAAA,MAAW,GAAA,IAAO,cAAA,CAAe,IAAA,EAAM,MAAM,CAAA,EAAG;AAC9C,QAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,SAAA,EAAW,QAAQ,CAAA,CAAE,EAAA,EAAI,OAAA,EAAS,CAAA,EAAG,IAAI,GAAG,CAAA,EAAA,EAAK,GAAA,CAAI,OAAO,IAAI,CAAA;AAAA,MACvF;AAAA,IACF;AAIA,IAAA,MAAM,MAAA,GAAS,IAAA,EAAM,IAAA,IAAQ,CAAA,CAAE,IAAA;AAC/B,IAAA,OAAO;AAAA,MACL,IAAI,CAAA,CAAE,EAAA;AAAA,MACN,IAAA,EAAM,MAAA;AAAA,MACN,QAAA,EAAU,EAAE,CAAA,EAAG,CAAA,CAAE,QAAA,EAAU,CAAA,IAAK,CAAA,EAAG,CAAA,EAAG,CAAA,CAAE,QAAA,EAAU,CAAA,IAAK,CAAA,EAAE;AAAA;AAAA,MAEzD,GAAI,EAAE,QAAA,GAAW,EAAE,UAAU,CAAA,CAAE,QAAA,KAAa,EAAC;AAAA,MAC7C,GAAI,EAAE,MAAA,GAAS,EAAE,QAAQ,CAAA,CAAE,MAAA,KAAW,EAAC;AAAA,MACvC,GAAI,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,GAAW,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,EAAM,GAAI,EAAC;AAAA,MACxD,GAAI,OAAO,CAAA,CAAE,MAAA,KAAW,QAAA,GAAW,EAAE,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO,GAAI,EAAC;AAAA,MAC3D,GAAI,EAAE,KAAA,GAAQ,EAAE,OAAO,CAAA,CAAE,KAAA,KAAU,EAAC;AAAA,MACpC,IAAA,EAAM;AAAA,QACJ,IAAA,EAAM,MAAA;AAAA,QACN,KAAA,EAAO,CAAA,CAAE,KAAA,IAAS,IAAA,EAAM,SAAS,CAAA,CAAE,IAAA;AAAA,QACnC,aAAa,CAAA,CAAE,WAAA;AAAA,QACf,GAAI,EAAE,WAAA,GAAc,EAAE,aAAa,CAAA,CAAE,WAAA,KAAgB,EAAC;AAAA,QACtD,GAAI,EAAE,WAAA,GAAc,EAAE,aAAa,CAAA,CAAE,WAAA,KAAgB,EAAC;AAAA,QACtD,MAAA;AAAA;AAAA;AAAA,QAGA,GAAI,EAAE,MAAA,GAAS,EAAE,QAAQ,CAAA,CAAE,MAAA,KAAW,EAAC;AAAA,QACvC,GAAI,EAAE,OAAA,GAAU,EAAE,SAAS,CAAA,CAAE,OAAA,KAAY;AAAC;AAC5C,KACF;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,KAAA,CAAM,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,EAAE,CAAC,CAAA;AAC9C,EAAA,MAAM,KAAA,GAAoB,QAAA,CACvB,GAAA,CAAI,CAAC,CAAA,KAAM;AACV,IAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,MAAM,CAAA,EAAG;AAC1B,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,CAAA,CAAE,EAAA,EAAI,OAAA,EAAS,CAAA,aAAA,EAAgB,CAAA,CAAE,MAAM,CAAA,YAAA,CAAA,EAAgB,CAAA;AAC/F,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,MAAM,CAAA,EAAG;AAC1B,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,CAAA,CAAE,EAAA,EAAI,OAAA,EAAS,CAAA,aAAA,EAAgB,CAAA,CAAE,MAAM,CAAA,YAAA,CAAA,EAAgB,CAAA;AAC/F,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO;AAAA,MACL,IAAI,CAAA,CAAE,EAAA;AAAA,MACN,QAAQ,CAAA,CAAE,MAAA;AAAA,MACV,QAAQ,CAAA,CAAE,MAAA;AAAA,MACV,cAAc,CAAA,CAAE,YAAA;AAAA,MAChB,cAAc,CAAA,CAAE,YAAA;AAAA,MAChB,OAAO,CAAA,CAAE;AAAA,KACX;AAAA,EACF,CAAC,CAAA,CACA,MAAA,CAAO,CAAC,CAAA,KAAqB,MAAM,IAAI,CAAA;AAW1C,EAAA,MAAA,CAAO,KAAK,GAAG,sBAAA,CAAuB,EAAE,KAAA,EAAO,KAAA,EAAO,CAAC,CAAA;AAEvD,EAAA,MAAM,KAAK,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,OAAO,CAAA;AAKlD,EAAA,MAAM,iBAAiB,KAAA,CAAM,OAAA,CAAQ,EAAE,MAAM,CAAA,GACxC,EAAE,MAAA,CAA2B,MAAA;AAAA,IAC5B,CAAC,UACC,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,QAAA,IAAY,OAAQ,KAAA,CAAwB,IAAA,KAAS;AAAA,GAC5F,GACA,MAAA;AAEJ,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,KAAA,EAAO;AAAA,MACL,KAAA;AAAA,MACA,KAAA;AAAA,MACA,GAAI,kBAAkB,cAAA,CAAe,MAAA,GAAS,IAAI,EAAE,MAAA,EAAQ,cAAA,EAAe,GAAI;AAAC,KAClF;AAAA,IACA;AAAA,GACF;AACF;AAGO,SAAS,eAAe,MAAA,EAA8B;AAC3D,EAAA,OAAO,IAAI,IAAA,CAAK,CAAC,IAAA,CAAK,SAAA,CAAU,MAAA,EAAQ,IAAA,EAAM,CAAC,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,oBAAoB,CAAA;AACjF","file":"chunk-7DJ56PUA.js","sourcesContent":["import type { FlowEdge, FlowGraph, FlowNode, PortDescriptor, WorkflowInput } from \"../types\";\nimport { checkGraphConnectivity } from \"../analysis/graph-connectivity\";\nimport { defaultConfigFor, getNodeKind, validateConfig } from \"../registry/registry\";\nimport { resolveNodePorts } from \"../registry/ports\";\n\n/** Schema version. Bump on breaking shape changes; add migrations as needed. */\nexport const WORKFLOW_SCHEMA_VERSION = 1 as const;\nexport const WORKFLOW_SCHEMA_URL = \"https://particle.academy/schemas/workflow/v1.json\";\n\nexport type WorkflowSchema = {\n $schema: typeof WORKFLOW_SCHEMA_URL;\n version: typeof WORKFLOW_SCHEMA_VERSION;\n metadata?: WorkflowMetadata;\n /**\n * What this workflow accepts at run start, passed BY NAME.\n *\n * Lives beside `graph` rather than inside it because it describes the\n * workflow's CONTRACT, not its topology: a caller reads this to know what to\n * pass, and reading it should not mean walking nodes looking for a trigger.\n *\n * Omitted entirely when a workflow takes none, so every existing saved\n * document is unchanged byte for byte.\n */\n inputs?: WorkflowInput[];\n graph: {\n nodes: WorkflowSchemaNode[];\n edges: WorkflowSchemaEdge[];\n };\n view?: {\n viewport?: { x: number; y: number; zoom: number };\n };\n};\n\nexport type WorkflowMetadata = {\n id?: string;\n name?: string;\n description?: string;\n createdAt?: number;\n updatedAt?: number;\n author?: string;\n tags?: string[];\n};\n\nexport type WorkflowSchemaNode = {\n id: string;\n /** Registry kind name (e.g. \"memory_store\"). */\n kind: string;\n position: { x: number; y: number };\n label?: string;\n description?: string;\n /**\n * Announced to a person just before / just after this node runs. Omitted\n * entirely when unset, so a graph of ordinary plumbing nodes does not carry\n * a pair of empty keys per node and every diff of a saved graph stays\n * readable.\n */\n startingMsg?: string;\n stoppingMsg?: string;\n config?: Record<string, unknown>;\n /**\n * Resolved ports, written on export.\n *\n * A kind may derive its ports from config (`switch_case` cases,\n * `llm_branch` routes), and that derivation is a JavaScript function — a\n * runtime in another language cannot execute it. Without the resolved ports\n * in the document, the PHP twin sees no declared outputs and falls back to a\n * single `out`, so every branch edge in an exported flow silently stops\n * firing. Serializing them keeps the schema self-describing and preserves\n * the cross-runtime guarantee: same JSON in, same routing out.\n *\n * Optional and additive — a hand-written schema may omit them, and each\n * runtime then falls back to its own kind registry.\n */\n inputs?: PortDescriptor[];\n outputs?: PortDescriptor[];\n /**\n * Node grouping and visual layout — additive + optional. `parentId`/`extent`\n * say which container a node sits in (swimlanes); `width`/`height` carry an\n * explicit (resized) size; `style` carries inline presentation.\n *\n * **`parentId` is NOT decoration, and a runtime that drops it is wrong.** It\n * decides which terminal a node talks to — a terminal lane owns one session\n * and membership is this field, deliberately, so there is no second copy of\n * the grouping in each node's config to drift from what the canvas shows.\n *\n * This comment used to say these fields exist \"purely for the canvas\", so a\n * runtime that only walks edges and ports could ignore all of them. The\n * Python twin read that literally and carried none of them: a graph loaded\n * there and saved back lost every grouping a person had drawn, silently. The\n * sentence was true when it was written and became false without being\n * revisited, which is the more common way a comment does harm.\n *\n * An older reader that does not know these fields still drops them — that is\n * forward-compatibility, not permission.\n */\n parentId?: string;\n extent?: \"parent\" | [[number, number], [number, number]];\n width?: number;\n height?: number;\n style?: Record<string, unknown>;\n};\n\nexport type WorkflowSchemaEdge = {\n id: string;\n source: string;\n target: string;\n sourceHandle?: string;\n targetHandle?: string;\n label?: string;\n};\n\nexport type ImportIssue = {\n level: \"error\" | \"warning\";\n nodeId?: string;\n edgeId?: string;\n message: string;\n};\n\nexport type ImportResult = {\n graph: FlowGraph;\n issues: ImportIssue[];\n /** True when the import produced a usable graph (errors may have been\n * rewritten to warnings via `lenient: true`, except an unsupported schema\n * version, which is always an error). */\n ok: boolean;\n};\n\n/** Snapshot the in-memory graph as a portable WorkflowSchema. */\nexport function exportWorkflow(\n graph: FlowGraph,\n metadata?: WorkflowMetadata,\n view?: WorkflowSchema[\"view\"],\n): WorkflowSchema {\n return {\n $schema: WORKFLOW_SCHEMA_URL,\n version: WORKFLOW_SCHEMA_VERSION,\n metadata: metadata ? { ...metadata, updatedAt: Date.now() } : undefined,\n // Round-trips, or a saved workflow silently forgets its own contract and\n // every caller's props become \"unknown input\" on the next load.\n ...(graph.inputs && graph.inputs.length > 0 ? { inputs: graph.inputs } : {}),\n graph: {\n nodes: graph.nodes.map(toSchemaNode),\n edges: graph.edges.map(toSchemaEdge),\n },\n view,\n };\n}\n\nfunction toSchemaNode(n: FlowNode): WorkflowSchemaNode {\n const data: any = n.data ?? {};\n const kindName = data.kind ?? n.type ?? \"custom\";\n // Resolve through the same helper the canvas and runtime use, so a\n // config-driven kind writes its ACTUAL ports into the document instead of\n // leaving another language's runtime to guess at them.\n const ports = resolveNodePorts(n, getNodeKind(kindName) ?? undefined);\n const node = n as any;\n return {\n id: n.id,\n kind: kindName,\n position: { x: n.position.x, y: n.position.y },\n label: data.label,\n description: data.description,\n ...(typeof data.startingMsg === \"string\" && data.startingMsg !== \"\" ? { startingMsg: data.startingMsg } : {}),\n ...(typeof data.stoppingMsg === \"string\" && data.stoppingMsg !== \"\" ? { stoppingMsg: data.stoppingMsg } : {}),\n config: data.config,\n inputs: ports.inputs,\n outputs: ports.outputs,\n // Visual layout — only when explicitly set (never persist auto-`measured`\n // dimensions, which are derived, not authored).\n ...(node.parentId ? { parentId: node.parentId } : {}),\n ...(node.extent ? { extent: node.extent } : {}),\n ...(typeof node.width === \"number\" ? { width: node.width } : {}),\n ...(typeof node.height === \"number\" ? { height: node.height } : {}),\n ...(node.style ? { style: node.style } : {}),\n };\n}\n\nfunction toSchemaEdge(e: FlowEdge): WorkflowSchemaEdge {\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle ?? undefined,\n targetHandle: e.targetHandle ?? undefined,\n label: typeof e.label === \"string\" ? e.label : undefined,\n };\n}\n\nexport type ImportOptions = {\n /** When true, unknown kinds become warnings + a \"custom\" placeholder\n * instead of errors. Default false. It never softens the schema version:\n * a document that is not `version: 1` (after migration) is refused either\n * way, because no runtime can honour a format it does not know. */\n lenient?: boolean;\n};\n\n/** A migration step: takes a version-N document to version N+1. */\nexport type MigrationStep = (schema: Record<string, unknown>) => Record<string, unknown>;\n\n/**\n * Every migration step, keyed by the version it upgrades FROM.\n *\n * Empty today because v1 is current — when a BREAKING bump lands, add the step\n * here and every stored document upgrades on read, in this runtime and its\n * PHP and Python twins.\n */\nexport const MIGRATIONS: Record<number, MigrationStep> = {};\n\n/**\n * Migrate a raw schema object up to the current version, as far as it can go.\n *\n * Additive changes need no migration — an older document simply lacks the newer\n * optional fields. This is the seam for a future BREAKING bump, and\n * `importWorkflow` runs it before validating the version.\n *\n * ## The three rules, each with a reason\n *\n * - **A PAST version migrates forward**, step by step, to the current one.\n * - **A FUTURE version is left ALONE.** We cannot know what a later schema\n * means, and migrating downward would be guessing. Untouched hands it to the\n * version check, which reports it honestly.\n * - **A GAP in the table is left alone too.** A missing step is not a licence\n * to guess.\n *\n * ## Why `steps` is a parameter\n *\n * With only v1 in existence there is no old document to migrate, so a seam\n * tested against the built-in (empty) table is a check that CANNOT fail — it\n * would pass identically against a function that returned its input untouched,\n * which is what this was until the twins needed the same seam.\n *\n * The PHP and Python runtimes carry the identical shape. That mattered more\n * than it sounds: they had no migration at all and errored on any version\n * mismatch, so a v2 bump would have made every stored Op unreadable on exactly\n * the runtimes where durable runs resume.\n */\nexport function migrateSchema(\n schema: unknown,\n steps: Record<number, MigrationStep> = MIGRATIONS,\n): unknown {\n if (!schema || typeof schema !== \"object\") return schema;\n\n const doc = schema as Record<string, unknown>;\n let version = doc.version;\n\n if (typeof version !== \"number\" || !Number.isInteger(version) || version >= WORKFLOW_SCHEMA_VERSION) {\n return schema;\n }\n\n let out = doc;\n while (version < WORKFLOW_SCHEMA_VERSION) {\n const step = steps[version];\n if (!step) return out;\n\n out = step(out);\n version += 1;\n out.version = version;\n }\n\n return out;\n}\n\n/**\n * Hydrate a schema into runtime FlowGraph + validate kinds/configs against\n * the registry. Reports issues for unknown kinds, missing required config,\n * and dangling edges.\n */\nexport function importWorkflow(schema: unknown, options: ImportOptions = {}): ImportResult {\n const issues: ImportIssue[] = [];\n const lenient = options.lenient === true;\n schema = migrateSchema(schema);\n\n if (!schema || typeof schema !== \"object\") {\n return { ok: false, graph: { nodes: [], edges: [] }, issues: [{ level: \"error\", message: \"Schema is not an object.\" }] };\n }\n const s = schema as Partial<WorkflowSchema>;\n // NEVER softened by `lenient`. That flag is about unknown VOCABULARY (a kind\n // this host has not registered); a version is the format itself, and a\n // runtime cannot honour a format it does not know. It used to become a\n // warning, and fancy-flow-php imports leniently on every run(), so one\n // versionless document ran in Laravel and was refused by a default (strict)\n // import here. Past versions were migrated above, so anything still unequal\n // is refused.\n if (s.version !== WORKFLOW_SCHEMA_VERSION) {\n issues.push({\n level: \"error\",\n message: `Unsupported workflow schema version: ${s.version} (expected ${WORKFLOW_SCHEMA_VERSION})`,\n });\n return { ok: false, graph: { nodes: [], edges: [] }, issues };\n }\n\n const rawNodes = s.graph?.nodes ?? [];\n const rawEdges = s.graph?.edges ?? [];\n\n const nodes: FlowNode[] = rawNodes.map((n) => {\n const kind = getNodeKind(n.kind);\n if (!kind) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n nodeId: n.id,\n message: `Unknown kind \"${n.kind}\" — register it before importing.`,\n });\n }\n const config = n.config ?? (kind ? defaultConfigFor(kind) : {});\n if (kind) {\n for (const iss of validateConfig(kind, config)) {\n issues.push({ level: \"warning\", nodeId: n.id, message: `${iss.key}: ${iss.message}` });\n }\n }\n // Canonicalise on the way in: a document may carry a pre-namespace id, and\n // rewriting it here means the graph converges on the canonical id the next\n // time it is saved, rather than carrying the ambiguous name forever.\n const kindId = kind?.name ?? n.kind;\n return {\n id: n.id,\n type: kindId,\n position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },\n // Rehydrate visual layout onto the node's top level (where xyflow reads it).\n ...(n.parentId ? { parentId: n.parentId } : {}),\n ...(n.extent ? { extent: n.extent } : {}),\n ...(typeof n.width === \"number\" ? { width: n.width } : {}),\n ...(typeof n.height === \"number\" ? { height: n.height } : {}),\n ...(n.style ? { style: n.style } : {}),\n data: {\n kind: kindId,\n label: n.label ?? kind?.label ?? n.kind,\n description: n.description,\n ...(n.startingMsg ? { startingMsg: n.startingMsg } : {}),\n ...(n.stoppingMsg ? { stoppingMsg: n.stoppingMsg } : {}),\n config,\n // Carry serialized ports back onto the node so a round-trip is stable\n // and an unknown kind still routes the way the document described.\n ...(n.inputs ? { inputs: n.inputs } : {}),\n ...(n.outputs ? { outputs: n.outputs } : {}),\n } as any,\n };\n });\n\n const nodeIds = new Set(nodes.map((n) => n.id));\n const edges: FlowEdge[] = rawEdges\n .map((e) => {\n if (!nodeIds.has(e.source)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge source \"${e.source}\" not found.` });\n return null;\n }\n if (!nodeIds.has(e.target)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge target \"${e.target}\" not found.` });\n return null;\n }\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle,\n targetHandle: e.targetHandle,\n label: e.label,\n } as FlowEdge;\n })\n .filter((e): e is FlowEdge => e !== null);\n\n // WIRING, not merely dataflow: a node no edge reaches and that reaches no\n // edge, and an edge reading from a node that publishes nothing.\n //\n // Deliberately AFTER the edge loop, so it sees the same edges the engine will\n // -- a dangling edge is dropped with a warning above, and running this first\n // would let a dropped edge count as a connection.\n //\n // Deliberately NOT gated on `lenient`. That flag is about unknown VOCABULARY\n // (a kind this host has not registered), never about wiring.\n issues.push(...checkGraphConnectivity({ nodes, edges }));\n\n const ok = issues.every((i) => i.level !== \"error\");\n // The declaration comes back with the graph. Dropping it on import would be\n // the worse half of losing it on export: the document still says what it\n // accepts, the loaded graph does not, and every prop a caller passes becomes\n // \"unknown input\" — an error pointing at the caller for a bug in the loader.\n const declaredInputs = Array.isArray(s.inputs)\n ? (s.inputs as WorkflowInput[]).filter(\n (input): input is WorkflowInput =>\n input !== null && typeof input === \"object\" && typeof (input as WorkflowInput).name === \"string\",\n )\n : undefined;\n\n return {\n ok,\n graph: {\n nodes,\n edges,\n ...(declaredInputs && declaredInputs.length > 0 ? { inputs: declaredInputs } : {}),\n },\n issues,\n };\n}\n\n/** Convenience: serialize a schema as a downloadable JSON Blob. */\nexport function workflowToBlob(schema: WorkflowSchema): Blob {\n return new Blob([JSON.stringify(schema, null, 2)], { type: \"application/json\" });\n}\n"]}
|
|
@@ -634,7 +634,7 @@ function resolveWorkflowProps(declared, passed) {
|
|
|
634
634
|
// src/runtime/run-flow.ts
|
|
635
635
|
async function runFlow(graph, executors, onEvent = () => {
|
|
636
636
|
}, options = {}) {
|
|
637
|
-
const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {}, entryNodes } = options;
|
|
637
|
+
const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {}, entryNodes, nodeExecutors } = options;
|
|
638
638
|
const run = options.run === void 0 ? void 0 : RunIdentity.from(options.run);
|
|
639
639
|
const outputs = {};
|
|
640
640
|
const portValues = /* @__PURE__ */ new Map();
|
|
@@ -722,7 +722,7 @@ async function runFlow(graph, executors, onEvent = () => {
|
|
|
722
722
|
onEvent({ type: "node-status", nodeId: node.id, status: "running" });
|
|
723
723
|
announce(onEvent, node, "start");
|
|
724
724
|
const inputs = collectInputs(node, incoming, portValues, initialInputs, props, declaresProps);
|
|
725
|
-
const exec = pickExecutor(executors, node);
|
|
725
|
+
const exec = pickExecutor(executors, node, nodeExecutors);
|
|
726
726
|
if (!exec) {
|
|
727
727
|
const tried = executorLookupIds(node);
|
|
728
728
|
const msg = `No executor registered for kind=${node.type} \u2014 tried ${tried.map((id) => `"${id}"`).join(", ")}. Key your registry by one of those.`;
|
|
@@ -842,7 +842,9 @@ function executorLookupIds(node) {
|
|
|
842
842
|
ids.push("*");
|
|
843
843
|
return [...new Set(ids)];
|
|
844
844
|
}
|
|
845
|
-
function pickExecutor(executors, node) {
|
|
845
|
+
function pickExecutor(executors, node, nodeExecutors) {
|
|
846
|
+
const pinned = nodeExecutors && Object.prototype.hasOwnProperty.call(nodeExecutors, node.id) ? nodeExecutors[node.id] : void 0;
|
|
847
|
+
if (pinned) return pinned;
|
|
846
848
|
for (const id of executorLookupIds(node)) {
|
|
847
849
|
if (executors[id]) return executors[id];
|
|
848
850
|
}
|
|
@@ -2839,5 +2841,5 @@ function describeExpressionGrammar() {
|
|
|
2839
2841
|
}
|
|
2840
2842
|
|
|
2841
2843
|
export { BUILTIN_KIND_DATA, DEFAULT_MAX_DEPTH, RunIdentity, UnresolvedPathError, applyKindSchemaOverlay, availableVariables, baseVariables, categoryAccent, clearKindSchemaOverlays, clearNodeKindOverrides, declaredRoutes, defaultConfigFor, describeExpressionGrammar, ensureBuiltinKinds, escapeSegment, evaluateConfig, evaluateExpression, getNodeKind, humanInputFields, kindIds, listNodeKinds, llmRouterExecutor, nodeConfig, onNodeKindsChanged, outputFieldsFor, overrideNodeKind, registerBuiltinKindInternal, registerNodeKind, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePath, resolvePortSpec, runFlow, subflowExecutor, subflowMode, subflowPorts, text, truthy, tryResolvePath, undeliveredEdgeWarnings, unregisterNodeKind, validateConfig };
|
|
2842
|
-
//# sourceMappingURL=chunk-
|
|
2843
|
-
//# sourceMappingURL=chunk-
|
|
2844
|
+
//# sourceMappingURL=chunk-EX34GCKK.js.map
|
|
2845
|
+
//# sourceMappingURL=chunk-EX34GCKK.js.map
|