@particle-academy/fancy-flow 0.59.0 → 0.64.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.
Files changed (66) hide show
  1. package/dist/{HumanPrompt-Bw09OyS3.d.ts → HumanPrompt-BKThiyjB.d.ts} +1 -1
  2. package/dist/{HumanPrompt-DV5-xnDa.d.cts → HumanPrompt-Bpp3JiVH.d.cts} +1 -1
  3. package/dist/{chunk-M7M6GLFV.js → chunk-5H54OTKT.js} +3 -3
  4. package/dist/{chunk-M7M6GLFV.js.map → chunk-5H54OTKT.js.map} +1 -1
  5. package/dist/chunk-77V4QC6Y.js +54 -0
  6. package/dist/chunk-77V4QC6Y.js.map +1 -0
  7. package/dist/{chunk-MX4SJDNI.js → chunk-EO6444T2.js} +4 -4
  8. package/dist/{chunk-MX4SJDNI.js.map → chunk-EO6444T2.js.map} +1 -1
  9. package/dist/{chunk-HBE4LVPX.js → chunk-JF6WCRBU.js} +9 -7
  10. package/dist/chunk-JF6WCRBU.js.map +1 -0
  11. package/dist/{chunk-LGLOBK6Q.js → chunk-MBVX4ZRB.js} +3 -3
  12. package/dist/{chunk-LGLOBK6Q.js.map → chunk-MBVX4ZRB.js.map} +1 -1
  13. package/dist/{chunk-A3TLZS5B.js → chunk-RIAFHQT5.js} +3 -3
  14. package/dist/{chunk-A3TLZS5B.js.map → chunk-RIAFHQT5.js.map} +1 -1
  15. package/dist/{chunk-BHM5HGXH.js → chunk-UM4C46AF.js} +38 -14
  16. package/dist/chunk-UM4C46AF.js.map +1 -0
  17. package/dist/{chunk-FGYG5JVW.js → chunk-W5DPKJY4.js} +5 -3
  18. package/dist/chunk-W5DPKJY4.js.map +1 -0
  19. package/dist/connectors.d.cts +1 -1
  20. package/dist/connectors.d.ts +1 -1
  21. package/dist/durable.cjs +7 -5
  22. package/dist/durable.cjs.map +1 -1
  23. package/dist/durable.js +2 -2
  24. package/dist/engine.cjs +189 -29
  25. package/dist/engine.cjs.map +1 -1
  26. package/dist/engine.d.cts +67 -5
  27. package/dist/engine.d.ts +67 -5
  28. package/dist/engine.js +7 -5
  29. package/dist/engine.js.map +1 -1
  30. package/dist/expr-BfXDrs4H.d.cts +149 -0
  31. package/dist/expr-BfXDrs4H.d.ts +149 -0
  32. package/dist/fields/react-fancy.d.cts +2 -2
  33. package/dist/fields/react-fancy.d.ts +2 -2
  34. package/dist/index.cjs +94 -16
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +7 -84
  37. package/dist/index.d.ts +7 -84
  38. package/dist/index.js +13 -12
  39. package/dist/index.js.map +1 -1
  40. package/dist/registry/index.d.cts +3 -3
  41. package/dist/registry/index.d.ts +3 -3
  42. package/dist/{registry-1qhmRtvC.d.cts → registry-CntAdUcY.d.cts} +1 -1
  43. package/dist/{registry-D2qocEsY.d.ts → registry-DYoV-MQi.d.ts} +1 -1
  44. package/dist/registry.cjs +7 -5
  45. package/dist/registry.cjs.map +1 -1
  46. package/dist/registry.js +2 -2
  47. package/dist/runtime.cjs +7 -5
  48. package/dist/runtime.cjs.map +1 -1
  49. package/dist/runtime.js +3 -3
  50. package/dist/schema.cjs +57 -5
  51. package/dist/schema.cjs.map +1 -1
  52. package/dist/schema.js +3 -2
  53. package/dist/screens.cjs +7 -5
  54. package/dist/screens.cjs.map +1 -1
  55. package/dist/screens.js +4 -4
  56. package/dist/{types-CLSUZW8c.d.ts → types-Ckhz-YwC.d.ts} +7 -2
  57. package/dist/{types-BLVpNNLN.d.cts → types-D71SKA5A.d.cts} +7 -2
  58. package/dist/ux.cjs +7 -5
  59. package/dist/ux.cjs.map +1 -1
  60. package/dist/ux.d.cts +1 -1
  61. package/dist/ux.d.ts +1 -1
  62. package/dist/ux.js +1 -1
  63. package/package.json +2 -2
  64. package/dist/chunk-BHM5HGXH.js.map +0 -1
  65. package/dist/chunk-FGYG5JVW.js.map +0 -1
  66. package/dist/chunk-HBE4LVPX.js.map +0 -1
package/dist/ux.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { EffectRegistry, DispatchActor } from '@particle-academy/fancy-auto-common';
2
2
  export { AutoActivityEvent } from '@particle-academy/fancy-auto-common';
3
- import { a as NodeCategory, C as ConfigField } from './types-BLVpNNLN.cjs';
3
+ import { a as NodeCategory, C as ConfigField } from './types-D71SKA5A.cjs';
4
4
  import { E as ExecutorRegistry } from './types-JFYjPJAG.cjs';
5
5
  import 'react';
6
6
  import '@xyflow/react';
package/dist/ux.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { EffectRegistry, DispatchActor } from '@particle-academy/fancy-auto-common';
2
2
  export { AutoActivityEvent } from '@particle-academy/fancy-auto-common';
3
- import { a as NodeCategory, C as ConfigField } from './types-CLSUZW8c.js';
3
+ import { a as NodeCategory, C as ConfigField } from './types-Ckhz-YwC.js';
4
4
  import { E as ExecutorRegistry } from './types-JFYjPJAG.js';
5
5
  import 'react';
6
6
  import '@xyflow/react';
package/dist/ux.js CHANGED
@@ -1,4 +1,4 @@
1
- import { registerNodeKind } from './chunk-HBE4LVPX.js';
1
+ import { registerNodeKind } from './chunk-JF6WCRBU.js';
2
2
  import './chunk-F5RPRB7A.js';
3
3
  import './chunk-USL4FMFU.js';
4
4
  import { useMemo } from 'react';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/fancy-flow",
3
- "version": "0.59.0",
3
+ "version": "0.64.0",
4
4
  "description": "Workflow editor + runner. Six built-in node kits (trigger / action / decision / output / note / subgraph), tokenized theme, topological execution with per-node status. React-flow bundled; consumers npm install fancy-flow and get nothing extra.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -190,7 +190,7 @@
190
190
  "devDependencies": {
191
191
  "@dagrejs/dagre": "^3.0.0",
192
192
  "@particle-academy/fancy-cms-ui": "^0.2.0",
193
- "@particle-academy/fancy-conformance": "^0.19.0",
193
+ "@particle-academy/fancy-conformance": "^0.20.0",
194
194
  "@particle-academy/fancy-query": "^0.8.0",
195
195
  "@particle-academy/fancy-screens": "^0.4.3",
196
196
  "@particle-academy/react-fancy": "^5.17.1",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/expressions/expr.ts"],"names":[],"mappings":"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","file":"chunk-BHM5HGXH.js","sourcesContent":["/**\n * `{{ }}` resolution — the TypeScript twin of `FancyFlow\\Nodes\\Support\\Expr`.\n *\n * ## Why this is opt-in and not wired into `runFlow`\n *\n * The PHP runtime resolves expressions inside its batteries-included executors.\n * The JS runtime never has: `runFlow` hands `node.data.config` to your executor\n * verbatim, so every host that uses `{{ }}` today already resolves it itself.\n * Turning resolution on inside `runFlow` would interpolate a second time over\n * values those hosts had already substituted — silently, and only for graphs\n * that happen to contain `{{` in their DATA.\n *\n * So this exports the semantics and changes no behaviour. Call it yourself:\n *\n * ```ts\n * const url = evaluateExpression(node.data.config.url, inputs);\n * ```\n *\n * ## The semantics are the PHP file's, deliberately\n *\n * This is not a general expression language and must not grow into one. It\n * resolves a dot-path against a context and nothing else — no arithmetic, no\n * comparisons, no calls. Hosts that want real expressions override the executor\n * (the PHP docblock points at symfony/expression-language for the same reason).\n *\n * Divergence here is a correctness bug rather than a style difference: the same\n * graph is authored once and may run on either runtime. `suites/shared/expr`\n * in `@particle-academy/fancy-conformance` is the fixture table both sides run,\n * so parity is a test result instead of a claim.\n */\n\n/** Anything a context or a resolved value can be. */\nexport type ExprValue = unknown;\n\n/** The context an expression resolves against — the executor's `inputs`. */\nexport type ExprContext = Record<string, ExprValue>;\n\n/**\n * `{{ … }}` is parsed by SCANNING, not by a regular expression.\n *\n * Two CodeQL `js/polynomial-redos` alerts (#2, #3) came out of the regex\n * version, and the second survived the obvious fix — which is the useful part.\n * Dropping the ambiguous `\\s*` around the capture killed one witness\n * (`'{{{{' + ' '.repeat(n)`) and CodeQL immediately produced another\n * (`'{{{{a'` repeated). That is not a pattern bug: a GLOBAL lazy scan for a\n * delimiter that never arrives is quadratic by construction — O(n) starts, each\n * scanning O(n) forward — and no amount of tuning the pattern removes it.\n *\n * `indexOf` has no backtracking at all. Each character is visited a bounded\n * number of times, so this is linear by construction rather than by careful\n * pattern-writing, and there is no next witness to find.\n *\n * The behaviour is deliberately identical to the regexes it replaces, including\n * the odd corner: `{{a}}{{b}}` is a WHOLE expression whose path is `a}}{{b`\n * (which resolves to null), because the old pattern was `$`-anchored and its\n * lazy capture had to grow to reach the end. The PHP twin does the same, and\n * `shared/expr` is what holds the two together.\n */\n\n/** The inner text of a template that is exactly one expression, else `null`. */\nfunction wholeExpression(trimmed: string): string | null {\n if (trimmed.length < 4) return null;\n if (!trimmed.startsWith(\"{{\") || !trimmed.endsWith(\"}}\")) return null;\n return trimmed.slice(2, -2);\n}\n\n/**\n * Replace every `{{ … }}` run, left to right, in a single pass.\n *\n * An unterminated `{{` is literal text — the same thing the regex did by simply\n * not matching, and the case an author hits constantly while typing.\n */\nfunction interpolate(template: string, resolve: (path: string) => string): string {\n let out = \"\";\n let i = 0;\n\n for (;;) {\n const open = template.indexOf(\"{{\", i);\n if (open === -1) return out + template.slice(i);\n\n const close = template.indexOf(\"}}\", open + 2);\n if (close === -1) return out + template.slice(i);\n\n out += template.slice(i, open) + resolve(template.slice(open + 2, close));\n i = close + 2;\n }\n}\n\n/**\n * Strings PHP's `Expr::truthy` treats as false.\n *\n * `\"0\"` and `\"false\"` are the ones that matter: a branch condition arriving as\n * text from a form or a JSON body would otherwise be truthy for every non-empty\n * string, and `\"false\"` taking the true branch is the kind of bug that looks\n * like the engine is broken.\n */\nconst FALSY_STRINGS = new Set([\"\", \"0\", \"false\", \"no\", \"off\", \"null\"]);\n\n/**\n * Resolve a dot-path against the context, honouring the `$json` / `$input`\n * alias.\n *\n * Both aliases point at the `in` port value when the context has one, and at\n * the whole context otherwise — the same fallback the PHP does, which is what\n * makes `{{ $json.x }}` work on a trigger node that has no upstream input.\n *\n * A path that does not resolve returns `null`, never `undefined`: PHP has one\n * absent value and JS has two, and letting the difference leak would make the\n * two runtimes disagree about `{{ missing }}` for no useful reason.\n */\nexport function resolvePath(path: string, context: ExprContext): ExprValue {\n const trimmed = path.trim();\n if (trimmed === \"\") return null;\n\n const segments = trimmed.split(\".\");\n let cursor: ExprValue;\n\n const head = segments[0];\n if (head === \"$json\" || head === \"$input\") {\n cursor = context !== null && typeof context === \"object\" && \"in\" in context ? context.in : context;\n segments.shift();\n } else {\n cursor = context;\n }\n\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return null;\n if (typeof cursor !== \"object\") return null;\n // Arrays are indexed by their numeric keys, matching PHP's list access.\n const next = (cursor as Record<string, ExprValue>)[segment];\n if (next === undefined) return null;\n cursor = next;\n }\n\n return cursor === undefined ? null : cursor;\n}\n\n/**\n * Evaluate a template against a context.\n *\n * A string that is EXACTLY one expression returns the resolved value with its\n * type intact — `{{ $json.count }}` gives you a number, not `\"3\"`. Anything\n * else interpolates each run as text. That distinction is load-bearing: it is\n * what lets a config field carry either a value or a sentence.\n *\n * Non-string templates pass through untouched, so this is safe to map over a\n * whole config object.\n */\nexport function evaluateExpression(template: ExprValue, context: ExprContext): ExprValue {\n if (typeof template !== \"string\") return template;\n\n const whole = wholeExpression(template.trim());\n if (whole !== null) return resolvePath(whole, context);\n\n return interpolate(template, (path) => stringify(resolvePath(path, context)));\n}\n\n/**\n * Truthiness for branch / switch decisions.\n *\n * Mirrors PHP's rules rather than JavaScript's, because the graph is authored\n * once and may run on either side. The two disagree in exactly the places a\n * workflow hits: `\"0\"` and `\"false\"` are truthy in JS and falsy here, and an\n * empty array is truthy in JS and falsy here.\n */\nexport function truthy(value: ExprValue): boolean {\n if (typeof value === \"boolean\") return value;\n if (value === null || value === undefined) return false;\n if (typeof value === \"string\") return !FALSY_STRINGS.has(value.trim().toLowerCase());\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === \"number\") return value !== 0;\n return Boolean(value);\n}\n\n/** Coerce a value to text the way interpolation does. */\nexport function text(value: ExprValue): string {\n return stringify(value);\n}\n\nfunction stringify(value: ExprValue): string {\n if (typeof value === \"string\") return value;\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"number\" || typeof value === \"bigint\") return String(value);\n try {\n return JSON.stringify(value) ?? \"\";\n } catch {\n return \"\";\n }\n}\n\n/**\n * Resolve every string in a config object, one level of nesting at a time.\n *\n * The convenience most hosts actually want, and the shape their hand-rolled\n * version usually takes. Opt-in like the rest of this module.\n */\nexport function evaluateConfig<T extends Record<string, ExprValue>>(config: T, context: ExprContext): T {\n const out: Record<string, ExprValue> = {};\n for (const [key, value] of Object.entries(config)) {\n out[key] = Array.isArray(value)\n ? value.map((v) => evaluateExpression(v, context))\n : value !== null && typeof value === \"object\"\n ? evaluateConfig(value as Record<string, ExprValue>, context)\n : evaluateExpression(value, context);\n }\n return out as T;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schema/workflow-schema.ts"],"names":[],"mappings":"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","file":"chunk-FGYG5JVW.js","sourcesContent":["import type { FlowEdge, FlowGraph, FlowNode, PortDescriptor, WorkflowInput } from \"../types\";\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 * Visual layout — additive + optional. `parentId`/`extent` carry node grouping\n * (swimlanes / containers); `width`/`height` carry an explicit (resized) size;\n * `style` carries inline presentation. A runtime that only walks edges/ports\n * (e.g. the PHP twin) ignores all of these — they exist purely for the canvas,\n * so an older reader that doesn't know them simply drops them.\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`). */\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. */\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 if (s.version !== WORKFLOW_SCHEMA_VERSION) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n message: `Unsupported workflow schema version: ${s.version} (expected ${WORKFLOW_SCHEMA_VERSION})`,\n });\n if (!lenient) 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 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"]}