@synergenius/flow-weaver 0.33.1 → 0.44.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/LICENSE +201 -226
- package/NOTICE +13 -0
- package/README.md +152 -244
- package/dist/agent/agent-loop.d.ts +4 -2
- package/dist/agent/cli-session.d.ts +3 -3
- package/dist/agent/cli-session.js +511 -438
- package/dist/agent/cli-spawn-config.d.ts +4 -4
- package/dist/agent/gate.d.ts +164 -0
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +174 -28
- package/dist/agent/mcp-bridge.d.ts +1 -1
- package/dist/agent/mcp-bridge.js +114 -128
- package/dist/agent/mcp-tool-server.d.ts +2 -2
- package/dist/agent/mcp-tool-server.js +147 -189
- package/dist/agent/profiles.d.ts +69 -0
- package/dist/agent/providers/anthropic.d.ts +2 -4
- package/dist/agent/providers/claude-cli.d.ts +4 -5
- package/dist/agent/providers/openai-compat.d.ts +8 -1
- package/dist/agent/streaming.d.ts +2 -2
- package/dist/agent/types.d.ts +16 -10
- package/dist/api/builder.d.ts +3 -3
- package/dist/api/compile.d.ts +9 -4
- package/dist/api/durable-validation.d.ts +15 -0
- package/dist/api/generate-in-place.d.ts +21 -19
- package/dist/api/generate.d.ts +21 -16
- package/dist/api/generated-module/local-node-functions.d.ts +24 -0
- package/dist/api/generated-module/module-format.d.ts +23 -0
- package/dist/api/generated-module/module-writer.d.ts +34 -0
- package/dist/api/generated-module/node-type-imports.d.ts +39 -0
- package/dist/api/generated-module/preamble.d.ts +16 -0
- package/dist/api/generated-module/workflow-functions.d.ts +29 -0
- package/dist/api/graph-identity.d.ts +39 -0
- package/dist/api/helpers.d.ts +4 -60
- package/dist/api/in-place/built-in-nodes.d.ts +18 -0
- package/dist/api/in-place/function-body.d.ts +21 -0
- package/dist/api/in-place/fw-imports.d.ts +30 -0
- package/dist/api/in-place/macro-coverage.d.ts +14 -0
- package/dist/api/in-place/node-type-functions.d.ts +18 -0
- package/dist/api/in-place/node-type-jsdoc.d.ts +20 -0
- package/dist/api/in-place/orphaned-node-types.d.ts +16 -0
- package/dist/api/in-place/runtime-section.d.ts +16 -0
- package/dist/api/in-place/signature.d.ts +47 -0
- package/dist/api/in-place/source-file.d.ts +19 -0
- package/dist/api/in-place/workflow-jsdoc.d.ts +19 -0
- package/dist/api/index.d.ts +18 -21
- package/dist/api/index.js +259 -53
- package/dist/api/inline-engine.generated.d.ts +3 -0
- package/dist/api/inline-runtime.d.ts +10 -2
- package/dist/api/manipulation/connections.d.ts +1 -1
- package/dist/api/manipulation/index.d.ts +8 -8
- package/dist/api/manipulation/node-types.d.ts +1 -1
- package/dist/api/manipulation/nodes.d.ts +3 -13
- package/dist/api/manipulation/ports.d.ts +1 -1
- package/dist/api/manipulation/scopes.d.ts +1 -1
- package/dist/api/manipulation/workflow.d.ts +1 -1
- package/dist/api/modify-operation.d.ts +0 -2
- package/dist/api/parse.d.ts +22 -1
- package/dist/api/port-references.d.ts +66 -0
- package/dist/api/query.d.ts +18 -2
- package/dist/api/templates.d.ts +1 -1
- package/dist/api/transform.d.ts +1 -1
- package/dist/api/validate.d.ts +1 -1
- package/dist/api/validation-registry.d.ts +2 -2
- package/dist/artifacts/brief.d.ts +43 -0
- package/dist/artifacts/index.d.ts +31 -0
- package/dist/artifacts/pdf.d.ts +19 -0
- package/dist/ast/builder.d.ts +3 -3
- package/dist/ast/index.d.ts +4 -4
- package/dist/ast/index.js +60 -5
- package/dist/ast/serialization-node.d.ts +1 -1
- package/dist/ast/serialization.d.ts +1 -1
- package/dist/ast/types.d.ts +82 -291
- package/dist/ast/workflow-utils.d.ts +4 -4
- package/dist/built-in-nodes/coercion-types.d.ts +1 -1
- package/dist/built-in-nodes/delay.d.ts +3 -1
- package/dist/built-in-nodes/generated-registry.d.ts +1 -1
- package/dist/built-in-nodes/index.d.ts +6 -5
- package/dist/built-in-nodes/index.js +79 -5
- package/dist/built-in-nodes/invoke-workflow.d.ts +2 -1
- package/dist/built-in-nodes/mock-types.d.ts +45 -14
- package/dist/built-in-nodes/sleep.d.ts +12 -0
- package/dist/built-in-nodes/wait-for-agent.d.ts +2 -1
- package/dist/built-in-nodes/wait-for-event.d.ts +2 -1
- package/dist/chevrotain-parser/coerce-parser.d.ts +1 -1
- package/dist/chevrotain-parser/connect-parser.d.ts +1 -1
- package/dist/chevrotain-parser/grammar-diagrams.d.ts +1 -1
- package/dist/chevrotain-parser/index.d.ts +20 -23
- package/dist/chevrotain-parser/node-parser.d.ts +7 -8
- package/dist/chevrotain-parser/parse-line.d.ts +54 -0
- package/dist/chevrotain-parser/tokens.d.ts +0 -17
- package/dist/chevrotain-parser/trigger-cancel-parser.d.ts +6 -1
- package/dist/chunks/chunk-4EUSHAY6.js +298 -0
- package/dist/chunks/chunk-4KDUEOFD.js +197 -0
- package/dist/chunks/chunk-5Y3AIUWY.js +911 -0
- package/dist/chunks/chunk-6KMVP6AU.js +20 -0
- package/dist/chunks/chunk-6PNJORFL.js +128 -0
- package/dist/chunks/chunk-7AAQNGRH.js +2001 -0
- package/dist/chunks/chunk-7YYKTPJ5.js +22705 -0
- package/dist/chunks/chunk-A5MBOFVY.js +33 -0
- package/dist/chunks/chunk-AXNQRVJB.js +18 -0
- package/dist/chunks/chunk-B6HQ6YS5.js +572 -0
- package/dist/chunks/chunk-CY3RNVML.js +296 -0
- package/dist/chunks/chunk-CYHYDLV4.js +653 -0
- package/dist/chunks/chunk-CZBCCK4Y.js +82 -0
- package/dist/chunks/chunk-DCMGQP5V.js +505 -0
- package/dist/chunks/chunk-DIAA66GL.js +72 -0
- package/dist/chunks/chunk-DUO6H5PR.js +20 -0
- package/dist/chunks/chunk-E5WN7IGO.js +125 -0
- package/dist/chunks/chunk-EFGFXXQF.js +184 -0
- package/dist/chunks/chunk-F3RTZV2A.js +2811 -0
- package/dist/chunks/chunk-FMTLP5ZX.js +1397 -0
- package/dist/chunks/chunk-HKZLQJDV.js +79 -0
- package/dist/chunks/chunk-INDEVYEC.js +146 -0
- package/dist/chunks/chunk-JQTF4R5L.js +390 -0
- package/dist/chunks/chunk-KMCAYNUM.js +331 -0
- package/dist/chunks/chunk-KMWCCOUJ.js +1444 -0
- package/dist/chunks/chunk-KU3CLJI2.js +338 -0
- package/dist/chunks/chunk-NDSTGMKR.js +3300 -0
- package/dist/chunks/chunk-NQNLUAPO.js +505 -0
- package/dist/chunks/chunk-O2U2LO57.js +466 -0
- package/dist/chunks/chunk-OOQG7UPP.js +457 -0
- package/dist/chunks/chunk-OZQAW5ZG.js +112 -0
- package/dist/chunks/chunk-P2CVDGHR.js +487 -0
- package/dist/chunks/chunk-P44RIZ7C.js +592 -0
- package/dist/chunks/chunk-PEZCT37T.js +1475 -0
- package/dist/chunks/chunk-PKWSAW6O.js +152 -0
- package/dist/chunks/chunk-Q6MDDZMT.js +4373 -0
- package/dist/chunks/chunk-QDE4Z7KW.js +6 -0
- package/dist/chunks/chunk-QJDAVSNY.js +8 -0
- package/dist/chunks/chunk-QYPORSLO.js +7552 -0
- package/dist/chunks/chunk-R4QGNK34.js +183 -0
- package/dist/chunks/chunk-REEYC73I.js +3002 -0
- package/dist/chunks/chunk-SEWDMQ6Q.js +46 -0
- package/dist/chunks/chunk-TAIUWKZB.js +3057 -0
- package/dist/chunks/chunk-TN4YZ3O7.js +196 -0
- package/dist/chunks/chunk-TRMFB43C.js +143 -0
- package/dist/chunks/chunk-VO4QXG2X.js +1016 -0
- package/dist/chunks/chunk-VP2P3BPA.js +1002 -0
- package/dist/chunks/chunk-VQ7H33J4.js +2176 -0
- package/dist/chunks/chunk-VWA7MCV2.js +105 -0
- package/dist/chunks/chunk-XAV22WD7.js +253 -0
- package/dist/chunks/chunk-YSPBPZDV.js +148 -0
- package/dist/chunks/chunk-ZKEGXO73.js +282 -0
- package/dist/chunks/chunk-ZMGN6F3S.js +4256 -0
- package/dist/chunks/grammar-diagrams-AI7VDXOM.js +14 -0
- package/dist/chunks/index.min-ECJD6GZC.js +37 -0
- package/dist/chunks/manifest-EPTR5MYH.js +21 -0
- package/dist/chunks/openapi-document-6TCGVDUW.js +69 -0
- package/dist/chunks/registry-K2U5KUTY.js +23 -0
- package/dist/chunks/serialization-node-UGY6CFH6.js +38 -0
- package/dist/chunks/validate-GEKSZPIN.js +24 -0
- package/dist/chunks/validation-registry-IQZX3UE3.js +7 -0
- package/dist/chunks/validator-VXFHLPDW.js +26 -0
- package/dist/chunks/workflow-executor-KBGFKHNR.js +36 -0
- package/dist/cli/commands/agents.d.ts +16 -0
- package/dist/cli/commands/artifact.d.ts +10 -0
- package/dist/cli/commands/compile.d.ts +0 -30
- package/dist/cli/commands/console.d.ts +32 -0
- package/dist/cli/commands/context.d.ts +0 -3
- package/dist/cli/commands/describe.js +386 -352
- package/dist/cli/commands/dev.d.ts +9 -4
- package/dist/cli/commands/diagram.d.ts +3 -5
- package/dist/cli/commands/doctor.d.ts +27 -4
- package/dist/cli/commands/doctor.js +3307 -633
- package/dist/cli/commands/export.d.ts +6 -4
- package/dist/cli/commands/grammar.d.ts +0 -3
- package/dist/cli/commands/implement.d.ts +1 -1
- package/dist/cli/commands/init/agent-handoff.d.ts +28 -0
- package/dist/cli/commands/init/config.d.ts +3 -0
- package/dist/cli/commands/init/files.d.ts +12 -0
- package/dist/cli/commands/init/naming.d.ts +13 -0
- package/dist/cli/commands/init/output.d.ts +12 -0
- package/dist/cli/commands/init/setup.d.ts +21 -0
- package/dist/cli/commands/init/types.d.ts +53 -0
- package/dist/cli/commands/init-personas.d.ts +9 -1
- package/dist/cli/commands/init.d.ts +24 -88
- package/dist/cli/commands/market.d.ts +1 -1
- package/dist/cli/commands/mcp-setup.d.ts +1 -4
- package/dist/cli/commands/modify.d.ts +0 -5
- package/dist/cli/commands/openapi.d.ts +9 -11
- package/dist/cli/commands/run.d.ts +0 -4
- package/dist/cli/commands/serve.d.ts +24 -26
- package/dist/cli/commands/status.d.ts +1 -1
- package/dist/cli/exports.d.ts +3 -4
- package/dist/cli/exports.js +124 -23
- package/dist/cli/flow-weaver-agents-H653446S.mjs +81 -0
- package/dist/cli/flow-weaver-artifact-WBFYTW76.mjs +58 -0
- package/dist/cli/flow-weaver-chunk-265CEK74.mjs +2 -0
- package/dist/cli/flow-weaver-chunk-2C7PZCV5.mjs +186 -0
- package/dist/cli/flow-weaver-chunk-3BSYCWPL.mjs +3109 -0
- package/dist/cli/flow-weaver-chunk-3MKUQP3D.mjs +547 -0
- package/dist/cli/flow-weaver-chunk-3ZLMPQTH.mjs +27 -0
- package/dist/cli/flow-weaver-chunk-4DVY73ML.mjs +280 -0
- package/dist/cli/flow-weaver-chunk-54M4KNEL.mjs +200 -0
- package/dist/cli/flow-weaver-chunk-55QMUZIA.mjs +256 -0
- package/dist/cli/flow-weaver-chunk-6ZUJ7LMX.mjs +2003 -0
- package/dist/cli/flow-weaver-chunk-7ET6N32V.mjs +419 -0
- package/dist/cli/flow-weaver-chunk-7G63RR4K.mjs +8880 -0
- package/dist/cli/flow-weaver-chunk-A6M5K3W7.mjs +4418 -0
- package/dist/cli/flow-weaver-chunk-AA3NATMX.mjs +154 -0
- package/dist/cli/flow-weaver-chunk-APX6MQ5A.mjs +5652 -0
- package/dist/cli/flow-weaver-chunk-BHCC5FE5.mjs +17 -0
- package/dist/cli/flow-weaver-chunk-DM4BBRRU.mjs +35 -0
- package/dist/cli/flow-weaver-chunk-DRN7KPWY.mjs +168 -0
- package/dist/cli/flow-weaver-chunk-EKWF27G6.mjs +23 -0
- package/dist/cli/flow-weaver-chunk-EM5HWQ4R.mjs +338 -0
- package/dist/cli/flow-weaver-chunk-FPPPU2DG.mjs +226 -0
- package/dist/cli/flow-weaver-chunk-GAQXZHNO.mjs +3005 -0
- package/dist/cli/flow-weaver-chunk-GBUNNP33.mjs +1329 -0
- package/dist/cli/flow-weaver-chunk-HMAKDE55.mjs +636 -0
- package/dist/cli/flow-weaver-chunk-HQ3ZAN44.mjs +367 -0
- package/dist/cli/flow-weaver-chunk-I4APCXYN.mjs +21442 -0
- package/dist/cli/flow-weaver-chunk-IJJ4JXTL.mjs +2297 -0
- package/dist/cli/flow-weaver-chunk-J7EPZW2I.mjs +33 -0
- package/dist/cli/flow-weaver-chunk-KOEMJHLG.mjs +83 -0
- package/dist/cli/flow-weaver-chunk-M6CBWQKJ.mjs +271 -0
- package/dist/cli/flow-weaver-chunk-MHJ4N652.mjs +1580 -0
- package/dist/cli/flow-weaver-chunk-MKAFQWLP.mjs +202 -0
- package/dist/cli/flow-weaver-chunk-MMV2XGD3.mjs +3291 -0
- package/dist/cli/flow-weaver-chunk-MSI2UJES.mjs +125 -0
- package/dist/cli/flow-weaver-chunk-N767YRAI.mjs +48 -0
- package/dist/cli/flow-weaver-chunk-NQKKWDGF.mjs +56 -0
- package/dist/cli/flow-weaver-chunk-NYZDQYFU.mjs +45 -0
- package/dist/cli/flow-weaver-chunk-OMVWSNHJ.mjs +14 -0
- package/dist/cli/flow-weaver-chunk-PUWZ47H5.mjs +2188 -0
- package/dist/cli/flow-weaver-chunk-QOPRVDES.mjs +46 -0
- package/dist/cli/flow-weaver-chunk-RIWE7YTR.mjs +216 -0
- package/dist/cli/flow-weaver-chunk-S63Q3JIU.mjs +789 -0
- package/dist/cli/flow-weaver-chunk-T7X5A23H.mjs +558 -0
- package/dist/cli/flow-weaver-chunk-TLV3VZUN.mjs +2 -0
- package/dist/cli/flow-weaver-chunk-TS5P3PIN.mjs +194 -0
- package/dist/cli/flow-weaver-chunk-U3EO3X4B.mjs +649 -0
- package/dist/cli/flow-weaver-chunk-UNFS3NQP.mjs +34 -0
- package/dist/cli/flow-weaver-chunk-V6DHWFQG.mjs +2811 -0
- package/dist/cli/flow-weaver-chunk-VXWNLYQU.mjs +9 -0
- package/dist/cli/flow-weaver-chunk-W3YNK26P.mjs +149 -0
- package/dist/cli/flow-weaver-chunk-XG2WKXYZ.mjs +324 -0
- package/dist/cli/flow-weaver-chunk-YLSRZWFK.mjs +2219 -0
- package/dist/cli/flow-weaver-chunk-YVPURWS6.mjs +40 -0
- package/dist/cli/flow-weaver-chunk-Z44JK332.mjs +582 -0
- package/dist/cli/flow-weaver-chunk-Z5ZJPNSY.mjs +517 -0
- package/dist/cli/flow-weaver-chunk-ZQALO25T.mjs +594 -0
- package/dist/cli/flow-weaver-compile-A2CABDSB.mjs +37 -0
- package/dist/cli/flow-weaver-console-6EBHDBTN.mjs +71 -0
- package/dist/cli/flow-weaver-context-ECLYANFF.mjs +17 -0
- package/dist/cli/flow-weaver-context-ZRUMW2KE.mjs +69 -0
- package/dist/cli/flow-weaver-create-WIP4EFNP.mjs +124 -0
- package/dist/cli/flow-weaver-deployment-WJJRK7NO.mjs +14 -0
- package/dist/cli/flow-weaver-describe-XDIFF5LI.mjs +41 -0
- package/dist/cli/flow-weaver-dev-LG2NSQAU.mjs +196 -0
- package/dist/cli/flow-weaver-diagram-3EVQARQE.mjs +52 -0
- package/dist/cli/flow-weaver-diagram-7LR7O4M2.mjs +44 -0
- package/dist/cli/flow-weaver-diff-USF7DJLU.mjs +85 -0
- package/dist/cli/flow-weaver-docs-QU2ZOXKH.mjs +96 -0
- package/dist/cli/flow-weaver-doctor-K447YWHR.mjs +43 -0
- package/dist/cli/flow-weaver-export-LAS6N56Y.mjs +140 -0
- package/dist/cli/flow-weaver-grammar-RIRUS3Q6.mjs +49 -0
- package/dist/cli/flow-weaver-grammar-diagrams-WXRTW5SK.mjs +15 -0
- package/dist/cli/flow-weaver-implement-5JG6BCFZ.mjs +114 -0
- package/dist/cli/flow-weaver-index.min-OR5SIHXD.mjs +39 -0
- package/dist/cli/flow-weaver-init-34YZNPQX.mjs +972 -0
- package/dist/cli/flow-weaver-init-personas-NOGZTQQ2.mjs +48 -0
- package/dist/cli/flow-weaver-manifest-4JNUSEB3.mjs +18 -0
- package/dist/cli/flow-weaver-market-WA4CKHKV.mjs +444 -0
- package/dist/cli/flow-weaver-mcp-setup-O26MGXSD.mjs +22 -0
- package/dist/cli/flow-weaver-migrate-VFVEUTPH.mjs +119 -0
- package/dist/cli/flow-weaver-modify-VM3JP5K6.mjs +97 -0
- package/dist/cli/flow-weaver-openapi-IRBWI3HM.mjs +71 -0
- package/dist/cli/flow-weaver-openapi-document-7N6YMPG3.mjs +30 -0
- package/dist/cli/flow-weaver-pack-commands-OS2QRXAK.mjs +146 -0
- package/dist/cli/flow-weaver-registry-5MLFRPPT.mjs +25 -0
- package/dist/cli/flow-weaver-run-DSTLSTHR.mjs +555 -0
- package/dist/cli/flow-weaver-serialization-node-LE6PCG4M.mjs +50 -0
- package/dist/cli/flow-weaver-serve-NVLCLSOW.mjs +192 -0
- package/dist/cli/flow-weaver-server-7IERLVXE.mjs +2925 -0
- package/dist/cli/flow-weaver-server-KWF4PXYC.mjs +19112 -0
- package/dist/cli/flow-weaver-status-75W2S3YB.mjs +138 -0
- package/dist/cli/flow-weaver-strip-AZPXUYVA.mjs +79 -0
- package/dist/cli/flow-weaver-templates-DSNMB6K7.mjs +72 -0
- package/dist/cli/flow-weaver-validate-A4UWDQN6.mjs +19 -0
- package/dist/cli/flow-weaver-validate-GRBZ5W4C.mjs +296 -0
- package/dist/cli/flow-weaver-validation-registry-BINGUNXX.mjs +9 -0
- package/dist/cli/flow-weaver-validator-TY56CI42.mjs +21 -0
- package/dist/cli/flow-weaver-watch-OPPERXL6.mjs +114 -0
- package/dist/cli/flow-weaver-workflow-executor-6AHYPXAN.mjs +26 -0
- package/dist/cli/flow-weaver.mjs +3557 -89498
- package/dist/cli/index.d.ts +5 -2
- package/dist/cli/pack-commands-needed.d.ts +12 -0
- package/dist/cli/program.d.ts +14 -0
- package/dist/cli/templates/ai/mock-provider.d.ts +1 -1
- package/dist/cli/templates/index.d.ts +1 -8
- package/dist/cli/templates/nodes/agent-router.d.ts +1 -1
- package/dist/cli/templates/nodes/aggregator.d.ts +1 -1
- package/dist/cli/templates/nodes/conversation-memory.d.ts +1 -1
- package/dist/cli/templates/nodes/http.d.ts +1 -1
- package/dist/cli/templates/nodes/human-approval.d.ts +1 -1
- package/dist/cli/templates/nodes/json-extractor.d.ts +1 -1
- package/dist/cli/templates/nodes/llm-call.d.ts +1 -1
- package/dist/cli/templates/nodes/prompt-template.d.ts +1 -1
- package/dist/cli/templates/nodes/rag-retriever.d.ts +1 -1
- package/dist/cli/templates/nodes/tool-executor.d.ts +1 -1
- package/dist/cli/templates/nodes/transformer.d.ts +1 -1
- package/dist/cli/templates/nodes/validator.d.ts +1 -1
- package/dist/cli/templates/shared/approval-types.d.ts +1 -1
- package/dist/cli/templates/shared/case.d.ts +14 -0
- package/dist/cli/templates/shared/llm-types.d.ts +4 -4
- package/dist/cli/templates/workflows/aggregator.d.ts +1 -1
- package/dist/cli/templates/workflows/ai-agent.d.ts +1 -1
- package/dist/cli/templates/workflows/ai-chat.d.ts +1 -1
- package/dist/cli/templates/workflows/ai-rag.d.ts +1 -1
- package/dist/cli/templates/workflows/ai-react.d.ts +1 -1
- package/dist/cli/templates/workflows/approval.d.ts +10 -0
- package/dist/cli/templates/workflows/conditional.d.ts +2 -2
- package/dist/cli/templates/workflows/error-handler.d.ts +2 -2
- package/dist/cli/templates/workflows/foreach.d.ts +1 -1
- package/dist/cli/templates/workflows/sequential.d.ts +1 -1
- package/dist/cli/templates/workflows/webhook.d.ts +1 -1
- package/dist/cli/utils/interactive.d.ts +6 -0
- package/dist/cli/utils/json-option.d.ts +8 -0
- package/dist/cli/utils/logger.d.ts +2 -0
- package/dist/compiler/durable-source-proof.d.ts +23 -0
- package/dist/compiler/executable-artifact.d.ts +29 -0
- package/dist/compiler/index.d.ts +4 -0
- package/dist/compiler/index.js +36 -0
- package/dist/console/app.js +106 -0
- package/dist/console/assets/synergenius.svg +18 -0
- package/dist/console/author.d.ts +32 -0
- package/dist/console/cli-catalog.d.ts +21 -0
- package/dist/console/cli-run.d.ts +35 -0
- package/dist/console/cli-run.js +46 -0
- package/dist/console/debug.d.ts +67 -0
- package/dist/console/diff-view.d.ts +96 -0
- package/dist/console/export.d.ts +36 -0
- package/dist/console/git.d.ts +27 -0
- package/dist/console/index.d.ts +4 -0
- package/dist/console/index.html +16 -0
- package/dist/console/index.js +203 -0
- package/dist/console/packs.d.ts +100 -0
- package/dist/console/request-guard.d.ts +10 -0
- package/dist/console/respond.d.ts +16 -0
- package/dist/console/router.d.ts +64 -0
- package/dist/console/routes-agents.d.ts +14 -0
- package/dist/console/routes-project.d.ts +3 -0
- package/dist/console/routes-runs.d.ts +3 -0
- package/dist/console/routes-services.d.ts +7 -0
- package/dist/console/routes-tools.d.ts +3 -0
- package/dist/console/runs.d.ts +69 -0
- package/dist/console/scan.d.ts +49 -0
- package/dist/console/schema.d.ts +74 -0
- package/dist/console/server.d.ts +34 -0
- package/dist/console/server.js +43264 -0
- package/dist/console/services.d.ts +119 -0
- package/dist/console/source.d.ts +23 -0
- package/dist/console/status.d.ts +68 -0
- package/dist/console/styles.css +1023 -0
- package/dist/console/terminals.d.ts +22 -0
- package/dist/console/workflow-view.d.ts +153 -0
- package/dist/constants.d.ts +5 -1
- package/dist/constants.js +56 -167
- package/dist/context/index.d.ts +10 -0
- package/dist/context/index.js +14 -191
- package/dist/coordinator/agent-gate.d.ts +107 -0
- package/dist/coordinator/bundle-digest.d.ts +20 -0
- package/dist/coordinator/clock-tick.d.ts +4 -0
- package/dist/coordinator/effect-receipts.d.ts +10 -0
- package/dist/coordinator/errors.d.ts +31 -0
- package/dist/coordinator/file-store.d.ts +3 -0
- package/dist/coordinator/gate-labeling.d.ts +33 -0
- package/dist/coordinator/gate-resolution.d.ts +28 -0
- package/dist/coordinator/index.d.ts +12 -0
- package/dist/coordinator/index.js +107 -0
- package/dist/coordinator/memory-store.d.ts +3 -0
- package/dist/coordinator/params.d.ts +18 -0
- package/dist/coordinator/run-annotations.d.ts +6 -0
- package/dist/coordinator/run-context.d.ts +11 -0
- package/dist/coordinator/run-ending.d.ts +5 -0
- package/dist/coordinator/run-outcome.d.ts +31 -0
- package/dist/coordinator/run-resume.d.ts +6 -0
- package/dist/coordinator/run-start.d.ts +4 -0
- package/dist/coordinator/run-store.d.ts +275 -0
- package/dist/coordinator/run-trace.d.ts +41 -0
- package/dist/coordinator/run-views.d.ts +12 -0
- package/dist/coordinator/runs-dir.d.ts +26 -0
- package/dist/coordinator/store.d.ts +67 -0
- package/dist/coordinator/time.d.ts +39 -0
- package/dist/coordinator/workflow-selection.d.ts +6 -0
- package/dist/deployment/index.d.ts +11 -16
- package/dist/deployment/index.js +541 -50
- package/dist/deployment/targets/base.d.ts +11 -13
- package/dist/deployment/targets/openapi-parts.d.ts +92 -0
- package/dist/diagram/arms.d.ts +70 -0
- package/dist/diagram/ascii-renderer.d.ts +1 -1
- package/dist/diagram/geometry.d.ts +22 -29
- package/dist/diagram/graph/bounds.d.ts +30 -0
- package/dist/diagram/graph/metrics.d.ts +21 -0
- package/dist/diagram/graph/nodes.d.ts +33 -0
- package/dist/diagram/graph/placement.d.ts +22 -0
- package/dist/diagram/graph/ports.d.ts +23 -0
- package/dist/diagram/graph/routing.d.ts +40 -0
- package/dist/diagram/graph/scopes.d.ts +30 -0
- package/dist/diagram/index.d.ts +15 -23
- package/dist/diagram/index.js +632 -111
- package/dist/diagram/labels.d.ts +12 -0
- package/dist/diagram/lanes.d.ts +93 -0
- package/dist/diagram/layout.d.ts +1 -1
- package/dist/diagram/material-symbols.d.ts +7 -0
- package/dist/diagram/orthogonal-router.d.ts +15 -51
- package/dist/diagram/process-view.d.ts +79 -0
- package/dist/diagram/spine.d.ts +25 -0
- package/dist/diagram/theme.d.ts +3 -5
- package/dist/diagram/types.d.ts +6 -20
- package/dist/diff/index.js +53 -9
- package/dist/diff/types.d.ts +1 -5
- package/dist/doc-metadata/extractors/annotations.d.ts +0 -4
- package/dist/doc-metadata/extractors/cli-commands.d.ts +2 -1
- package/dist/doc-metadata/extractors/core-metadata.d.ts +19 -4
- package/dist/doc-metadata/extractors/error-codes.d.ts +1 -1
- package/dist/doc-metadata/extractors/mcp-tools.d.ts +7 -3
- package/dist/doc-metadata/index.d.ts +1 -2
- package/dist/doc-metadata/index.js +2368 -9
- package/dist/doc-metadata/types.d.ts +7 -24
- package/dist/docs/guide.d.ts +33 -0
- package/dist/docs/index.d.ts +7 -4
- package/dist/docs/index.js +298 -298
- package/dist/docs/pack-topics.d.ts +18 -0
- package/dist/editor-completions/annotationValues.d.ts +1 -1
- package/dist/editor-completions/contextParser.d.ts +3 -2
- package/dist/editor-completions/dataTypes.d.ts +1 -1
- package/dist/editor-completions/goToDefinition.d.ts +1 -1
- package/dist/editor-completions/index.d.ts +8 -8
- package/dist/editor-completions/index.js +1244 -175
- package/dist/editor-completions/jsDocAnnotations.d.ts +1 -1
- package/dist/editor-completions/modifierCompletions.d.ts +1 -1
- package/dist/export/index.d.ts +7 -1
- package/dist/generated-branding.js +8 -21
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +7 -3
- package/dist/generator/annotation-generator.d.ts +84 -0
- package/dist/generator/async-detection.d.ts +1 -1
- package/dist/{body-generator.d.ts → generator/body-generator.d.ts} +7 -3
- package/dist/generator/code-utils.d.ts +15 -60
- package/dist/generator/connection-reads.d.ts +94 -0
- package/dist/generator/control-flow-plan.d.ts +74 -0
- package/dist/generator/control-flow.d.ts +1 -1
- package/dist/generator/exit-assembly.d.ts +11 -0
- package/dist/generator/index.d.ts +4 -5
- package/dist/generator/index.js +34 -11
- package/dist/generator/input-port-values.d.ts +22 -0
- package/dist/generator/{scope-function-generator.d.ts → node-arguments.d.ts} +76 -1
- package/dist/generator/node-invocation.d.ts +74 -0
- package/dist/{node-types-generator.d.ts → generator/node-types-generator.d.ts} +3 -2
- package/dist/generator/scope-closure.d.ts +23 -0
- package/dist/generator/scoped-child.d.ts +49 -0
- package/dist/{sugar-optimizer.d.ts → generator/sugar-optimizer.d.ts} +1 -1
- package/dist/generator/unified.d.ts +14 -2
- package/dist/generator/workflow-generator.d.ts +37 -0
- package/dist/index.d.ts +29 -26
- package/dist/index.js +1322 -92
- package/dist/jsdoc-port-sync/apply-diff.d.ts +15 -0
- package/dist/jsdoc-port-sync/callback-signature.d.ts +17 -0
- package/dist/jsdoc-port-sync/diff.d.ts +5 -7
- package/dist/jsdoc-port-sync/incomplete-lines.d.ts +41 -0
- package/dist/jsdoc-port-sync/index.d.ts +11 -11
- package/dist/jsdoc-port-sync/index.js +88 -45
- package/dist/jsdoc-port-sync/jsdoc-to-signature.d.ts +66 -0
- package/dist/jsdoc-port-sync/port-parser.d.ts +6 -24
- package/dist/jsdoc-port-sync/port-tag-placement.d.ts +55 -0
- package/dist/jsdoc-port-sync/scoped-ports.d.ts +21 -0
- package/dist/jsdoc-port-sync/signature-parser.d.ts +8 -1
- package/dist/jsdoc-port-sync/signature-to-jsdoc.d.ts +77 -0
- package/dist/jsdoc-port-sync/sync.d.ts +4 -1
- package/dist/marketplace/index.d.ts +5 -3
- package/dist/marketplace/index.js +62 -10
- package/dist/marketplace/install.d.ts +24 -0
- package/dist/marketplace/manifest.d.ts +1 -1
- package/dist/marketplace/npmrc.d.ts +24 -0
- package/dist/marketplace/registry.d.ts +46 -11
- package/dist/marketplace/types.d.ts +41 -24
- package/dist/marketplace/validator.d.ts +1 -1
- package/dist/mcp/debug-session.d.ts +2 -4
- package/dist/mcp/dist-imports.d.ts +12 -0
- package/dist/mcp/response-utils.d.ts +11 -9
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/tools-context.d.ts +11 -0
- package/dist/mcp/tools-diagram.d.ts +1 -2
- package/dist/mcp/tools-export.d.ts +2 -2
- package/dist/mcp/tools-marketplace.d.ts +1 -1
- package/dist/mcp/tools-run.d.ts +17 -0
- package/dist/mcp/tools-workflow.d.ts +3 -0
- package/dist/mcp/types.d.ts +4 -14
- package/dist/mcp/workflow-executor.d.ts +57 -19
- package/dist/npm-packages.d.ts +1 -1
- package/dist/npm-packages.js +10 -473
- package/dist/parser/annotation-parser.d.ts +88 -0
- package/dist/parser/annotation-suggestion.d.ts +23 -0
- package/dist/parser/durable-effect-contract.d.ts +10 -0
- package/dist/parser/expression-references.d.ts +101 -0
- package/dist/parser/external-node-types.d.ts +69 -0
- package/dist/{function-like.d.ts → parser/function-like.d.ts} +10 -3
- package/dist/parser/generated-sections.d.ts +25 -0
- package/dist/parser/import-resolution.d.ts +67 -0
- package/dist/parser/import-stub.d.ts +29 -0
- package/dist/parser/jsdoc/config-types.d.ts +209 -0
- package/dist/parser/jsdoc/graph-tags.d.ts +54 -0
- package/dist/parser/jsdoc/node-type-block.d.ts +23 -0
- package/dist/parser/jsdoc/parse-line-once.d.ts +11 -0
- package/dist/parser/jsdoc/port-tags.d.ts +42 -0
- package/dist/parser/jsdoc/runtime-tags.d.ts +65 -0
- package/dist/parser/jsdoc/signature-types.d.ts +52 -0
- package/dist/parser/jsdoc/workflow-block.d.ts +22 -0
- package/dist/parser/jsdoc-parser.d.ts +23 -0
- package/dist/parser/macro-expansion.d.ts +80 -0
- package/dist/parser/module-resolution.d.ts +38 -0
- package/dist/parser/node-inference.d.ts +39 -0
- package/dist/parser/npm-node-types.d.ts +24 -0
- package/dist/parser/pack-handlers.d.ts +12 -0
- package/dist/parser/path-data-resolution.d.ts +75 -0
- package/dist/parser/port-inference.d.ts +15 -0
- package/dist/{resolve-package-types.d.ts → parser/resolve-package-types.d.ts} +4 -0
- package/dist/parser/serialize-deploy-annotations.d.ts +19 -0
- package/dist/parser/tag-registry.d.ts +22 -0
- package/dist/parser/workflow-extraction.d.ts +39 -0
- package/dist/runtime/ExecutionContext.d.ts +87 -99
- package/dist/runtime/continuation-core.d.ts +147 -0
- package/dist/runtime/continuation.d.ts +48 -0
- package/dist/runtime/debug-controller.d.ts +7 -22
- package/dist/runtime/durable-execution.d.ts +176 -0
- package/dist/runtime/events.d.ts +16 -1
- package/dist/runtime/executable-module-contract.d.ts +16 -0
- package/dist/runtime/index.d.ts +13 -10
- package/dist/runtime/index.js +947 -9
- package/dist/runtime/strict-json.d.ts +15 -0
- package/dist/server/access.d.ts +12 -0
- package/dist/server/api.d.ts +126 -0
- package/dist/server/callback-delivery.d.ts +62 -0
- package/dist/server/callback-url.d.ts +44 -0
- package/dist/server/context.d.ts +35 -0
- package/dist/server/http-error.d.ts +10 -0
- package/dist/server/index.d.ts +7 -1
- package/dist/server/index.js +125 -6
- package/dist/server/openapi-document.d.ts +33 -0
- package/dist/server/openapi.d.ts +32 -0
- package/dist/server/respond.d.ts +35 -0
- package/dist/server/route-params.d.ts +17 -0
- package/dist/server/route-plan.d.ts +41 -0
- package/dist/server/routes-catalog.d.ts +5 -0
- package/dist/server/routes-declared.d.ts +12 -0
- package/dist/server/routes-runs.d.ts +8 -0
- package/dist/server/run-driver.d.ts +58 -0
- package/dist/server/run-view.d.ts +26 -0
- package/dist/server/transport.d.ts +35 -0
- package/dist/server/types.d.ts +143 -52
- package/dist/server/webhook-server.d.ts +12 -38
- package/dist/server/workflow-registry.d.ts +7 -4
- package/dist/service-registry.d.ts +49 -0
- package/dist/service-registry.js +123 -0
- package/dist/testing/assertions.d.ts +2 -2
- package/dist/testing/index.d.ts +14 -13
- package/dist/testing/index.js +582 -24
- package/dist/testing/mock-approval.d.ts +3 -5
- package/dist/testing/mock-llm.d.ts +2 -3
- package/dist/testing/recorder.d.ts +5 -6
- package/dist/testing/replayer.d.ts +6 -7
- package/dist/testing/run-store-check.d.ts +3 -0
- package/dist/testing/token-tracker.d.ts +2 -2
- package/dist/{type-mappings.d.ts → types/type-mappings.d.ts} +13 -1
- package/dist/utils/error-utils.d.ts +4 -2
- package/dist/utils/port-ordering.d.ts +2 -2
- package/dist/utils/port-tag-utils.d.ts +1 -1
- package/dist/validation/agent-detection.d.ts +17 -5
- package/dist/validation/agent-rules.d.ts +6 -7
- package/dist/validation/core-rules.d.ts +49 -0
- package/dist/validation/design-rules.d.ts +9 -3
- package/dist/validation/rule-registry.d.ts +3 -3
- package/dist/validation/rules/connections.d.ts +27 -0
- package/dist/validation/rules/context.d.ts +11 -0
- package/dist/validation/rules/cycles.d.ts +17 -0
- package/dist/validation/rules/data-flow.d.ts +19 -0
- package/dist/validation/rules/scope-topology.d.ts +35 -0
- package/dist/validation/rules/type-compatibility.d.ts +30 -0
- package/dist/validation/type-checker.d.ts +39 -0
- package/dist/validation/validator-helpers.d.ts +31 -0
- package/dist/validation/validator.d.ts +30 -0
- package/docs/reference/advanced-annotations.md +277 -60
- package/docs/reference/built-in-nodes.md +164 -94
- package/docs/reference/cancellation.md +44 -0
- package/docs/reference/cli-reference.md +252 -234
- package/docs/reference/compilation.md +36 -109
- package/docs/reference/concepts.md +80 -63
- package/docs/reference/console.md +169 -0
- package/docs/reference/debugging.md +26 -57
- package/docs/reference/deployment.md +217 -143
- package/docs/reference/durable-gates.md +445 -0
- package/docs/reference/error-codes.md +90 -43
- package/docs/reference/export-interface.md +91 -26
- package/docs/reference/iterative-development.md +50 -28
- package/docs/reference/jsdoc-grammar.md +66 -311
- package/docs/reference/library.md +250 -0
- package/docs/reference/marketplace.md +301 -152
- package/docs/reference/mcp-tools.md +102 -0
- package/docs/reference/node-conversion.md +84 -59
- package/docs/reference/orientation.md +68 -0
- package/docs/reference/scaffold.md +5 -4
- package/docs/reference/tutorial.md +106 -166
- package/docs/reference/visual-reference.md +1 -1
- package/package.json +85 -50
- package/dist/agent/agent-loop.js +0 -156
- package/dist/agent/cli-spawn-config.js +0 -65
- package/dist/agent/device-connection.d.ts +0 -79
- package/dist/agent/device-connection.js +0 -184
- package/dist/agent/env-allowlist.js +0 -60
- package/dist/agent/providers/anthropic.js +0 -198
- package/dist/agent/providers/claude-cli.js +0 -161
- package/dist/agent/providers/openai-compat.js +0 -205
- package/dist/agent/providers/platform.d.ts +0 -23
- package/dist/agent/providers/platform.js +0 -132
- package/dist/agent/streaming.js +0 -186
- package/dist/agent/types.js +0 -20
- package/dist/annotation-generator.d.ts +0 -50
- package/dist/annotation-generator.js +0 -743
- package/dist/api/builder.js +0 -345
- package/dist/api/command-runner.js +0 -787
- package/dist/api/compile.js +0 -148
- package/dist/api/extract-types.js +0 -57
- package/dist/api/generate-in-place.js +0 -1477
- package/dist/api/generate.js +0 -514
- package/dist/api/helpers.js +0 -285
- package/dist/api/inline-runtime.js +0 -526
- package/dist/api/manipulation/connections.js +0 -151
- package/dist/api/manipulation/index.js +0 -41
- package/dist/api/manipulation/node-types.js +0 -200
- package/dist/api/manipulation/nodes.js +0 -355
- package/dist/api/manipulation/ports.js +0 -228
- package/dist/api/manipulation/scopes.js +0 -156
- package/dist/api/manipulation/workflow.js +0 -116
- package/dist/api/modify-operation.js +0 -177
- package/dist/api/parse.js +0 -115
- package/dist/api/patterns.d.ts +0 -112
- package/dist/api/patterns.js +0 -303
- package/dist/api/query.js +0 -840
- package/dist/api/templates.js +0 -117
- package/dist/api/transform.js +0 -40
- package/dist/api/validate.js +0 -64
- package/dist/api/validation-registry.js +0 -10
- package/dist/api/workflow-file-operations.js +0 -180
- package/dist/ast/builder.js +0 -395
- package/dist/ast/serialization-node.js +0 -30
- package/dist/ast/serialization.js +0 -134
- package/dist/ast/types.js +0 -2
- package/dist/ast/workflow-utils.js +0 -114
- package/dist/body-generator.js +0 -35
- package/dist/built-in-nodes/coercion-types.js +0 -61
- package/dist/built-in-nodes/delay.js +0 -29
- package/dist/built-in-nodes/generated-registry.js +0 -299
- package/dist/built-in-nodes/invoke-workflow.js +0 -36
- package/dist/built-in-nodes/mock-types.js +0 -42
- package/dist/built-in-nodes/wait-for-agent.js +0 -31
- package/dist/built-in-nodes/wait-for-event.js +0 -25
- package/dist/chevrotain-parser/coerce-parser.js +0 -103
- package/dist/chevrotain-parser/connect-parser.js +0 -136
- package/dist/chevrotain-parser/fan-parser.js +0 -149
- package/dist/chevrotain-parser/grammar-diagrams.js +0 -273
- package/dist/chevrotain-parser/index.js +0 -31
- package/dist/chevrotain-parser/map-parser.js +0 -130
- package/dist/chevrotain-parser/node-parser.js +0 -558
- package/dist/chevrotain-parser/path-parser.js +0 -140
- package/dist/chevrotain-parser/port-parser.js +0 -519
- package/dist/chevrotain-parser/position-parser.d.ts +0 -20
- package/dist/chevrotain-parser/position-parser.js +0 -83
- package/dist/chevrotain-parser/scope-parser.js +0 -104
- package/dist/chevrotain-parser/tokens.js +0 -429
- package/dist/chevrotain-parser/trigger-cancel-parser.js +0 -291
- package/dist/cli/commands/account.d.ts +0 -4
- package/dist/cli/commands/account.js +0 -50
- package/dist/cli/commands/ai-credentials.d.ts +0 -12
- package/dist/cli/commands/ai-credentials.js +0 -95
- package/dist/cli/commands/apikey.d.ts +0 -4
- package/dist/cli/commands/apikey.js +0 -64
- package/dist/cli/commands/auth.d.ts +0 -9
- package/dist/cli/commands/auth.js +0 -255
- package/dist/cli/commands/compile.js +0 -331
- package/dist/cli/commands/connect.d.ts +0 -4
- package/dist/cli/commands/connect.js +0 -163
- package/dist/cli/commands/context.js +0 -51
- package/dist/cli/commands/create.js +0 -139
- package/dist/cli/commands/deploy.d.ts +0 -6
- package/dist/cli/commands/deploy.js +0 -74
- package/dist/cli/commands/dev.js +0 -227
- package/dist/cli/commands/diagram.js +0 -39
- package/dist/cli/commands/diff.js +0 -57
- package/dist/cli/commands/docs.js +0 -74
- package/dist/cli/commands/export.js +0 -153
- package/dist/cli/commands/grammar.js +0 -40
- package/dist/cli/commands/implement.js +0 -91
- package/dist/cli/commands/init-personas.js +0 -541
- package/dist/cli/commands/init.js +0 -797
- package/dist/cli/commands/market.js +0 -440
- package/dist/cli/commands/mcp-setup.js +0 -433
- package/dist/cli/commands/migrate.js +0 -91
- package/dist/cli/commands/modify.js +0 -58
- package/dist/cli/commands/openapi.js +0 -71
- package/dist/cli/commands/org.d.ts +0 -8
- package/dist/cli/commands/org.js +0 -136
- package/dist/cli/commands/pattern.d.ts +0 -34
- package/dist/cli/commands/pattern.js +0 -186
- package/dist/cli/commands/plugin.d.ts +0 -16
- package/dist/cli/commands/plugin.js +0 -176
- package/dist/cli/commands/run.js +0 -742
- package/dist/cli/commands/serve.js +0 -93
- package/dist/cli/commands/status.js +0 -123
- package/dist/cli/commands/strip.js +0 -61
- package/dist/cli/commands/templates.js +0 -58
- package/dist/cli/commands/validate.js +0 -267
- package/dist/cli/commands/watch.js +0 -71
- package/dist/cli/config/credentials.d.ts +0 -15
- package/dist/cli/config/credentials.js +0 -43
- package/dist/cli/config/platform-client.d.ts +0 -123
- package/dist/cli/config/platform-client.js +0 -248
- package/dist/cli/env-setup.js +0 -12
- package/dist/cli/index.js +0 -850
- package/dist/cli/pack-commands.js +0 -122
- package/dist/cli/postinstall.d.ts +0 -16
- package/dist/cli/postinstall.js +0 -119
- package/dist/cli/templates/ai/mock-provider.js +0 -64
- package/dist/cli/templates/ai/types.js +0 -5
- package/dist/cli/templates/approvals/index.d.ts +0 -15
- package/dist/cli/templates/approvals/index.js +0 -241
- package/dist/cli/templates/index.js +0 -123
- package/dist/cli/templates/nodes/agent-router.js +0 -114
- package/dist/cli/templates/nodes/aggregator.js +0 -63
- package/dist/cli/templates/nodes/conversation-memory.js +0 -85
- package/dist/cli/templates/nodes/http.js +0 -80
- package/dist/cli/templates/nodes/human-approval.js +0 -109
- package/dist/cli/templates/nodes/json-extractor.js +0 -119
- package/dist/cli/templates/nodes/llm-call.js +0 -106
- package/dist/cli/templates/nodes/prompt-template.js +0 -52
- package/dist/cli/templates/nodes/rag-retriever.js +0 -128
- package/dist/cli/templates/nodes/tool-executor.js +0 -108
- package/dist/cli/templates/nodes/transformer.js +0 -68
- package/dist/cli/templates/nodes/validator.js +0 -62
- package/dist/cli/templates/pack-loader.js +0 -58
- package/dist/cli/templates/providers/index.js +0 -239
- package/dist/cli/templates/shared/approval-types.js +0 -31
- package/dist/cli/templates/shared/llm-types.js +0 -109
- package/dist/cli/templates/workflows/aggregator.js +0 -101
- package/dist/cli/templates/workflows/ai-agent.js +0 -326
- package/dist/cli/templates/workflows/ai-chat.js +0 -167
- package/dist/cli/templates/workflows/ai-rag.js +0 -184
- package/dist/cli/templates/workflows/ai-react.js +0 -293
- package/dist/cli/templates/workflows/conditional.js +0 -139
- package/dist/cli/templates/workflows/error-handler.js +0 -145
- package/dist/cli/templates/workflows/foreach.js +0 -140
- package/dist/cli/templates/workflows/sequential.js +0 -200
- package/dist/cli/templates/workflows/webhook.js +0 -158
- package/dist/cli/utils/cli-helpers.d.ts +0 -44
- package/dist/cli/utils/cli-helpers.js +0 -99
- package/dist/cli/utils/logger.js +0 -126
- package/dist/cli/utils/parse-int-strict.js +0 -17
- package/dist/cli/utils/safe-write.js +0 -54
- package/dist/deployment/config/defaults.d.ts +0 -29
- package/dist/deployment/config/defaults.js +0 -98
- package/dist/deployment/config/loader.d.ts +0 -24
- package/dist/deployment/config/loader.js +0 -237
- package/dist/deployment/config/types.d.ts +0 -117
- package/dist/deployment/config/types.js +0 -5
- package/dist/deployment/core/adapters.d.ts +0 -66
- package/dist/deployment/core/adapters.js +0 -156
- package/dist/deployment/core/executor.d.ts +0 -62
- package/dist/deployment/core/executor.js +0 -197
- package/dist/deployment/core/formatters.d.ts +0 -42
- package/dist/deployment/core/formatters.js +0 -146
- package/dist/deployment/openapi/generator.d.ts +0 -146
- package/dist/deployment/openapi/generator.js +0 -347
- package/dist/deployment/openapi/schema-converter.d.ts +0 -49
- package/dist/deployment/openapi/schema-converter.js +0 -192
- package/dist/deployment/targets/base.js +0 -855
- package/dist/deployment/types.d.ts +0 -140
- package/dist/deployment/types.js +0 -8
- package/dist/diagram/ascii-renderer.js +0 -528
- package/dist/diagram/geometry.js +0 -1267
- package/dist/diagram/html-viewer.d.ts +0 -21
- package/dist/diagram/html-viewer.js +0 -1689
- package/dist/diagram/layout.js +0 -143
- package/dist/diagram/orthogonal-router.js +0 -568
- package/dist/diagram/renderer.d.ts +0 -3
- package/dist/diagram/renderer.js +0 -266
- package/dist/diagram/theme.js +0 -282
- package/dist/diagram/types.js +0 -2
- package/dist/diff/WorkflowDiffer.js +0 -429
- package/dist/diff/formatDiff.js +0 -239
- package/dist/diff/impact.js +0 -119
- package/dist/diff/types.js +0 -35
- package/dist/doc-metadata/extractors/annotations.js +0 -638
- package/dist/doc-metadata/extractors/cli-commands.js +0 -326
- package/dist/doc-metadata/extractors/core-metadata.js +0 -61
- package/dist/doc-metadata/extractors/error-codes.js +0 -372
- package/dist/doc-metadata/extractors/grammar-rules.js +0 -51
- package/dist/doc-metadata/extractors/mcp-tools.js +0 -705
- package/dist/doc-metadata/extractors/plugin-api.d.ts +0 -19
- package/dist/doc-metadata/extractors/plugin-api.js +0 -279
- package/dist/doc-metadata/types.js +0 -5
- package/dist/editor-completions/annotationValues.js +0 -146
- package/dist/editor-completions/contextParser.js +0 -410
- package/dist/editor-completions/dataTypes.js +0 -95
- package/dist/editor-completions/goToDefinition.js +0 -112
- package/dist/editor-completions/jsDocAnnotations.js +0 -359
- package/dist/editor-completions/modifierCompletions.js +0 -197
- package/dist/editor-completions/types.js +0 -8
- package/dist/export/index.js +0 -212
- package/dist/extensions/index.d.ts +0 -16
- package/dist/extensions/index.js +0 -17
- package/dist/friendly-errors.js +0 -761
- package/dist/function-like.js +0 -84
- package/dist/generator/async-detection.js +0 -56
- package/dist/generator/code-utils.js +0 -551
- package/dist/generator/compile-target-registry.d.ts +0 -20
- package/dist/generator/compile-target-registry.js +0 -20
- package/dist/generator/control-flow.js +0 -358
- package/dist/generator/dev-mode-registry.d.ts +0 -27
- package/dist/generator/dev-mode-registry.js +0 -20
- package/dist/generator/scope-function-generator.js +0 -397
- package/dist/generator/unified.js +0 -1831
- package/dist/generator.d.ts +0 -54
- package/dist/generator.js +0 -101
- package/dist/jsdoc-parser.d.ts +0 -377
- package/dist/jsdoc-parser.js +0 -1189
- package/dist/jsdoc-port-sync/constants.js +0 -103
- package/dist/jsdoc-port-sync/diff.js +0 -319
- package/dist/jsdoc-port-sync/port-parser.js +0 -579
- package/dist/jsdoc-port-sync/rename.js +0 -256
- package/dist/jsdoc-port-sync/signature-parser.js +0 -559
- package/dist/jsdoc-port-sync/sync.js +0 -653
- package/dist/marketplace/manifest.js +0 -194
- package/dist/marketplace/registry.js +0 -198
- package/dist/marketplace/types.js +0 -9
- package/dist/marketplace/validator.js +0 -150
- package/dist/mcp/agent-channel.d.ts +0 -35
- package/dist/mcp/agent-channel.js +0 -61
- package/dist/mcp/auto-registration.d.ts +0 -3
- package/dist/mcp/auto-registration.js +0 -62
- package/dist/mcp/debug-session.js +0 -25
- package/dist/mcp/index.d.ts +0 -11
- package/dist/mcp/index.js +0 -10
- package/dist/mcp/pack-tools.js +0 -59
- package/dist/mcp/prompts.js +0 -68
- package/dist/mcp/response-utils.js +0 -97
- package/dist/mcp/run-registry.d.ts +0 -29
- package/dist/mcp/run-registry.js +0 -24
- package/dist/mcp/server.js +0 -63
- package/dist/mcp/tools-context.js +0 -51
- package/dist/mcp/tools-debug.js +0 -451
- package/dist/mcp/tools-diagram.js +0 -97
- package/dist/mcp/tools-docs.js +0 -62
- package/dist/mcp/tools-export.js +0 -222
- package/dist/mcp/tools-marketplace.js +0 -132
- package/dist/mcp/tools-model.d.ts +0 -3
- package/dist/mcp/tools-model.js +0 -279
- package/dist/mcp/tools-pattern.d.ts +0 -3
- package/dist/mcp/tools-pattern.js +0 -604
- package/dist/mcp/tools-query.js +0 -377
- package/dist/mcp/tools-resources.js +0 -41
- package/dist/mcp/tools-template.js +0 -119
- package/dist/mcp/tools-workflow-run.d.ts +0 -40
- package/dist/mcp/tools-workflow-run.js +0 -150
- package/dist/mcp/types.js +0 -2
- package/dist/mcp/workflow-executor.js +0 -242
- package/dist/migration/registry.js +0 -29
- package/dist/node-types-generator.js +0 -139
- package/dist/parser/tag-registry.js +0 -57
- package/dist/parser.d.ts +0 -232
- package/dist/parser.js +0 -2500
- package/dist/plugin/PluginPanel.d.ts +0 -12
- package/dist/plugin/PluginPanel.js +0 -5
- package/dist/plugin/index.d.ts +0 -13
- package/dist/plugin/index.js +0 -14
- package/dist/plugin/types.d.ts +0 -75
- package/dist/plugin/types.js +0 -8
- package/dist/resolve-package-types.js +0 -123
- package/dist/runtime/CancellationError.js +0 -20
- package/dist/runtime/ExecutionContext.js +0 -280
- package/dist/runtime/builtin-functions.js +0 -549
- package/dist/runtime/checkpoint.d.ts +0 -84
- package/dist/runtime/checkpoint.js +0 -225
- package/dist/runtime/debug-controller.js +0 -247
- package/dist/runtime/events.js +0 -2
- package/dist/runtime/function-registry.js +0 -66
- package/dist/runtime/parameter-resolver.js +0 -113
- package/dist/server/types.js +0 -5
- package/dist/server/webhook-server.js +0 -269
- package/dist/server/workflow-registry.js +0 -202
- package/dist/shared-project.js +0 -28
- package/dist/sugar-optimizer.js +0 -498
- package/dist/testing/assertions.js +0 -127
- package/dist/testing/mock-approval.js +0 -98
- package/dist/testing/mock-llm.js +0 -119
- package/dist/testing/recorder.js +0 -70
- package/dist/testing/replayer.js +0 -143
- package/dist/testing/token-tracker.js +0 -94
- package/dist/type-checker.d.ts +0 -42
- package/dist/type-checker.js +0 -190
- package/dist/type-mappings.js +0 -131
- package/dist/types/branded-ports.d.ts +0 -151
- package/dist/types/branded-ports.js +0 -121
- package/dist/types/index.d.ts +0 -5
- package/dist/types/index.js +0 -5
- package/dist/types.d.ts +0 -139
- package/dist/types.js +0 -15
- package/dist/utils/error-utils.js +0 -27
- package/dist/utils/lru-cache.js +0 -40
- package/dist/utils/port-ordering.js +0 -81
- package/dist/utils/port-tag-utils.js +0 -41
- package/dist/utils/string-distance.js +0 -56
- package/dist/validation/agent-detection.js +0 -115
- package/dist/validation/agent-rules.js +0 -262
- package/dist/validation/design-rules.js +0 -377
- package/dist/validation/rule-registry.js +0 -37
- package/dist/validator.d.ts +0 -113
- package/dist/validator.js +0 -1631
- package/docs/reference/patterns.md +0 -159
- package/scripts/postinstall.cjs +0 -86
- /package/dist/{shared-project.d.ts → parser/shared-project.d.ts} +0 -0
- /package/dist/{friendly-errors.d.ts → validation/friendly-errors.d.ts} +0 -0
package/dist/validator.js
DELETED
|
@@ -1,1631 +0,0 @@
|
|
|
1
|
-
import { RESERVED_NODE_NAMES, isStartNode, isExitNode, isPseudoNode, isExecutePort, isReservedNodeName, VALID_NODE_COLORS, EXECUTION_STRATEGIES, } from './constants.js';
|
|
2
|
-
import { findClosestMatches } from './utils/string-distance.js';
|
|
3
|
-
import { parseFunctionSignature } from './jsdoc-port-sync/signature-parser.js';
|
|
4
|
-
import { checkTypeCompatibilityFromStrings } from './type-checker.js';
|
|
5
|
-
import { isValidPortType } from './type-mappings.js';
|
|
6
|
-
import { VALID_NODE_ICONS } from './diagram/theme.js';
|
|
7
|
-
const DOCS_BASE = 'https://docs.flowweaver.dev/reference';
|
|
8
|
-
/** Map error codes to the documentation page that explains how to fix them. */
|
|
9
|
-
const ERROR_DOC_URLS = {
|
|
10
|
-
UNKNOWN_NODE_TYPE: `${DOCS_BASE}/concepts#node-registration`,
|
|
11
|
-
UNKNOWN_SOURCE_PORT: `${DOCS_BASE}/concepts#port-architecture`,
|
|
12
|
-
UNKNOWN_TARGET_PORT: `${DOCS_BASE}/concepts#port-architecture`,
|
|
13
|
-
TYPE_MISMATCH: `${DOCS_BASE}/compilation#type-compatibility`,
|
|
14
|
-
UNREACHABLE_NODE: `${DOCS_BASE}/concepts#graph-structure`,
|
|
15
|
-
MISSING_START_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
|
|
16
|
-
MISSING_EXIT_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
|
|
17
|
-
INFERRED_NODE_TYPE: `${DOCS_BASE}/node-conversion`,
|
|
18
|
-
DUPLICATE_CONNECTION: `${DOCS_BASE}/concepts#connections`,
|
|
19
|
-
STUB_NODE: `${DOCS_BASE}/model-driven#stub-nodes`,
|
|
20
|
-
COERCE_TYPE_MISMATCH: `${DOCS_BASE}/compilation#type-coercion`,
|
|
21
|
-
REDUNDANT_COERCE: `${DOCS_BASE}/compilation#type-coercion`,
|
|
22
|
-
COERCE_ON_FUNCTION_PORT: `${DOCS_BASE}/compilation#type-coercion`,
|
|
23
|
-
};
|
|
24
|
-
/** Map coerce type to the dataType it produces */
|
|
25
|
-
const COERCE_OUTPUT_TYPE = {
|
|
26
|
-
string: 'STRING', number: 'NUMBER', boolean: 'BOOLEAN',
|
|
27
|
-
json: 'STRING', object: 'OBJECT',
|
|
28
|
-
};
|
|
29
|
-
/** Suggest the correct `as <type>` for a given target dataType */
|
|
30
|
-
function suggestCoerceType(targetType) {
|
|
31
|
-
switch (targetType) {
|
|
32
|
-
case 'STRING': return 'string';
|
|
33
|
-
case 'NUMBER': return 'number';
|
|
34
|
-
case 'BOOLEAN': return 'boolean';
|
|
35
|
-
case 'OBJECT': return 'object';
|
|
36
|
-
default: return '<type>';
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export class WorkflowValidator {
|
|
40
|
-
errors = [];
|
|
41
|
-
warnings = [];
|
|
42
|
-
strictMode = false;
|
|
43
|
-
draftMode = false;
|
|
44
|
-
/** Look up instance sourceLocation by instance ID */
|
|
45
|
-
getInstanceLocation(workflow, instanceId) {
|
|
46
|
-
const instance = workflow.instances.find((inst) => inst.id === instanceId);
|
|
47
|
-
return instance?.sourceLocation;
|
|
48
|
-
}
|
|
49
|
-
/** Look up connection sourceLocation */
|
|
50
|
-
getConnectionLocation(conn) {
|
|
51
|
-
return conn.sourceLocation;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Validate a single node type for scoped port requirements
|
|
55
|
-
*
|
|
56
|
-
* Scoped Port Architecture Rules:
|
|
57
|
-
* - Scope names must be valid JavaScript identifiers
|
|
58
|
-
*
|
|
59
|
-
* Per-Port Scope Architecture:
|
|
60
|
-
* - Scoped OUTPUT ports become callback PARAMETERS (data flows to children)
|
|
61
|
-
* - Scoped INPUT ports become callback RETURN VALUES (data flows from children)
|
|
62
|
-
* - Scoped ports can be ANY data type - they're not functions themselves
|
|
63
|
-
* - The callback function is passed as a function parameter (e.g., forEach's itemProcessor)
|
|
64
|
-
*
|
|
65
|
-
* NOTE: execute/onSuccess/onFailure ports are mandatory base interface ports
|
|
66
|
-
* that are auto-added to ALL nodes - no validation needed for those.
|
|
67
|
-
*/
|
|
68
|
-
validateNodeType(nodeType) {
|
|
69
|
-
const errors = [];
|
|
70
|
-
// Get all scoped ports (both INPUT and OUTPUT)
|
|
71
|
-
const scopedPorts = [
|
|
72
|
-
...Object.entries(nodeType.inputs).filter(([_, portDef]) => portDef.scope !== undefined),
|
|
73
|
-
...Object.entries(nodeType.outputs).filter(([_, portDef]) => portDef.scope !== undefined),
|
|
74
|
-
];
|
|
75
|
-
// Rule: Validate scope names are valid JavaScript identifiers
|
|
76
|
-
const scopeNameRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
77
|
-
scopedPorts.forEach(([portName, portDef]) => {
|
|
78
|
-
if (portDef.scope && !scopeNameRegex.test(portDef.scope)) {
|
|
79
|
-
errors.push(`Port "${portName}" has invalid scope name "${portDef.scope}". Scope names must be valid JavaScript identifiers (letters, numbers, underscore, dollar sign; cannot start with number).`);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
// Note: Scoped ports can be ANY data type in the per-port scope architecture
|
|
83
|
-
// They become callback parameters/returns, not functions themselves
|
|
84
|
-
return errors;
|
|
85
|
-
}
|
|
86
|
-
validate(workflow, options) {
|
|
87
|
-
this.errors = [];
|
|
88
|
-
this.warnings = [];
|
|
89
|
-
this.strictMode = options?.strictMode ?? false;
|
|
90
|
-
this.draftMode = options?.mode === 'draft';
|
|
91
|
-
const nodeTypeMap = new Map();
|
|
92
|
-
// Map by both functionName and name to support npm nodes (name='npm/pkg/func', functionName='func')
|
|
93
|
-
workflow.nodeTypes.forEach((nodeType) => {
|
|
94
|
-
nodeTypeMap.set(nodeType.functionName, nodeType);
|
|
95
|
-
if (nodeType.name !== nodeType.functionName) {
|
|
96
|
-
nodeTypeMap.set(nodeType.name, nodeType);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
// Build instance map: instance ID -> node type
|
|
100
|
-
const instanceMap = new Map();
|
|
101
|
-
workflow.instances.forEach((instance) => {
|
|
102
|
-
// Check both name (for npm nodes like 'npm/pkg/func') and functionName (for local nodes)
|
|
103
|
-
const nodeType = workflow.nodeTypes.find((nt) => nt.name === instance.nodeType || nt.functionName === instance.nodeType);
|
|
104
|
-
if (nodeType) {
|
|
105
|
-
instanceMap.set(instance.id, nodeType);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// Check if the function exists but is unannotated
|
|
109
|
-
const isUnannotatedFunction = workflow.availableFunctionNames?.includes(instance.nodeType) ?? false;
|
|
110
|
-
let hint;
|
|
111
|
-
if (isUnannotatedFunction) {
|
|
112
|
-
hint = ` Function "${instance.nodeType}" exists but has no @flowWeaver nodeType annotation. Add /** @flowWeaver nodeType */ above it.`;
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
const availableTypes = workflow.nodeTypes.map((nt) => nt.functionName);
|
|
116
|
-
const suggestions = findClosestMatches(instance.nodeType, availableTypes);
|
|
117
|
-
hint = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
118
|
-
}
|
|
119
|
-
this.errors.push({
|
|
120
|
-
type: 'error',
|
|
121
|
-
code: 'UNKNOWN_NODE_TYPE',
|
|
122
|
-
message: `Node "${instance.id}" references unknown node type "${instance.nodeType}".${hint}`,
|
|
123
|
-
node: instance.id,
|
|
124
|
-
location: instance.sourceLocation,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
// Info diagnostic for auto-inferred node types
|
|
129
|
-
workflow.instances.forEach((instance) => {
|
|
130
|
-
// Check both name (for npm nodes like 'npm/pkg/func') and functionName (for local nodes)
|
|
131
|
-
const nodeType = workflow.nodeTypes.find((nt) => nt.name === instance.nodeType || nt.functionName === instance.nodeType);
|
|
132
|
-
if (nodeType?.inferred && nodeType.variant !== 'STUB') {
|
|
133
|
-
this.warnings.push({
|
|
134
|
-
type: 'warning',
|
|
135
|
-
code: 'INFERRED_NODE_TYPE',
|
|
136
|
-
message: `Node type "${instance.nodeType}" was auto-inferred from function signature (expression mode). Add @flowWeaver nodeType for explicit port control.`,
|
|
137
|
-
node: instance.id,
|
|
138
|
-
location: instance.sourceLocation,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
// Stub node diagnostics: always emit as errors, draft mode reclassifies at the end
|
|
143
|
-
workflow.instances.forEach((instance) => {
|
|
144
|
-
const nodeType = workflow.nodeTypes.find((nt) => nt.name === instance.nodeType || nt.functionName === instance.nodeType);
|
|
145
|
-
if (nodeType?.variant === 'STUB') {
|
|
146
|
-
this.errors.push({
|
|
147
|
-
type: 'error',
|
|
148
|
-
code: 'STUB_NODE',
|
|
149
|
-
message: `Node "${instance.id}" uses stub type "${instance.nodeType}" which has no implementation. Use draft mode to validate structure, or implement the node.`,
|
|
150
|
-
node: instance.id,
|
|
151
|
-
location: instance.sourceLocation,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
// Structural validation
|
|
156
|
-
this.validateStructure(workflow);
|
|
157
|
-
this.validateDuplicateNodeNames(workflow);
|
|
158
|
-
this.validateDuplicateInstanceIds(workflow);
|
|
159
|
-
this.validateMutableBindings(workflow);
|
|
160
|
-
// Connection and node validation
|
|
161
|
-
this.validateReservedNames(workflow, nodeTypeMap);
|
|
162
|
-
this.validateConnections(workflow, instanceMap);
|
|
163
|
-
this.validateDuplicateConnections(workflow);
|
|
164
|
-
this.validateNodeReferences(workflow, instanceMap);
|
|
165
|
-
this.validateTypeCompatibility(workflow, instanceMap);
|
|
166
|
-
this.validateRequiredInputs(workflow, instanceMap);
|
|
167
|
-
this.detectUnusedNodes(workflow, instanceMap);
|
|
168
|
-
this.validateStartAndExit(workflow);
|
|
169
|
-
this.validateDataFlow(workflow, instanceMap);
|
|
170
|
-
this.validateCycles(workflow);
|
|
171
|
-
this.validateMultipleInputConnections(workflow, instanceMap);
|
|
172
|
-
this.validateAnnotationSignatureConsistency(workflow);
|
|
173
|
-
this.validateVisualAnnotations(workflow, instanceMap);
|
|
174
|
-
this.validatePortTypes(workflow);
|
|
175
|
-
this.validatePortConfigReferences(workflow, instanceMap);
|
|
176
|
-
this.validateExecuteWhen(workflow);
|
|
177
|
-
this.validateScopeTopology(workflow, instanceMap);
|
|
178
|
-
// Deduplicate cascading errors: if a node has UNKNOWN_NODE_TYPE,
|
|
179
|
-
// suppress UNKNOWN_SOURCE_NODE, UNKNOWN_TARGET_NODE, and UNDEFINED_NODE
|
|
180
|
-
// that reference the same node IDs (they're just noise).
|
|
181
|
-
const unknownTypeInstanceIds = new Set(workflow.instances.filter((inst) => !nodeTypeMap.has(inst.nodeType)).map((inst) => inst.id));
|
|
182
|
-
if (unknownTypeInstanceIds.size > 0) {
|
|
183
|
-
this.errors = this.errors.filter((error) => {
|
|
184
|
-
if (error.code === 'UNKNOWN_NODE_TYPE')
|
|
185
|
-
return true; // Always keep root cause
|
|
186
|
-
// Suppress cascading errors that reference unknown-type instances
|
|
187
|
-
const cascadingCodes = new Set([
|
|
188
|
-
'UNKNOWN_SOURCE_NODE',
|
|
189
|
-
'UNKNOWN_TARGET_NODE',
|
|
190
|
-
'UNDEFINED_NODE',
|
|
191
|
-
'MISSING_REQUIRED_INPUT',
|
|
192
|
-
]);
|
|
193
|
-
if (!cascadingCodes.has(error.code))
|
|
194
|
-
return true;
|
|
195
|
-
// Check if this error references an unknown-type instance
|
|
196
|
-
if (error.node && unknownTypeInstanceIds.has(error.node))
|
|
197
|
-
return false;
|
|
198
|
-
if (error.connection) {
|
|
199
|
-
if (unknownTypeInstanceIds.has(error.connection.from.node))
|
|
200
|
-
return false;
|
|
201
|
-
if (unknownTypeInstanceIds.has(error.connection.to.node))
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
return true;
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
// Draft mode post-processing: reclassify stub-related errors as warnings
|
|
208
|
-
if (this.draftMode) {
|
|
209
|
-
const stubTypeNames = new Set(workflow.nodeTypes.filter((nt) => nt.variant === 'STUB').map((nt) => nt.functionName));
|
|
210
|
-
// Also index by name for npm-style nodes
|
|
211
|
-
workflow.nodeTypes.filter((nt) => nt.variant === 'STUB').forEach((nt) => {
|
|
212
|
-
stubTypeNames.add(nt.name);
|
|
213
|
-
});
|
|
214
|
-
const stubInstanceIds = new Set(workflow.instances
|
|
215
|
-
.filter((inst) => stubTypeNames.has(inst.nodeType))
|
|
216
|
-
.map((inst) => inst.id));
|
|
217
|
-
const promoted = [];
|
|
218
|
-
this.errors = this.errors.filter((err) => {
|
|
219
|
-
if (err.code === 'STUB_NODE') {
|
|
220
|
-
promoted.push({ ...err, type: 'warning' });
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
if (err.code === 'MISSING_REQUIRED_INPUT' && err.node && stubInstanceIds.has(err.node)) {
|
|
224
|
-
promoted.push({ ...err, type: 'warning' });
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
return true;
|
|
228
|
-
});
|
|
229
|
-
this.warnings.push(...promoted);
|
|
230
|
-
}
|
|
231
|
-
// Filter out warnings suppressed by per-instance [suppress: "CODE"] annotations
|
|
232
|
-
const suppressMap = new Map();
|
|
233
|
-
for (const inst of workflow.instances) {
|
|
234
|
-
if (inst.config?.suppressWarnings?.length) {
|
|
235
|
-
suppressMap.set(inst.id, new Set(inst.config.suppressWarnings));
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (suppressMap.size > 0) {
|
|
239
|
-
this.warnings = this.warnings.filter((w) => {
|
|
240
|
-
if (!w.node)
|
|
241
|
-
return true;
|
|
242
|
-
const codes = suppressMap.get(w.node);
|
|
243
|
-
return !codes || !codes.has(w.code);
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
// Attach doc URLs to diagnostics that have mapped error codes
|
|
247
|
-
for (const diag of [...this.errors, ...this.warnings]) {
|
|
248
|
-
if (!diag.docUrl && ERROR_DOC_URLS[diag.code]) {
|
|
249
|
-
diag.docUrl = ERROR_DOC_URLS[diag.code];
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return {
|
|
253
|
-
valid: this.errors.length === 0,
|
|
254
|
-
errors: this.errors,
|
|
255
|
-
warnings: this.warnings,
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
validateStructure(workflow) {
|
|
259
|
-
if (!workflow.name) {
|
|
260
|
-
this.errors.push({
|
|
261
|
-
type: 'error',
|
|
262
|
-
code: 'MISSING_WORKFLOW_NAME',
|
|
263
|
-
message: 'Workflow must have a name',
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
if (!workflow.functionName) {
|
|
267
|
-
this.errors.push({
|
|
268
|
-
type: 'error',
|
|
269
|
-
code: 'MISSING_FUNCTION_NAME',
|
|
270
|
-
message: 'Workflow must have a functionName',
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
validateDuplicateNodeNames(workflow) {
|
|
275
|
-
const nodeNames = new Set();
|
|
276
|
-
workflow.nodeTypes.forEach((nodeType) => {
|
|
277
|
-
if (nodeNames.has(nodeType.functionName)) {
|
|
278
|
-
this.errors.push({
|
|
279
|
-
type: 'error',
|
|
280
|
-
code: 'DUPLICATE_NODE_NAME',
|
|
281
|
-
message: `Duplicate node type name: "${nodeType.functionName}"`,
|
|
282
|
-
node: nodeType.functionName,
|
|
283
|
-
location: nodeType.sourceLocation,
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
nodeNames.add(nodeType.functionName);
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
validateMutableBindings(workflow) {
|
|
290
|
-
workflow.nodeTypes.forEach((nodeType) => {
|
|
291
|
-
if (nodeType.declarationKind && nodeType.declarationKind !== 'const') {
|
|
292
|
-
this.warnings.push({
|
|
293
|
-
type: 'warning',
|
|
294
|
-
code: 'MUTABLE_NODE_TYPE_BINDING',
|
|
295
|
-
message: `Node type "${nodeType.functionName}" is declared with "${nodeType.declarationKind}" instead of "const". Use "const" to prevent accidental reassignment.`,
|
|
296
|
-
node: nodeType.functionName,
|
|
297
|
-
location: nodeType.sourceLocation,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
validateReservedNames(workflow, nodeTypeMap) {
|
|
303
|
-
// Check for node types with reserved names (Start, Exit)
|
|
304
|
-
// Note: Port name validation is done during parsing
|
|
305
|
-
nodeTypeMap.forEach((nodeType, nodeName) => {
|
|
306
|
-
if (isReservedNodeName(nodeName)) {
|
|
307
|
-
this.errors.push({
|
|
308
|
-
type: 'error',
|
|
309
|
-
code: 'RESERVED_NODE_NAME',
|
|
310
|
-
message: `Node type name "${nodeName}" is reserved. Reserved node names: ${Object.values(RESERVED_NODE_NAMES).join(', ')}`,
|
|
311
|
-
node: nodeName,
|
|
312
|
-
location: nodeType.sourceLocation,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
// Check for instances with reserved IDs
|
|
317
|
-
workflow.instances.forEach((instance) => {
|
|
318
|
-
if (isReservedNodeName(instance.id)) {
|
|
319
|
-
this.errors.push({
|
|
320
|
-
type: 'error',
|
|
321
|
-
code: 'RESERVED_INSTANCE_ID',
|
|
322
|
-
message: `Instance ID "${instance.id}" is reserved. Reserved names: ${Object.values(RESERVED_NODE_NAMES).join(', ')}`,
|
|
323
|
-
node: instance.id,
|
|
324
|
-
location: instance.sourceLocation,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
validateConnections(workflow, instanceMap) {
|
|
330
|
-
workflow.connections.forEach((conn, _index) => {
|
|
331
|
-
const fromNode = conn.from.node;
|
|
332
|
-
const fromPort = conn.from.port;
|
|
333
|
-
const connLocation = this.getConnectionLocation(conn);
|
|
334
|
-
if (!isStartNode(fromNode) && !isPseudoNode(fromNode) && !instanceMap.has(fromNode)) {
|
|
335
|
-
const instanceIds = [...instanceMap.keys()];
|
|
336
|
-
const suggestions = findClosestMatches(fromNode, instanceIds);
|
|
337
|
-
const suggestion = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
338
|
-
this.errors.push({
|
|
339
|
-
type: 'error',
|
|
340
|
-
code: 'UNKNOWN_SOURCE_NODE',
|
|
341
|
-
message: `Connection references unknown source node: "${fromNode}"${suggestion}`,
|
|
342
|
-
connection: conn,
|
|
343
|
-
location: connLocation,
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
const toNode = conn.to.node;
|
|
347
|
-
const toPort = conn.to.port;
|
|
348
|
-
if (!isExitNode(toNode) && !instanceMap.has(toNode)) {
|
|
349
|
-
const instanceIds = [...instanceMap.keys()];
|
|
350
|
-
const suggestions = findClosestMatches(toNode, instanceIds);
|
|
351
|
-
const suggestion = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
352
|
-
this.errors.push({
|
|
353
|
-
type: 'error',
|
|
354
|
-
code: 'UNKNOWN_TARGET_NODE',
|
|
355
|
-
message: `Connection references unknown target node: "${toNode}"${suggestion}`,
|
|
356
|
-
connection: conn,
|
|
357
|
-
location: connLocation,
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
if (!isStartNode(fromNode)) {
|
|
361
|
-
const sourceNode = instanceMap.get(fromNode);
|
|
362
|
-
if (sourceNode && !sourceNode.outputs.hasOwnProperty(fromPort)) {
|
|
363
|
-
const portNames = Object.keys(sourceNode.outputs);
|
|
364
|
-
const suggestions = findClosestMatches(fromPort, portNames);
|
|
365
|
-
const suggestion = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
366
|
-
this.errors.push({
|
|
367
|
-
type: 'error',
|
|
368
|
-
code: 'UNKNOWN_SOURCE_PORT',
|
|
369
|
-
message: `Node "${fromNode}" does not have output port "${fromPort}"${suggestion}`,
|
|
370
|
-
node: fromNode,
|
|
371
|
-
connection: conn,
|
|
372
|
-
location: connLocation,
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
// Validate Start output port against declared @param ports
|
|
378
|
-
// 'execute' is always implicit on Start
|
|
379
|
-
const validStartPorts = new Set(['execute', ...Object.keys(workflow.startPorts)]);
|
|
380
|
-
if (!validStartPorts.has(fromPort)) {
|
|
381
|
-
const portNames = Array.from(validStartPorts);
|
|
382
|
-
const suggestions = findClosestMatches(fromPort, portNames);
|
|
383
|
-
let hint;
|
|
384
|
-
if (suggestions.length > 0) {
|
|
385
|
-
hint = ` Did you mean "${suggestions[0]}"?`;
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
hint = `\nAdd '@param ${fromPort}' to the workflow JSDoc and include it in the params object:\n(execute: boolean, params: { ${fromPort}: type, ... })`;
|
|
389
|
-
}
|
|
390
|
-
this.errors.push({
|
|
391
|
-
type: 'error',
|
|
392
|
-
code: 'UNKNOWN_SOURCE_PORT',
|
|
393
|
-
message: `Start node does not have output port "${fromPort}".${hint}`,
|
|
394
|
-
node: fromNode,
|
|
395
|
-
connection: conn,
|
|
396
|
-
location: connLocation,
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (!isExitNode(toNode)) {
|
|
401
|
-
const targetNode = instanceMap.get(toNode);
|
|
402
|
-
if (targetNode && !targetNode.inputs.hasOwnProperty(toPort)) {
|
|
403
|
-
const portNames = Object.keys(targetNode.inputs);
|
|
404
|
-
const suggestions = findClosestMatches(toPort, portNames);
|
|
405
|
-
const suggestion = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
406
|
-
this.errors.push({
|
|
407
|
-
type: 'error',
|
|
408
|
-
code: 'UNKNOWN_TARGET_PORT',
|
|
409
|
-
message: `Node "${toNode}" does not have input port "${toPort}"${suggestion}`,
|
|
410
|
-
node: toNode,
|
|
411
|
-
connection: conn,
|
|
412
|
-
location: connLocation,
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
// Validate Exit input port against declared @returns ports
|
|
418
|
-
// 'onSuccess' and 'onFailure' are always implicit on Exit
|
|
419
|
-
const validExitPorts = new Set([
|
|
420
|
-
'onSuccess',
|
|
421
|
-
'onFailure',
|
|
422
|
-
...Object.keys(workflow.exitPorts),
|
|
423
|
-
]);
|
|
424
|
-
if (!validExitPorts.has(toPort)) {
|
|
425
|
-
const portNames = Array.from(validExitPorts);
|
|
426
|
-
const suggestions = findClosestMatches(toPort, portNames);
|
|
427
|
-
const suggestion = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
428
|
-
this.errors.push({
|
|
429
|
-
type: 'error',
|
|
430
|
-
code: 'UNKNOWN_TARGET_PORT',
|
|
431
|
-
message: `Exit node does not have input port "${toPort}"${suggestion}`,
|
|
432
|
-
node: toNode,
|
|
433
|
-
connection: conn,
|
|
434
|
-
location: connLocation,
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Validate type compatibility for connections with coercion support
|
|
442
|
-
*
|
|
443
|
-
* Type coercion rules:
|
|
444
|
-
* - ANY type connections always allowed (no warning)
|
|
445
|
-
* - Safe coercions: NUMBER → STRING, BOOLEAN → STRING (no warning)
|
|
446
|
-
* - Lossy coercions: STRING → NUMBER, STRING → BOOLEAN, OBJECT → STRING (warning or error in strict mode)
|
|
447
|
-
* - Unusual coercions: NUMBER → BOOLEAN, BOOLEAN → NUMBER (warning or error in strict mode)
|
|
448
|
-
* - Same type connections always allowed (no warning)
|
|
449
|
-
*
|
|
450
|
-
* Strict types mode (@strictTypes):
|
|
451
|
-
* - When enabled, type incompatibilities are errors instead of warnings
|
|
452
|
-
*/
|
|
453
|
-
validateTypeCompatibility(workflow, instanceMap) {
|
|
454
|
-
const strictTypes = this.strictMode || workflow.options?.strictTypes === true;
|
|
455
|
-
// Helper to push to errors or warnings based on strictTypes mode
|
|
456
|
-
const pushTypeIssue = (issue, isIncompatible = false) => {
|
|
457
|
-
if (strictTypes && isIncompatible) {
|
|
458
|
-
this.errors.push({ ...issue, type: 'error', code: 'TYPE_INCOMPATIBLE' });
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
this.warnings.push(issue);
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
workflow.connections.forEach((conn) => {
|
|
465
|
-
const fromNode = conn.from.node;
|
|
466
|
-
const fromPort = conn.from.port;
|
|
467
|
-
const toNode = conn.to.node;
|
|
468
|
-
const toPort = conn.to.port;
|
|
469
|
-
const connLocation = this.getConnectionLocation(conn);
|
|
470
|
-
// Skip Start and Exit nodes (they handle types dynamically)
|
|
471
|
-
if (isStartNode(fromNode) || isExitNode(toNode)) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
// Get source and target port types
|
|
475
|
-
const sourceNode = instanceMap.get(fromNode);
|
|
476
|
-
const targetNode = instanceMap.get(toNode);
|
|
477
|
-
if (!sourceNode || !targetNode) {
|
|
478
|
-
return; // Already caught by validateConnections
|
|
479
|
-
}
|
|
480
|
-
const sourcePortDef = sourceNode.outputs[fromPort];
|
|
481
|
-
const targetPortDef = targetNode.inputs[toPort];
|
|
482
|
-
if (!sourcePortDef || !targetPortDef) {
|
|
483
|
-
return; // Already caught by validateConnections
|
|
484
|
-
}
|
|
485
|
-
const sourceType = sourcePortDef.dataType;
|
|
486
|
-
const targetType = targetPortDef.dataType;
|
|
487
|
-
const sourceTsType = sourcePortDef.tsType;
|
|
488
|
-
const targetTsType = targetPortDef.tsType;
|
|
489
|
-
// Validate STEP port connections - STEP must connect to STEP only
|
|
490
|
-
if (sourceType === 'STEP' && targetType !== 'STEP') {
|
|
491
|
-
this.errors.push({
|
|
492
|
-
type: 'error',
|
|
493
|
-
code: 'STEP_PORT_TYPE_MISMATCH',
|
|
494
|
-
message: `STEP port "${fromPort}" on node "${fromNode}" cannot connect to non-STEP port "${toPort}" (${this.formatType(targetType, targetTsType)}) on node "${toNode}"`,
|
|
495
|
-
connection: conn,
|
|
496
|
-
location: connLocation,
|
|
497
|
-
});
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
if (targetType === 'STEP' && sourceType !== 'STEP') {
|
|
501
|
-
this.errors.push({
|
|
502
|
-
type: 'error',
|
|
503
|
-
code: 'STEP_PORT_TYPE_MISMATCH',
|
|
504
|
-
message: `Non-STEP port "${fromPort}" (${this.formatType(sourceType, sourceTsType)}) on node "${fromNode}" cannot connect to STEP port "${toPort}" on node "${toNode}"`,
|
|
505
|
-
connection: conn,
|
|
506
|
-
location: connLocation,
|
|
507
|
-
});
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
// Skip further type checking for STEP-to-STEP (valid control flow)
|
|
511
|
-
if (sourceType === 'STEP' && targetType === 'STEP') {
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
// Block coercion on FUNCTION ports — coercing a function value is nonsensical
|
|
515
|
-
if (conn.coerce && (sourceType === 'FUNCTION' || targetType === 'FUNCTION')) {
|
|
516
|
-
this.errors.push({
|
|
517
|
-
type: 'error',
|
|
518
|
-
code: 'COERCE_ON_FUNCTION_PORT',
|
|
519
|
-
message: `Coercion \`as ${conn.coerce}\` cannot be used on FUNCTION ports in connection "${fromNode}.${fromPort}" → "${toNode}.${toPort}". FUNCTION values cannot be meaningfully coerced.`,
|
|
520
|
-
connection: conn,
|
|
521
|
-
location: connLocation,
|
|
522
|
-
});
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
// Same type - check for structural compatibility if both are OBJECT
|
|
526
|
-
if (sourceType === targetType) {
|
|
527
|
-
// Redundant coerce: same types don't need coercion
|
|
528
|
-
if (conn.coerce) {
|
|
529
|
-
this.warnings.push({
|
|
530
|
-
type: 'warning',
|
|
531
|
-
code: 'REDUNDANT_COERCE',
|
|
532
|
-
message: `Coercion \`as ${conn.coerce}\` on connection "${fromNode}.${fromPort}" → "${toNode}.${toPort}" is redundant — source and target are both ${sourceType}.`,
|
|
533
|
-
connection: conn,
|
|
534
|
-
location: connLocation,
|
|
535
|
-
});
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
// For OBJECT types, check if tsType differs (structural mismatch)
|
|
539
|
-
if (sourceType === 'OBJECT' && sourcePortDef.tsType && targetPortDef.tsType) {
|
|
540
|
-
const normalizedSource = this.normalizeTypeString(sourcePortDef.tsType);
|
|
541
|
-
const normalizedTarget = this.normalizeTypeString(targetPortDef.tsType);
|
|
542
|
-
if (normalizedSource !== normalizedTarget) {
|
|
543
|
-
// Use string-based compatibility check to suppress false positives (e.g. when one side is 'any')
|
|
544
|
-
const compat = checkTypeCompatibilityFromStrings(sourcePortDef.tsType, targetPortDef.tsType);
|
|
545
|
-
if (!compat.isCompatible) {
|
|
546
|
-
this.warnings.push({
|
|
547
|
-
type: 'warning',
|
|
548
|
-
code: 'OBJECT_TYPE_MISMATCH',
|
|
549
|
-
message: `Structural type mismatch: ${fromNode}.${fromPort} outputs "${sourcePortDef.tsType}" but ${toNode}.${toPort} expects "${targetPortDef.tsType}". Verify the object shapes are compatible.`,
|
|
550
|
-
connection: conn,
|
|
551
|
-
location: connLocation,
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
// ANY type - always compatible (no warning)
|
|
559
|
-
if (sourceType === 'ANY' || targetType === 'ANY') {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
// Validate explicit coerce: check that produced type matches target
|
|
563
|
-
if (conn.coerce) {
|
|
564
|
-
const producedType = COERCE_OUTPUT_TYPE[conn.coerce];
|
|
565
|
-
if (producedType === targetType) {
|
|
566
|
-
return; // Coerce correctly resolves the mismatch
|
|
567
|
-
}
|
|
568
|
-
// Wrong coerce type — produced type doesn't match target
|
|
569
|
-
pushTypeIssue({
|
|
570
|
-
type: 'warning',
|
|
571
|
-
code: 'COERCE_TYPE_MISMATCH',
|
|
572
|
-
message: `Coercion \`as ${conn.coerce}\` produces ${producedType} but target port "${toPort}" on "${toNode}" expects ${targetType}. Use \`as ${suggestCoerceType(targetType)}\` instead.`,
|
|
573
|
-
connection: conn,
|
|
574
|
-
location: connLocation,
|
|
575
|
-
}, true);
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
// Safe coercions (no warning)
|
|
579
|
-
const safeCoercions = [
|
|
580
|
-
['NUMBER', 'STRING'],
|
|
581
|
-
['BOOLEAN', 'STRING'],
|
|
582
|
-
];
|
|
583
|
-
for (const [from, to] of safeCoercions) {
|
|
584
|
-
if (sourceType === from && targetType === to) {
|
|
585
|
-
return; // Safe coercion, no warning
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
// Lossy coercions (warning)
|
|
589
|
-
const lossyCoercions = [
|
|
590
|
-
['STRING', 'NUMBER', 'May result in NaN if string is not a valid number'],
|
|
591
|
-
['STRING', 'BOOLEAN', 'Will use JavaScript truthy/falsy conversion'],
|
|
592
|
-
['OBJECT', 'STRING', 'Will use JSON.stringify()'],
|
|
593
|
-
['ARRAY', 'STRING', 'Will use JSON.stringify()'],
|
|
594
|
-
];
|
|
595
|
-
for (const [from, to, reason] of lossyCoercions) {
|
|
596
|
-
if (sourceType === from && targetType === to) {
|
|
597
|
-
pushTypeIssue({
|
|
598
|
-
type: 'warning',
|
|
599
|
-
code: 'LOSSY_TYPE_COERCION',
|
|
600
|
-
message: `Lossy type coercion from ${this.formatType(sourceType, sourceTsType)} to ${this.formatType(targetType, targetTsType)} in connection ${fromNode}.${fromPort} → ${toNode}.${toPort}. ${reason}. Add @strictTypes to your workflow annotation to enforce type safety.`,
|
|
601
|
-
connection: conn,
|
|
602
|
-
location: connLocation,
|
|
603
|
-
}, true);
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
// Unusual coercions (warning)
|
|
608
|
-
const unusualCoercions = [
|
|
609
|
-
[
|
|
610
|
-
'NUMBER',
|
|
611
|
-
'BOOLEAN',
|
|
612
|
-
'Will use JavaScript truthy/falsy conversion (0 = false, non-zero = true)',
|
|
613
|
-
],
|
|
614
|
-
['BOOLEAN', 'NUMBER', 'Will convert false to 0, true to 1'],
|
|
615
|
-
['STRING', 'OBJECT', 'May fail if string is not valid JSON'],
|
|
616
|
-
['STRING', 'ARRAY', 'May fail if string is not valid JSON array'],
|
|
617
|
-
];
|
|
618
|
-
for (const [from, to, reason] of unusualCoercions) {
|
|
619
|
-
if (sourceType === from && targetType === to) {
|
|
620
|
-
pushTypeIssue({
|
|
621
|
-
type: 'warning',
|
|
622
|
-
code: 'UNUSUAL_TYPE_COERCION',
|
|
623
|
-
message: `Unusual type coercion from ${this.formatType(sourceType, sourceTsType)} to ${this.formatType(targetType, targetTsType)} in connection ${fromNode}.${fromPort} → ${toNode}.${toPort}. ${reason}.`,
|
|
624
|
-
connection: conn,
|
|
625
|
-
location: connLocation,
|
|
626
|
-
}, true);
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
// All other type mismatches
|
|
631
|
-
pushTypeIssue({
|
|
632
|
-
type: 'warning',
|
|
633
|
-
code: 'TYPE_MISMATCH',
|
|
634
|
-
message: `Type mismatch in connection ${fromNode}.${fromPort} (${this.formatType(sourceType, sourceTsType)}) → ${toNode}.${toPort} (${this.formatType(targetType, targetTsType)}). Runtime coercion will be attempted.`,
|
|
635
|
-
connection: conn,
|
|
636
|
-
location: connLocation,
|
|
637
|
-
}, true);
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
validateNodeReferences(workflow, instanceMap) {
|
|
641
|
-
const referencedNodes = new Set();
|
|
642
|
-
workflow.connections.forEach((conn) => {
|
|
643
|
-
const fromNode = conn.from.node;
|
|
644
|
-
const toNode = conn.to.node;
|
|
645
|
-
if (!isStartNode(fromNode) && !isExitNode(fromNode) && !isPseudoNode(fromNode)) {
|
|
646
|
-
referencedNodes.add(fromNode);
|
|
647
|
-
}
|
|
648
|
-
if (!isStartNode(toNode) && !isExitNode(toNode)) {
|
|
649
|
-
referencedNodes.add(toNode);
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
referencedNodes.forEach((nodeName) => {
|
|
653
|
-
if (!instanceMap.has(nodeName)) {
|
|
654
|
-
this.errors.push({
|
|
655
|
-
type: 'error',
|
|
656
|
-
code: 'UNDEFINED_NODE',
|
|
657
|
-
message: `Workflow references undefined node: "${nodeName}"`,
|
|
658
|
-
node: nodeName,
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
validateRequiredInputs(workflow, instanceMap) {
|
|
664
|
-
instanceMap.forEach((nodeType, instanceId) => {
|
|
665
|
-
// Find the instance to check for port-level constant expressions
|
|
666
|
-
const instance = workflow.instances.find((inst) => inst.id === instanceId);
|
|
667
|
-
Object.entries(nodeType.inputs).forEach(([portName, portConfig]) => {
|
|
668
|
-
if (isExecutePort(portName))
|
|
669
|
-
return;
|
|
670
|
-
// Skip scoped INPUT ports - they're provided by scope function execution, not external connections
|
|
671
|
-
if (portConfig.scope)
|
|
672
|
-
return;
|
|
673
|
-
// Check if instance has an expression for this port
|
|
674
|
-
const instancePortConfig = instance?.config?.portConfigs?.find((pc) => pc.portName === portName && (pc.direction == null || pc.direction === 'INPUT'));
|
|
675
|
-
const hasInstanceExpression = instancePortConfig?.expression !== undefined;
|
|
676
|
-
const isRequired = !portConfig.optional &&
|
|
677
|
-
portConfig.default === undefined &&
|
|
678
|
-
!portConfig.expression &&
|
|
679
|
-
!hasInstanceExpression;
|
|
680
|
-
if (isRequired) {
|
|
681
|
-
const isConnected = workflow.connections.some((conn) => {
|
|
682
|
-
return conn.to.node === instanceId && conn.to.port === portName;
|
|
683
|
-
});
|
|
684
|
-
if (!isConnected) {
|
|
685
|
-
this.errors.push({
|
|
686
|
-
type: 'error',
|
|
687
|
-
code: 'MISSING_REQUIRED_INPUT',
|
|
688
|
-
message: `Node "${instanceId}" has unconnected required input port "${portName}". Connect a value to it, or mark it optional with @input [${portName}].`,
|
|
689
|
-
node: instanceId,
|
|
690
|
-
location: instance?.sourceLocation,
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
});
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
detectUnusedNodes(workflow, instanceMap) {
|
|
698
|
-
const usedNodes = new Set();
|
|
699
|
-
workflow.connections.forEach((conn) => {
|
|
700
|
-
const fromNode = conn.from.node;
|
|
701
|
-
const toNode = conn.to.node;
|
|
702
|
-
if (!isStartNode(fromNode) && !isExitNode(fromNode)) {
|
|
703
|
-
usedNodes.add(fromNode);
|
|
704
|
-
}
|
|
705
|
-
if (!isStartNode(toNode) && !isExitNode(toNode)) {
|
|
706
|
-
usedNodes.add(toNode);
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
instanceMap.forEach((_nodeType, instanceId) => {
|
|
710
|
-
if (!usedNodes.has(instanceId)) {
|
|
711
|
-
this.warnings.push({
|
|
712
|
-
type: 'warning',
|
|
713
|
-
code: 'UNUSED_NODE',
|
|
714
|
-
message: `Node "${instanceId}" is defined but never used in workflow`,
|
|
715
|
-
node: instanceId,
|
|
716
|
-
location: this.getInstanceLocation(workflow, instanceId),
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
validateStartAndExit(workflow) {
|
|
722
|
-
const hasStartConnections = workflow.connections.some((conn) => {
|
|
723
|
-
return isStartNode(conn.from.node);
|
|
724
|
-
});
|
|
725
|
-
if (!hasStartConnections) {
|
|
726
|
-
this.warnings.push({
|
|
727
|
-
type: 'warning',
|
|
728
|
-
code: 'NO_START_CONNECTIONS',
|
|
729
|
-
message: 'Workflow has no connections from Start node',
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
const hasExitConnections = workflow.connections.some((conn) => {
|
|
733
|
-
return isExitNode(conn.to.node);
|
|
734
|
-
});
|
|
735
|
-
if (!hasExitConnections) {
|
|
736
|
-
this.warnings.push({
|
|
737
|
-
type: 'warning',
|
|
738
|
-
code: 'NO_EXIT_CONNECTIONS',
|
|
739
|
-
message: 'Workflow has no connections to Exit node (no return value)',
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
// Validate that onSuccess and onFailure exit ports are STEP type
|
|
743
|
-
if (workflow.exitPorts.onSuccess) {
|
|
744
|
-
if (workflow.exitPorts.onSuccess.dataType !== 'STEP') {
|
|
745
|
-
this.errors.push({
|
|
746
|
-
type: 'error',
|
|
747
|
-
code: 'INVALID_EXIT_PORT_TYPE',
|
|
748
|
-
message: "Exit port 'onSuccess' must be of type STEP (control flow), found: " +
|
|
749
|
-
workflow.exitPorts.onSuccess.dataType,
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
if (workflow.exitPorts.onFailure) {
|
|
754
|
-
if (workflow.exitPorts.onFailure.dataType !== 'STEP') {
|
|
755
|
-
this.errors.push({
|
|
756
|
-
type: 'error',
|
|
757
|
-
code: 'INVALID_EXIT_PORT_TYPE',
|
|
758
|
-
message: "Exit port 'onFailure' must be of type STEP (control flow), found: " +
|
|
759
|
-
workflow.exitPorts.onFailure.dataType,
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* Validate data flow in the workflow
|
|
766
|
-
*
|
|
767
|
-
* Checks for:
|
|
768
|
-
* - Unused output ports (data produced but never consumed)
|
|
769
|
-
* - Unreachable Exit ports (Exit expects data but no connection provides it)
|
|
770
|
-
* - Dead-end data paths (data that never reaches Exit)
|
|
771
|
-
*/
|
|
772
|
-
validateDataFlow(workflow, instanceMap) {
|
|
773
|
-
// Track which output ports are connected
|
|
774
|
-
const connectedOutputPorts = new Set();
|
|
775
|
-
workflow.connections.forEach((conn) => {
|
|
776
|
-
const portKey = `${conn.from.node}.${conn.from.port}`;
|
|
777
|
-
connectedOutputPorts.add(portKey);
|
|
778
|
-
});
|
|
779
|
-
// Check for unused output ports (excluding control flow ports and scoped ports)
|
|
780
|
-
instanceMap.forEach((nodeType, instanceId) => {
|
|
781
|
-
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
782
|
-
const portKey = `${instanceId}.${portName}`;
|
|
783
|
-
const portDef = nodeType.outputs[portName];
|
|
784
|
-
// Skip control flow ports (onSuccess, onFailure)
|
|
785
|
-
if (portDef.isControlFlow || portDef.failure) {
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
// Skip scoped output ports - they flow through the scope function, not external connections
|
|
789
|
-
if (portDef.scope) {
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
if (!connectedOutputPorts.has(portKey)) {
|
|
793
|
-
this.warnings.push({
|
|
794
|
-
type: 'warning',
|
|
795
|
-
code: 'UNUSED_OUTPUT_PORT',
|
|
796
|
-
message: `Output port "${portName}" of node "${instanceId}" is never connected. Data will be discarded.`,
|
|
797
|
-
node: instanceId,
|
|
798
|
-
location: this.getInstanceLocation(workflow, instanceId),
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
});
|
|
803
|
-
// Check for unreachable Exit ports and multiple connections to same Exit port
|
|
804
|
-
// Build a map of Exit ports to their incoming connections
|
|
805
|
-
const exitPortConnections = new Map();
|
|
806
|
-
workflow.connections.forEach((conn) => {
|
|
807
|
-
if (isExitNode(conn.to.node)) {
|
|
808
|
-
const port = conn.to.port;
|
|
809
|
-
if (!exitPortConnections.has(port)) {
|
|
810
|
-
exitPortConnections.set(port, []);
|
|
811
|
-
}
|
|
812
|
-
exitPortConnections.get(port).push(conn);
|
|
813
|
-
}
|
|
814
|
-
});
|
|
815
|
-
// Check if all Exit ports have connections
|
|
816
|
-
Object.entries(workflow.exitPorts).forEach(([portName, portDef]) => {
|
|
817
|
-
// Skip control flow ports
|
|
818
|
-
if (portDef.isControlFlow) {
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
if (!exitPortConnections.has(portName)) {
|
|
822
|
-
this.warnings.push({
|
|
823
|
-
type: 'warning',
|
|
824
|
-
code: 'UNREACHABLE_EXIT_PORT',
|
|
825
|
-
message: `Exit port "${portName}" has no incoming connection. Return value will be undefined.`,
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
});
|
|
829
|
-
// Check for multiple connections to the same Exit port
|
|
830
|
-
exitPortConnections.forEach((connections, portName) => {
|
|
831
|
-
if (connections.length > 1) {
|
|
832
|
-
// Skip control flow ports — multiple STEP connections to Exit.onSuccess
|
|
833
|
-
// is the standard convergence pattern for parallel terminal nodes
|
|
834
|
-
const exitPort = workflow.exitPorts[portName];
|
|
835
|
-
if (exitPort?.isControlFlow || exitPort?.dataType === 'STEP') {
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
const sourceNodes = connections.map((c) => c.from.node);
|
|
839
|
-
if (this.areMutuallyExclusive(sourceNodes, workflow, instanceMap)) {
|
|
840
|
-
return; // Suppress — mutually exclusive branches
|
|
841
|
-
}
|
|
842
|
-
const sources = connections.map((c) => `${c.from.node}.${c.from.port}`).join(', ');
|
|
843
|
-
this.warnings.push({
|
|
844
|
-
type: 'warning',
|
|
845
|
-
code: 'MULTIPLE_EXIT_CONNECTIONS',
|
|
846
|
-
message: `Exit port "${portName}" has ${connections.length} incoming connections (${sources}). Only one value will be used - consider using separate Exit ports.`,
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* Validate for cycles (loops) in the workflow graph.
|
|
853
|
-
* Cycles are connections that form a loop back to an earlier node.
|
|
854
|
-
* Scoped nodes handle loops internally, so cycles within same scope are checked.
|
|
855
|
-
*/
|
|
856
|
-
validateCycles(workflow) {
|
|
857
|
-
// Group instances by parent (scope layer)
|
|
858
|
-
const instancesByParent = new Map();
|
|
859
|
-
instancesByParent.set(null, []);
|
|
860
|
-
for (const instance of workflow.instances) {
|
|
861
|
-
const parentId = instance.parent?.id || null;
|
|
862
|
-
if (!instancesByParent.has(parentId)) {
|
|
863
|
-
instancesByParent.set(parentId, []);
|
|
864
|
-
}
|
|
865
|
-
instancesByParent.get(parentId).push(instance);
|
|
866
|
-
}
|
|
867
|
-
// Group connections by parent (only connections where both ends are in same scope)
|
|
868
|
-
const connectionsByParent = new Map();
|
|
869
|
-
connectionsByParent.set(null, []);
|
|
870
|
-
for (const connection of workflow.connections) {
|
|
871
|
-
const sourceInstance = workflow.instances.find((n) => n.id === connection.from.node);
|
|
872
|
-
const targetInstance = workflow.instances.find((n) => n.id === connection.to.node);
|
|
873
|
-
// Skip connections involving Start/Exit (they're virtual)
|
|
874
|
-
if (!sourceInstance || !targetInstance)
|
|
875
|
-
continue;
|
|
876
|
-
const sourceParent = sourceInstance.parent?.id || null;
|
|
877
|
-
const targetParent = targetInstance.parent?.id || null;
|
|
878
|
-
// Only check connections within the same scope
|
|
879
|
-
if (sourceParent === targetParent) {
|
|
880
|
-
if (!connectionsByParent.has(sourceParent)) {
|
|
881
|
-
connectionsByParent.set(sourceParent, []);
|
|
882
|
-
}
|
|
883
|
-
connectionsByParent.get(sourceParent).push(connection);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
// Run cycle detection per scope layer
|
|
887
|
-
for (const [parentId, instances] of instancesByParent.entries()) {
|
|
888
|
-
const connections = connectionsByParent.get(parentId) || [];
|
|
889
|
-
this.detectCyclesInLayer(parentId, instances, connections);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
detectCyclesInLayer(parentId, instances, connections) {
|
|
893
|
-
const visited = new Set();
|
|
894
|
-
const recursionStack = new Set();
|
|
895
|
-
const reportedCycles = new Set(); // Track reported cycles to avoid duplicates
|
|
896
|
-
// Self-loops are allowed (used for iteration patterns)
|
|
897
|
-
const selfLoopNodes = new Set(connections.filter((c) => c.from.node === c.to.node).map((c) => c.from.node));
|
|
898
|
-
// Exclude self-loops from cycle detection
|
|
899
|
-
const nonSelfLoopConnections = connections.filter((c) => c.from.node !== c.to.node);
|
|
900
|
-
const dfs = (nodeName, path) => {
|
|
901
|
-
if (recursionStack.has(nodeName)) {
|
|
902
|
-
// Node with self-loop is not considered a cycle entry point
|
|
903
|
-
if (selfLoopNodes.has(nodeName)) {
|
|
904
|
-
return false;
|
|
905
|
-
}
|
|
906
|
-
const cycleStart = path.indexOf(nodeName);
|
|
907
|
-
const cyclePath = [...path.slice(cycleStart), nodeName];
|
|
908
|
-
// Normalize cycle for deduplication (start from smallest node name)
|
|
909
|
-
const cycleNodes = cyclePath.slice(0, -1); // Remove duplicate end node
|
|
910
|
-
const sortedCycle = [...cycleNodes].sort();
|
|
911
|
-
const cycleKey = sortedCycle.join(',');
|
|
912
|
-
// Only report if not already reported
|
|
913
|
-
if (!reportedCycles.has(cycleKey)) {
|
|
914
|
-
reportedCycles.add(cycleKey);
|
|
915
|
-
const parentContext = parentId ? ` in scope "${parentId}"` : '';
|
|
916
|
-
const instance = instances.find((n) => n.id === nodeName);
|
|
917
|
-
this.errors.push({
|
|
918
|
-
type: 'error',
|
|
919
|
-
code: 'CYCLE_DETECTED',
|
|
920
|
-
message: `Loop detected${parentContext}: ${cyclePath.join(' -> ')}`,
|
|
921
|
-
node: nodeName,
|
|
922
|
-
location: instance?.sourceLocation,
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
return true;
|
|
926
|
-
}
|
|
927
|
-
if (visited.has(nodeName)) {
|
|
928
|
-
return false;
|
|
929
|
-
}
|
|
930
|
-
recursionStack.add(nodeName);
|
|
931
|
-
const newPath = [...path, nodeName];
|
|
932
|
-
const instance = instances.find((n) => n.id === nodeName);
|
|
933
|
-
if (!instance) {
|
|
934
|
-
recursionStack.delete(nodeName);
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
const outgoing = nonSelfLoopConnections.filter((c) => c.from.node === nodeName);
|
|
938
|
-
let hasCycle = false;
|
|
939
|
-
for (const conn of outgoing) {
|
|
940
|
-
if (dfs(conn.to.node, newPath)) {
|
|
941
|
-
hasCycle = true;
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
recursionStack.delete(nodeName);
|
|
945
|
-
if (!hasCycle) {
|
|
946
|
-
visited.add(nodeName);
|
|
947
|
-
}
|
|
948
|
-
return hasCycle;
|
|
949
|
-
};
|
|
950
|
-
// Run DFS from each node (but use shared visited/reportedCycles sets)
|
|
951
|
-
for (const instance of instances) {
|
|
952
|
-
if (!visited.has(instance.id)) {
|
|
953
|
-
dfs(instance.id, []);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Validate that no input port has multiple connections.
|
|
959
|
-
* Only one value can be received per input port.
|
|
960
|
-
* (STEP ports can have multiple connections as they're control flow)
|
|
961
|
-
*/
|
|
962
|
-
validateMultipleInputConnections(workflow, instanceMap) {
|
|
963
|
-
const inputConnections = new Map();
|
|
964
|
-
for (const conn of workflow.connections) {
|
|
965
|
-
const targetKey = `${conn.to.node}.${conn.to.port}`;
|
|
966
|
-
// Skip Exit node (handled separately in validateDataFlow)
|
|
967
|
-
if (isExitNode(conn.to.node))
|
|
968
|
-
continue;
|
|
969
|
-
// Get target port type to check if it's STEP or has mergeStrategy
|
|
970
|
-
const targetNodeType = instanceMap.get(conn.to.node);
|
|
971
|
-
if (targetNodeType) {
|
|
972
|
-
const targetPortDef = targetNodeType.inputs[conn.to.port];
|
|
973
|
-
// STEP ports can have multiple connections (control flow)
|
|
974
|
-
if (targetPortDef?.dataType === 'STEP')
|
|
975
|
-
continue;
|
|
976
|
-
// Ports with mergeStrategy can have multiple connections (fan-in)
|
|
977
|
-
if (targetPortDef?.mergeStrategy)
|
|
978
|
-
continue;
|
|
979
|
-
}
|
|
980
|
-
if (!inputConnections.has(targetKey)) {
|
|
981
|
-
inputConnections.set(targetKey, []);
|
|
982
|
-
}
|
|
983
|
-
inputConnections.get(targetKey).push(conn);
|
|
984
|
-
}
|
|
985
|
-
for (const [, connections] of inputConnections) {
|
|
986
|
-
if (connections.length > 1) {
|
|
987
|
-
const [firstConn] = connections;
|
|
988
|
-
const sources = connections.map((c) => `${c.from.node}.${c.from.port}`).join(', ');
|
|
989
|
-
this.errors.push({
|
|
990
|
-
type: 'error',
|
|
991
|
-
code: 'MULTIPLE_CONNECTIONS_TO_INPUT',
|
|
992
|
-
message: `Input port "${firstConn.to.port}" on node "${firstConn.to.node}" has ${connections.length} connections (${sources}). Only one value can be received.`,
|
|
993
|
-
node: firstConn.to.node,
|
|
994
|
-
connection: firstConn,
|
|
995
|
-
location: this.getConnectionLocation(firstConn),
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Cross-check @input annotations against TypeScript function signatures.
|
|
1002
|
-
* Warns on optionality and type mismatches between annotations and actual code.
|
|
1003
|
-
*/
|
|
1004
|
-
validateAnnotationSignatureConsistency(workflow) {
|
|
1005
|
-
for (const nodeType of workflow.nodeTypes) {
|
|
1006
|
-
if (!nodeType.functionText)
|
|
1007
|
-
continue;
|
|
1008
|
-
let sigParams;
|
|
1009
|
-
try {
|
|
1010
|
-
const sig = parseFunctionSignature(nodeType.functionText);
|
|
1011
|
-
sigParams = sig.params;
|
|
1012
|
-
}
|
|
1013
|
-
catch {
|
|
1014
|
-
continue; // Can't parse signature, skip
|
|
1015
|
-
}
|
|
1016
|
-
// Skip the first param (execute: boolean) - it's a control flow param
|
|
1017
|
-
const sigParamMap = new Map();
|
|
1018
|
-
for (const p of sigParams.slice(1)) {
|
|
1019
|
-
sigParamMap.set(p.name, p);
|
|
1020
|
-
}
|
|
1021
|
-
for (const [portName, portDef] of Object.entries(nodeType.inputs)) {
|
|
1022
|
-
if (portName === 'execute')
|
|
1023
|
-
continue; // Skip control flow port
|
|
1024
|
-
const sigParam = sigParamMap.get(portName);
|
|
1025
|
-
if (!sigParam)
|
|
1026
|
-
continue; // Port not in signature (may be added by framework)
|
|
1027
|
-
// Check optionality mismatch: annotation says required but sig says optional
|
|
1028
|
-
if (!portDef.optional && sigParam.optional) {
|
|
1029
|
-
this.warnings.push({
|
|
1030
|
-
type: 'warning',
|
|
1031
|
-
code: 'ANNOTATION_SIGNATURE_MISMATCH',
|
|
1032
|
-
message: `Port "${portName}" in node type "${nodeType.functionName}" is optional in signature but required in annotation. Consider using @input [${portName}] to mark it optional.`,
|
|
1033
|
-
node: nodeType.functionName,
|
|
1034
|
-
location: nodeType.sourceLocation,
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
// Check type mismatch: annotation specifies a type that differs from signature
|
|
1038
|
-
if (portDef.tsType && sigParam.tsType) {
|
|
1039
|
-
const annotationType = this.normalizeTypeString(portDef.tsType);
|
|
1040
|
-
const signatureType = this.normalizeTypeString(sigParam.tsType);
|
|
1041
|
-
// Skip if no real type info (untyped JS)
|
|
1042
|
-
if (!signatureType || signatureType === 'any')
|
|
1043
|
-
continue;
|
|
1044
|
-
if (annotationType !== signatureType) {
|
|
1045
|
-
this.warnings.push({
|
|
1046
|
-
type: 'warning',
|
|
1047
|
-
code: 'ANNOTATION_SIGNATURE_TYPE_MISMATCH',
|
|
1048
|
-
message: `Port "${portName}" in node type "${nodeType.functionName}" has type "${portDef.tsType}" in annotation but "${sigParam.tsType}" in function signature.`,
|
|
1049
|
-
node: nodeType.functionName,
|
|
1050
|
-
location: nodeType.sourceLocation,
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
/**
|
|
1058
|
-
* Check if a set of source nodes are mutually exclusive — i.e., they descend
|
|
1059
|
-
* from opposite branches (onSuccess vs onFailure) of the same branching node.
|
|
1060
|
-
*/
|
|
1061
|
-
areMutuallyExclusive(sourceNodes, workflow, instanceMap) {
|
|
1062
|
-
if (sourceNodes.length < 2)
|
|
1063
|
-
return false;
|
|
1064
|
-
// Build reverse connection map: targetNode -> [{fromNode, fromPort}]
|
|
1065
|
-
const reverseMap = new Map();
|
|
1066
|
-
for (const conn of workflow.connections) {
|
|
1067
|
-
if (!reverseMap.has(conn.to.node)) {
|
|
1068
|
-
reverseMap.set(conn.to.node, []);
|
|
1069
|
-
}
|
|
1070
|
-
reverseMap.get(conn.to.node).push({ fromNode: conn.from.node, fromPort: conn.from.port });
|
|
1071
|
-
}
|
|
1072
|
-
const findBranchAncestor = (nodeId) => {
|
|
1073
|
-
const visited = new Set();
|
|
1074
|
-
const queue = [nodeId];
|
|
1075
|
-
while (queue.length > 0) {
|
|
1076
|
-
const current = queue.shift();
|
|
1077
|
-
if (visited.has(current))
|
|
1078
|
-
continue;
|
|
1079
|
-
visited.add(current);
|
|
1080
|
-
const incomingEdges = reverseMap.get(current);
|
|
1081
|
-
if (!incomingEdges)
|
|
1082
|
-
continue;
|
|
1083
|
-
for (const edge of incomingEdges) {
|
|
1084
|
-
// Check if this incoming edge is from a branching port
|
|
1085
|
-
if (edge.fromPort === 'onSuccess' || edge.fromPort === 'onFailure') {
|
|
1086
|
-
const parentNodeType = instanceMap.get(edge.fromNode);
|
|
1087
|
-
if (parentNodeType?.hasSuccessPort && parentNodeType?.hasFailurePort) {
|
|
1088
|
-
return { branchNode: edge.fromNode, branch: edge.fromPort };
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
queue.push(edge.fromNode);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
return null;
|
|
1095
|
-
};
|
|
1096
|
-
// Get branch info for all source nodes
|
|
1097
|
-
const branchInfos = sourceNodes.map(findBranchAncestor);
|
|
1098
|
-
// All must have a branch ancestor
|
|
1099
|
-
if (branchInfos.some((info) => info === null))
|
|
1100
|
-
return false;
|
|
1101
|
-
// All must share the same branch node
|
|
1102
|
-
const branchNode = branchInfos[0].branchNode;
|
|
1103
|
-
if (!branchInfos.every((info) => info.branchNode === branchNode))
|
|
1104
|
-
return false;
|
|
1105
|
-
// They must be on different branches (not all on the same one)
|
|
1106
|
-
const branches = new Set(branchInfos.map((info) => info.branch));
|
|
1107
|
-
return branches.size > 1;
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Validate inner graph topology for nodes that have scoped ports.
|
|
1111
|
-
*
|
|
1112
|
-
* For each scoped node instance, checks that:
|
|
1113
|
-
* 1. Inner nodes (children) have their required inputs connected within the scope
|
|
1114
|
-
* 2. Scoped input ports (callback returns) have connections from inner nodes
|
|
1115
|
-
*/
|
|
1116
|
-
validateScopeTopology(workflow, instanceMap) {
|
|
1117
|
-
// P: Scope consistency - check if any instance appears in multiple scope arrays
|
|
1118
|
-
if (workflow.scopes) {
|
|
1119
|
-
const instanceToScope = new Map();
|
|
1120
|
-
for (const [scopeKey, childIds] of Object.entries(workflow.scopes)) {
|
|
1121
|
-
for (const childId of childIds) {
|
|
1122
|
-
const existing = instanceToScope.get(childId);
|
|
1123
|
-
if (existing && existing !== scopeKey) {
|
|
1124
|
-
this.errors.push({
|
|
1125
|
-
type: 'error',
|
|
1126
|
-
code: 'SCOPE_INCONSISTENT',
|
|
1127
|
-
message: `Instance "${childId}" appears in multiple scopes: "${existing}" and "${scopeKey}". A node can only belong to one scope.`,
|
|
1128
|
-
node: childId,
|
|
1129
|
-
location: this.getInstanceLocation(workflow, childId),
|
|
1130
|
-
});
|
|
1131
|
-
}
|
|
1132
|
-
instanceToScope.set(childId, scopeKey);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
// Find all instances that have scoped ports
|
|
1137
|
-
for (const instance of workflow.instances) {
|
|
1138
|
-
const nodeType = instanceMap.get(instance.id);
|
|
1139
|
-
if (!nodeType)
|
|
1140
|
-
continue;
|
|
1141
|
-
// Collect scoped port pairs per scope name
|
|
1142
|
-
const scopeNames = new Set();
|
|
1143
|
-
for (const portDef of Object.values(nodeType.outputs)) {
|
|
1144
|
-
if (portDef.scope)
|
|
1145
|
-
scopeNames.add(portDef.scope);
|
|
1146
|
-
}
|
|
1147
|
-
for (const portDef of Object.values(nodeType.inputs)) {
|
|
1148
|
-
if (portDef.scope)
|
|
1149
|
-
scopeNames.add(portDef.scope);
|
|
1150
|
-
}
|
|
1151
|
-
if (scopeNames.size === 0)
|
|
1152
|
-
continue;
|
|
1153
|
-
// Check: connections with scope qualifiers must reference valid scope names on this instance
|
|
1154
|
-
for (const conn of workflow.connections) {
|
|
1155
|
-
if (conn.from.scope && conn.from.node === instance.id) {
|
|
1156
|
-
if (!scopeNames.has(conn.from.scope)) {
|
|
1157
|
-
this.errors.push({
|
|
1158
|
-
type: 'error',
|
|
1159
|
-
code: 'SCOPE_WRONG_SCOPE_NAME',
|
|
1160
|
-
message: `Connection from "${instance.id}.${conn.from.port}" uses scope qualifier ":${conn.from.scope}" but node "${instance.id}" does not define scope "${conn.from.scope}". Available scopes: ${[...scopeNames].join(', ')}.`,
|
|
1161
|
-
connection: conn,
|
|
1162
|
-
location: this.getConnectionLocation(conn),
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
if (conn.to.scope && conn.to.node === instance.id) {
|
|
1167
|
-
if (!scopeNames.has(conn.to.scope)) {
|
|
1168
|
-
this.errors.push({
|
|
1169
|
-
type: 'error',
|
|
1170
|
-
code: 'SCOPE_WRONG_SCOPE_NAME',
|
|
1171
|
-
message: `Connection to "${instance.id}.${conn.to.port}" uses scope qualifier ":${conn.to.scope}" but node "${instance.id}" does not define scope "${conn.to.scope}". Available scopes: ${[...scopeNames].join(', ')}.`,
|
|
1172
|
-
connection: conn,
|
|
1173
|
-
location: this.getConnectionLocation(conn),
|
|
1174
|
-
});
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
for (const scopeName of scopeNames) {
|
|
1179
|
-
// Find children in this scope
|
|
1180
|
-
const childIds = [];
|
|
1181
|
-
for (const child of workflow.instances) {
|
|
1182
|
-
if (child.parent &&
|
|
1183
|
-
child.parent.id === instance.id &&
|
|
1184
|
-
child.parent.scope === scopeName) {
|
|
1185
|
-
childIds.push(child.id);
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
// O: Empty scope warning
|
|
1189
|
-
if (childIds.length === 0) {
|
|
1190
|
-
this.warnings.push({
|
|
1191
|
-
type: 'warning',
|
|
1192
|
-
code: 'SCOPE_EMPTY',
|
|
1193
|
-
message: `Scope "${scopeName}" on node "${instance.id}" has no child nodes.`,
|
|
1194
|
-
node: instance.id,
|
|
1195
|
-
location: this.getInstanceLocation(workflow, instance.id),
|
|
1196
|
-
});
|
|
1197
|
-
continue;
|
|
1198
|
-
}
|
|
1199
|
-
// Collect scoped connections (connections with scope tags)
|
|
1200
|
-
const scopedConnections = workflow.connections.filter((conn) => (conn.from.scope === scopeName && conn.from.node === instance.id) ||
|
|
1201
|
-
(conn.to.scope === scopeName && conn.to.node === instance.id) ||
|
|
1202
|
-
(conn.from.scope === scopeName && childIds.includes(conn.from.node)) ||
|
|
1203
|
-
(conn.to.scope === scopeName && childIds.includes(conn.to.node)));
|
|
1204
|
-
// Check: scoped connections reference actual scoped ports on the parent
|
|
1205
|
-
for (const conn of scopedConnections) {
|
|
1206
|
-
if (conn.from.node === instance.id && conn.from.scope === scopeName) {
|
|
1207
|
-
const portDef = nodeType.outputs[conn.from.port];
|
|
1208
|
-
if (!portDef) {
|
|
1209
|
-
const availablePorts = Object.entries(nodeType.outputs)
|
|
1210
|
-
.filter(([, p]) => p.scope === scopeName)
|
|
1211
|
-
.map(([n]) => n);
|
|
1212
|
-
this.errors.push({
|
|
1213
|
-
type: 'error',
|
|
1214
|
-
code: 'SCOPE_UNKNOWN_PORT',
|
|
1215
|
-
message: `Scoped connection references non-existent output port "${conn.from.port}" on "${instance.id}" in scope "${scopeName}". Available scoped outputs: ${availablePorts.join(', ') || 'none'}.`,
|
|
1216
|
-
connection: conn,
|
|
1217
|
-
location: this.getConnectionLocation(conn),
|
|
1218
|
-
});
|
|
1219
|
-
}
|
|
1220
|
-
else if (portDef.scope !== scopeName) {
|
|
1221
|
-
this.errors.push({
|
|
1222
|
-
type: 'error',
|
|
1223
|
-
code: 'SCOPE_UNKNOWN_PORT',
|
|
1224
|
-
message: `Output port "${conn.from.port}" on "${instance.id}" is not a scoped port of scope "${scopeName}"${portDef.scope ? ` (it belongs to scope "${portDef.scope}")` : ' (it is an unscoped port)'}.`,
|
|
1225
|
-
connection: conn,
|
|
1226
|
-
location: this.getConnectionLocation(conn),
|
|
1227
|
-
});
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
if (conn.to.node === instance.id && conn.to.scope === scopeName) {
|
|
1231
|
-
const portDef = nodeType.inputs[conn.to.port];
|
|
1232
|
-
if (!portDef) {
|
|
1233
|
-
const availablePorts = Object.entries(nodeType.inputs)
|
|
1234
|
-
.filter(([, p]) => p.scope === scopeName)
|
|
1235
|
-
.map(([n]) => n);
|
|
1236
|
-
this.errors.push({
|
|
1237
|
-
type: 'error',
|
|
1238
|
-
code: 'SCOPE_UNKNOWN_PORT',
|
|
1239
|
-
message: `Scoped connection references non-existent input port "${conn.to.port}" on "${instance.id}" in scope "${scopeName}". Available scoped inputs: ${availablePorts.join(', ') || 'none'}.`,
|
|
1240
|
-
connection: conn,
|
|
1241
|
-
location: this.getConnectionLocation(conn),
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
else if (portDef.scope !== scopeName) {
|
|
1245
|
-
this.errors.push({
|
|
1246
|
-
type: 'error',
|
|
1247
|
-
code: 'SCOPE_UNKNOWN_PORT',
|
|
1248
|
-
message: `Input port "${conn.to.port}" on "${instance.id}" is not a scoped port of scope "${scopeName}"${portDef.scope ? ` (it belongs to scope "${portDef.scope}")` : ' (it is an unscoped port)'}.`,
|
|
1249
|
-
connection: conn,
|
|
1250
|
-
location: this.getConnectionLocation(conn),
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
// Check: scoped connections must stay within the scope boundary
|
|
1256
|
-
for (const conn of scopedConnections) {
|
|
1257
|
-
if (conn.from.scope === scopeName && childIds.includes(conn.from.node)) {
|
|
1258
|
-
if (conn.to.node !== instance.id && !childIds.includes(conn.to.node)) {
|
|
1259
|
-
this.errors.push({
|
|
1260
|
-
type: 'error',
|
|
1261
|
-
code: 'SCOPE_CONNECTION_OUTSIDE',
|
|
1262
|
-
message: `Scoped connection from "${conn.from.node}.${conn.from.port}" targets "${conn.to.node}" which is not inside scope "${scopeName}" of "${instance.id}".`,
|
|
1263
|
-
connection: conn,
|
|
1264
|
-
location: this.getConnectionLocation(conn),
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
if (conn.to.scope === scopeName && childIds.includes(conn.to.node)) {
|
|
1269
|
-
if (conn.from.node !== instance.id && !childIds.includes(conn.from.node)) {
|
|
1270
|
-
this.errors.push({
|
|
1271
|
-
type: 'error',
|
|
1272
|
-
code: 'SCOPE_CONNECTION_OUTSIDE',
|
|
1273
|
-
message: `Scoped connection to "${conn.to.node}.${conn.to.port}" sources from "${conn.from.node}" which is not inside scope "${scopeName}" of "${instance.id}".`,
|
|
1274
|
-
connection: conn,
|
|
1275
|
-
location: this.getConnectionLocation(conn),
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
// Check: type compatibility for scoped connections between parent and children
|
|
1281
|
-
for (const conn of scopedConnections) {
|
|
1282
|
-
// Parent scoped output -> child input
|
|
1283
|
-
if (conn.from.node === instance.id && conn.from.scope === scopeName) {
|
|
1284
|
-
const parentPort = nodeType.outputs[conn.from.port];
|
|
1285
|
-
const childType = instanceMap.get(conn.to.node);
|
|
1286
|
-
const childPort = childType?.inputs[conn.to.port];
|
|
1287
|
-
if (parentPort && childPort && parentPort.dataType !== 'STEP' && childPort.dataType !== 'STEP') {
|
|
1288
|
-
if (parentPort.dataType !== childPort.dataType && parentPort.dataType !== 'ANY' && childPort.dataType !== 'ANY') {
|
|
1289
|
-
this.warnings.push({
|
|
1290
|
-
type: 'warning',
|
|
1291
|
-
code: 'SCOPE_PORT_TYPE_MISMATCH',
|
|
1292
|
-
message: `Type mismatch in scope "${scopeName}": "${instance.id}.${conn.from.port}" outputs ${this.formatType(parentPort.dataType, parentPort.tsType)} but "${conn.to.node}.${conn.to.port}" expects ${this.formatType(childPort.dataType, childPort.tsType)}.`,
|
|
1293
|
-
connection: conn,
|
|
1294
|
-
location: this.getConnectionLocation(conn),
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
// Child output -> parent scoped input
|
|
1300
|
-
if (conn.to.node === instance.id && conn.to.scope === scopeName) {
|
|
1301
|
-
const parentPort = nodeType.inputs[conn.to.port];
|
|
1302
|
-
const childType = instanceMap.get(conn.from.node);
|
|
1303
|
-
const childPort = childType?.outputs[conn.from.port];
|
|
1304
|
-
if (parentPort && childPort && parentPort.dataType !== 'STEP' && childPort.dataType !== 'STEP') {
|
|
1305
|
-
if (parentPort.dataType !== childPort.dataType && parentPort.dataType !== 'ANY' && childPort.dataType !== 'ANY') {
|
|
1306
|
-
this.warnings.push({
|
|
1307
|
-
type: 'warning',
|
|
1308
|
-
code: 'SCOPE_PORT_TYPE_MISMATCH',
|
|
1309
|
-
message: `Type mismatch in scope "${scopeName}": "${conn.from.node}.${conn.from.port}" outputs ${this.formatType(childPort.dataType, childPort.tsType)} but "${instance.id}.${conn.to.port}" expects ${this.formatType(parentPort.dataType, parentPort.tsType)}.`,
|
|
1310
|
-
connection: conn,
|
|
1311
|
-
location: this.getConnectionLocation(conn),
|
|
1312
|
-
});
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
// Check: each child's required inputs must be satisfied within the scope
|
|
1318
|
-
for (const childId of childIds) {
|
|
1319
|
-
const childType = instanceMap.get(childId);
|
|
1320
|
-
if (!childType)
|
|
1321
|
-
continue;
|
|
1322
|
-
for (const [portName, portConfig] of Object.entries(childType.inputs)) {
|
|
1323
|
-
if (isExecutePort(portName))
|
|
1324
|
-
continue;
|
|
1325
|
-
if (portConfig.scope)
|
|
1326
|
-
continue; // Skip scoped ports on children
|
|
1327
|
-
if (portConfig.optional || portConfig.default !== undefined)
|
|
1328
|
-
continue;
|
|
1329
|
-
// Check instance expression overrides
|
|
1330
|
-
const childInstance = workflow.instances.find((i) => i.id === childId);
|
|
1331
|
-
const instancePortConfig = childInstance?.config?.portConfigs?.find((pc) => pc.portName === portName && (pc.direction == null || pc.direction === 'INPUT'));
|
|
1332
|
-
if (portConfig.expression || instancePortConfig?.expression !== undefined)
|
|
1333
|
-
continue;
|
|
1334
|
-
// Check if connected by any connection (scoped, inter-child, or outer)
|
|
1335
|
-
const isConnected = workflow.connections.some((conn) => conn.to.node === childId && conn.to.port === portName);
|
|
1336
|
-
if (!isConnected) {
|
|
1337
|
-
this.errors.push({
|
|
1338
|
-
type: 'error',
|
|
1339
|
-
code: 'SCOPE_MISSING_REQUIRED_INPUT',
|
|
1340
|
-
message: `Scoped child "${childId}" has unconnected required input "${portName}" within scope "${scopeName}" of "${instance.id}".`,
|
|
1341
|
-
node: childId,
|
|
1342
|
-
location: childInstance?.sourceLocation,
|
|
1343
|
-
});
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
// Check: scoped INPUT ports should have connections from inner nodes
|
|
1348
|
-
const scopedInputPorts = Object.entries(nodeType.inputs).filter(([_, portDef]) => portDef.scope === scopeName);
|
|
1349
|
-
for (const [portName] of scopedInputPorts) {
|
|
1350
|
-
const hasConnection = scopedConnections.some((conn) => conn.to.node === instance.id &&
|
|
1351
|
-
conn.to.port === portName &&
|
|
1352
|
-
conn.to.scope === scopeName);
|
|
1353
|
-
if (!hasConnection) {
|
|
1354
|
-
this.warnings.push({
|
|
1355
|
-
type: 'warning',
|
|
1356
|
-
code: 'SCOPE_UNUSED_INPUT',
|
|
1357
|
-
message: `Scoped input port "${portName}" of "${instance.id}" (scope "${scopeName}") has no connection from inner nodes. Data will not flow back from the scope.`,
|
|
1358
|
-
node: instance.id,
|
|
1359
|
-
location: this.getInstanceLocation(workflow, instance.id),
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
// Check: each child should have at least one scoped connection to/from the parent
|
|
1364
|
-
for (const childId of childIds) {
|
|
1365
|
-
const hasConnectionFromParent = scopedConnections.some((conn) => conn.from.node === instance.id &&
|
|
1366
|
-
conn.from.scope === scopeName &&
|
|
1367
|
-
conn.to.node === childId);
|
|
1368
|
-
const hasConnectionToParent = scopedConnections.some((conn) => conn.to.node === instance.id &&
|
|
1369
|
-
conn.to.scope === scopeName &&
|
|
1370
|
-
conn.from.node === childId);
|
|
1371
|
-
if (!hasConnectionFromParent && !hasConnectionToParent) {
|
|
1372
|
-
this.warnings.push({
|
|
1373
|
-
type: 'warning',
|
|
1374
|
-
code: 'SCOPE_ORPHANED_CHILD',
|
|
1375
|
-
message: `Child node "${childId}" is declared inside scope "${scopeName}" of "${instance.id}" but has no scoped connections to or from the parent. It is disconnected from the scope's data flow.`,
|
|
1376
|
-
node: childId,
|
|
1377
|
-
location: this.getInstanceLocation(workflow, childId),
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
// Check: non-scoped connections must not cross scope boundaries
|
|
1384
|
-
// when the parent is a scoped node type (has scoped ports).
|
|
1385
|
-
// A child in scope "a" cannot connect to a child in scope "b" (or root),
|
|
1386
|
-
// because they execute in separate callback contexts.
|
|
1387
|
-
// Only applies to per-port scoped parents (not flat grouping scopes).
|
|
1388
|
-
const parentOf = new Map();
|
|
1389
|
-
for (const inst of workflow.instances) {
|
|
1390
|
-
parentOf.set(inst.id, inst.parent ?? undefined);
|
|
1391
|
-
}
|
|
1392
|
-
// Build set of node IDs that are scoped parent types (have scoped ports)
|
|
1393
|
-
const scopedParentIds = new Set();
|
|
1394
|
-
for (const inst of workflow.instances) {
|
|
1395
|
-
const nt = instanceMap.get(inst.id);
|
|
1396
|
-
if (!nt)
|
|
1397
|
-
continue;
|
|
1398
|
-
const hasScopedPorts = [
|
|
1399
|
-
...Object.values(nt.inputs ?? {}),
|
|
1400
|
-
...Object.values(nt.outputs ?? {}),
|
|
1401
|
-
].some((p) => p.scope);
|
|
1402
|
-
if (hasScopedPorts)
|
|
1403
|
-
scopedParentIds.add(inst.id);
|
|
1404
|
-
}
|
|
1405
|
-
for (const conn of workflow.connections) {
|
|
1406
|
-
// Skip scoped connections (already validated above)
|
|
1407
|
-
if (conn.from.scope || conn.to.scope)
|
|
1408
|
-
continue;
|
|
1409
|
-
const fromParent = parentOf.get(conn.from.node);
|
|
1410
|
-
const toParent = parentOf.get(conn.to.node);
|
|
1411
|
-
// Both must exist in the workflow
|
|
1412
|
-
if (!parentOf.has(conn.from.node) || !parentOf.has(conn.to.node))
|
|
1413
|
-
continue;
|
|
1414
|
-
// If neither node is inside a scope, skip (root-to-root is fine)
|
|
1415
|
-
if (!fromParent && !toParent)
|
|
1416
|
-
continue;
|
|
1417
|
-
// Only validate when the parent is a scoped node type (has scoped ports).
|
|
1418
|
-
// Flat grouping scopes (createScope without scoped ports) are allowed to cross.
|
|
1419
|
-
const fromInScopedParent = fromParent && scopedParentIds.has(fromParent.id);
|
|
1420
|
-
const toInScopedParent = toParent && scopedParentIds.has(toParent.id);
|
|
1421
|
-
if (!fromInScopedParent && !toInScopedParent)
|
|
1422
|
-
continue;
|
|
1423
|
-
// Allow connections where one side is the scoped PARENT itself (not a child)
|
|
1424
|
-
const fromIsParentOfTo = toParent && toParent.id === conn.from.node;
|
|
1425
|
-
const toIsParentOfFrom = fromParent && fromParent.id === conn.to.node;
|
|
1426
|
-
if (fromIsParentOfTo || toIsParentOfFrom)
|
|
1427
|
-
continue;
|
|
1428
|
-
// Check if they share the same parent+scope
|
|
1429
|
-
const sameScope = fromParent && toParent &&
|
|
1430
|
-
fromParent.id === toParent.id &&
|
|
1431
|
-
fromParent.scope === toParent.scope;
|
|
1432
|
-
if (!sameScope) {
|
|
1433
|
-
const fromCtx = fromParent ? `${fromParent.id}.${fromParent.scope}` : 'root';
|
|
1434
|
-
const toCtx = toParent ? `${toParent.id}.${toParent.scope}` : 'root';
|
|
1435
|
-
this.errors.push({
|
|
1436
|
-
type: 'error',
|
|
1437
|
-
code: 'CROSS_SCOPE_CONNECTION',
|
|
1438
|
-
message: `Connection from "${conn.from.node}.${conn.from.port}" (in ${fromCtx}) to "${conn.to.node}.${conn.to.port}" (in ${toCtx}) crosses scope boundaries. Nodes in different scopes cannot connect directly.`,
|
|
1439
|
-
connection: conn,
|
|
1440
|
-
location: this.getConnectionLocation(conn),
|
|
1441
|
-
});
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
// ── H: Duplicate instance IDs ──────────────────────────────────────────
|
|
1446
|
-
validateDuplicateInstanceIds(workflow) {
|
|
1447
|
-
const seen = new Set();
|
|
1448
|
-
for (const instance of workflow.instances) {
|
|
1449
|
-
if (seen.has(instance.id)) {
|
|
1450
|
-
this.errors.push({
|
|
1451
|
-
type: 'error',
|
|
1452
|
-
code: 'DUPLICATE_INSTANCE_ID',
|
|
1453
|
-
message: `Duplicate instance ID "${instance.id}" in workflow. Each @node must have a unique ID.`,
|
|
1454
|
-
node: instance.id,
|
|
1455
|
-
location: instance.sourceLocation,
|
|
1456
|
-
});
|
|
1457
|
-
}
|
|
1458
|
-
seen.add(instance.id);
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
// ── I: Duplicate connections ──────────────────────────────────────────
|
|
1462
|
-
validateDuplicateConnections(workflow) {
|
|
1463
|
-
const seen = new Set();
|
|
1464
|
-
for (const conn of workflow.connections) {
|
|
1465
|
-
const key = `${conn.from.node}.${conn.from.port}->${conn.to.node}.${conn.to.port}`;
|
|
1466
|
-
if (seen.has(key)) {
|
|
1467
|
-
this.errors.push({
|
|
1468
|
-
type: 'error',
|
|
1469
|
-
code: 'DUPLICATE_CONNECTION',
|
|
1470
|
-
message: `Duplicate connection: ${key}`,
|
|
1471
|
-
connection: conn,
|
|
1472
|
-
location: this.getConnectionLocation(conn),
|
|
1473
|
-
});
|
|
1474
|
-
}
|
|
1475
|
-
seen.add(key);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
// ── J+K: Visual annotation validation ────────────────────────────────
|
|
1479
|
-
validateVisualAnnotations(workflow, instanceMap) {
|
|
1480
|
-
const validColors = VALID_NODE_COLORS;
|
|
1481
|
-
const validIcons = VALID_NODE_ICONS;
|
|
1482
|
-
// Check node type colors and icons (stored in visuals)
|
|
1483
|
-
for (const nodeType of workflow.nodeTypes) {
|
|
1484
|
-
const color = nodeType.visuals?.color;
|
|
1485
|
-
const icon = nodeType.visuals?.icon;
|
|
1486
|
-
if (color && !validColors.includes(color)) {
|
|
1487
|
-
const suggestions = findClosestMatches(color, [...validColors]);
|
|
1488
|
-
const hint = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
1489
|
-
this.warnings.push({
|
|
1490
|
-
type: 'warning',
|
|
1491
|
-
code: 'INVALID_COLOR',
|
|
1492
|
-
message: `Node type "${nodeType.functionName}" has invalid color "${color}".${hint} Valid colors: ${validColors.join(', ')}.`,
|
|
1493
|
-
node: nodeType.functionName,
|
|
1494
|
-
location: nodeType.sourceLocation,
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
if (icon && !validIcons.includes(icon)) {
|
|
1498
|
-
const suggestions = findClosestMatches(icon, [...validIcons]);
|
|
1499
|
-
const hint = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
1500
|
-
this.warnings.push({
|
|
1501
|
-
type: 'warning',
|
|
1502
|
-
code: 'INVALID_ICON',
|
|
1503
|
-
message: `Node type "${nodeType.functionName}" has invalid icon "${icon}".${hint}`,
|
|
1504
|
-
node: nodeType.functionName,
|
|
1505
|
-
location: nodeType.sourceLocation,
|
|
1506
|
-
});
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
// Check instance-level color and icon overrides
|
|
1510
|
-
for (const instance of workflow.instances) {
|
|
1511
|
-
if (instance.config?.color && !validColors.includes(instance.config.color)) {
|
|
1512
|
-
const suggestions = findClosestMatches(instance.config.color, [...validColors]);
|
|
1513
|
-
const hint = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
1514
|
-
this.warnings.push({
|
|
1515
|
-
type: 'warning',
|
|
1516
|
-
code: 'INVALID_COLOR',
|
|
1517
|
-
message: `Instance "${instance.id}" has invalid color "${instance.config.color}".${hint} Valid colors: ${validColors.join(', ')}.`,
|
|
1518
|
-
node: instance.id,
|
|
1519
|
-
location: instance.sourceLocation,
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
if (instance.config?.icon && !validIcons.includes(instance.config.icon)) {
|
|
1523
|
-
const suggestions = findClosestMatches(instance.config.icon, [...validIcons]);
|
|
1524
|
-
const hint = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
1525
|
-
this.warnings.push({
|
|
1526
|
-
type: 'warning',
|
|
1527
|
-
code: 'INVALID_ICON',
|
|
1528
|
-
message: `Instance "${instance.id}" has invalid icon "${instance.config.icon}".${hint}`,
|
|
1529
|
-
node: instance.id,
|
|
1530
|
-
location: instance.sourceLocation,
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
// ── L: Port type validation ──────────────────────────────────────────
|
|
1536
|
-
validatePortTypes(workflow) {
|
|
1537
|
-
for (const nodeType of workflow.nodeTypes) {
|
|
1538
|
-
for (const [portName, portDef] of Object.entries(nodeType.inputs)) {
|
|
1539
|
-
if (!isValidPortType(portDef.dataType)) {
|
|
1540
|
-
this.warnings.push({
|
|
1541
|
-
type: 'warning',
|
|
1542
|
-
code: 'INVALID_PORT_TYPE',
|
|
1543
|
-
message: `Port "${portName}" on node type "${nodeType.functionName}" has invalid type "${portDef.dataType}".`,
|
|
1544
|
-
node: nodeType.functionName,
|
|
1545
|
-
location: nodeType.sourceLocation,
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
for (const [portName, portDef] of Object.entries(nodeType.outputs)) {
|
|
1550
|
-
if (!isValidPortType(portDef.dataType)) {
|
|
1551
|
-
this.warnings.push({
|
|
1552
|
-
type: 'warning',
|
|
1553
|
-
code: 'INVALID_PORT_TYPE',
|
|
1554
|
-
message: `Port "${portName}" on node type "${nodeType.functionName}" has invalid type "${portDef.dataType}".`,
|
|
1555
|
-
node: nodeType.functionName,
|
|
1556
|
-
location: nodeType.sourceLocation,
|
|
1557
|
-
});
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
// ── M: portOrder/portLabel reference validation ──────────────────────
|
|
1563
|
-
validatePortConfigReferences(workflow, instanceMap) {
|
|
1564
|
-
for (const instance of workflow.instances) {
|
|
1565
|
-
const portConfigs = instance.config?.portConfigs;
|
|
1566
|
-
if (!portConfigs)
|
|
1567
|
-
continue;
|
|
1568
|
-
const nodeType = instanceMap.get(instance.id);
|
|
1569
|
-
if (!nodeType)
|
|
1570
|
-
continue;
|
|
1571
|
-
const allPorts = new Set([
|
|
1572
|
-
...Object.keys(nodeType.inputs),
|
|
1573
|
-
...Object.keys(nodeType.outputs),
|
|
1574
|
-
]);
|
|
1575
|
-
for (const pc of portConfigs) {
|
|
1576
|
-
if (!allPorts.has(pc.portName)) {
|
|
1577
|
-
const suggestions = findClosestMatches(pc.portName, [...allPorts]);
|
|
1578
|
-
const hint = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
1579
|
-
this.warnings.push({
|
|
1580
|
-
type: 'warning',
|
|
1581
|
-
code: 'INVALID_PORT_CONFIG_REF',
|
|
1582
|
-
message: `Instance "${instance.id}" references port "${pc.portName}" in portConfig, but this port does not exist on node type "${instance.nodeType}".${hint}`,
|
|
1583
|
-
node: instance.id,
|
|
1584
|
-
location: instance.sourceLocation,
|
|
1585
|
-
});
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
// ── N: @executeWhen value validation ─────────────────────────────────
|
|
1591
|
-
validateExecuteWhen(workflow) {
|
|
1592
|
-
const validStrategies = Object.values(EXECUTION_STRATEGIES);
|
|
1593
|
-
for (const nodeType of workflow.nodeTypes) {
|
|
1594
|
-
if (nodeType.executeWhen && !validStrategies.includes(nodeType.executeWhen)) {
|
|
1595
|
-
const suggestions = findClosestMatches(nodeType.executeWhen, validStrategies);
|
|
1596
|
-
const hint = suggestions.length > 0 ? ` Did you mean "${suggestions[0]}"?` : '';
|
|
1597
|
-
this.warnings.push({
|
|
1598
|
-
type: 'warning',
|
|
1599
|
-
code: 'INVALID_EXECUTE_WHEN',
|
|
1600
|
-
message: `Node type "${nodeType.functionName}" has invalid @executeWhen value "${nodeType.executeWhen}".${hint} Valid values: ${validStrategies.join(', ')}.`,
|
|
1601
|
-
node: nodeType.functionName,
|
|
1602
|
-
location: nodeType.sourceLocation,
|
|
1603
|
-
});
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
/**
|
|
1608
|
-
* Format a type for display in error messages.
|
|
1609
|
-
* Prefers the structural TypeScript type when available, falling back to the enum name.
|
|
1610
|
-
*/
|
|
1611
|
-
formatType(dataType, tsType) {
|
|
1612
|
-
if (tsType) {
|
|
1613
|
-
return `${tsType} (${dataType})`;
|
|
1614
|
-
}
|
|
1615
|
-
return dataType;
|
|
1616
|
-
}
|
|
1617
|
-
normalizeTypeString(type) {
|
|
1618
|
-
let n = type;
|
|
1619
|
-
// Remove all whitespace
|
|
1620
|
-
n = n.replace(/\s+/g, '');
|
|
1621
|
-
// Normalize Array<T> → T[]
|
|
1622
|
-
n = n.replace(/Array<(.+?)>/g, '$1[]');
|
|
1623
|
-
// Remove trailing semicolons before closing braces/brackets
|
|
1624
|
-
n = n.replace(/;(?=[}\]])/g, '');
|
|
1625
|
-
// Lowercase for case-insensitive compare
|
|
1626
|
-
n = n.toLowerCase();
|
|
1627
|
-
return n;
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
export const validator = new WorkflowValidator();
|
|
1631
|
-
//# sourceMappingURL=validator.js.map
|