@synergenius/flow-weaver 0.27.5 → 0.29.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/dist/api/generate-in-place.js +54 -0
- package/dist/api/generate.js +20 -2
- package/dist/api/query.d.ts +3 -1
- package/dist/api/query.js +25 -1
- package/dist/ast/types.d.ts +7 -1
- package/dist/built-in-nodes/generated-registry.d.ts +9 -0
- package/dist/built-in-nodes/generated-registry.js +299 -0
- package/dist/cli/commands/init-personas.js +8 -8
- package/dist/cli/commands/init.js +18 -12
- package/dist/cli/commands/run.js +6 -0
- package/dist/cli/flow-weaver.mjs +533 -74
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/generator/scope-function-generator.js +69 -43
- package/dist/parser.js +56 -12
- package/package.json +4 -2
|
@@ -46,12 +46,66 @@ export function generateInPlace(sourceCode, ast, options = {}) {
|
|
|
46
46
|
path.resolve(nodeType.sourceLocation.file) !== path.resolve(ast.sourceFile)) {
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
+
// Skip built-in auto-injected nodes — step 1.2 handles their insertion
|
|
50
|
+
if (!nodeType.sourceLocation && nodeType.helperText != null) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
49
53
|
const nodeTypeResult = replaceNodeTypeJSDoc(result, nodeType);
|
|
50
54
|
if (nodeTypeResult.changed) {
|
|
51
55
|
result = nodeTypeResult.code;
|
|
52
56
|
hasChanges = true;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
59
|
+
// Step 1.2: Insert built-in node functions that are auto-injected (no source in the file)
|
|
60
|
+
const usedNodeTypes = new Set(ast.instances.map((i) => i.nodeType));
|
|
61
|
+
const builtInNodes = ast.nodeTypes.filter((nt) => !nt.sourceLocation && nt.functionText && nt.helperText != null && usedNodeTypes.has(nt.name));
|
|
62
|
+
if (builtInNodes.length > 0) {
|
|
63
|
+
// Find insertion point: just before the workflow function's JSDoc
|
|
64
|
+
const workflowFnPattern = new RegExp(`(/\\*\\*[\\s\\S]*?@flowWeaver\\s+workflow[\\s\\S]*?\\*/)\\s*\\n\\s*(?:export\\s+)?(?:async\\s+)?function\\s+${ast.functionName}\\b`);
|
|
65
|
+
const match = result.match(workflowFnPattern);
|
|
66
|
+
if (match && match.index !== undefined) {
|
|
67
|
+
const insertionLines = [];
|
|
68
|
+
// Emit shared helpers once (deduplicated)
|
|
69
|
+
const emittedHelpers = new Set();
|
|
70
|
+
for (const node of builtInNodes) {
|
|
71
|
+
const helperText = production ? (node.helperTextProduction ?? null) : (node.helperText ?? null);
|
|
72
|
+
if (helperText && !emittedHelpers.has(helperText)) {
|
|
73
|
+
emittedHelpers.add(helperText);
|
|
74
|
+
insertionLines.push(helperText);
|
|
75
|
+
insertionLines.push('');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Emit each built-in function with its JSDoc
|
|
79
|
+
for (const node of builtInNodes) {
|
|
80
|
+
const funcText = (production && node.functionTextProduction != null) ? node.functionTextProduction : node.functionText;
|
|
81
|
+
if (funcText) {
|
|
82
|
+
// Add JSDoc annotation so the function is recognized on re-parse
|
|
83
|
+
const portAnnotations = [];
|
|
84
|
+
portAnnotations.push('/**');
|
|
85
|
+
portAnnotations.push(` * @flowWeaver nodeType`);
|
|
86
|
+
for (const [name, port] of Object.entries(node.inputs)) {
|
|
87
|
+
if (name === 'execute')
|
|
88
|
+
continue;
|
|
89
|
+
const optPrefix = port.optional ? '[' : '';
|
|
90
|
+
const optSuffix = port.optional ? ']' : '';
|
|
91
|
+
portAnnotations.push(` * @input ${optPrefix}${name}${optSuffix} - ${port.label || name}`);
|
|
92
|
+
}
|
|
93
|
+
for (const [name, port] of Object.entries(node.outputs)) {
|
|
94
|
+
if (name === 'onSuccess' || name === 'onFailure')
|
|
95
|
+
continue;
|
|
96
|
+
portAnnotations.push(` * @output ${name} - ${port.label || name}`);
|
|
97
|
+
}
|
|
98
|
+
portAnnotations.push(' */');
|
|
99
|
+
insertionLines.push(portAnnotations.join('\n'));
|
|
100
|
+
insertionLines.push(funcText);
|
|
101
|
+
insertionLines.push('');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const insertionCode = insertionLines.join('\n');
|
|
105
|
+
result = result.slice(0, match.index) + insertionCode + '\n' + result.slice(match.index);
|
|
106
|
+
hasChanges = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
55
109
|
// Step 1.5: Remove orphaned nodeType functions (functions that don't match any AST nodeType)
|
|
56
110
|
// When multi-workflow, consider ALL workflows' node types to avoid deleting types used by siblings
|
|
57
111
|
const cleanupResult = removeOrphanedNodeTypeFunctions(result, ast, allWorkflows);
|
package/dist/api/generate.js
CHANGED
|
@@ -126,7 +126,9 @@ export function generateCode(ast, options) {
|
|
|
126
126
|
// 2. relative file imports (sourceLocation file differs from workflow source)
|
|
127
127
|
// 3. local nodes (same source file)
|
|
128
128
|
const npmPackageNodes = ast.nodeTypes.filter((n) => n.importSource);
|
|
129
|
-
|
|
129
|
+
// Only include built-in nodes (no sourceLocation) that are actually used by this workflow
|
|
130
|
+
const referencedNodeTypes = new Set(ast.instances.map((i) => i.nodeType));
|
|
131
|
+
const localNodes = ast.nodeTypes.filter((n) => !n.importSource && (n.sourceLocation?.file === ast.sourceFile || (!n.sourceLocation && n.functionText && n.helperText != null && referencedNodeTypes.has(n.name))));
|
|
130
132
|
const importedNodes = ast.nodeTypes.filter((n) => !n.importSource && n.sourceLocation?.file !== ast.sourceFile);
|
|
131
133
|
if (importedNodes.length > 0 || npmPackageNodes.length > 0) {
|
|
132
134
|
// Separate FUNCTION imports (from source) vs IMPORTED_WORKFLOW imports (from generated)
|
|
@@ -264,10 +266,26 @@ export function generateCode(ast, options) {
|
|
|
264
266
|
if (inlineFunctions.length > 0) {
|
|
265
267
|
lines.push('');
|
|
266
268
|
addLine();
|
|
269
|
+
// Emit shared helper functions once (deduplicated across built-in nodes)
|
|
270
|
+
// In production mode, use helperTextProduction if set; if undefined, skip helpers entirely
|
|
271
|
+
// (production built-in nodes don't need mock helpers)
|
|
272
|
+
const emittedHelpers = new Set();
|
|
273
|
+
inlineFunctions.forEach((node) => {
|
|
274
|
+
if (node.importSource)
|
|
275
|
+
return;
|
|
276
|
+
const helperText = production ? (node.helperTextProduction ?? null) : (node.helperText ?? null);
|
|
277
|
+
if (helperText && !emittedHelpers.has(helperText)) {
|
|
278
|
+
emittedHelpers.add(helperText);
|
|
279
|
+
lines.push(helperText);
|
|
280
|
+
helperText.split('\n').forEach(() => addLine());
|
|
281
|
+
lines.push('');
|
|
282
|
+
addLine();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
267
285
|
inlineFunctions.forEach((node) => {
|
|
268
286
|
if (node.importSource)
|
|
269
287
|
return; // Never inline npm package functions
|
|
270
|
-
const functionText = node.functionText;
|
|
288
|
+
const functionText = (production && node.functionTextProduction != null) ? node.functionTextProduction : node.functionText;
|
|
271
289
|
if (functionText) {
|
|
272
290
|
const functionWithoutDecorators = removeDecorators(functionText);
|
|
273
291
|
// Add source mapping for the node function
|
package/dist/api/query.d.ts
CHANGED
|
@@ -299,7 +299,9 @@ export declare function findPath(ast: TWorkflowAST, fromNodeId: string, toNodeId
|
|
|
299
299
|
* }
|
|
300
300
|
* ```
|
|
301
301
|
*/
|
|
302
|
-
export declare function getTopologicalOrder(ast: TWorkflowAST
|
|
302
|
+
export declare function getTopologicalOrder(ast: TWorkflowAST, options?: {
|
|
303
|
+
includeScopedChildren?: boolean;
|
|
304
|
+
}): string[];
|
|
303
305
|
/**
|
|
304
306
|
* Group nodes by execution level (nodes at same level can execute in parallel)
|
|
305
307
|
*
|
package/dist/api/query.js
CHANGED
|
@@ -521,7 +521,7 @@ export function findPath(ast, fromNodeId, toNodeId) {
|
|
|
521
521
|
* }
|
|
522
522
|
* ```
|
|
523
523
|
*/
|
|
524
|
-
export function getTopologicalOrder(ast) {
|
|
524
|
+
export function getTopologicalOrder(ast, options) {
|
|
525
525
|
const mainInstances = getMainFlowInstances(ast);
|
|
526
526
|
const mainConnections = getMainFlowConnections(ast);
|
|
527
527
|
const inDegree = new Map();
|
|
@@ -565,6 +565,30 @@ export function getTopologicalOrder(ast) {
|
|
|
565
565
|
if (result.length !== mainInstances.length) {
|
|
566
566
|
throw new Error('Cannot compute topological order: workflow contains cycles');
|
|
567
567
|
}
|
|
568
|
+
// Optionally append scoped children (for debug: breakpoints need to know about them)
|
|
569
|
+
if (options?.includeScopedChildren) {
|
|
570
|
+
const scopedChildren = ast.instances.filter((inst) => isPerPortScopedChild(inst, ast, ast.nodeTypes));
|
|
571
|
+
// Append scoped children after their parent node in the result
|
|
572
|
+
const expanded = [];
|
|
573
|
+
const scopedByParent = new Map();
|
|
574
|
+
for (const child of scopedChildren) {
|
|
575
|
+
if (child.parent) {
|
|
576
|
+
const parentId = child.parent.id;
|
|
577
|
+
if (!scopedByParent.has(parentId)) {
|
|
578
|
+
scopedByParent.set(parentId, []);
|
|
579
|
+
}
|
|
580
|
+
scopedByParent.get(parentId).push(child.id);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
for (const nodeId of result) {
|
|
584
|
+
expanded.push(nodeId);
|
|
585
|
+
const children = scopedByParent.get(nodeId);
|
|
586
|
+
if (children) {
|
|
587
|
+
expanded.push(...children);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return expanded;
|
|
591
|
+
}
|
|
568
592
|
return result;
|
|
569
593
|
}
|
|
570
594
|
/**
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -259,8 +259,14 @@ export type TNodeTypeAST = {
|
|
|
259
259
|
y?: number;
|
|
260
260
|
/** Source code location */
|
|
261
261
|
sourceLocation?: TSourceLocation;
|
|
262
|
-
/** Original function source text */
|
|
262
|
+
/** Original function source text (main function only) */
|
|
263
263
|
functionText?: string;
|
|
264
|
+
/** Production-optimized function source text (mock code stripped) */
|
|
265
|
+
functionTextProduction?: string;
|
|
266
|
+
/** Shared helper functions (mock helpers, utility functions) emitted once before main functions */
|
|
267
|
+
helperText?: string;
|
|
268
|
+
/** Production-optimized helper text */
|
|
269
|
+
helperTextProduction?: string;
|
|
264
270
|
/** How success/failure branching works */
|
|
265
271
|
branchingStrategy?: TBranchingStrategy;
|
|
266
272
|
/** Field name for value-based branching */
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DO NOT EDIT - generated by scripts/generate-built-in-registry.ts
|
|
3
|
+
*
|
|
4
|
+
* Registry of built-in node types for auto-injection into the parser.
|
|
5
|
+
* Generated from the actual source files in src/built-in-nodes/.
|
|
6
|
+
*/
|
|
7
|
+
import type { TNodeTypeAST } from '../ast/types.js';
|
|
8
|
+
export declare const BUILT_IN_NODE_TYPES: TNodeTypeAST[];
|
|
9
|
+
//# sourceMappingURL=generated-registry.d.ts.map
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DO NOT EDIT - generated by scripts/generate-built-in-registry.ts
|
|
3
|
+
*
|
|
4
|
+
* Registry of built-in node types for auto-injection into the parser.
|
|
5
|
+
* Generated from the actual source files in src/built-in-nodes/.
|
|
6
|
+
*/
|
|
7
|
+
export const BUILT_IN_NODE_TYPES = [
|
|
8
|
+
{
|
|
9
|
+
type: 'NodeType',
|
|
10
|
+
name: 'delay',
|
|
11
|
+
functionName: 'delay',
|
|
12
|
+
isAsync: true,
|
|
13
|
+
hasSuccessPort: true,
|
|
14
|
+
hasFailurePort: true,
|
|
15
|
+
executeWhen: 'CONJUNCTION',
|
|
16
|
+
variant: 'FUNCTION',
|
|
17
|
+
inputs: {
|
|
18
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
19
|
+
duration: { dataType: 'STRING', label: 'Duration to sleep (e.g. "30s", "5m", "1h", "2d")', tsType: 'string' },
|
|
20
|
+
},
|
|
21
|
+
outputs: {
|
|
22
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
23
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', isControlFlow: true, failure: true },
|
|
24
|
+
elapsed: { dataType: 'BOOLEAN', label: 'Always true after sleep completes', tsType: 'boolean' },
|
|
25
|
+
},
|
|
26
|
+
helperText: `
|
|
27
|
+
function __fw_getMockConfig() {
|
|
28
|
+
return globalThis.__fw_mocks__;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function __fw_lookupMock(section, key) {
|
|
32
|
+
if (!section)
|
|
33
|
+
return undefined;
|
|
34
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
35
|
+
if (nodeId) {
|
|
36
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
37
|
+
if (qualified !== undefined)
|
|
38
|
+
return qualified;
|
|
39
|
+
}
|
|
40
|
+
return section[key];
|
|
41
|
+
}
|
|
42
|
+
`.trim(),
|
|
43
|
+
helperTextProduction: undefined,
|
|
44
|
+
functionText: `
|
|
45
|
+
async function delay(execute, duration) {
|
|
46
|
+
if (!execute)
|
|
47
|
+
return { onSuccess: false, onFailure: false, elapsed: false };
|
|
48
|
+
const mocks = __fw_getMockConfig();
|
|
49
|
+
if (mocks?.fast) {
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const ms = __fw_parseDuration(duration);
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
55
|
+
}
|
|
56
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
57
|
+
}
|
|
58
|
+
function __fw_parseDuration(duration) {
|
|
59
|
+
const match = duration.match(/^(\\d+)(ms|s|m|h|d)$/);
|
|
60
|
+
if (!match)
|
|
61
|
+
return 0;
|
|
62
|
+
const [, value, unit] = match;
|
|
63
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
64
|
+
return parseInt(value) * (multipliers[unit] || 0);
|
|
65
|
+
}
|
|
66
|
+
`.trim(),
|
|
67
|
+
functionTextProduction: `
|
|
68
|
+
async function delay(execute, duration) {
|
|
69
|
+
if (!execute)
|
|
70
|
+
return { onSuccess: false, onFailure: false, elapsed: false };
|
|
71
|
+
const ms = __fw_parseDuration(duration);
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
73
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
74
|
+
}
|
|
75
|
+
function __fw_parseDuration(duration) {
|
|
76
|
+
const match = duration.match(/^(\\d+)(ms|s|m|h|d)$/);
|
|
77
|
+
if (!match)
|
|
78
|
+
return 0;
|
|
79
|
+
const [, value, unit] = match;
|
|
80
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
81
|
+
return parseInt(value) * (multipliers[unit] || 0);
|
|
82
|
+
}
|
|
83
|
+
`.trim(),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'NodeType',
|
|
87
|
+
name: 'waitForEvent',
|
|
88
|
+
functionName: 'waitForEvent',
|
|
89
|
+
isAsync: true,
|
|
90
|
+
hasSuccessPort: true,
|
|
91
|
+
hasFailurePort: true,
|
|
92
|
+
executeWhen: 'CONJUNCTION',
|
|
93
|
+
variant: 'FUNCTION',
|
|
94
|
+
inputs: {
|
|
95
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
96
|
+
eventName: { dataType: 'STRING', label: 'Event name to wait for (e.g. "app/approval.received")', tsType: 'string' },
|
|
97
|
+
match: { dataType: 'STRING', label: 'Field to match between trigger and waited event (e.g. "data.requestId")', tsType: 'string', optional: true },
|
|
98
|
+
timeout: { dataType: 'STRING', label: 'Max wait time (e.g. "24h", "7d"). Empty = no timeout', tsType: 'string', optional: true },
|
|
99
|
+
},
|
|
100
|
+
outputs: {
|
|
101
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
102
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', isControlFlow: true, failure: true },
|
|
103
|
+
eventData: { dataType: 'OBJECT', label: 'The received event\'s data payload', tsType: 'object' },
|
|
104
|
+
},
|
|
105
|
+
helperText: `
|
|
106
|
+
function __fw_getMockConfig() {
|
|
107
|
+
return globalThis.__fw_mocks__;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function __fw_lookupMock(section, key) {
|
|
111
|
+
if (!section)
|
|
112
|
+
return undefined;
|
|
113
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
114
|
+
if (nodeId) {
|
|
115
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
116
|
+
if (qualified !== undefined)
|
|
117
|
+
return qualified;
|
|
118
|
+
}
|
|
119
|
+
return section[key];
|
|
120
|
+
}
|
|
121
|
+
`.trim(),
|
|
122
|
+
helperTextProduction: undefined,
|
|
123
|
+
functionText: `
|
|
124
|
+
async function waitForEvent(execute, eventName, match, timeout) {
|
|
125
|
+
if (!execute)
|
|
126
|
+
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
127
|
+
const mocks = __fw_getMockConfig();
|
|
128
|
+
if (mocks) {
|
|
129
|
+
const mockData = __fw_lookupMock(mocks.events, eventName);
|
|
130
|
+
if (mockData !== undefined) {
|
|
131
|
+
return { onSuccess: true, onFailure: false, eventData: mockData };
|
|
132
|
+
}
|
|
133
|
+
return { onSuccess: false, onFailure: true, eventData: {} };
|
|
134
|
+
}
|
|
135
|
+
return { onSuccess: true, onFailure: false, eventData: {} };
|
|
136
|
+
}
|
|
137
|
+
`.trim(),
|
|
138
|
+
functionTextProduction: `
|
|
139
|
+
async function waitForEvent(execute, eventName, match, timeout) {
|
|
140
|
+
if (!execute)
|
|
141
|
+
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
142
|
+
return { onSuccess: true, onFailure: false, eventData: {} };
|
|
143
|
+
}
|
|
144
|
+
`.trim(),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'NodeType',
|
|
148
|
+
name: 'invokeWorkflow',
|
|
149
|
+
functionName: 'invokeWorkflow',
|
|
150
|
+
isAsync: true,
|
|
151
|
+
hasSuccessPort: true,
|
|
152
|
+
hasFailurePort: true,
|
|
153
|
+
executeWhen: 'CONJUNCTION',
|
|
154
|
+
variant: 'FUNCTION',
|
|
155
|
+
inputs: {
|
|
156
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
157
|
+
functionId: { dataType: 'STRING', label: 'Function ID of the workflow to invoke (e.g. "my-service/sub-workflow")', tsType: 'string' },
|
|
158
|
+
payload: { dataType: 'OBJECT', label: 'Data to pass as event.data to the invoked function', tsType: 'object' },
|
|
159
|
+
timeout: { dataType: 'STRING', label: 'Max wait time (e.g. "1h")', tsType: 'string', optional: true },
|
|
160
|
+
},
|
|
161
|
+
outputs: {
|
|
162
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
163
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', isControlFlow: true, failure: true },
|
|
164
|
+
result: { dataType: 'OBJECT', label: 'Return value from the invoked function', tsType: 'object' },
|
|
165
|
+
},
|
|
166
|
+
helperText: `
|
|
167
|
+
function __fw_getMockConfig() {
|
|
168
|
+
return globalThis.__fw_mocks__;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function __fw_lookupMock(section, key) {
|
|
172
|
+
if (!section)
|
|
173
|
+
return undefined;
|
|
174
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
175
|
+
if (nodeId) {
|
|
176
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
177
|
+
if (qualified !== undefined)
|
|
178
|
+
return qualified;
|
|
179
|
+
}
|
|
180
|
+
return section[key];
|
|
181
|
+
}
|
|
182
|
+
`.trim(),
|
|
183
|
+
helperTextProduction: undefined,
|
|
184
|
+
functionText: `
|
|
185
|
+
async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
186
|
+
if (!execute)
|
|
187
|
+
return { onSuccess: false, onFailure: false, result: {} };
|
|
188
|
+
const mocks = __fw_getMockConfig();
|
|
189
|
+
if (mocks) {
|
|
190
|
+
const mockResult = __fw_lookupMock(mocks.invocations, functionId);
|
|
191
|
+
if (mockResult !== undefined) {
|
|
192
|
+
return { onSuccess: true, onFailure: false, result: mockResult };
|
|
193
|
+
}
|
|
194
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
195
|
+
}
|
|
196
|
+
const registry = globalThis.__fw_workflow_registry__;
|
|
197
|
+
if (registry?.[functionId]) {
|
|
198
|
+
try {
|
|
199
|
+
const result = await registry[functionId](true, payload);
|
|
200
|
+
return { onSuccess: true, onFailure: false, result: result ?? {} };
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
207
|
+
}
|
|
208
|
+
`.trim(),
|
|
209
|
+
functionTextProduction: `
|
|
210
|
+
async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
211
|
+
if (!execute)
|
|
212
|
+
return { onSuccess: false, onFailure: false, result: {} };
|
|
213
|
+
const registry = globalThis.__fw_workflow_registry__;
|
|
214
|
+
if (registry?.[functionId]) {
|
|
215
|
+
try {
|
|
216
|
+
const result = await registry[functionId](true, payload);
|
|
217
|
+
return { onSuccess: true, onFailure: false, result: result ?? {} };
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
224
|
+
}
|
|
225
|
+
`.trim(),
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: 'NodeType',
|
|
229
|
+
name: 'waitForAgent',
|
|
230
|
+
functionName: 'waitForAgent',
|
|
231
|
+
isAsync: true,
|
|
232
|
+
hasSuccessPort: true,
|
|
233
|
+
hasFailurePort: true,
|
|
234
|
+
executeWhen: 'CONJUNCTION',
|
|
235
|
+
variant: 'FUNCTION',
|
|
236
|
+
inputs: {
|
|
237
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
238
|
+
agentId: { dataType: 'STRING', label: 'Agent/task identifier', tsType: 'string' },
|
|
239
|
+
context: { dataType: 'OBJECT', label: 'Context data to send to the agent', tsType: 'object' },
|
|
240
|
+
prompt: { dataType: 'STRING', label: 'Message to display when requesting input', tsType: 'string', optional: true },
|
|
241
|
+
},
|
|
242
|
+
outputs: {
|
|
243
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
244
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', isControlFlow: true, failure: true },
|
|
245
|
+
agentResult: { dataType: 'OBJECT', label: 'Result returned by the agent', tsType: 'object' },
|
|
246
|
+
},
|
|
247
|
+
helperText: `
|
|
248
|
+
function __fw_getMockConfig() {
|
|
249
|
+
return globalThis.__fw_mocks__;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function __fw_lookupMock(section, key) {
|
|
253
|
+
if (!section)
|
|
254
|
+
return undefined;
|
|
255
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
256
|
+
if (nodeId) {
|
|
257
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
258
|
+
if (qualified !== undefined)
|
|
259
|
+
return qualified;
|
|
260
|
+
}
|
|
261
|
+
return section[key];
|
|
262
|
+
}
|
|
263
|
+
`.trim(),
|
|
264
|
+
helperTextProduction: undefined,
|
|
265
|
+
functionText: `
|
|
266
|
+
async function waitForAgent(execute, agentId, context, prompt) {
|
|
267
|
+
if (!execute)
|
|
268
|
+
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
269
|
+
const mocks = __fw_getMockConfig();
|
|
270
|
+
const mockResult = __fw_lookupMock(mocks?.agents, agentId);
|
|
271
|
+
if (mockResult !== undefined) {
|
|
272
|
+
return { onSuccess: true, onFailure: false, agentResult: mockResult };
|
|
273
|
+
}
|
|
274
|
+
if (mocks?.agents) {
|
|
275
|
+
return { onSuccess: false, onFailure: true, agentResult: {} };
|
|
276
|
+
}
|
|
277
|
+
const channel = globalThis.__fw_agent_channel__;
|
|
278
|
+
if (channel) {
|
|
279
|
+
const result = await channel.request({ agentId, context, prompt });
|
|
280
|
+
return { onSuccess: true, onFailure: false, agentResult: result };
|
|
281
|
+
}
|
|
282
|
+
return { onSuccess: true, onFailure: false, agentResult: {} };
|
|
283
|
+
}
|
|
284
|
+
`.trim(),
|
|
285
|
+
functionTextProduction: `
|
|
286
|
+
async function waitForAgent(execute, agentId, context, prompt) {
|
|
287
|
+
if (!execute)
|
|
288
|
+
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
289
|
+
const channel = globalThis.__fw_agent_channel__;
|
|
290
|
+
if (channel) {
|
|
291
|
+
const result = await channel.request({ agentId, context, prompt });
|
|
292
|
+
return { onSuccess: true, onFailure: false, agentResult: result };
|
|
293
|
+
}
|
|
294
|
+
return { onSuccess: true, onFailure: false, agentResult: {} };
|
|
295
|
+
}
|
|
296
|
+
`.trim(),
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
//# sourceMappingURL=generated-registry.js.map
|
|
@@ -306,9 +306,9 @@ function printNocodeGuidance(_projectName) {
|
|
|
306
306
|
logger.newline();
|
|
307
307
|
logger.log(` ${logger.bold('Useful commands')}`);
|
|
308
308
|
logger.newline();
|
|
309
|
-
logger.log(` fw run src/*.ts ${logger.dim('Run your workflow')}`);
|
|
310
|
-
logger.log(` fw diagram src/*.ts ${logger.dim('See a visual diagram')}`);
|
|
311
|
-
logger.log(` fw mcp-setup ${logger.dim('Connect more AI editors')}`);
|
|
309
|
+
logger.log(` npx fw run src/*.ts ${logger.dim('Run your workflow')}`);
|
|
310
|
+
logger.log(` npx fw diagram src/*.ts ${logger.dim('See a visual diagram')}`);
|
|
311
|
+
logger.log(` npx fw mcp-setup ${logger.dim('Connect more AI editors')}`);
|
|
312
312
|
}
|
|
313
313
|
function printVibecoderGuidance() {
|
|
314
314
|
logger.newline();
|
|
@@ -326,17 +326,17 @@ function printLowcodeGuidance() {
|
|
|
326
326
|
logger.newline();
|
|
327
327
|
logger.log(` ${logger.bold('Explore and customize')}`);
|
|
328
328
|
logger.newline();
|
|
329
|
-
logger.log(` fw templates ${logger.dim('List all 16 workflow templates')}`);
|
|
330
|
-
logger.log(` fw describe src/*.ts ${logger.dim('See the workflow structure')}`);
|
|
331
|
-
logger.log(` fw docs annotations ${logger.dim('Annotation reference')}`);
|
|
329
|
+
logger.log(` npx fw templates ${logger.dim('List all 16 workflow templates')}`);
|
|
330
|
+
logger.log(` npx fw describe src/*.ts ${logger.dim('See the workflow structure')}`);
|
|
331
|
+
logger.log(` npx fw docs annotations ${logger.dim('Annotation reference')}`);
|
|
332
332
|
logger.newline();
|
|
333
333
|
logger.log(` Your project includes an example in ${logger.highlight('examples/')} to study.`);
|
|
334
334
|
logger.log(` With MCP connected, AI can help modify nodes and connections.`);
|
|
335
335
|
}
|
|
336
336
|
function printExpertGuidance() {
|
|
337
337
|
logger.newline();
|
|
338
|
-
logger.log(` fw mcp-setup ${logger.dim('Connect AI editors (Claude, Cursor, VS Code)')}`);
|
|
339
|
-
logger.log(` fw docs ${logger.dim('Browse reference docs')}`);
|
|
338
|
+
logger.log(` npx fw mcp-setup ${logger.dim('Connect AI editors (Claude, Cursor, VS Code)')}`);
|
|
339
|
+
logger.log(` npx fw docs ${logger.dim('Browse reference docs')}`);
|
|
340
340
|
}
|
|
341
341
|
/** Pad a filename to align descriptions */
|
|
342
342
|
function pad(displayName, width) {
|
|
@@ -350,16 +350,19 @@ export function generateProjectFiles(projectName, template, format = 'esm', pers
|
|
|
350
350
|
'',
|
|
351
351
|
`import { ${workflowName} } from './${workflowJsFile}';`,
|
|
352
352
|
'',
|
|
353
|
-
'
|
|
354
|
-
` const result = ${workflowName}(true, { data: { message: 'hello world' } });`,
|
|
355
|
-
' console.log(result);',
|
|
356
|
-
'}
|
|
353
|
+
'async function main() {',
|
|
354
|
+
` const result = await ${workflowName}(true, { data: { message: 'hello world' } });`,
|
|
355
|
+
' console.log(JSON.stringify(result, null, 2));',
|
|
356
|
+
'}',
|
|
357
|
+
'',
|
|
358
|
+
'main().catch((e) => {',
|
|
357
359
|
" if (e instanceof Error && e.message.startsWith('Compile with:')) {",
|
|
358
360
|
" console.error('Workflow not compiled yet. Run: npm run dev');",
|
|
359
361
|
' process.exit(1);',
|
|
360
362
|
' }',
|
|
361
|
-
'
|
|
362
|
-
'
|
|
363
|
+
' console.error(e);',
|
|
364
|
+
' process.exit(1);',
|
|
365
|
+
'});',
|
|
363
366
|
'',
|
|
364
367
|
].join('\n');
|
|
365
368
|
}
|
|
@@ -376,16 +379,19 @@ export function generateProjectFiles(projectName, template, format = 'esm', pers
|
|
|
376
379
|
'',
|
|
377
380
|
`const { ${workflowName} } = require('./${workflowJsFile}');`,
|
|
378
381
|
'',
|
|
379
|
-
'
|
|
380
|
-
` const result = ${workflowName}(true, { data: { message: 'hello world' } });`,
|
|
381
|
-
' console.log(result);',
|
|
382
|
-
'}
|
|
382
|
+
'async function main() {',
|
|
383
|
+
` const result = await ${workflowName}(true, { data: { message: 'hello world' } });`,
|
|
384
|
+
' console.log(JSON.stringify(result, null, 2));',
|
|
385
|
+
'}',
|
|
386
|
+
'',
|
|
387
|
+
'main().catch((e) => {',
|
|
383
388
|
" if (e instanceof Error && e.message.startsWith('Compile with:')) {",
|
|
384
389
|
" console.error('Workflow not compiled yet. Run: npm run dev');",
|
|
385
390
|
' process.exit(1);',
|
|
386
391
|
' }',
|
|
387
|
-
'
|
|
388
|
-
'
|
|
392
|
+
' console.error(e);',
|
|
393
|
+
' process.exit(1);',
|
|
394
|
+
'});',
|
|
389
395
|
'',
|
|
390
396
|
].join('\n');
|
|
391
397
|
}
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -339,6 +339,12 @@ async function runCommandInner(input, options) {
|
|
|
339
339
|
logger.newline();
|
|
340
340
|
logger.section('Result');
|
|
341
341
|
logger.log(JSON.stringify(result.result, null, 2));
|
|
342
|
+
// Hint when the workflow failed and no params were provided
|
|
343
|
+
const resultObj = result.result;
|
|
344
|
+
if (resultObj?.onFailure === true && !options.params && !options.paramsFile) {
|
|
345
|
+
logger.newline();
|
|
346
|
+
logger.warn('Tip: use --params to provide input. Run `fw describe <file>` to see expected inputs.');
|
|
347
|
+
}
|
|
342
348
|
// Show trace summary only when --trace is explicitly set (not on --stream, which already printed live)
|
|
343
349
|
if (options.trace && !options.stream && result.trace && result.trace.length > 0) {
|
|
344
350
|
logger.newline();
|