@synergenius/flow-weaver 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +2 -1
  2. package/dist/annotation-generator.js +29 -4
  3. package/dist/api/generate-in-place.js +3 -0
  4. package/dist/ast/types.d.ts +22 -2
  5. package/dist/built-in-nodes/coercion-types.d.ts +15 -0
  6. package/dist/built-in-nodes/coercion-types.js +61 -0
  7. package/dist/built-in-nodes/invoke-workflow.js +3 -3
  8. package/dist/built-in-nodes/mock-types.d.ts +20 -0
  9. package/dist/built-in-nodes/mock-types.js +30 -0
  10. package/dist/built-in-nodes/wait-for-agent.js +5 -4
  11. package/dist/built-in-nodes/wait-for-event.js +3 -3
  12. package/dist/chevrotain-parser/coerce-parser.d.ts +33 -0
  13. package/dist/chevrotain-parser/coerce-parser.js +103 -0
  14. package/dist/chevrotain-parser/index.d.ts +3 -1
  15. package/dist/chevrotain-parser/index.js +3 -1
  16. package/dist/chevrotain-parser/port-parser.js +2 -1
  17. package/dist/chevrotain-parser/tokens.d.ts +2 -0
  18. package/dist/chevrotain-parser/tokens.js +10 -0
  19. package/dist/cli/commands/compile.js +6 -0
  20. package/dist/cli/commands/init.js +2 -0
  21. package/dist/cli/commands/run.d.ts +4 -0
  22. package/dist/cli/commands/run.js +68 -1
  23. package/dist/cli/commands/validate.js +12 -0
  24. package/dist/cli/flow-weaver.mjs +764 -65
  25. package/dist/cli/index.js +1 -0
  26. package/dist/doc-metadata/extractors/annotations.js +17 -0
  27. package/dist/doc-metadata/extractors/error-codes.js +50 -0
  28. package/dist/friendly-errors.js +131 -5
  29. package/dist/generator/inngest.js +27 -0
  30. package/dist/generator/unified.d.ts +7 -2
  31. package/dist/generator/unified.js +31 -3
  32. package/dist/jsdoc-parser.d.ts +14 -0
  33. package/dist/jsdoc-parser.js +19 -1
  34. package/dist/mcp/tools-editor.js +20 -1
  35. package/dist/mcp/workflow-executor.d.ts +1 -0
  36. package/dist/mcp/workflow-executor.js +11 -4
  37. package/dist/parser.d.ts +4 -0
  38. package/dist/parser.js +69 -0
  39. package/dist/validator.d.ts +5 -0
  40. package/dist/validator.js +207 -14
  41. package/docs/reference/advanced-annotations.md +71 -2
  42. package/docs/reference/concepts.md +44 -4
  43. package/docs/reference/debugging.md +33 -0
  44. package/docs/reference/error-codes.md +7 -0
  45. package/package.json +1 -1
@@ -11812,14 +11812,84 @@ var init_signature_parser = __esm({
11812
11812
  }
11813
11813
  });
11814
11814
 
11815
+ // src/type-checker.ts
11816
+ function isRuntimeCoercible(sourceText, targetText) {
11817
+ const sourceLower = sourceText.toLowerCase();
11818
+ const targetLower = targetText.toLowerCase();
11819
+ for (const [from, to] of SAFE_COERCIONS) {
11820
+ if (sourceLower === from && targetLower === to) {
11821
+ return true;
11822
+ }
11823
+ }
11824
+ return false;
11825
+ }
11826
+ function checkTypeCompatibilityFromStrings(sourceText, targetText) {
11827
+ if (sourceText === targetText) {
11828
+ return {
11829
+ isCompatible: true,
11830
+ reason: "exact",
11831
+ sourceType: sourceText,
11832
+ targetType: targetText
11833
+ };
11834
+ }
11835
+ if (sourceText === "any" || targetText === "any") {
11836
+ return {
11837
+ isCompatible: true,
11838
+ reason: "assignable",
11839
+ sourceType: sourceText,
11840
+ targetType: targetText
11841
+ };
11842
+ }
11843
+ if (isRuntimeCoercible(sourceText, targetText)) {
11844
+ return {
11845
+ isCompatible: true,
11846
+ reason: "coercible",
11847
+ sourceType: sourceText,
11848
+ targetType: targetText
11849
+ };
11850
+ }
11851
+ return {
11852
+ isCompatible: false,
11853
+ reason: "incompatible",
11854
+ sourceType: sourceText,
11855
+ targetType: targetText,
11856
+ errorMessage: `Type '${sourceText}' is not assignable to type '${targetText}'`
11857
+ };
11858
+ }
11859
+ var SAFE_COERCIONS;
11860
+ var init_type_checker = __esm({
11861
+ "src/type-checker.ts"() {
11862
+ "use strict";
11863
+ SAFE_COERCIONS = [
11864
+ ["number", "string"],
11865
+ // Number.toString()
11866
+ ["boolean", "string"]
11867
+ // Boolean.toString()
11868
+ ];
11869
+ }
11870
+ });
11871
+
11815
11872
  // src/validator.ts
11816
- var WorkflowValidator, validator;
11873
+ var DOCS_BASE, ERROR_DOC_URLS, WorkflowValidator, validator;
11817
11874
  var init_validator = __esm({
11818
11875
  "src/validator.ts"() {
11819
11876
  "use strict";
11820
11877
  init_constants();
11821
11878
  init_string_distance();
11822
11879
  init_signature_parser();
11880
+ init_type_checker();
11881
+ DOCS_BASE = "https://docs.flowweaver.dev/reference";
11882
+ ERROR_DOC_URLS = {
11883
+ UNKNOWN_NODE_TYPE: `${DOCS_BASE}/concepts#node-registration`,
11884
+ UNKNOWN_SOURCE_PORT: `${DOCS_BASE}/concepts#port-architecture`,
11885
+ UNKNOWN_TARGET_PORT: `${DOCS_BASE}/concepts#port-architecture`,
11886
+ TYPE_MISMATCH: `${DOCS_BASE}/compilation#type-compatibility`,
11887
+ UNREACHABLE_NODE: `${DOCS_BASE}/concepts#graph-structure`,
11888
+ MISSING_START_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
11889
+ MISSING_EXIT_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
11890
+ INFERRED_NODE_TYPE: `${DOCS_BASE}/node-conversion`,
11891
+ DUPLICATE_CONNECTION: `${DOCS_BASE}/concepts#connections`
11892
+ };
11823
11893
  WorkflowValidator = class {
11824
11894
  errors = [];
11825
11895
  warnings = [];
@@ -11947,6 +12017,11 @@ var init_validator = __esm({
11947
12017
  return true;
11948
12018
  });
11949
12019
  }
12020
+ for (const diag of [...this.errors, ...this.warnings]) {
12021
+ if (!diag.docUrl && ERROR_DOC_URLS[diag.code]) {
12022
+ diag.docUrl = ERROR_DOC_URLS[diag.code];
12023
+ }
12024
+ }
11950
12025
  return {
11951
12026
  valid: this.errors.length === 0,
11952
12027
  errors: this.errors,
@@ -12170,11 +12245,13 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12170
12245
  }
12171
12246
  const sourceType = sourcePortDef.dataType;
12172
12247
  const targetType = targetPortDef.dataType;
12248
+ const sourceTsType = sourcePortDef.tsType;
12249
+ const targetTsType = targetPortDef.tsType;
12173
12250
  if (sourceType === "STEP" && targetType !== "STEP") {
12174
12251
  this.errors.push({
12175
12252
  type: "error",
12176
12253
  code: "STEP_PORT_TYPE_MISMATCH",
12177
- message: `STEP port "${fromPort}" on node "${fromNode}" cannot connect to non-STEP port "${toPort}" (${targetType}) on node "${toNode}"`,
12254
+ message: `STEP port "${fromPort}" on node "${fromNode}" cannot connect to non-STEP port "${toPort}" (${this.formatType(targetType, targetTsType)}) on node "${toNode}"`,
12178
12255
  connection: conn,
12179
12256
  location: connLocation
12180
12257
  });
@@ -12184,7 +12261,7 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12184
12261
  this.errors.push({
12185
12262
  type: "error",
12186
12263
  code: "STEP_PORT_TYPE_MISMATCH",
12187
- message: `Non-STEP port "${fromPort}" (${sourceType}) on node "${fromNode}" cannot connect to STEP port "${toPort}" on node "${toNode}"`,
12264
+ message: `Non-STEP port "${fromPort}" (${this.formatType(sourceType, sourceTsType)}) on node "${fromNode}" cannot connect to STEP port "${toPort}" on node "${toNode}"`,
12188
12265
  connection: conn,
12189
12266
  location: connLocation
12190
12267
  });
@@ -12195,14 +12272,19 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12195
12272
  }
12196
12273
  if (sourceType === targetType) {
12197
12274
  if (sourceType === "OBJECT" && sourcePortDef.tsType && targetPortDef.tsType) {
12198
- if (this.normalizeTypeString(sourcePortDef.tsType) !== this.normalizeTypeString(targetPortDef.tsType)) {
12199
- this.warnings.push({
12200
- type: "warning",
12201
- code: "OBJECT_TYPE_MISMATCH",
12202
- message: `Structural type mismatch: ${fromNode}.${fromPort} outputs "${sourcePortDef.tsType}" but ${toNode}.${toPort} expects "${targetPortDef.tsType}". Verify the object shapes are compatible.`,
12203
- connection: conn,
12204
- location: connLocation
12205
- });
12275
+ const normalizedSource = this.normalizeTypeString(sourcePortDef.tsType);
12276
+ const normalizedTarget = this.normalizeTypeString(targetPortDef.tsType);
12277
+ if (normalizedSource !== normalizedTarget) {
12278
+ const compat = checkTypeCompatibilityFromStrings(sourcePortDef.tsType, targetPortDef.tsType);
12279
+ if (!compat.isCompatible) {
12280
+ this.warnings.push({
12281
+ type: "warning",
12282
+ code: "OBJECT_TYPE_MISMATCH",
12283
+ message: `Structural type mismatch: ${fromNode}.${fromPort} outputs "${sourcePortDef.tsType}" but ${toNode}.${toPort} expects "${targetPortDef.tsType}". Verify the object shapes are compatible.`,
12284
+ connection: conn,
12285
+ location: connLocation
12286
+ });
12287
+ }
12206
12288
  }
12207
12289
  }
12208
12290
  return;
@@ -12231,7 +12313,7 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12231
12313
  {
12232
12314
  type: "warning",
12233
12315
  code: "LOSSY_TYPE_COERCION",
12234
- message: `Lossy type coercion from ${sourceType} to ${targetType} in connection ${fromNode}.${fromPort} \u2192 ${toNode}.${toPort}. ${reason}. Add @strictTypes to your workflow annotation to enforce type safety.`,
12316
+ message: `Lossy type coercion from ${this.formatType(sourceType, sourceTsType)} to ${this.formatType(targetType, targetTsType)} in connection ${fromNode}.${fromPort} \u2192 ${toNode}.${toPort}. ${reason}. Add @strictTypes to your workflow annotation to enforce type safety.`,
12235
12317
  connection: conn,
12236
12318
  location: connLocation
12237
12319
  },
@@ -12256,7 +12338,7 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12256
12338
  {
12257
12339
  type: "warning",
12258
12340
  code: "UNUSUAL_TYPE_COERCION",
12259
- message: `Unusual type coercion from ${sourceType} to ${targetType} in connection ${fromNode}.${fromPort} \u2192 ${toNode}.${toPort}. ${reason}.`,
12341
+ message: `Unusual type coercion from ${this.formatType(sourceType, sourceTsType)} to ${this.formatType(targetType, targetTsType)} in connection ${fromNode}.${fromPort} \u2192 ${toNode}.${toPort}. ${reason}.`,
12260
12342
  connection: conn,
12261
12343
  location: connLocation
12262
12344
  },
@@ -12269,7 +12351,7 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12269
12351
  {
12270
12352
  type: "warning",
12271
12353
  code: "TYPE_MISMATCH",
12272
- message: `Type mismatch in connection ${fromNode}.${fromPort} (${sourceType}) \u2192 ${toNode}.${toPort} (${targetType}). Runtime coercion will be attempted.`,
12354
+ message: `Type mismatch in connection ${fromNode}.${fromPort} (${this.formatType(sourceType, sourceTsType)}) \u2192 ${toNode}.${toPort} (${this.formatType(targetType, targetTsType)}). Runtime coercion will be attempted.`,
12273
12355
  connection: conn,
12274
12356
  location: connLocation
12275
12357
  },
@@ -12704,6 +12786,30 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12704
12786
  if (portDef.scope) scopeNames.add(portDef.scope);
12705
12787
  }
12706
12788
  if (scopeNames.size === 0) continue;
12789
+ for (const conn of workflow.connections) {
12790
+ if (conn.from.scope && conn.from.node === instance.id) {
12791
+ if (!scopeNames.has(conn.from.scope)) {
12792
+ this.errors.push({
12793
+ type: "error",
12794
+ code: "SCOPE_WRONG_SCOPE_NAME",
12795
+ 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(", ")}.`,
12796
+ connection: conn,
12797
+ location: this.getConnectionLocation(conn)
12798
+ });
12799
+ }
12800
+ }
12801
+ if (conn.to.scope && conn.to.node === instance.id) {
12802
+ if (!scopeNames.has(conn.to.scope)) {
12803
+ this.errors.push({
12804
+ type: "error",
12805
+ code: "SCOPE_WRONG_SCOPE_NAME",
12806
+ 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(", ")}.`,
12807
+ connection: conn,
12808
+ location: this.getConnectionLocation(conn)
12809
+ });
12810
+ }
12811
+ }
12812
+ }
12707
12813
  for (const scopeName of scopeNames) {
12708
12814
  const childIds = [];
12709
12815
  for (const child of workflow.instances) {
@@ -12715,6 +12821,108 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12715
12821
  const scopedConnections = workflow.connections.filter(
12716
12822
  (conn) => conn.from.scope === scopeName && conn.from.node === instance.id || conn.to.scope === scopeName && conn.to.node === instance.id || conn.from.scope === scopeName && childIds.includes(conn.from.node) || conn.to.scope === scopeName && childIds.includes(conn.to.node)
12717
12823
  );
12824
+ for (const conn of scopedConnections) {
12825
+ if (conn.from.node === instance.id && conn.from.scope === scopeName) {
12826
+ const portDef = nodeType.outputs[conn.from.port];
12827
+ if (!portDef) {
12828
+ const availablePorts = Object.entries(nodeType.outputs).filter(([, p]) => p.scope === scopeName).map(([n]) => n);
12829
+ this.errors.push({
12830
+ type: "error",
12831
+ code: "SCOPE_UNKNOWN_PORT",
12832
+ message: `Scoped connection references non-existent output port "${conn.from.port}" on "${instance.id}" in scope "${scopeName}". Available scoped outputs: ${availablePorts.join(", ") || "none"}.`,
12833
+ connection: conn,
12834
+ location: this.getConnectionLocation(conn)
12835
+ });
12836
+ } else if (portDef.scope !== scopeName) {
12837
+ this.errors.push({
12838
+ type: "error",
12839
+ code: "SCOPE_UNKNOWN_PORT",
12840
+ 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)"}.`,
12841
+ connection: conn,
12842
+ location: this.getConnectionLocation(conn)
12843
+ });
12844
+ }
12845
+ }
12846
+ if (conn.to.node === instance.id && conn.to.scope === scopeName) {
12847
+ const portDef = nodeType.inputs[conn.to.port];
12848
+ if (!portDef) {
12849
+ const availablePorts = Object.entries(nodeType.inputs).filter(([, p]) => p.scope === scopeName).map(([n]) => n);
12850
+ this.errors.push({
12851
+ type: "error",
12852
+ code: "SCOPE_UNKNOWN_PORT",
12853
+ message: `Scoped connection references non-existent input port "${conn.to.port}" on "${instance.id}" in scope "${scopeName}". Available scoped inputs: ${availablePorts.join(", ") || "none"}.`,
12854
+ connection: conn,
12855
+ location: this.getConnectionLocation(conn)
12856
+ });
12857
+ } else if (portDef.scope !== scopeName) {
12858
+ this.errors.push({
12859
+ type: "error",
12860
+ code: "SCOPE_UNKNOWN_PORT",
12861
+ 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)"}.`,
12862
+ connection: conn,
12863
+ location: this.getConnectionLocation(conn)
12864
+ });
12865
+ }
12866
+ }
12867
+ }
12868
+ for (const conn of scopedConnections) {
12869
+ if (conn.from.scope === scopeName && childIds.includes(conn.from.node)) {
12870
+ if (conn.to.node !== instance.id && !childIds.includes(conn.to.node)) {
12871
+ this.errors.push({
12872
+ type: "error",
12873
+ code: "SCOPE_CONNECTION_OUTSIDE",
12874
+ message: `Scoped connection from "${conn.from.node}.${conn.from.port}" targets "${conn.to.node}" which is not inside scope "${scopeName}" of "${instance.id}".`,
12875
+ connection: conn,
12876
+ location: this.getConnectionLocation(conn)
12877
+ });
12878
+ }
12879
+ }
12880
+ if (conn.to.scope === scopeName && childIds.includes(conn.to.node)) {
12881
+ if (conn.from.node !== instance.id && !childIds.includes(conn.from.node)) {
12882
+ this.errors.push({
12883
+ type: "error",
12884
+ code: "SCOPE_CONNECTION_OUTSIDE",
12885
+ message: `Scoped connection to "${conn.to.node}.${conn.to.port}" sources from "${conn.from.node}" which is not inside scope "${scopeName}" of "${instance.id}".`,
12886
+ connection: conn,
12887
+ location: this.getConnectionLocation(conn)
12888
+ });
12889
+ }
12890
+ }
12891
+ }
12892
+ for (const conn of scopedConnections) {
12893
+ if (conn.from.node === instance.id && conn.from.scope === scopeName) {
12894
+ const parentPort = nodeType.outputs[conn.from.port];
12895
+ const childType = instanceMap.get(conn.to.node);
12896
+ const childPort = childType?.inputs[conn.to.port];
12897
+ if (parentPort && childPort && parentPort.dataType !== "STEP" && childPort.dataType !== "STEP") {
12898
+ if (parentPort.dataType !== childPort.dataType && parentPort.dataType !== "ANY" && childPort.dataType !== "ANY") {
12899
+ this.warnings.push({
12900
+ type: "warning",
12901
+ code: "SCOPE_PORT_TYPE_MISMATCH",
12902
+ 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)}.`,
12903
+ connection: conn,
12904
+ location: this.getConnectionLocation(conn)
12905
+ });
12906
+ }
12907
+ }
12908
+ }
12909
+ if (conn.to.node === instance.id && conn.to.scope === scopeName) {
12910
+ const parentPort = nodeType.inputs[conn.to.port];
12911
+ const childType = instanceMap.get(conn.from.node);
12912
+ const childPort = childType?.outputs[conn.from.port];
12913
+ if (parentPort && childPort && parentPort.dataType !== "STEP" && childPort.dataType !== "STEP") {
12914
+ if (parentPort.dataType !== childPort.dataType && parentPort.dataType !== "ANY" && childPort.dataType !== "ANY") {
12915
+ this.warnings.push({
12916
+ type: "warning",
12917
+ code: "SCOPE_PORT_TYPE_MISMATCH",
12918
+ 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)}.`,
12919
+ connection: conn,
12920
+ location: this.getConnectionLocation(conn)
12921
+ });
12922
+ }
12923
+ }
12924
+ }
12925
+ }
12718
12926
  for (const childId of childIds) {
12719
12927
  const childType = instanceMap.get(childId);
12720
12928
  if (!childType) continue;
@@ -12758,9 +12966,36 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
12758
12966
  });
12759
12967
  }
12760
12968
  }
12969
+ for (const childId of childIds) {
12970
+ const hasConnectionFromParent = scopedConnections.some(
12971
+ (conn) => conn.from.node === instance.id && conn.from.scope === scopeName && conn.to.node === childId
12972
+ );
12973
+ const hasConnectionToParent = scopedConnections.some(
12974
+ (conn) => conn.to.node === instance.id && conn.to.scope === scopeName && conn.from.node === childId
12975
+ );
12976
+ if (!hasConnectionFromParent && !hasConnectionToParent) {
12977
+ this.warnings.push({
12978
+ type: "warning",
12979
+ code: "SCOPE_ORPHANED_CHILD",
12980
+ 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.`,
12981
+ node: childId,
12982
+ location: this.getInstanceLocation(workflow, childId)
12983
+ });
12984
+ }
12985
+ }
12761
12986
  }
12762
12987
  }
12763
12988
  }
12989
+ /**
12990
+ * Format a type for display in error messages.
12991
+ * Prefers the structural TypeScript type when available, falling back to the enum name.
12992
+ */
12993
+ formatType(dataType, tsType) {
12994
+ if (tsType) {
12995
+ return `${tsType} (${dataType})`;
12996
+ }
12997
+ return dataType;
12998
+ }
12764
12999
  normalizeTypeString(type2) {
12765
13000
  let n = type2;
12766
13001
  n = n.replace(/\s+/g, "");
@@ -28985,7 +29220,29 @@ function generateNodeCallWithContext(instance, nodeType, workflow, _availableVar
28985
29220
  });
28986
29221
  const resultVar = `${safeId}Result`;
28987
29222
  const awaitKeyword = nodeType.isAsync ? "await " : "";
28988
- if (nodeType.expression) {
29223
+ if (nodeType.variant === "COERCION") {
29224
+ const coerceExprMap = {
29225
+ __fw_toString: "String",
29226
+ __fw_toNumber: "Number",
29227
+ __fw_toBoolean: "Boolean",
29228
+ __fw_toJSON: "JSON.stringify",
29229
+ __fw_parseJSON: "JSON.parse"
29230
+ };
29231
+ const coerceExpr = coerceExprMap[functionName] || "String";
29232
+ const valueArg = args[0] || "undefined";
29233
+ lines.push(
29234
+ `${indent} const ${resultVar} = ${coerceExpr}(${valueArg});`
29235
+ );
29236
+ lines.push(
29237
+ `${indent} ${setCall}({ id: '${instanceId}', portName: 'result', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${resultVar});`
29238
+ );
29239
+ lines.push(
29240
+ `${indent} ${setCall}({ id: '${instanceId}', portName: 'onSuccess', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, true);`
29241
+ );
29242
+ lines.push(
29243
+ `${indent} ${setCall}({ id: '${instanceId}', portName: 'onFailure', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, false);`
29244
+ );
29245
+ } else if (nodeType.expression) {
28989
29246
  lines.push(
28990
29247
  `${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${args.join(", ")});`
28991
29248
  );
@@ -30510,6 +30767,9 @@ var AnnotationGenerator = class {
30510
30767
  if (nodeType.variant === "MAP_ITERATOR") {
30511
30768
  return;
30512
30769
  }
30770
+ if (nodeType.variant === "COERCION") {
30771
+ return;
30772
+ }
30513
30773
  lines.push(
30514
30774
  ...this.generateNodeTypeAnnotation(
30515
30775
  nodeType,
@@ -30534,7 +30794,9 @@ var AnnotationGenerator = class {
30534
30794
  }
30535
30795
  lines.push("/**");
30536
30796
  if (includeComments && nodeType.description) {
30537
- lines.push(` * ${nodeType.description}`);
30797
+ for (const descLine of nodeType.description.split("\n")) {
30798
+ lines.push(` * ${descLine}`);
30799
+ }
30538
30800
  lines.push(` *`);
30539
30801
  }
30540
30802
  lines.push(" * @flowWeaver nodeType");
@@ -30618,12 +30880,15 @@ var AnnotationGenerator = class {
30618
30880
  const macroInstanceIds = /* @__PURE__ */ new Set();
30619
30881
  const macroChildIds = /* @__PURE__ */ new Set();
30620
30882
  const macroScopeNames = /* @__PURE__ */ new Set();
30883
+ const coerceInstanceIds = /* @__PURE__ */ new Set();
30621
30884
  if (workflow.macros && workflow.macros.length > 0) {
30622
30885
  for (const macro of workflow.macros) {
30623
30886
  if (macro.type === "map") {
30624
30887
  macroInstanceIds.add(macro.instanceId);
30625
30888
  macroChildIds.add(macro.childId);
30626
30889
  macroScopeNames.add(`${macro.instanceId}.iterate`);
30890
+ } else if (macro.type === "coerce") {
30891
+ coerceInstanceIds.add(macro.instanceId);
30627
30892
  }
30628
30893
  }
30629
30894
  }
@@ -30673,6 +30938,7 @@ var AnnotationGenerator = class {
30673
30938
  }
30674
30939
  workflow.instances.forEach((instance) => {
30675
30940
  if (macroInstanceIds.has(instance.id)) return;
30941
+ if (coerceInstanceIds.has(instance.id)) return;
30676
30942
  if (macroChildIds.has(instance.id) && instance.parent) {
30677
30943
  const stripped = { ...instance, parent: void 0 };
30678
30944
  lines.push(generateNodeInstanceTag(stripped));
@@ -30717,6 +30983,10 @@ var AnnotationGenerator = class {
30717
30983
  const srcs = macro.sources.map((s) => s.port ? `${s.node}.${s.port}` : s.node).join(", ");
30718
30984
  const tgt = `${macro.target.node}.${macro.target.port}`;
30719
30985
  lines.push(` * @fanIn ${srcs} -> ${tgt}`);
30986
+ } else if (macro.type === "coerce") {
30987
+ const src = `${macro.source.node}.${macro.source.port}`;
30988
+ const tgt = `${macro.target.node}.${macro.target.port}`;
30989
+ lines.push(` * @coerce ${macro.instanceId} ${src} -> ${tgt} as ${macro.targetType}`);
30720
30990
  }
30721
30991
  }
30722
30992
  }
@@ -30872,6 +31142,11 @@ function isConnectionCoveredByMacroStatic(conn, macros) {
30872
31142
  }
30873
31143
  }
30874
31144
  }
31145
+ } else if (macro.type === "coerce") {
31146
+ if (conn.from.scope || conn.to.scope) continue;
31147
+ if (conn.to.node === macro.instanceId || conn.from.node === macro.instanceId) {
31148
+ return true;
31149
+ }
30875
31150
  }
30876
31151
  }
30877
31152
  return false;
@@ -31254,6 +31529,9 @@ function replaceWorkflowFunctionBody(source, functionName, newBody) {
31254
31529
  return { code: source, changed: false };
31255
31530
  }
31256
31531
  const closeBraceIdx = functionNode.body.end - 1;
31532
+ if (closeBraceIdx <= openBraceIdx) {
31533
+ return { code: source, changed: false };
31534
+ }
31257
31535
  const before = source.slice(0, openBraceIdx + 1);
31258
31536
  const after = source.slice(closeBraceIdx);
31259
31537
  const newBodyWithMarkers = [
@@ -39792,17 +40070,17 @@ var CstVisitorDefinitionError;
39792
40070
  CstVisitorDefinitionError2[CstVisitorDefinitionError2["REDUNDANT_METHOD"] = 0] = "REDUNDANT_METHOD";
39793
40071
  CstVisitorDefinitionError2[CstVisitorDefinitionError2["MISSING_METHOD"] = 1] = "MISSING_METHOD";
39794
40072
  })(CstVisitorDefinitionError || (CstVisitorDefinitionError = {}));
39795
- function validateVisitor(visitorInstance10, ruleNames) {
39796
- const missingErrors = validateMissingCstMethods(visitorInstance10, ruleNames);
40073
+ function validateVisitor(visitorInstance11, ruleNames) {
40074
+ const missingErrors = validateMissingCstMethods(visitorInstance11, ruleNames);
39797
40075
  return missingErrors;
39798
40076
  }
39799
- function validateMissingCstMethods(visitorInstance10, ruleNames) {
40077
+ function validateMissingCstMethods(visitorInstance11, ruleNames) {
39800
40078
  const missingRuleNames = filter_default(ruleNames, (currRuleName) => {
39801
- return isFunction_default(visitorInstance10[currRuleName]) === false;
40079
+ return isFunction_default(visitorInstance11[currRuleName]) === false;
39802
40080
  });
39803
40081
  const errors2 = map_default(missingRuleNames, (currRuleName) => {
39804
40082
  return {
39805
- msg: `Missing visitor method: <${currRuleName}> on ${visitorInstance10.constructor.name} CST Visitor.`,
40083
+ msg: `Missing visitor method: <${currRuleName}> on ${visitorInstance11.constructor.name} CST Visitor.`,
39806
40084
  type: CstVisitorDefinitionError.MISSING_METHOD,
39807
40085
  methodName: currRuleName
39808
40086
  };
@@ -41207,7 +41485,7 @@ var Parser = class _Parser {
41207
41485
  /**
41208
41486
  * @deprecated use the **instance** method with the same name instead
41209
41487
  */
41210
- static performSelfAnalysis(parserInstance10) {
41488
+ static performSelfAnalysis(parserInstance11) {
41211
41489
  throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.");
41212
41490
  }
41213
41491
  performSelfAnalysis() {
@@ -41418,6 +41696,10 @@ var FanInTag = createToken({
41418
41696
  name: "FanInTag",
41419
41697
  pattern: /@fanIn\b/
41420
41698
  });
41699
+ var CoerceTag = createToken({
41700
+ name: "CoerceTag",
41701
+ pattern: /@coerce\b/
41702
+ });
41421
41703
  var TriggerTag = createToken({
41422
41704
  name: "TriggerTag",
41423
41705
  pattern: /@trigger\b/
@@ -41442,6 +41724,10 @@ var OverKeyword = createToken({
41442
41724
  name: "OverKeyword",
41443
41725
  pattern: /over\b/
41444
41726
  });
41727
+ var AsKeyword = createToken({
41728
+ name: "AsKeyword",
41729
+ pattern: /as\b/
41730
+ });
41445
41731
  var ParamTag = createToken({
41446
41732
  name: "ParamTag",
41447
41733
  pattern: /@param\b/
@@ -41661,6 +41947,7 @@ var allTokens = [
41661
41947
  PathTag,
41662
41948
  FanOutTag,
41663
41949
  FanInTag,
41950
+ CoerceTag,
41664
41951
  TriggerTag,
41665
41952
  CancelOnTag,
41666
41953
  RetriesTag,
@@ -41694,6 +41981,7 @@ var allTokens = [
41694
41981
  PeriodEq,
41695
41982
  // Keywords (before Identifier)
41696
41983
  OverKeyword,
41984
+ AsKeyword,
41697
41985
  MinimizedKeyword,
41698
41986
  TopKeyword,
41699
41987
  BottomKeyword,
@@ -41870,6 +42158,7 @@ var PortParser = class extends CstParser {
41870
42158
  { ALT: () => this.CONSUME(Asterisk) },
41871
42159
  // Keywords that can appear in description text
41872
42160
  { ALT: () => this.CONSUME(OverKeyword) },
42161
+ { ALT: () => this.CONSUME(AsKeyword) },
41873
42162
  { ALT: () => this.CONSUME(TopKeyword) },
41874
42163
  { ALT: () => this.CONSUME(BottomKeyword) },
41875
42164
  { ALT: () => this.CONSUME(TrueKeyword) },
@@ -43021,6 +43310,77 @@ function getFanGrammar() {
43021
43310
  return parserInstance8.getSerializedGastProductions();
43022
43311
  }
43023
43312
 
43313
+ // src/chevrotain-parser/coerce-parser.ts
43314
+ var VALID_COERCE_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "json", "object"]);
43315
+ var CoerceParser = class extends CstParser {
43316
+ constructor() {
43317
+ super(allTokens);
43318
+ this.performSelfAnalysis();
43319
+ }
43320
+ // @coerce Identifier portRef Arrow portRef AsKeyword Identifier
43321
+ coerceLine = this.RULE("coerceLine", () => {
43322
+ this.CONSUME(CoerceTag);
43323
+ this.CONSUME(Identifier, { LABEL: "instanceId" });
43324
+ this.SUBRULE(this.portRef, { LABEL: "source" });
43325
+ this.CONSUME(Arrow);
43326
+ this.SUBRULE2(this.portRef, { LABEL: "target" });
43327
+ this.CONSUME(AsKeyword);
43328
+ this.CONSUME2(Identifier, { LABEL: "typeName" });
43329
+ });
43330
+ // portRef: Identifier Dot Identifier
43331
+ portRef = this.RULE("portRef", () => {
43332
+ this.CONSUME(Identifier, { LABEL: "nodeName" });
43333
+ this.CONSUME(Dot);
43334
+ this.CONSUME2(Identifier, { LABEL: "portName" });
43335
+ });
43336
+ };
43337
+ var parserInstance9 = new CoerceParser();
43338
+ var BaseVisitor9 = parserInstance9.getBaseCstVisitorConstructor();
43339
+ var CoerceVisitor = class extends BaseVisitor9 {
43340
+ constructor() {
43341
+ super();
43342
+ this.validateVisitor();
43343
+ }
43344
+ coerceLine(ctx) {
43345
+ const instanceId = ctx.instanceId[0].image;
43346
+ const source = this.portRef(ctx.source[0].children);
43347
+ const target = this.portRef(ctx.target[0].children);
43348
+ const targetType = ctx.typeName[0].image;
43349
+ return { instanceId, source, target, targetType };
43350
+ }
43351
+ portRef(ctx) {
43352
+ return {
43353
+ node: ctx.nodeName[0].image,
43354
+ port: ctx.portName[0].image
43355
+ };
43356
+ }
43357
+ };
43358
+ var visitorInstance9 = new CoerceVisitor();
43359
+ function parseCoerceLine(input, warnings) {
43360
+ const lexResult = JSDocLexer.tokenize(input);
43361
+ if (lexResult.errors.length > 0 || lexResult.tokens.length === 0) return null;
43362
+ if (lexResult.tokens[0].tokenType !== CoerceTag) return null;
43363
+ parserInstance9.input = lexResult.tokens;
43364
+ const cst = parserInstance9.coerceLine();
43365
+ if (parserInstance9.errors.length > 0) {
43366
+ const truncatedInput = input.length > 80 ? input.substring(0, 80) + "..." : input;
43367
+ warnings.push(
43368
+ `Failed to parse @coerce line: "${truncatedInput}"
43369
+ Error: ${parserInstance9.errors[0].message}
43370
+ Expected format: @coerce instanceId source.port -> target.port as type`
43371
+ );
43372
+ return null;
43373
+ }
43374
+ const result = visitorInstance9.visit(cst);
43375
+ if (!VALID_COERCE_TYPES.has(result.targetType)) {
43376
+ warnings.push(
43377
+ `@coerce: invalid target type "${result.targetType}". Valid types: ${[...VALID_COERCE_TYPES].join(", ")}`
43378
+ );
43379
+ return null;
43380
+ }
43381
+ return result;
43382
+ }
43383
+
43024
43384
  // src/chevrotain-parser/trigger-cancel-parser.ts
43025
43385
  function stripQuotes(s) {
43026
43386
  if (s.startsWith('"') && s.endsWith('"')) {
@@ -43085,9 +43445,9 @@ var TriggerCancelParser = class extends CstParser {
43085
43445
  });
43086
43446
  });
43087
43447
  };
43088
- var parserInstance9 = new TriggerCancelParser();
43089
- var BaseVisitor9 = parserInstance9.getBaseCstVisitorConstructor();
43090
- var TriggerCancelVisitor = class extends BaseVisitor9 {
43448
+ var parserInstance10 = new TriggerCancelParser();
43449
+ var BaseVisitor10 = parserInstance10.getBaseCstVisitorConstructor();
43450
+ var TriggerCancelVisitor = class extends BaseVisitor10 {
43091
43451
  constructor() {
43092
43452
  super();
43093
43453
  this.validateVisitor();
@@ -43130,7 +43490,7 @@ var TriggerCancelVisitor = class extends BaseVisitor9 {
43130
43490
  return result;
43131
43491
  }
43132
43492
  };
43133
- var visitorInstance9 = new TriggerCancelVisitor();
43493
+ var visitorInstance10 = new TriggerCancelVisitor();
43134
43494
  function parseTriggerLine(input, warnings) {
43135
43495
  const lexResult = JSDocLexer.tokenize(input);
43136
43496
  if (lexResult.errors.length > 0) {
@@ -43143,10 +43503,10 @@ function parseTriggerLine(input, warnings) {
43143
43503
  if (firstToken.tokenType !== TriggerTag) {
43144
43504
  return null;
43145
43505
  }
43146
- parserInstance9.input = lexResult.tokens;
43147
- const cst = parserInstance9.triggerLine();
43148
- if (parserInstance9.errors.length > 0) {
43149
- const firstError = parserInstance9.errors[0];
43506
+ parserInstance10.input = lexResult.tokens;
43507
+ const cst = parserInstance10.triggerLine();
43508
+ if (parserInstance10.errors.length > 0) {
43509
+ const firstError = parserInstance10.errors[0];
43150
43510
  const truncatedInput = input.length > 60 ? input.substring(0, 60) + "..." : input;
43151
43511
  warnings.push(
43152
43512
  `Failed to parse trigger line: "${truncatedInput}"
@@ -43155,7 +43515,7 @@ function parseTriggerLine(input, warnings) {
43155
43515
  );
43156
43516
  return null;
43157
43517
  }
43158
- const result = visitorInstance9.visit(cst);
43518
+ const result = visitorInstance10.visit(cst);
43159
43519
  if (result.cron && !CRON_REGEX.test(result.cron)) {
43160
43520
  warnings.push(`Invalid cron expression: "${result.cron}". Expected 5 fields (minute hour day month weekday).`);
43161
43521
  }
@@ -43173,10 +43533,10 @@ function parseCancelOnLine(input, warnings) {
43173
43533
  if (firstToken.tokenType !== CancelOnTag) {
43174
43534
  return null;
43175
43535
  }
43176
- parserInstance9.input = lexResult.tokens;
43177
- const cst = parserInstance9.cancelOnLine();
43178
- if (parserInstance9.errors.length > 0) {
43179
- const firstError = parserInstance9.errors[0];
43536
+ parserInstance10.input = lexResult.tokens;
43537
+ const cst = parserInstance10.cancelOnLine();
43538
+ if (parserInstance10.errors.length > 0) {
43539
+ const firstError = parserInstance10.errors[0];
43180
43540
  const truncatedInput = input.length > 60 ? input.substring(0, 60) + "..." : input;
43181
43541
  warnings.push(
43182
43542
  `Failed to parse cancelOn line: "${truncatedInput}"
@@ -43185,10 +43545,10 @@ function parseCancelOnLine(input, warnings) {
43185
43545
  );
43186
43546
  return null;
43187
43547
  }
43188
- return visitorInstance9.visit(cst);
43548
+ return visitorInstance10.visit(cst);
43189
43549
  }
43190
43550
  function getTriggerCancelGrammar() {
43191
- return parserInstance9.getSerializedGastProductions();
43551
+ return parserInstance10.getSerializedGastProductions();
43192
43552
  }
43193
43553
  function parseThrottleLine(input, warnings) {
43194
43554
  const lexResult = JSDocLexer.tokenize(input);
@@ -43202,10 +43562,10 @@ function parseThrottleLine(input, warnings) {
43202
43562
  if (firstToken.tokenType !== ThrottleTag) {
43203
43563
  return null;
43204
43564
  }
43205
- parserInstance9.input = lexResult.tokens;
43206
- const cst = parserInstance9.throttleLine();
43207
- if (parserInstance9.errors.length > 0) {
43208
- const firstError = parserInstance9.errors[0];
43565
+ parserInstance10.input = lexResult.tokens;
43566
+ const cst = parserInstance10.throttleLine();
43567
+ if (parserInstance10.errors.length > 0) {
43568
+ const firstError = parserInstance10.errors[0];
43209
43569
  const truncatedInput = input.length > 60 ? input.substring(0, 60) + "..." : input;
43210
43570
  warnings.push(
43211
43571
  `Failed to parse throttle line: "${truncatedInput}"
@@ -43214,7 +43574,7 @@ function parseThrottleLine(input, warnings) {
43214
43574
  );
43215
43575
  return null;
43216
43576
  }
43217
- return visitorInstance9.visit(cst);
43577
+ return visitorInstance10.visit(cst);
43218
43578
  }
43219
43579
 
43220
43580
  // src/chevrotain-parser/grammar-diagrams.ts
@@ -43632,6 +43992,9 @@ var JSDocParser = class {
43632
43992
  case "fanIn":
43633
43993
  this.parseFanInTag(tag, config2, warnings);
43634
43994
  break;
43995
+ case "coerce":
43996
+ this.parseCoerceTag(tag, config2, warnings);
43997
+ break;
43635
43998
  case "trigger":
43636
43999
  this.parseTriggerTag(tag, config2, warnings);
43637
44000
  break;
@@ -44229,6 +44592,21 @@ var JSDocParser = class {
44229
44592
  target: { node: result.target.node, port: result.target.port }
44230
44593
  });
44231
44594
  }
44595
+ parseCoerceTag(tag, config2, warnings) {
44596
+ const comment = tag.getCommentText() || "";
44597
+ const result = parseCoerceLine(`@coerce ${comment}`, warnings);
44598
+ if (!result) {
44599
+ warnings.push(`Invalid @coerce tag format: ${comment}`);
44600
+ return;
44601
+ }
44602
+ config2.coercions = config2.coercions || [];
44603
+ config2.coercions.push({
44604
+ instanceId: result.instanceId,
44605
+ source: result.source,
44606
+ target: result.target,
44607
+ targetType: result.targetType
44608
+ });
44609
+ }
44232
44610
  /**
44233
44611
  * Parse @trigger tag using Chevrotain parser.
44234
44612
  */
@@ -44484,6 +44862,49 @@ var LRUCache2 = class {
44484
44862
  }
44485
44863
  };
44486
44864
 
44865
+ // src/built-in-nodes/coercion-types.ts
44866
+ var COERCE_TYPE_MAP = {
44867
+ string: "__fw_toString",
44868
+ number: "__fw_toNumber",
44869
+ boolean: "__fw_toBoolean",
44870
+ json: "__fw_toJSON",
44871
+ object: "__fw_parseJSON"
44872
+ };
44873
+ function makeCoercionNodeType(name, outputType, outputTsType) {
44874
+ return {
44875
+ type: "NodeType",
44876
+ name,
44877
+ functionName: name,
44878
+ expression: true,
44879
+ isAsync: false,
44880
+ hasSuccessPort: true,
44881
+ hasFailurePort: true,
44882
+ executeWhen: "CONJUNCTION",
44883
+ variant: "COERCION",
44884
+ inputs: {
44885
+ execute: { dataType: "STEP", label: "Execute" },
44886
+ value: { dataType: "ANY", label: "Value", tsType: "unknown" }
44887
+ },
44888
+ outputs: {
44889
+ onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
44890
+ onFailure: { dataType: "STEP", label: "On Failure", failure: true, isControlFlow: true },
44891
+ result: { dataType: outputType, label: "Result", tsType: outputTsType }
44892
+ },
44893
+ visuals: {
44894
+ color: "#0d9488",
44895
+ icon: "transform",
44896
+ tags: [{ label: "coerce" }]
44897
+ }
44898
+ };
44899
+ }
44900
+ var COERCION_NODE_TYPES = {
44901
+ __fw_toString: makeCoercionNodeType("__fw_toString", "STRING", "string"),
44902
+ __fw_toNumber: makeCoercionNodeType("__fw_toNumber", "NUMBER", "number"),
44903
+ __fw_toBoolean: makeCoercionNodeType("__fw_toBoolean", "BOOLEAN", "boolean"),
44904
+ __fw_toJSON: makeCoercionNodeType("__fw_toJSON", "STRING", "string"),
44905
+ __fw_parseJSON: makeCoercionNodeType("__fw_parseJSON", "OBJECT", "object")
44906
+ };
44907
+
44487
44908
  // src/parser.ts
44488
44909
  function externalToAST(ext2) {
44489
44910
  const inputs = {};
@@ -45314,9 +45735,19 @@ ${fn.getText()}` : fn.getText();
45314
45735
  if (config2.fanIns && config2.fanIns.length > 0) {
45315
45736
  this.expandFanInMacros(config2.fanIns, instances, connections, startPorts, exitPorts, macros, errors2);
45316
45737
  }
45738
+ if (config2.coercions && config2.coercions.length > 0) {
45739
+ this.expandCoerceMacros(config2.coercions, instances, connections, startPorts, exitPorts, macros, errors2);
45740
+ }
45317
45741
  const importedNames = new Set(importedNpmNodeTypes.map((nt) => nt.name));
45318
45742
  const dedupedAvailableTypes = allAvailableNodeTypes.filter((nt) => !importedNames.has(nt.name));
45319
45743
  const workflowNodeTypes = [...dedupedAvailableTypes, ...importedNpmNodeTypes];
45744
+ for (const inst of instances) {
45745
+ if (inst.nodeType.startsWith("__fw_") && COERCION_NODE_TYPES[inst.nodeType]) {
45746
+ if (!workflowNodeTypes.some((nt) => nt.functionName === inst.nodeType)) {
45747
+ workflowNodeTypes.push(COERCION_NODE_TYPES[inst.nodeType]);
45748
+ }
45749
+ }
45750
+ }
45320
45751
  const ui = {};
45321
45752
  const startPosition = config2.positions?.["Start"];
45322
45753
  if (startPosition) {
@@ -45922,6 +46353,57 @@ ${fn.getText()}` : fn.getText();
45922
46353
  });
45923
46354
  }
45924
46355
  }
46356
+ /**
46357
+ * Expand @coerce macros into synthetic coercion node instances + connections.
46358
+ */
46359
+ expandCoerceMacros(coerceConfigs, instances, connections, startPorts, exitPorts, macros, errors2) {
46360
+ const instanceIds = new Set(instances.map((i) => i.id));
46361
+ instanceIds.add("Start");
46362
+ instanceIds.add("Exit");
46363
+ for (const config2 of coerceConfigs) {
46364
+ const { instanceId, source, target, targetType } = config2;
46365
+ if (!instanceIds.has(source.node)) {
46366
+ errors2.push(`@coerce: source node "${source.node}" does not exist`);
46367
+ continue;
46368
+ }
46369
+ if (!instanceIds.has(target.node)) {
46370
+ errors2.push(`@coerce: target node "${target.node}" does not exist`);
46371
+ continue;
46372
+ }
46373
+ if (instanceIds.has(instanceId)) {
46374
+ errors2.push(`@coerce: instance ID "${instanceId}" already exists`);
46375
+ continue;
46376
+ }
46377
+ const nodeTypeName = COERCE_TYPE_MAP[targetType];
46378
+ if (!nodeTypeName) {
46379
+ errors2.push(`@coerce: unknown target type "${targetType}"`);
46380
+ continue;
46381
+ }
46382
+ instances.push({
46383
+ type: "NodeInstance",
46384
+ id: instanceId,
46385
+ nodeType: nodeTypeName
46386
+ });
46387
+ instanceIds.add(instanceId);
46388
+ connections.push({
46389
+ type: "Connection",
46390
+ from: { node: source.node, port: source.port },
46391
+ to: { node: instanceId, port: "value" }
46392
+ });
46393
+ connections.push({
46394
+ type: "Connection",
46395
+ from: { node: instanceId, port: "result" },
46396
+ to: { node: target.node, port: target.port }
46397
+ });
46398
+ macros.push({
46399
+ type: "coerce",
46400
+ instanceId,
46401
+ source: { node: source.node, port: source.port },
46402
+ target: { node: target.node, port: target.port },
46403
+ targetType
46404
+ });
46405
+ }
46406
+ }
45925
46407
  /**
45926
46408
  * Generate automatic connections for @autoConnect workflows.
45927
46409
  * Wires nodes in declaration order as a linear pipeline:
@@ -52545,12 +53027,31 @@ function extractQuoted(message) {
52545
53027
  return matches ? matches.map((m) => m.replace(/"/g, "")) : [];
52546
53028
  }
52547
53029
  function extractTypes(message) {
53030
+ const structuralMatch = message.match(/from (.+?) \(\w+\) to (.+?) \(\w+\)/);
53031
+ if (structuralMatch) return { source: structuralMatch[1], target: structuralMatch[2] };
52548
53032
  const fromToMatch = message.match(/from (\w+) to (\w+)/i);
52549
53033
  if (fromToMatch) return { source: fromToMatch[1], target: fromToMatch[2] };
53034
+ const structuralParenMatch = message.match(/\((.+?) \(\w+\)\) .* \((.+?) \(\w+\)\)/);
53035
+ if (structuralParenMatch) return { source: structuralParenMatch[1], target: structuralParenMatch[2] };
52550
53036
  const parenMatch = message.match(/\((\w+)\) .* \((\w+)\)/);
52551
53037
  if (parenMatch) return { source: parenMatch[1], target: parenMatch[2] };
52552
53038
  return null;
52553
53039
  }
53040
+ var COERCE_TARGET_TYPES = {
53041
+ STRING: "string",
53042
+ NUMBER: "number",
53043
+ BOOLEAN: "boolean",
53044
+ OBJECT: "object"
53045
+ };
53046
+ function buildCoerceSuggestion(quoted, targetType) {
53047
+ if (quoted.length < 2) return null;
53048
+ const portRefPattern = /^[\w]+\.[\w]+$/;
53049
+ const portRefs = quoted.filter((q) => portRefPattern.test(q));
53050
+ if (portRefs.length < 2) return null;
53051
+ const coerceType = COERCE_TARGET_TYPES[targetType.toUpperCase()] || targetType.toLowerCase();
53052
+ if (!["string", "number", "boolean", "json", "object"].includes(coerceType)) return null;
53053
+ return `@coerce c1 ${portRefs[0]} -> ${portRefs[1]} as ${coerceType}`;
53054
+ }
52554
53055
  function extractCyclePath(message) {
52555
53056
  const match2 = message.match(/:\s*(.+ -> .+)/);
52556
53057
  return match2 ? match2[1] : null;
@@ -52653,10 +53154,12 @@ var errorMappers = {
52653
53154
  const types2 = extractTypes(error2.message);
52654
53155
  const source = types2?.source || "unknown";
52655
53156
  const target = types2?.target || "unknown";
53157
+ const quoted = extractQuoted(error2.message);
53158
+ const coerceSuggestion = buildCoerceSuggestion(quoted, target);
52656
53159
  return {
52657
53160
  title: "Type Mismatch",
52658
53161
  explanation: `Type mismatch: you're connecting a ${source} to a ${target}. The value will be automatically converted, but this might cause unexpected behavior.`,
52659
- fix: `Add a conversion node between the two ports, or change one of the port types to match. You can also use @strictTypes to turn this into an error.`,
53162
+ fix: coerceSuggestion ? `Add an explicit coercion: \`${coerceSuggestion}\`, change one of the port types, or use @strictTypes to turn this into an error.` : `Add a @coerce annotation between the two ports, change one of the port types, or use @strictTypes to turn this into an error.`,
52660
53163
  code: error2.code
52661
53164
  };
52662
53165
  },
@@ -52750,10 +53253,12 @@ var errorMappers = {
52750
53253
  const types2 = extractTypes(error2.message);
52751
53254
  const source = types2?.source || "unknown";
52752
53255
  const target = types2?.target || "unknown";
53256
+ const quoted = extractQuoted(error2.message);
53257
+ const coerceSuggestion = buildCoerceSuggestion(quoted, target);
52753
53258
  return {
52754
53259
  title: "Type Incompatible",
52755
53260
  explanation: `Type mismatch: ${source} to ${target}. With @strictTypes enabled, this is an error instead of a warning.`,
52756
- fix: `Add a conversion node between the ports, change one of the port types, or remove @strictTypes to allow implicit coercions.`,
53261
+ fix: coerceSuggestion ? `Add an explicit coercion: \`${coerceSuggestion}\`, change one of the port types, or remove @strictTypes to allow implicit coercions.` : `Add a @coerce annotation between the ports, change one of the port types, or remove @strictTypes to allow implicit coercions.`,
52757
53262
  code: error2.code
52758
53263
  };
52759
53264
  },
@@ -52761,10 +53266,12 @@ var errorMappers = {
52761
53266
  const types2 = extractTypes(error2.message);
52762
53267
  const source = types2?.source || "unknown";
52763
53268
  const target = types2?.target || "unknown";
53269
+ const quoted = extractQuoted(error2.message);
53270
+ const coerceSuggestion = buildCoerceSuggestion(quoted, target);
52764
53271
  return {
52765
53272
  title: "Unusual Type Coercion",
52766
53273
  explanation: `Converting ${source} to ${target} is technically valid but semantically unusual and may produce unexpected behavior.`,
52767
- fix: `Add an explicit conversion node if this is intentional, or use @strictTypes to enforce type safety.`,
53274
+ fix: coerceSuggestion ? `If intentional, add an explicit coercion: \`${coerceSuggestion}\`, or use @strictTypes to enforce type safety.` : `If intentional, add an explicit @coerce annotation, or use @strictTypes to enforce type safety.`,
52768
53275
  code: error2.code
52769
53276
  };
52770
53277
  },
@@ -52789,6 +53296,82 @@ var errorMappers = {
52789
53296
  code: error2.code
52790
53297
  };
52791
53298
  },
53299
+ SCOPE_MISSING_REQUIRED_INPUT(error2) {
53300
+ const quoted = extractQuoted(error2.message);
53301
+ const childId = quoted[0] || error2.node || "unknown";
53302
+ const portName = quoted[1] || "unknown";
53303
+ const scopeName = quoted[2] || "unknown";
53304
+ const parentId = quoted[3] || "unknown";
53305
+ return {
53306
+ title: "Scope Child Missing Input",
53307
+ explanation: `Child node '${childId}' inside scope '${scopeName}' of '${parentId}' has a required input '${portName}' with no connection.`,
53308
+ fix: `Connect the parent's scoped output to '${childId}.${portName}' within the scope, or mark the port as optional.`,
53309
+ code: error2.code
53310
+ };
53311
+ },
53312
+ SCOPE_UNUSED_INPUT(error2) {
53313
+ const quoted = extractQuoted(error2.message);
53314
+ const portName = quoted[0] || "unknown";
53315
+ const parentId = quoted[1] || error2.node || "unknown";
53316
+ const scopeName = quoted[2] || "unknown";
53317
+ return {
53318
+ title: "Scope Input Unused",
53319
+ explanation: `Scoped input '${portName}' on '${parentId}' expects data back from inner nodes, but nothing connects to it within scope '${scopeName}'.`,
53320
+ fix: `Connect an inner node's output to '${parentId}.${portName}:${scopeName}' to return data from the scope.`,
53321
+ code: error2.code
53322
+ };
53323
+ },
53324
+ SCOPE_WRONG_SCOPE_NAME(error2) {
53325
+ const quoted = extractQuoted(error2.message);
53326
+ const portRef = quoted[0] || "unknown";
53327
+ const nodeName = quoted[2] || "unknown";
53328
+ return {
53329
+ title: "Invalid Scope Qualifier",
53330
+ explanation: `A connection at '${portRef}' uses an invalid scope name. Node '${nodeName}' doesn't define that scope.`,
53331
+ fix: `Check the scope names defined on the node type. Fix the :scopeName qualifier in the @connect annotation to match an existing scope.`,
53332
+ code: error2.code
53333
+ };
53334
+ },
53335
+ SCOPE_CONNECTION_OUTSIDE(error2) {
53336
+ const quoted = extractQuoted(error2.message);
53337
+ return {
53338
+ title: "Scope Connection Leak",
53339
+ explanation: `A scoped connection references a node that is outside the scope boundary. Scoped connections must stay within the scope.`,
53340
+ fix: `Move the target node inside the scope (declare it with the scope parent in @node), or remove the scope qualifier from the connection.`,
53341
+ code: error2.code
53342
+ };
53343
+ },
53344
+ SCOPE_PORT_TYPE_MISMATCH(error2) {
53345
+ const quoted = extractQuoted(error2.message);
53346
+ return {
53347
+ title: "Scope Port Type Mismatch",
53348
+ explanation: `Type mismatch within scope '${quoted[0] || "unknown"}'. The parent's scoped port and the child's port have incompatible types.`,
53349
+ fix: `Change one of the port types to match, or add a transformation node inside the scope.`,
53350
+ code: error2.code
53351
+ };
53352
+ },
53353
+ SCOPE_UNKNOWN_PORT(error2) {
53354
+ const quoted = extractQuoted(error2.message);
53355
+ const portName = quoted[0] || "unknown";
53356
+ return {
53357
+ title: "Unknown Scoped Port",
53358
+ explanation: `Port '${portName}' is referenced in a scoped connection but doesn't exist as a scoped port on that node, or belongs to a different scope.`,
53359
+ fix: `Add the port as a scoped port with @output ${portName} scope:scopeName, or fix the port name in the @connect annotation.`,
53360
+ code: error2.code
53361
+ };
53362
+ },
53363
+ SCOPE_ORPHANED_CHILD(error2) {
53364
+ const quoted = extractQuoted(error2.message);
53365
+ const childId = quoted[0] || error2.node || "unknown";
53366
+ const scopeName = quoted[1] || "unknown";
53367
+ const parentId = quoted[2] || "unknown";
53368
+ return {
53369
+ title: "Orphaned Scope Child",
53370
+ explanation: `Node '${childId}' is declared inside scope '${scopeName}' of '${parentId}' but has no scoped connections. It won't receive data from or return data to the scope.`,
53371
+ fix: `Connect the parent's scoped output ports to '${childId}' inputs, and connect '${childId}' outputs back to the parent's scoped input ports.`,
53372
+ code: error2.code
53373
+ };
53374
+ },
52792
53375
  OBJECT_TYPE_MISMATCH(error2) {
52793
53376
  const quoted = extractQuoted(error2.message);
52794
53377
  const sourceType = quoted[0] || "unknown";
@@ -52917,10 +53500,12 @@ var errorMappers = {
52917
53500
  const types2 = extractTypes(error2.message);
52918
53501
  const source = types2?.source || "unknown";
52919
53502
  const target = types2?.target || "unknown";
53503
+ const quoted = extractQuoted(error2.message);
53504
+ const coerceSuggestion = buildCoerceSuggestion(quoted, target);
52920
53505
  return {
52921
53506
  title: "Lossy Type Conversion",
52922
53507
  explanation: `Converting ${source} to ${target} may lose data or produce unexpected results (e.g., NaN, truncation).`,
52923
- fix: `Add an explicit conversion node, or use @strictTypes on the workflow to enforce type safety and catch these at validation time.`,
53508
+ fix: coerceSuggestion ? `Add an explicit coercion: \`${coerceSuggestion}\`, or use @strictTypes to enforce type safety.` : `Add an explicit conversion with @coerce, or use @strictTypes to enforce type safety.`,
52924
53509
  code: error2.code
52925
53510
  };
52926
53511
  },
@@ -56397,6 +56982,18 @@ ${indent}});`;
56397
56982
  function generateExpressionCall(instanceId, nodeType, workflow, nodeTypes, indent) {
56398
56983
  const args = buildNodeArgs(instanceId, nodeType, workflow, nodeTypes);
56399
56984
  const safeId = toValidIdentifier(instanceId);
56985
+ if (nodeType.variant === "COERCION") {
56986
+ const coerceExprMap = {
56987
+ __fw_toString: "String",
56988
+ __fw_toNumber: "Number",
56989
+ __fw_toBoolean: "Boolean",
56990
+ __fw_toJSON: "JSON.stringify",
56991
+ __fw_parseJSON: "JSON.parse"
56992
+ };
56993
+ const coerceExpr = coerceExprMap[nodeType.functionName] || "String";
56994
+ const valueArg = args[0] || "undefined";
56995
+ return `${indent}${safeId}_result = ${coerceExpr}(${valueArg});`;
56996
+ }
56400
56997
  const fnCall = `${nodeType.functionName}(${args.join(", ")})`;
56401
56998
  const awaitPrefix = nodeType.isAsync ? "await " : "";
56402
56999
  return `${indent}${safeId}_result = ${awaitPrefix}${fnCall};`;
@@ -56605,6 +57202,18 @@ function emitPromiseAll(nodeIds, workflow, nodeTypes, indent, lines, generatedNo
56605
57202
  if (timeoutArg && timeoutArg !== "undefined") invokeCall += `, timeout: ${timeoutArg}`;
56606
57203
  invokeCall += ` })`;
56607
57204
  stepCalls.push(invokeCall);
57205
+ } else if (nt.variant === "COERCION") {
57206
+ const coerceExprMap = {
57207
+ __fw_toString: "String",
57208
+ __fw_toNumber: "Number",
57209
+ __fw_toBoolean: "Boolean",
57210
+ __fw_toJSON: "JSON.stringify",
57211
+ __fw_parseJSON: "JSON.parse"
57212
+ };
57213
+ const coerceExpr = coerceExprMap[nt.functionName] || "String";
57214
+ const args = buildNodeArgs(nodeId, nt, workflow, nodeTypes);
57215
+ const valueArg = args[0] || "undefined";
57216
+ stepCalls.push(`${indent} Promise.resolve(${coerceExpr}(${valueArg}))`);
56608
57217
  } else if (nt.expression) {
56609
57218
  const args = buildNodeArgs(nodeId, nt, workflow, nodeTypes);
56610
57219
  const fnCall = `${nt.functionName}(${args.join(", ")})`;
@@ -57352,12 +57961,18 @@ async function compileCommand(input, options = {}) {
57352
57961
  const loc = err.location ? `[line ${err.location.line}] ` : "";
57353
57962
  logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
57354
57963
  logger.info(` How to fix: ${friendly.fix}`);
57964
+ if (err.docUrl) {
57965
+ logger.info(` See: ${err.docUrl}`);
57966
+ }
57355
57967
  } else {
57356
57968
  let msg = ` - ${err.message}`;
57357
57969
  if (err.node) {
57358
57970
  msg += ` (node: ${err.node})`;
57359
57971
  }
57360
57972
  logger.error(msg);
57973
+ if (err.docUrl) {
57974
+ logger.info(` See: ${err.docUrl}`);
57975
+ }
57361
57976
  }
57362
57977
  });
57363
57978
  errorCount++;
@@ -61443,6 +62058,9 @@ async function validateCommand(input, options = {}) {
61443
62058
  const loc = err.location ? `[line ${err.location.line}] ` : "";
61444
62059
  logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
61445
62060
  logger.info(` How to fix: ${friendly.fix}`);
62061
+ if (err.docUrl) {
62062
+ logger.info(` See: ${err.docUrl}`);
62063
+ }
61446
62064
  } else {
61447
62065
  let msg = ` - ${err.message}`;
61448
62066
  if (err.location) {
@@ -61455,6 +62073,9 @@ async function validateCommand(input, options = {}) {
61455
62073
  msg += ` (connection: ${err.connection.from.node}:${err.connection.from.port} -> ${err.connection.to.node}:${err.connection.to.port})`;
61456
62074
  }
61457
62075
  logger.error(msg);
62076
+ if (err.docUrl) {
62077
+ logger.info(` See: ${err.docUrl}`);
62078
+ }
61458
62079
  }
61459
62080
  });
61460
62081
  }
@@ -61469,6 +62090,9 @@ async function validateCommand(input, options = {}) {
61469
62090
  const loc = warn.location ? `[line ${warn.location.line}] ` : "";
61470
62091
  logger.warn(` ${loc}${friendly.title}: ${friendly.explanation}`);
61471
62092
  logger.info(` How to fix: ${friendly.fix}`);
62093
+ if (warn.docUrl) {
62094
+ logger.info(` See: ${warn.docUrl}`);
62095
+ }
61472
62096
  } else {
61473
62097
  let msg = ` - ${warn.message}`;
61474
62098
  if (warn.location) {
@@ -61478,6 +62102,9 @@ async function validateCommand(input, options = {}) {
61478
62102
  msg += ` (node: ${warn.node})`;
61479
62103
  }
61480
62104
  logger.warn(msg);
62105
+ if (warn.docUrl) {
62106
+ logger.info(` See: ${warn.docUrl}`);
62107
+ }
61481
62108
  }
61482
62109
  });
61483
62110
  }
@@ -64783,13 +65410,16 @@ function generateProjectFiles(projectName, template, format = "esm") {
64783
65410
  const gitignore = `node_modules/
64784
65411
  dist/
64785
65412
  .tsbuildinfo
65413
+ `;
65414
+ const configYaml = `defaultFileType: ts
64786
65415
  `;
64787
65416
  return {
64788
65417
  "package.json": packageJson,
64789
65418
  "tsconfig.json": tsconfigJson,
64790
65419
  [`src/${workflowFile}`]: workflowCode,
64791
65420
  "src/main.ts": mainTs,
64792
- ".gitignore": gitignore
65421
+ ".gitignore": gitignore,
65422
+ ".flowweaver/config.yaml": configYaml
64793
65423
  };
64794
65424
  }
64795
65425
  function scaffoldProject(targetDir, files, options) {
@@ -64989,22 +65619,19 @@ async function watchCommand(input, options = {}) {
64989
65619
  init_esm5();
64990
65620
  import * as path19 from "path";
64991
65621
  import * as fs18 from "fs";
64992
- import * as os2 from "os";
65622
+ import * as os from "os";
64993
65623
  import { spawn } from "child_process";
64994
65624
 
64995
65625
  // src/mcp/workflow-executor.ts
64996
65626
  import * as path18 from "path";
64997
65627
  import * as fs17 from "fs";
64998
- import * as os from "os";
64999
65628
  import { pathToFileURL } from "url";
65000
65629
  import ts4 from "typescript";
65001
65630
  async function executeWorkflowFromFile(filePath, params, options) {
65002
65631
  const resolvedPath = path18.resolve(filePath);
65003
65632
  const includeTrace = options?.includeTrace !== false;
65004
- const tmpBase = path18.join(
65005
- os.tmpdir(),
65006
- `fw-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`
65007
- );
65633
+ const tmpId = `fw-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`;
65634
+ const tmpBase = path18.join(path18.dirname(resolvedPath), tmpId);
65008
65635
  const tmpTsFile = `${tmpBase}.ts`;
65009
65636
  const tmpFile = `${tmpBase}.mjs`;
65010
65637
  try {
@@ -65035,11 +65662,13 @@ async function executeWorkflowFromFile(filePath, params, options) {
65035
65662
  const trace = [];
65036
65663
  const debugger_ = includeTrace ? {
65037
65664
  sendEvent: (event) => {
65038
- trace.push({
65665
+ const traceEvent = {
65039
65666
  type: event.type || "UNKNOWN",
65040
65667
  timestamp: Date.now(),
65041
65668
  data: event
65042
- });
65669
+ };
65670
+ trace.push(traceEvent);
65671
+ options?.onEvent?.(traceEvent);
65043
65672
  },
65044
65673
  innerFlowInvocation: false
65045
65674
  } : void 0;
@@ -65298,7 +65927,7 @@ async function runInngestDevMode(filePath, options) {
65298
65927
  if (missingDeps.length > 0) {
65299
65928
  throw new Error(`Missing dependencies: ${missingDeps.join(", ")}. Install them with: npm install ${missingDeps.join(" ")}`);
65300
65929
  }
65301
- const tmpDir = fs18.mkdtempSync(path19.join(os2.tmpdir(), "fw-inngest-dev-"));
65930
+ const tmpDir = fs18.mkdtempSync(path19.join(os.tmpdir(), "fw-inngest-dev-"));
65302
65931
  const inngestOutputPath = path19.join(tmpDir, path19.basename(filePath).replace(/\.ts$/, ".inngest.ts"));
65303
65932
  let serverProcess = null;
65304
65933
  const compileInngest = async () => {
@@ -83673,15 +84302,30 @@ function registerEditorTools(mcp, connection, buffer) {
83673
84302
  params: external_exports.record(external_exports.unknown()).optional().describe("Optional execution parameters"),
83674
84303
  includeTrace: external_exports.boolean().optional().describe("Include execution trace events (default: true)")
83675
84304
  },
83676
- async (args) => {
84305
+ async (args, extra) => {
83677
84306
  if (args.filePath) {
83678
84307
  try {
83679
84308
  const channel = new AgentChannel();
83680
84309
  const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2)}`;
84310
+ const progressToken = extra._meta?.progressToken;
84311
+ let eventCount = 0;
84312
+ const onEvent = progressToken ? (event) => {
84313
+ eventCount++;
84314
+ extra.sendNotification({
84315
+ method: "notifications/progress",
84316
+ params: {
84317
+ progressToken,
84318
+ progress: eventCount,
84319
+ message: event.type === "STATUS_CHANGED" ? `${event.data?.id ?? ""}: ${event.data?.status ?? ""}` : event.type
84320
+ }
84321
+ }).catch(() => {
84322
+ });
84323
+ } : void 0;
83681
84324
  const execPromise = executeWorkflowFromFile(args.filePath, args.params, {
83682
84325
  workflowName: args.workflowName,
83683
84326
  includeTrace: args.includeTrace,
83684
- agentChannel: channel
84327
+ agentChannel: channel,
84328
+ onEvent
83685
84329
  });
83686
84330
  const raceResult = await Promise.race([
83687
84331
  execPromise.then((r) => ({ type: "completed", result: r })),
@@ -91637,6 +92281,9 @@ async function runCommand(input, options) {
91637
92281
  throw new Error(`Failed to parse mocks file: ${options.mocksFile}`);
91638
92282
  }
91639
92283
  }
92284
+ if (mocks && !options.json) {
92285
+ await validateMockConfig(mocks, filePath, options.workflow);
92286
+ }
91640
92287
  let timeoutId;
91641
92288
  let timedOut = false;
91642
92289
  if (options.timeout) {
@@ -91649,17 +92296,40 @@ async function runCommand(input, options) {
91649
92296
  }, options.timeout);
91650
92297
  }
91651
92298
  try {
91652
- const includeTrace = options.trace ?? !options.production;
92299
+ const includeTrace = options.stream || options.trace || !options.production;
91653
92300
  if (!options.json && mocks) {
91654
92301
  logger.info("Running with mock data");
91655
92302
  }
92303
+ const nodeStartTimes = /* @__PURE__ */ new Map();
92304
+ const onEvent = options.stream && !options.json ? (event) => {
92305
+ if (event.type === "STATUS_CHANGED" && event.data) {
92306
+ const nodeId = event.data.id;
92307
+ const status = event.data.status;
92308
+ if (!nodeId || !status) return;
92309
+ if (status === "RUNNING") {
92310
+ nodeStartTimes.set(nodeId, event.timestamp);
92311
+ logger.log(` [STATUS_CHANGED] ${nodeId}: \u2192 RUNNING`);
92312
+ } else {
92313
+ const startTime = nodeStartTimes.get(nodeId);
92314
+ const duration3 = startTime ? ` (${event.timestamp - startTime}ms)` : "";
92315
+ logger.log(` [STATUS_CHANGED] ${nodeId}: \u2192 ${status}${duration3}`);
92316
+ }
92317
+ } else if (event.type === "VARIABLE_SET" && event.data) {
92318
+ const nodeId = event.data.nodeId;
92319
+ const varName = event.data.name;
92320
+ if (nodeId && varName) {
92321
+ logger.log(` [VARIABLE_SET] ${nodeId}.${varName}`);
92322
+ }
92323
+ }
92324
+ } : void 0;
91656
92325
  const channel = new AgentChannel();
91657
92326
  const execPromise = executeWorkflowFromFile(filePath, params, {
91658
92327
  workflowName: options.workflow,
91659
92328
  production: options.production ?? false,
91660
92329
  includeTrace,
91661
92330
  mocks,
91662
- agentChannel: channel
92331
+ agentChannel: channel,
92332
+ onEvent
91663
92333
  });
91664
92334
  let result;
91665
92335
  let execDone = false;
@@ -91768,6 +92438,35 @@ async function runCommand(input, options) {
91768
92438
  }
91769
92439
  }
91770
92440
  }
92441
+ var VALID_MOCK_KEYS = /* @__PURE__ */ new Set(["events", "invocations", "agents", "fast"]);
92442
+ var MOCK_SECTION_TO_NODE = {
92443
+ events: "waitForEvent",
92444
+ invocations: "invokeWorkflow",
92445
+ agents: "waitForAgent"
92446
+ };
92447
+ async function validateMockConfig(mocks, filePath, workflowName) {
92448
+ for (const key of Object.keys(mocks)) {
92449
+ if (!VALID_MOCK_KEYS.has(key)) {
92450
+ logger.warn(`Mock config has unknown key "${key}". Valid keys: ${[...VALID_MOCK_KEYS].join(", ")}`);
92451
+ }
92452
+ }
92453
+ try {
92454
+ const result = await parseWorkflow(filePath, { workflowName });
92455
+ if (result.errors.length > 0 || !result.ast?.instances) return;
92456
+ const usedNodeTypes = new Set(result.ast.instances.map((i) => i.nodeType));
92457
+ for (const [section, nodeType] of Object.entries(MOCK_SECTION_TO_NODE)) {
92458
+ const mockSection = mocks[section];
92459
+ if (mockSection && typeof mockSection === "object" && Object.keys(mockSection).length > 0) {
92460
+ if (!usedNodeTypes.has(nodeType)) {
92461
+ logger.warn(
92462
+ `Mock config has "${section}" entries but workflow has no ${nodeType} nodes`
92463
+ );
92464
+ }
92465
+ }
92466
+ }
92467
+ } catch {
92468
+ }
92469
+ }
91771
92470
  function promptForInput(question) {
91772
92471
  return new Promise((resolve27) => {
91773
92472
  const rl = readline9.createInterface({
@@ -92264,7 +92963,7 @@ async function serveCommand(dir, options) {
92264
92963
  // src/export/index.ts
92265
92964
  import * as path33 from "path";
92266
92965
  import * as fs32 from "fs";
92267
- import * as os3 from "os";
92966
+ import * as os2 from "os";
92268
92967
  import { fileURLToPath as fileURLToPath5 } from "url";
92269
92968
 
92270
92969
  // src/export/templates.ts
@@ -92461,7 +93160,7 @@ async function exportMultiWorkflow(options) {
92461
93160
  throw new Error(`None of the requested workflows found. Available: ${available}`);
92462
93161
  }
92463
93162
  }
92464
- const workDir = isDryRun ? path33.join(os3.tmpdir(), `fw-export-multi-dryrun-${Date.now()}`) : outputDir;
93163
+ const workDir = isDryRun ? path33.join(os2.tmpdir(), `fw-export-multi-dryrun-${Date.now()}`) : outputDir;
92465
93164
  fs32.mkdirSync(workDir, { recursive: true });
92466
93165
  fs32.mkdirSync(path33.join(workDir, "workflows"), { recursive: true });
92467
93166
  fs32.mkdirSync(path33.join(workDir, "runtime"), { recursive: true });
@@ -93185,7 +93884,7 @@ async function exportWorkflow(options) {
93185
93884
  const available = parseResult.workflows.map((w) => w.name).join(", ");
93186
93885
  throw new Error(`Workflow "${options.workflow}" not found. Available: ${available}`);
93187
93886
  }
93188
- const workDir = isDryRun ? path33.join(os3.tmpdir(), `fw-export-dryrun-${Date.now()}`) : outputDir;
93887
+ const workDir = isDryRun ? path33.join(os2.tmpdir(), `fw-export-dryrun-${Date.now()}`) : outputDir;
93189
93888
  fs32.mkdirSync(workDir, { recursive: true });
93190
93889
  let compiledContent;
93191
93890
  let compiledPath;
@@ -94429,7 +95128,7 @@ function displayInstalledPackage(pkg) {
94429
95128
  }
94430
95129
 
94431
95130
  // src/cli/index.ts
94432
- var version2 = true ? "0.5.1" : "0.0.0-dev";
95131
+ var version2 = true ? "0.7.0" : "0.0.0-dev";
94433
95132
  var program2 = new Command();
94434
95133
  program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
94435
95134
  program2.configureOutput({
@@ -94633,7 +95332,7 @@ patternCmd.command("extract <source-file>").description("Extract a pattern from
94633
95332
  process.exit(1);
94634
95333
  }
94635
95334
  });
94636
- program2.command("run <input>").description("Execute a workflow file directly").option("-w, --workflow <name>", "Specific workflow name to run").option("--params <json>", "Input parameters as JSON string").option("--params-file <path>", "Path to JSON file with input parameters").option("-p, --production", "Production mode (no trace events)", false).option("-t, --trace", "Include execution trace events").option("--json", "Output result as JSON", false).option("--timeout <ms>", "Execution timeout in milliseconds", parseInt).option("--mocks <json>", "Mock config for built-in nodes (events, invocations, fast) as JSON").option("--mocks-file <path>", "Path to JSON file with mock config for built-in nodes").action(async (input, options) => {
95335
+ program2.command("run <input>").description("Execute a workflow file directly").option("-w, --workflow <name>", "Specific workflow name to run").option("--params <json>", "Input parameters as JSON string").option("--params-file <path>", "Path to JSON file with input parameters").option("-p, --production", "Production mode (no trace events)", false).option("-t, --trace", "Include execution trace events").option("-s, --stream", "Stream trace events in real-time").option("--json", "Output result as JSON", false).option("--timeout <ms>", "Execution timeout in milliseconds", parseInt).option("--mocks <json>", "Mock config for built-in nodes (events, invocations, fast) as JSON").option("--mocks-file <path>", "Path to JSON file with mock config for built-in nodes").action(async (input, options) => {
94637
95336
  try {
94638
95337
  await runCommand(input, options);
94639
95338
  } catch (error2) {