@synergenius/flow-weaver 0.2.1 → 0.4.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 (83) hide show
  1. package/README.md +261 -200
  2. package/dist/annotation-generator.js +36 -0
  3. package/dist/api/generate-in-place.js +39 -0
  4. package/dist/api/generate.js +11 -1
  5. package/dist/api/manipulation/nodes.js +22 -0
  6. package/dist/ast/types.d.ts +27 -1
  7. package/dist/built-in-nodes/index.d.ts +1 -0
  8. package/dist/built-in-nodes/index.js +1 -0
  9. package/dist/built-in-nodes/invoke-workflow.js +12 -1
  10. package/dist/built-in-nodes/mock-types.d.ts +2 -0
  11. package/dist/built-in-nodes/wait-for-agent.d.ts +13 -0
  12. package/dist/built-in-nodes/wait-for-agent.js +26 -0
  13. package/dist/chevrotain-parser/fan-parser.d.ts +38 -0
  14. package/dist/chevrotain-parser/fan-parser.js +149 -0
  15. package/dist/chevrotain-parser/grammar-diagrams.d.ts +1 -0
  16. package/dist/chevrotain-parser/grammar-diagrams.js +3 -0
  17. package/dist/chevrotain-parser/index.d.ts +3 -1
  18. package/dist/chevrotain-parser/index.js +3 -1
  19. package/dist/chevrotain-parser/tokens.d.ts +2 -0
  20. package/dist/chevrotain-parser/tokens.js +10 -0
  21. package/dist/cli/commands/diagram.d.ts +2 -1
  22. package/dist/cli/commands/diagram.js +9 -6
  23. package/dist/cli/commands/docs.d.ts +11 -0
  24. package/dist/cli/commands/docs.js +77 -0
  25. package/dist/cli/commands/run.js +59 -1
  26. package/dist/cli/flow-weaver.mjs +2447 -594
  27. package/dist/cli/index.js +40 -2
  28. package/dist/diagram/geometry.d.ts +9 -4
  29. package/dist/diagram/geometry.js +262 -31
  30. package/dist/diagram/html-viewer.d.ts +12 -0
  31. package/dist/diagram/html-viewer.js +399 -0
  32. package/dist/diagram/index.d.ts +12 -0
  33. package/dist/diagram/index.js +22 -0
  34. package/dist/diagram/renderer.js +137 -116
  35. package/dist/diagram/types.d.ts +1 -0
  36. package/dist/doc-metadata/extractors/annotations.js +282 -1
  37. package/dist/doc-metadata/types.d.ts +6 -0
  38. package/dist/docs/index.d.ts +54 -0
  39. package/dist/docs/index.js +256 -0
  40. package/dist/generator/control-flow.d.ts +13 -0
  41. package/dist/generator/control-flow.js +74 -0
  42. package/dist/generator/inngest.js +23 -0
  43. package/dist/generator/unified.js +122 -2
  44. package/dist/jsdoc-parser.d.ts +24 -0
  45. package/dist/jsdoc-parser.js +41 -1
  46. package/dist/mcp/agent-channel.d.ts +35 -0
  47. package/dist/mcp/agent-channel.js +61 -0
  48. package/dist/mcp/run-registry.d.ts +29 -0
  49. package/dist/mcp/run-registry.js +24 -0
  50. package/dist/mcp/server.js +2 -0
  51. package/dist/mcp/tools-diagram.d.ts +1 -1
  52. package/dist/mcp/tools-diagram.js +15 -7
  53. package/dist/mcp/tools-docs.d.ts +3 -0
  54. package/dist/mcp/tools-docs.js +62 -0
  55. package/dist/mcp/tools-editor.js +77 -3
  56. package/dist/mcp/tools-query.js +3 -1
  57. package/dist/mcp/workflow-executor.d.ts +28 -0
  58. package/dist/mcp/workflow-executor.js +66 -3
  59. package/dist/parser.d.ts +8 -0
  60. package/dist/parser.js +100 -0
  61. package/dist/runtime/ExecutionContext.d.ts +2 -0
  62. package/dist/runtime/ExecutionContext.js +2 -0
  63. package/dist/runtime/events.d.ts +1 -1
  64. package/dist/sugar-optimizer.js +28 -3
  65. package/dist/validator.d.ts +8 -0
  66. package/dist/validator.js +92 -0
  67. package/docs/reference/advanced-annotations.md +431 -0
  68. package/docs/reference/built-in-nodes.md +225 -0
  69. package/docs/reference/cli-reference.md +882 -0
  70. package/docs/reference/compilation.md +351 -0
  71. package/docs/reference/concepts.md +400 -0
  72. package/docs/reference/debugging.md +255 -0
  73. package/docs/reference/deployment.md +207 -0
  74. package/docs/reference/error-codes.md +686 -0
  75. package/docs/reference/export-interface.md +229 -0
  76. package/docs/reference/iterative-development.md +186 -0
  77. package/docs/reference/jsdoc-grammar.md +471 -0
  78. package/docs/reference/marketplace.md +205 -0
  79. package/docs/reference/node-conversion.md +308 -0
  80. package/docs/reference/patterns.md +161 -0
  81. package/docs/reference/scaffold.md +160 -0
  82. package/docs/reference/tutorial.md +519 -0
  83. package/package.json +10 -4
package/dist/parser.js CHANGED
@@ -916,6 +916,14 @@ export class AnnotationParser {
916
916
  if (config.paths && config.paths.length > 0) {
917
917
  this.expandPathMacros(config.paths, instances, connections, allAvailableNodeTypes, startPorts, exitPorts, macros, errors, warnings);
918
918
  }
919
+ // Expand @fanOut macros into 1-to-N connections
920
+ if (config.fanOuts && config.fanOuts.length > 0) {
921
+ this.expandFanOutMacros(config.fanOuts, instances, connections, startPorts, exitPorts, macros, errors);
922
+ }
923
+ // Expand @fanIn macros into N-to-1 connections
924
+ if (config.fanIns && config.fanIns.length > 0) {
925
+ this.expandFanInMacros(config.fanIns, instances, connections, startPorts, exitPorts, macros, errors);
926
+ }
919
927
  // Include ALL available nodeTypes in the workflow AST, plus imported npm types.
920
928
  // Previously this filtered to only nodeTypes used by instances, but that caused
921
929
  // a bug: when creating a new nodeType and then adding its first instance,
@@ -1502,6 +1510,98 @@ export class AnnotationParser {
1502
1510
  });
1503
1511
  }
1504
1512
  }
1513
+ /**
1514
+ * Expand @fanOut macros into 1-to-N connections.
1515
+ */
1516
+ expandFanOutMacros(fanOutConfigs, instances, connections, startPorts, exitPorts, macros, errors) {
1517
+ const instanceIds = new Set(instances.map(i => i.id));
1518
+ instanceIds.add('Start');
1519
+ instanceIds.add('Exit');
1520
+ for (const config of fanOutConfigs) {
1521
+ const { source, targets } = config;
1522
+ // Validate source node exists
1523
+ if (!instanceIds.has(source.node)) {
1524
+ errors.push(`@fanOut: source node "${source.node}" does not exist`);
1525
+ continue;
1526
+ }
1527
+ let valid = true;
1528
+ for (const target of targets) {
1529
+ if (!instanceIds.has(target.node)) {
1530
+ errors.push(`@fanOut: target node "${target.node}" does not exist`);
1531
+ valid = false;
1532
+ }
1533
+ }
1534
+ if (!valid)
1535
+ continue;
1536
+ // Create connections
1537
+ for (const target of targets) {
1538
+ const targetPort = target.port ?? source.port;
1539
+ const conn = {
1540
+ type: 'Connection',
1541
+ from: { node: source.node, port: source.port },
1542
+ to: { node: target.node, port: targetPort },
1543
+ };
1544
+ // Deduplicate
1545
+ const exists = connections.some(c => c.from.node === conn.from.node && c.from.port === conn.from.port &&
1546
+ c.to.node === conn.to.node && c.to.port === conn.to.port);
1547
+ if (!exists) {
1548
+ connections.push(conn);
1549
+ }
1550
+ }
1551
+ // Store macro for round-trip preservation
1552
+ macros.push({
1553
+ type: 'fanOut',
1554
+ source: { node: source.node, port: source.port },
1555
+ targets: targets.map(t => t.port ? { node: t.node, port: t.port } : { node: t.node }),
1556
+ });
1557
+ }
1558
+ }
1559
+ /**
1560
+ * Expand @fanIn macros into N-to-1 connections.
1561
+ */
1562
+ expandFanInMacros(fanInConfigs, instances, connections, startPorts, exitPorts, macros, errors) {
1563
+ const instanceIds = new Set(instances.map(i => i.id));
1564
+ instanceIds.add('Start');
1565
+ instanceIds.add('Exit');
1566
+ for (const config of fanInConfigs) {
1567
+ const { sources, target } = config;
1568
+ // Validate target node exists
1569
+ if (!instanceIds.has(target.node)) {
1570
+ errors.push(`@fanIn: target node "${target.node}" does not exist`);
1571
+ continue;
1572
+ }
1573
+ let valid = true;
1574
+ for (const source of sources) {
1575
+ if (!instanceIds.has(source.node)) {
1576
+ errors.push(`@fanIn: source node "${source.node}" does not exist`);
1577
+ valid = false;
1578
+ }
1579
+ }
1580
+ if (!valid)
1581
+ continue;
1582
+ // Create connections
1583
+ for (const source of sources) {
1584
+ const sourcePort = source.port ?? target.port;
1585
+ const conn = {
1586
+ type: 'Connection',
1587
+ from: { node: source.node, port: sourcePort },
1588
+ to: { node: target.node, port: target.port },
1589
+ };
1590
+ // Deduplicate
1591
+ const exists = connections.some(c => c.from.node === conn.from.node && c.from.port === conn.from.port &&
1592
+ c.to.node === conn.to.node && c.to.port === conn.to.port);
1593
+ if (!exists) {
1594
+ connections.push(conn);
1595
+ }
1596
+ }
1597
+ // Store macro for round-trip preservation
1598
+ macros.push({
1599
+ type: 'fanIn',
1600
+ sources: sources.map(s => s.port ? { node: s.node, port: s.port } : { node: s.node }),
1601
+ target: { node: target.node, port: target.port },
1602
+ });
1603
+ }
1604
+ }
1505
1605
  /**
1506
1606
  * Generate automatic connections for @autoConnect workflows.
1507
1607
  * Wires nodes in declaration order as a linear pipeline:
@@ -21,6 +21,8 @@ export interface VariableAddress {
21
21
  portName: string;
22
22
  executionIndex: number;
23
23
  nodeTypeName?: string | undefined;
24
+ scope?: string | undefined;
25
+ side?: 'start' | 'exit' | undefined;
24
26
  }
25
27
  export interface ExecutionInfo {
26
28
  id: string;
@@ -65,6 +65,8 @@ export class GeneratedExecutionContext {
65
65
  portName: address.portName,
66
66
  executionIndex: address.executionIndex,
67
67
  key: 'default',
68
+ ...(address.scope !== undefined && { scope: address.scope }),
69
+ ...(address.side !== undefined && { side: address.side }),
68
70
  },
69
71
  value: actualValue,
70
72
  });
@@ -1,4 +1,4 @@
1
- export type TStatusType = "RUNNING" | "SCHEDULED" | "SUCCEEDED" | "FAILED" | "CANCELLED" | "PENDING";
1
+ export type TStatusType = "RUNNING" | "SCHEDULED" | "SUCCEEDED" | "FAILED" | "CANCELLED" | "PENDING" | "WAITING_FOR_AGENT";
2
2
  export type TVariableIdentification = {
3
3
  nodeTypeName: string;
4
4
  id: string;
@@ -69,10 +69,23 @@ export function validatePathMacro(path, connections, instances) {
69
69
  * Non-path macros are passed through unchanged.
70
70
  */
71
71
  export function filterStaleMacros(macros, connections, instances) {
72
+ const instanceIds = new Set(instances.map(i => i.id));
73
+ instanceIds.add('Start');
74
+ instanceIds.add('Exit');
72
75
  return macros.filter(macro => {
73
- if (macro.type !== 'path')
74
- return true;
75
- return validatePathMacro(macro, connections, instances);
76
+ if (macro.type === 'path')
77
+ return validatePathMacro(macro, connections, instances);
78
+ if (macro.type === 'fanOut') {
79
+ if (!instanceIds.has(macro.source.node))
80
+ return false;
81
+ return macro.targets.every(t => instanceIds.has(t.node));
82
+ }
83
+ if (macro.type === 'fanIn') {
84
+ if (!instanceIds.has(macro.target.node))
85
+ return false;
86
+ return macro.sources.every(s => instanceIds.has(s.node));
87
+ }
88
+ return true;
76
89
  });
77
90
  }
78
91
  /**
@@ -381,6 +394,18 @@ function buildExistingMacroCoverage(macros) {
381
394
  covered.add(step.node);
382
395
  }
383
396
  }
397
+ else if (macro.type === 'fanOut') {
398
+ covered.add(macro.source.node);
399
+ for (const t of macro.targets) {
400
+ covered.add(t.node);
401
+ }
402
+ }
403
+ else if (macro.type === 'fanIn') {
404
+ for (const s of macro.sources) {
405
+ covered.add(s.node);
406
+ }
407
+ covered.add(macro.target.node);
408
+ }
384
409
  }
385
410
  return covered;
386
411
  }
@@ -86,6 +86,14 @@ export declare class WorkflowValidator {
86
86
  * from opposite branches (onSuccess vs onFailure) of the same branching node.
87
87
  */
88
88
  private areMutuallyExclusive;
89
+ /**
90
+ * Validate inner graph topology for nodes that have scoped ports.
91
+ *
92
+ * For each scoped node instance, checks that:
93
+ * 1. Inner nodes (children) have their required inputs connected within the scope
94
+ * 2. Scoped input ports (callback returns) have connections from inner nodes
95
+ */
96
+ private validateScopeTopology;
89
97
  private normalizeTypeString;
90
98
  }
91
99
  export declare const validator: WorkflowValidator;
package/dist/validator.js CHANGED
@@ -118,6 +118,7 @@ export class WorkflowValidator {
118
118
  this.validateCycles(workflow);
119
119
  this.validateMultipleInputConnections(workflow, instanceMap);
120
120
  this.validateAnnotationSignatureConsistency(workflow);
121
+ this.validateScopeTopology(workflow, instanceMap);
121
122
  // Deduplicate cascading errors: if a node has UNKNOWN_NODE_TYPE,
122
123
  // suppress UNKNOWN_SOURCE_NODE, UNKNOWN_TARGET_NODE, and UNDEFINED_NODE
123
124
  // that reference the same node IDs (they're just noise).
@@ -953,6 +954,97 @@ export class WorkflowValidator {
953
954
  const branches = new Set(branchInfos.map((info) => info.branch));
954
955
  return branches.size > 1;
955
956
  }
957
+ /**
958
+ * Validate inner graph topology for nodes that have scoped ports.
959
+ *
960
+ * For each scoped node instance, checks that:
961
+ * 1. Inner nodes (children) have their required inputs connected within the scope
962
+ * 2. Scoped input ports (callback returns) have connections from inner nodes
963
+ */
964
+ validateScopeTopology(workflow, instanceMap) {
965
+ // Find all instances that have scoped ports
966
+ for (const instance of workflow.instances) {
967
+ const nodeType = instanceMap.get(instance.id);
968
+ if (!nodeType)
969
+ continue;
970
+ // Collect scoped port pairs per scope name
971
+ const scopeNames = new Set();
972
+ for (const portDef of Object.values(nodeType.outputs)) {
973
+ if (portDef.scope)
974
+ scopeNames.add(portDef.scope);
975
+ }
976
+ for (const portDef of Object.values(nodeType.inputs)) {
977
+ if (portDef.scope)
978
+ scopeNames.add(portDef.scope);
979
+ }
980
+ if (scopeNames.size === 0)
981
+ continue;
982
+ for (const scopeName of scopeNames) {
983
+ // Find children in this scope
984
+ const childIds = [];
985
+ for (const child of workflow.instances) {
986
+ if (child.parent &&
987
+ child.parent.id === instance.id &&
988
+ child.parent.scope === scopeName) {
989
+ childIds.push(child.id);
990
+ }
991
+ }
992
+ if (childIds.length === 0)
993
+ continue;
994
+ // Collect scoped connections (connections with scope tags)
995
+ const scopedConnections = workflow.connections.filter((conn) => (conn.from.scope === scopeName && conn.from.node === instance.id) ||
996
+ (conn.to.scope === scopeName && conn.to.node === instance.id) ||
997
+ (conn.from.scope === scopeName && childIds.includes(conn.from.node)) ||
998
+ (conn.to.scope === scopeName && childIds.includes(conn.to.node)));
999
+ // Check: each child's required inputs must be satisfied within the scope
1000
+ for (const childId of childIds) {
1001
+ const childType = instanceMap.get(childId);
1002
+ if (!childType)
1003
+ continue;
1004
+ for (const [portName, portConfig] of Object.entries(childType.inputs)) {
1005
+ if (isExecutePort(portName))
1006
+ continue;
1007
+ if (portConfig.scope)
1008
+ continue; // Skip scoped ports on children
1009
+ if (portConfig.optional || portConfig.default !== undefined)
1010
+ continue;
1011
+ // Check instance expression overrides
1012
+ const childInstance = workflow.instances.find((i) => i.id === childId);
1013
+ const instancePortConfig = childInstance?.config?.portConfigs?.find((pc) => pc.portName === portName && (pc.direction == null || pc.direction === 'INPUT'));
1014
+ if (portConfig.expression || instancePortConfig?.expression !== undefined)
1015
+ continue;
1016
+ // Check if connected by any connection (scoped, inter-child, or outer)
1017
+ const isConnected = workflow.connections.some((conn) => conn.to.node === childId && conn.to.port === portName);
1018
+ if (!isConnected) {
1019
+ this.errors.push({
1020
+ type: 'error',
1021
+ code: 'SCOPE_MISSING_REQUIRED_INPUT',
1022
+ message: `Scoped child "${childId}" has unconnected required input "${portName}" within scope "${scopeName}" of "${instance.id}".`,
1023
+ node: childId,
1024
+ location: childInstance?.sourceLocation,
1025
+ });
1026
+ }
1027
+ }
1028
+ }
1029
+ // Check: scoped INPUT ports should have connections from inner nodes
1030
+ const scopedInputPorts = Object.entries(nodeType.inputs).filter(([_, portDef]) => portDef.scope === scopeName);
1031
+ for (const [portName] of scopedInputPorts) {
1032
+ const hasConnection = scopedConnections.some((conn) => conn.to.node === instance.id &&
1033
+ conn.to.port === portName &&
1034
+ conn.to.scope === scopeName);
1035
+ if (!hasConnection) {
1036
+ this.warnings.push({
1037
+ type: 'warning',
1038
+ code: 'SCOPE_UNUSED_INPUT',
1039
+ message: `Scoped input port "${portName}" of "${instance.id}" (scope "${scopeName}") has no connection from inner nodes. Data will not flow back from the scope.`,
1040
+ node: instance.id,
1041
+ location: this.getInstanceLocation(workflow, instance.id),
1042
+ });
1043
+ }
1044
+ }
1045
+ }
1046
+ }
1047
+ }
956
1048
  normalizeTypeString(type) {
957
1049
  let n = type;
958
1050
  // Remove all whitespace