@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.
- package/README.md +261 -200
- package/dist/annotation-generator.js +36 -0
- package/dist/api/generate-in-place.js +39 -0
- package/dist/api/generate.js +11 -1
- package/dist/api/manipulation/nodes.js +22 -0
- package/dist/ast/types.d.ts +27 -1
- package/dist/built-in-nodes/index.d.ts +1 -0
- package/dist/built-in-nodes/index.js +1 -0
- package/dist/built-in-nodes/invoke-workflow.js +12 -1
- package/dist/built-in-nodes/mock-types.d.ts +2 -0
- package/dist/built-in-nodes/wait-for-agent.d.ts +13 -0
- package/dist/built-in-nodes/wait-for-agent.js +26 -0
- package/dist/chevrotain-parser/fan-parser.d.ts +38 -0
- package/dist/chevrotain-parser/fan-parser.js +149 -0
- package/dist/chevrotain-parser/grammar-diagrams.d.ts +1 -0
- package/dist/chevrotain-parser/grammar-diagrams.js +3 -0
- package/dist/chevrotain-parser/index.d.ts +3 -1
- package/dist/chevrotain-parser/index.js +3 -1
- package/dist/chevrotain-parser/tokens.d.ts +2 -0
- package/dist/chevrotain-parser/tokens.js +10 -0
- package/dist/cli/commands/diagram.d.ts +2 -1
- package/dist/cli/commands/diagram.js +9 -6
- package/dist/cli/commands/docs.d.ts +11 -0
- package/dist/cli/commands/docs.js +77 -0
- package/dist/cli/commands/run.js +59 -1
- package/dist/cli/flow-weaver.mjs +2447 -594
- package/dist/cli/index.js +40 -2
- package/dist/diagram/geometry.d.ts +9 -4
- package/dist/diagram/geometry.js +262 -31
- package/dist/diagram/html-viewer.d.ts +12 -0
- package/dist/diagram/html-viewer.js +399 -0
- package/dist/diagram/index.d.ts +12 -0
- package/dist/diagram/index.js +22 -0
- package/dist/diagram/renderer.js +137 -116
- package/dist/diagram/types.d.ts +1 -0
- package/dist/doc-metadata/extractors/annotations.js +282 -1
- package/dist/doc-metadata/types.d.ts +6 -0
- package/dist/docs/index.d.ts +54 -0
- package/dist/docs/index.js +256 -0
- package/dist/generator/control-flow.d.ts +13 -0
- package/dist/generator/control-flow.js +74 -0
- package/dist/generator/inngest.js +23 -0
- package/dist/generator/unified.js +122 -2
- package/dist/jsdoc-parser.d.ts +24 -0
- package/dist/jsdoc-parser.js +41 -1
- package/dist/mcp/agent-channel.d.ts +35 -0
- package/dist/mcp/agent-channel.js +61 -0
- package/dist/mcp/run-registry.d.ts +29 -0
- package/dist/mcp/run-registry.js +24 -0
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/tools-diagram.d.ts +1 -1
- package/dist/mcp/tools-diagram.js +15 -7
- package/dist/mcp/tools-docs.d.ts +3 -0
- package/dist/mcp/tools-docs.js +62 -0
- package/dist/mcp/tools-editor.js +77 -3
- package/dist/mcp/tools-query.js +3 -1
- package/dist/mcp/workflow-executor.d.ts +28 -0
- package/dist/mcp/workflow-executor.js +66 -3
- package/dist/parser.d.ts +8 -0
- package/dist/parser.js +100 -0
- package/dist/runtime/ExecutionContext.d.ts +2 -0
- package/dist/runtime/ExecutionContext.js +2 -0
- package/dist/runtime/events.d.ts +1 -1
- package/dist/sugar-optimizer.js +28 -3
- package/dist/validator.d.ts +8 -0
- package/dist/validator.js +92 -0
- package/docs/reference/advanced-annotations.md +431 -0
- package/docs/reference/built-in-nodes.md +225 -0
- package/docs/reference/cli-reference.md +882 -0
- package/docs/reference/compilation.md +351 -0
- package/docs/reference/concepts.md +400 -0
- package/docs/reference/debugging.md +255 -0
- package/docs/reference/deployment.md +207 -0
- package/docs/reference/error-codes.md +686 -0
- package/docs/reference/export-interface.md +229 -0
- package/docs/reference/iterative-development.md +186 -0
- package/docs/reference/jsdoc-grammar.md +471 -0
- package/docs/reference/marketplace.md +205 -0
- package/docs/reference/node-conversion.md +308 -0
- package/docs/reference/patterns.md +161 -0
- package/docs/reference/scaffold.md +160 -0
- package/docs/reference/tutorial.md +519 -0
- 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:
|
|
@@ -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
|
});
|
package/dist/runtime/events.d.ts
CHANGED
|
@@ -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;
|
package/dist/sugar-optimizer.js
CHANGED
|
@@ -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
|
|
74
|
-
return
|
|
75
|
-
|
|
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
|
}
|
package/dist/validator.d.ts
CHANGED
|
@@ -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
|