@synergenius/flow-weaver 0.27.4 → 0.28.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.
@@ -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): string[];
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
  /**
@@ -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
- 'try {',
354
- ` const result = ${workflowName}(true, { data: { message: 'hello world' } });`,
355
- ' console.log(result);',
356
- '} catch (e) {',
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
- ' throw e;',
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
- 'try {',
380
- ` const result = ${workflowName}(true, { data: { message: 'hello world' } });`,
381
- ' console.log(result);',
382
- '} catch (e) {',
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
- ' throw e;',
388
- '}',
392
+ ' console.error(e);',
393
+ ' process.exit(1);',
394
+ '});',
389
395
  '',
390
396
  ].join('\n');
391
397
  }
@@ -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();
@@ -5987,7 +5987,7 @@ var VERSION;
5987
5987
  var init_generated_version = __esm({
5988
5988
  "src/generated-version.ts"() {
5989
5989
  "use strict";
5990
- VERSION = "0.27.4";
5990
+ VERSION = "0.28.0";
5991
5991
  }
5992
5992
  });
5993
5993
 
@@ -6645,18 +6645,28 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
6645
6645
  }
6646
6646
  const safeChildId = toValidIdentifier(child.id);
6647
6647
  const awaitPrefix = isAsync2 ? "await " : "";
6648
+ const emitDebugHooks = !production;
6649
+ let childIndent = " ";
6648
6650
  lines.push(``);
6649
6651
  lines.push(` // Execute: ${child.id} (${child.nodeType})`);
6650
- lines.push(` scopedCtx.checkAborted('${child.id}');`);
6651
- lines.push(` const ${safeChildId}Idx = scopedCtx.addExecution('${child.id}');`);
6652
- lines.push(` if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${child.id}';`);
6653
- lines.push(` ${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
6654
- lines.push(` nodeTypeName: '${child.nodeType}',`);
6655
- lines.push(` id: '${child.id}',`);
6656
- lines.push(` executionIndex: ${safeChildId}Idx,`);
6657
- lines.push(` status: 'RUNNING',`);
6658
- lines.push(` });`);
6659
- lines.push(` try {`);
6652
+ if (emitDebugHooks) {
6653
+ const awaitHook = isAsync2 ? "await " : "";
6654
+ lines.push(` let ${safeChildId}Idx: number;`);
6655
+ lines.push(` if (${awaitHook}__ctrl__.beforeNode('${child.id}', scopedCtx)) {`);
6656
+ childIndent = " ";
6657
+ }
6658
+ lines.push(`${childIndent}scopedCtx.checkAborted('${child.id}');`);
6659
+ const idxDecl = emitDebugHooks ? "" : "const ";
6660
+ lines.push(`${childIndent}${idxDecl}${safeChildId}Idx = scopedCtx.addExecution('${child.id}');`);
6661
+ lines.push(`${childIndent}if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${child.id}';`);
6662
+ lines.push(`${childIndent}${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
6663
+ lines.push(`${childIndent} nodeTypeName: '${child.nodeType}',`);
6664
+ lines.push(`${childIndent} id: '${child.id}',`);
6665
+ lines.push(`${childIndent} executionIndex: ${safeChildId}Idx,`);
6666
+ lines.push(`${childIndent} status: 'RUNNING',`);
6667
+ lines.push(`${childIndent}});`);
6668
+ lines.push(`${childIndent}try {`);
6669
+ const tryIndent = `${childIndent} `;
6660
6670
  const argLines = [];
6661
6671
  const getCall = isAsync2 ? "await scopedCtx.getVariable" : "scopedCtx.getVariable";
6662
6672
  const childSetCall = isAsync2 ? `await scopedCtx.setVariable` : `scopedCtx.setVariable`;
@@ -6673,10 +6683,10 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
6673
6683
  const targetPortDef = childNodeType.inputs[targetPort];
6674
6684
  const portType = targetPortDef ? mapToTypeScript(targetPortDef.dataType, targetPortDef.tsType) : "unknown";
6675
6685
  argLines.push(
6676
- ` const ${varName} = ${getCall}({ id: '${parentNodeId}', portName: '${conn.from.port}', executionIndex: ${scopeParamIdxVar} }) as ${portType};`
6686
+ `${tryIndent}const ${varName} = ${getCall}({ id: '${parentNodeId}', portName: '${conn.from.port}', executionIndex: ${scopeParamIdxVar} }) as ${portType};`
6677
6687
  );
6678
6688
  argLines.push(
6679
- ` ${childSetCall}({ id: '${child.id}', portName: '${targetPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${varName});`
6689
+ `${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${targetPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${varName});`
6680
6690
  );
6681
6691
  preHandledPorts.add(targetPort);
6682
6692
  }
@@ -6686,7 +6696,7 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
6686
6696
  workflow: scopeWorkflow,
6687
6697
  id: child.id,
6688
6698
  lines: argLines,
6689
- indent: ` `,
6699
+ indent: tryIndent,
6690
6700
  getCall,
6691
6701
  isAsync: isAsync2,
6692
6702
  instanceParent: child.parent ? `${child.parent.id}.${child.parent.scope}` : void 0,
@@ -6699,11 +6709,11 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
6699
6709
  argLines.forEach((line) => lines.push(line));
6700
6710
  if (childNodeType.expression) {
6701
6711
  lines.push(
6702
- ` const ${safeChildId}Result = ${awaitPrefix}${child.nodeType}(${args.join(", ")});`
6712
+ `${tryIndent}const ${safeChildId}Result = ${awaitPrefix}${child.nodeType}(${args.join(", ")});`
6703
6713
  );
6704
6714
  } else {
6705
6715
  lines.push(
6706
- ` const ${safeChildId}Result = ${awaitPrefix}${child.nodeType}(${args.join(", ")});`
6716
+ `${tryIndent}const ${safeChildId}Result = ${awaitPrefix}${child.nodeType}(${args.join(", ")});`
6707
6717
  );
6708
6718
  }
6709
6719
  if (childNodeType.expression) {
@@ -6711,49 +6721,56 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
6711
6721
  const portDef = childNodeType.outputs[outPort];
6712
6722
  if (portDef.failure || isFailurePort(outPort)) {
6713
6723
  lines.push(
6714
- ` ${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, false);`
6724
+ `${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, false);`
6715
6725
  );
6716
6726
  } else if (portDef.isControlFlow || isSuccessPort(outPort)) {
6717
6727
  lines.push(
6718
- ` ${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, true);`
6728
+ `${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, true);`
6719
6729
  );
6720
6730
  } else {
6721
6731
  lines.push(
6722
- ` ${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${safeChildId}Result.${outPort});`
6732
+ `${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${safeChildId}Result.${outPort});`
6723
6733
  );
6724
6734
  }
6725
6735
  });
6726
6736
  } else {
6727
6737
  Object.keys(childNodeType.outputs || {}).forEach((outPort) => {
6728
6738
  lines.push(
6729
- ` ${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${safeChildId}Result.${outPort});`
6739
+ `${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${safeChildId}Result.${outPort});`
6730
6740
  );
6731
6741
  });
6732
6742
  }
6733
- lines.push(` ${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
6734
- lines.push(` nodeTypeName: '${child.nodeType}',`);
6735
- lines.push(` id: '${child.id}',`);
6736
- lines.push(` executionIndex: ${safeChildId}Idx,`);
6737
- lines.push(` status: 'SUCCEEDED',`);
6738
- lines.push(` });`);
6739
- lines.push(` } catch (error: unknown) {`);
6740
- lines.push(` const isCancellation = CancellationError.isCancellationError(error);`);
6741
- lines.push(` ${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
6742
- lines.push(` nodeTypeName: '${child.nodeType}',`);
6743
- lines.push(` id: '${child.id}',`);
6744
- lines.push(` executionIndex: ${safeChildId}Idx,`);
6745
- lines.push(` status: isCancellation ? 'CANCELLED' : 'FAILED',`);
6746
- lines.push(` });`);
6747
- lines.push(` if (!isCancellation) {`);
6748
- lines.push(` scopedCtx.sendLogErrorEvent({`);
6749
- lines.push(` nodeTypeName: '${child.nodeType}',`);
6750
- lines.push(` id: '${child.id}',`);
6751
- lines.push(` executionIndex: ${safeChildId}Idx,`);
6752
- lines.push(` error: error instanceof Error ? error.message : String(error),`);
6753
- lines.push(` });`);
6754
- lines.push(` }`);
6755
- lines.push(` throw error;`);
6756
- lines.push(` }`);
6743
+ lines.push(`${tryIndent}${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
6744
+ lines.push(`${tryIndent} nodeTypeName: '${child.nodeType}',`);
6745
+ lines.push(`${tryIndent} id: '${child.id}',`);
6746
+ lines.push(`${tryIndent} executionIndex: ${safeChildId}Idx,`);
6747
+ lines.push(`${tryIndent} status: 'SUCCEEDED',`);
6748
+ lines.push(`${tryIndent}});`);
6749
+ if (emitDebugHooks) {
6750
+ const awaitHook = isAsync2 ? "await " : "";
6751
+ lines.push(`${tryIndent}${awaitHook}__ctrl__.afterNode('${child.id}', scopedCtx);`);
6752
+ }
6753
+ lines.push(`${childIndent}} catch (error: unknown) {`);
6754
+ lines.push(`${tryIndent}const isCancellation = CancellationError.isCancellationError(error);`);
6755
+ lines.push(`${tryIndent}${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
6756
+ lines.push(`${tryIndent} nodeTypeName: '${child.nodeType}',`);
6757
+ lines.push(`${tryIndent} id: '${child.id}',`);
6758
+ lines.push(`${tryIndent} executionIndex: ${safeChildId}Idx,`);
6759
+ lines.push(`${tryIndent} status: isCancellation ? 'CANCELLED' : 'FAILED',`);
6760
+ lines.push(`${tryIndent}});`);
6761
+ lines.push(`${tryIndent}if (!isCancellation) {`);
6762
+ lines.push(`${tryIndent} scopedCtx.sendLogErrorEvent({`);
6763
+ lines.push(`${tryIndent} nodeTypeName: '${child.nodeType}',`);
6764
+ lines.push(`${tryIndent} id: '${child.id}',`);
6765
+ lines.push(`${tryIndent} executionIndex: ${safeChildId}Idx,`);
6766
+ lines.push(`${tryIndent} error: error instanceof Error ? error.message : String(error),`);
6767
+ lines.push(`${tryIndent} });`);
6768
+ lines.push(`${tryIndent}}`);
6769
+ lines.push(`${tryIndent}throw error;`);
6770
+ lines.push(`${childIndent}}`);
6771
+ if (emitDebugHooks) {
6772
+ lines.push(` }`);
6773
+ }
6757
6774
  });
6758
6775
  lines.push(``);
6759
6776
  }
@@ -29005,6 +29022,10 @@ ${fn.getText()}` : fn.getText();
29005
29022
  const nextInputs = getInputPorts(nextId);
29006
29023
  for (const [inputName] of Object.entries(nextInputs)) {
29007
29024
  if (isControlFlowPort(inputName)) continue;
29025
+ const alreadyConnected = connections.some(
29026
+ (c) => c.to.node === nextId && c.to.port === inputName
29027
+ );
29028
+ if (alreadyConnected) continue;
29008
29029
  for (let j2 = i; j2 >= 0; j2--) {
29009
29030
  const ancestorId = steps[j2].node;
29010
29031
  const ancestorOutputs = getOutputPorts(ancestorId);
@@ -37919,7 +37940,7 @@ function getDependents(ast, nodeId) {
37919
37940
  });
37920
37941
  return Array.from(dependents);
37921
37942
  }
37922
- function getTopologicalOrder(ast) {
37943
+ function getTopologicalOrder(ast, options) {
37923
37944
  const mainInstances = getMainFlowInstances(ast);
37924
37945
  const mainConnections = getMainFlowConnections(ast);
37925
37946
  const inDegree = /* @__PURE__ */ new Map();
@@ -37958,6 +37979,30 @@ function getTopologicalOrder(ast) {
37958
37979
  if (result.length !== mainInstances.length) {
37959
37980
  throw new Error("Cannot compute topological order: workflow contains cycles");
37960
37981
  }
37982
+ if (options?.includeScopedChildren) {
37983
+ const scopedChildren = ast.instances.filter(
37984
+ (inst) => isPerPortScopedChild(inst, ast, ast.nodeTypes)
37985
+ );
37986
+ const expanded = [];
37987
+ const scopedByParent = /* @__PURE__ */ new Map();
37988
+ for (const child of scopedChildren) {
37989
+ if (child.parent) {
37990
+ const parentId = child.parent.id;
37991
+ if (!scopedByParent.has(parentId)) {
37992
+ scopedByParent.set(parentId, []);
37993
+ }
37994
+ scopedByParent.get(parentId).push(child.id);
37995
+ }
37996
+ }
37997
+ for (const nodeId of result) {
37998
+ expanded.push(nodeId);
37999
+ const children = scopedByParent.get(nodeId);
38000
+ if (children) {
38001
+ expanded.push(...children);
38002
+ }
38003
+ }
38004
+ return expanded;
38005
+ }
37961
38006
  return result;
37962
38007
  }
37963
38008
  function findIsolatedNodes(ast) {
@@ -58608,9 +58653,9 @@ function printNocodeGuidance(_projectName) {
58608
58653
  logger.newline();
58609
58654
  logger.log(` ${logger.bold("Useful commands")}`);
58610
58655
  logger.newline();
58611
- logger.log(` fw run src/*.ts ${logger.dim("Run your workflow")}`);
58612
- logger.log(` fw diagram src/*.ts ${logger.dim("See a visual diagram")}`);
58613
- logger.log(` fw mcp-setup ${logger.dim("Connect more AI editors")}`);
58656
+ logger.log(` npx fw run src/*.ts ${logger.dim("Run your workflow")}`);
58657
+ logger.log(` npx fw diagram src/*.ts ${logger.dim("See a visual diagram")}`);
58658
+ logger.log(` npx fw mcp-setup ${logger.dim("Connect more AI editors")}`);
58614
58659
  }
58615
58660
  function printVibecoderGuidance() {
58616
58661
  logger.newline();
@@ -58628,17 +58673,17 @@ function printLowcodeGuidance() {
58628
58673
  logger.newline();
58629
58674
  logger.log(` ${logger.bold("Explore and customize")}`);
58630
58675
  logger.newline();
58631
- logger.log(` fw templates ${logger.dim("List all 16 workflow templates")}`);
58632
- logger.log(` fw describe src/*.ts ${logger.dim("See the workflow structure")}`);
58633
- logger.log(` fw docs annotations ${logger.dim("Annotation reference")}`);
58676
+ logger.log(` npx fw templates ${logger.dim("List all 16 workflow templates")}`);
58677
+ logger.log(` npx fw describe src/*.ts ${logger.dim("See the workflow structure")}`);
58678
+ logger.log(` npx fw docs annotations ${logger.dim("Annotation reference")}`);
58634
58679
  logger.newline();
58635
58680
  logger.log(` Your project includes an example in ${logger.highlight("examples/")} to study.`);
58636
58681
  logger.log(` With MCP connected, AI can help modify nodes and connections.`);
58637
58682
  }
58638
58683
  function printExpertGuidance() {
58639
58684
  logger.newline();
58640
- logger.log(` fw mcp-setup ${logger.dim("Connect AI editors (Claude, Cursor, VS Code)")}`);
58641
- logger.log(` fw docs ${logger.dim("Browse reference docs")}`);
58685
+ logger.log(` npx fw mcp-setup ${logger.dim("Connect AI editors (Claude, Cursor, VS Code)")}`);
58686
+ logger.log(` npx fw docs ${logger.dim("Browse reference docs")}`);
58642
58687
  }
58643
58688
  function pad(displayName, width) {
58644
58689
  const padding = Math.max(1, width - displayName.length);
@@ -59588,16 +59633,19 @@ ${workflowCode}`;
59588
59633
  "",
59589
59634
  `import { ${workflowName} } from './${workflowJsFile}';`,
59590
59635
  "",
59591
- "try {",
59592
- ` const result = ${workflowName}(true, { data: { message: 'hello world' } });`,
59593
- " console.log(result);",
59594
- "} catch (e) {",
59636
+ "async function main() {",
59637
+ ` const result = await ${workflowName}(true, { data: { message: 'hello world' } });`,
59638
+ " console.log(JSON.stringify(result, null, 2));",
59639
+ "}",
59640
+ "",
59641
+ "main().catch((e) => {",
59595
59642
  " if (e instanceof Error && e.message.startsWith('Compile with:')) {",
59596
59643
  " console.error('Workflow not compiled yet. Run: npm run dev');",
59597
59644
  " process.exit(1);",
59598
59645
  " }",
59599
- " throw e;",
59600
- "}",
59646
+ " console.error(e);",
59647
+ " process.exit(1);",
59648
+ "});",
59601
59649
  ""
59602
59650
  ].join("\n");
59603
59651
  } else {
@@ -59613,16 +59661,19 @@ ${workflowCode}`;
59613
59661
  "",
59614
59662
  `const { ${workflowName} } = require('./${workflowJsFile}');`,
59615
59663
  "",
59616
- "try {",
59617
- ` const result = ${workflowName}(true, { data: { message: 'hello world' } });`,
59618
- " console.log(result);",
59619
- "} catch (e) {",
59664
+ "async function main() {",
59665
+ ` const result = await ${workflowName}(true, { data: { message: 'hello world' } });`,
59666
+ " console.log(JSON.stringify(result, null, 2));",
59667
+ "}",
59668
+ "",
59669
+ "main().catch((e) => {",
59620
59670
  " if (e instanceof Error && e.message.startsWith('Compile with:')) {",
59621
59671
  " console.error('Workflow not compiled yet. Run: npm run dev');",
59622
59672
  " process.exit(1);",
59623
59673
  " }",
59624
- " throw e;",
59625
- "}",
59674
+ " console.error(e);",
59675
+ " process.exit(1);",
59676
+ "});",
59626
59677
  ""
59627
59678
  ].join("\n");
59628
59679
  }
@@ -84560,6 +84611,13 @@ async function runCommandInner(input, options) {
84560
84611
  logger.newline();
84561
84612
  logger.section("Result");
84562
84613
  logger.log(JSON.stringify(result.result, null, 2));
84614
+ const resultObj = result.result;
84615
+ if (resultObj?.onFailure === true && !options.params && !options.paramsFile) {
84616
+ logger.newline();
84617
+ logger.warn(
84618
+ "Tip: use --params to provide input. Run `fw describe <file>` to see expected inputs."
84619
+ );
84620
+ }
84563
84621
  if (options.trace && !options.stream && result.trace && result.trace.length > 0) {
84564
84622
  logger.newline();
84565
84623
  logger.section("Trace");
@@ -88468,7 +88526,7 @@ function parseIntStrict(value) {
88468
88526
  // src/cli/index.ts
88469
88527
  init_logger();
88470
88528
  init_error_utils();
88471
- var version2 = true ? "0.27.4" : "0.0.0-dev";
88529
+ var version2 = true ? "0.28.0" : "0.0.0-dev";
88472
88530
  var program2 = new Command();
88473
88531
  program2.name("fw").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
88474
88532
  logger.banner(version2);
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.27.4";
1
+ export declare const VERSION = "0.28.0";
2
2
  //# sourceMappingURL=generated-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.ts — do not edit manually
2
- export const VERSION = '0.27.4';
2
+ export const VERSION = '0.28.0';
3
3
  //# sourceMappingURL=generated-version.js.map
@@ -187,18 +187,34 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
187
187
  }
188
188
  const safeChildId = toValidIdentifier(child.id);
189
189
  const awaitPrefix = isAsync ? 'await ' : '';
190
+ const emitDebugHooks = !production;
191
+ // Indentation increases when debug hooks wrap the child block
192
+ let childIndent = ' ';
190
193
  lines.push(``);
191
194
  lines.push(` // Execute: ${child.id} (${child.nodeType})`);
192
- lines.push(` scopedCtx.checkAborted('${child.id}');`);
193
- lines.push(` const ${safeChildId}Idx = scopedCtx.addExecution('${child.id}');`);
194
- lines.push(` if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${child.id}';`);
195
- lines.push(` ${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
196
- lines.push(` nodeTypeName: '${child.nodeType}',`);
197
- lines.push(` id: '${child.id}',`);
198
- lines.push(` executionIndex: ${safeChildId}Idx,`);
199
- lines.push(` status: 'RUNNING',`);
200
- lines.push(` });`);
201
- lines.push(` try {`);
195
+ // Debug controller: beforeNode hook for scoped children
196
+ // When enabled, wraps the child execution so breakpoints can pause on scoped nodes.
197
+ if (emitDebugHooks) {
198
+ const awaitHook = isAsync ? 'await ' : '';
199
+ // Hoist Idx declaration before the if block so it stays in scope after
200
+ lines.push(` let ${safeChildId}Idx: number;`);
201
+ lines.push(` if (${awaitHook}__ctrl__.beforeNode('${child.id}', scopedCtx)) {`);
202
+ childIndent = ' ';
203
+ }
204
+ lines.push(`${childIndent}scopedCtx.checkAborted('${child.id}');`);
205
+ // Use assignment when debug hooks hoist the declaration, const otherwise
206
+ const idxDecl = emitDebugHooks ? '' : 'const ';
207
+ lines.push(`${childIndent}${idxDecl}${safeChildId}Idx = scopedCtx.addExecution('${child.id}');`);
208
+ lines.push(`${childIndent}if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${child.id}';`);
209
+ lines.push(`${childIndent}${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
210
+ lines.push(`${childIndent} nodeTypeName: '${child.nodeType}',`);
211
+ lines.push(`${childIndent} id: '${child.id}',`);
212
+ lines.push(`${childIndent} executionIndex: ${safeChildId}Idx,`);
213
+ lines.push(`${childIndent} status: 'RUNNING',`);
214
+ lines.push(`${childIndent}});`);
215
+ lines.push(`${childIndent}try {`);
216
+ // Inner indentation: inside try block (childIndent + 2 spaces for try body)
217
+ const tryIndent = `${childIndent} `;
202
218
  // Pre-handle connections from parent scoped OUTPUT ports with correct index variables
203
219
  const argLines = [];
204
220
  const getCall = isAsync ? 'await scopedCtx.getVariable' : 'scopedCtx.getVariable';
@@ -218,9 +234,9 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
218
234
  const portType = targetPortDef
219
235
  ? mapToTypeScript(targetPortDef.dataType, targetPortDef.tsType)
220
236
  : 'unknown';
221
- argLines.push(` const ${varName} = ${getCall}({ id: '${parentNodeId}', portName: '${conn.from.port}', executionIndex: ${scopeParamIdxVar} }) as ${portType};`);
237
+ argLines.push(`${tryIndent}const ${varName} = ${getCall}({ id: '${parentNodeId}', portName: '${conn.from.port}', executionIndex: ${scopeParamIdxVar} }) as ${portType};`);
222
238
  // Emit VARIABLE_SET for the child's INPUT port so breakpoints and inspection work
223
- argLines.push(` ${childSetCall}({ id: '${child.id}', portName: '${targetPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${varName});`);
239
+ argLines.push(`${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${targetPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${varName});`);
224
240
  preHandledPorts.add(targetPort);
225
241
  }
226
242
  });
@@ -230,7 +246,7 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
230
246
  workflow: scopeWorkflow,
231
247
  id: child.id,
232
248
  lines: argLines,
233
- indent: ` `,
249
+ indent: tryIndent,
234
250
  getCall,
235
251
  isAsync,
236
252
  instanceParent: child.parent ? `${child.parent.id}.${child.parent.scope}` : undefined,
@@ -245,11 +261,11 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
245
261
  // Call the child node function
246
262
  if (childNodeType.expression) {
247
263
  // Expression nodes use original signature (positional args, no execute)
248
- lines.push(` const ${safeChildId}Result = ${awaitPrefix}${child.nodeType}(${args.join(', ')});`);
264
+ lines.push(`${tryIndent}const ${safeChildId}Result = ${awaitPrefix}${child.nodeType}(${args.join(', ')});`);
249
265
  }
250
266
  else {
251
267
  // Regular node call with positional arguments
252
- lines.push(` const ${safeChildId}Result = ${awaitPrefix}${child.nodeType}(${args.join(', ')});`);
268
+ lines.push(`${tryIndent}const ${safeChildId}Result = ${awaitPrefix}${child.nodeType}(${args.join(', ')});`);
253
269
  }
254
270
  // Store outputs (including onSuccess/onFailure for debugging)
255
271
  // Expression nodes don't return onSuccess/onFailure — hardcode them
@@ -258,48 +274,57 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
258
274
  const portDef = childNodeType.outputs[outPort];
259
275
  if (portDef.failure || isFailurePort(outPort)) {
260
276
  // Failure ports always false on success (expression nodes always succeed)
261
- lines.push(` ${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, false);`);
277
+ lines.push(`${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, false);`);
262
278
  }
263
279
  else if (portDef.isControlFlow || isSuccessPort(outPort)) {
264
280
  // Success control flow ports always true (expression nodes always succeed)
265
- lines.push(` ${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, true);`);
281
+ lines.push(`${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, true);`);
266
282
  }
267
283
  else {
268
284
  // Data outputs read from result object
269
- lines.push(` ${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${safeChildId}Result.${outPort});`);
285
+ lines.push(`${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${safeChildId}Result.${outPort});`);
270
286
  }
271
287
  });
272
288
  }
273
289
  else {
274
290
  Object.keys(childNodeType.outputs || {}).forEach((outPort) => {
275
- lines.push(` ${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${safeChildId}Result.${outPort});`);
291
+ lines.push(`${tryIndent}${childSetCall}({ id: '${child.id}', portName: '${outPort}', executionIndex: ${safeChildId}Idx, nodeTypeName: '${child.nodeType}' }, ${safeChildId}Result.${outPort});`);
276
292
  });
277
293
  }
278
294
  // Add SUCCEEDED status event
279
- lines.push(` ${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
280
- lines.push(` nodeTypeName: '${child.nodeType}',`);
281
- lines.push(` id: '${child.id}',`);
282
- lines.push(` executionIndex: ${safeChildId}Idx,`);
283
- lines.push(` status: 'SUCCEEDED',`);
284
- lines.push(` });`);
285
- lines.push(` } catch (error: unknown) {`);
286
- lines.push(` const isCancellation = CancellationError.isCancellationError(error);`);
287
- lines.push(` ${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
288
- lines.push(` nodeTypeName: '${child.nodeType}',`);
289
- lines.push(` id: '${child.id}',`);
290
- lines.push(` executionIndex: ${safeChildId}Idx,`);
291
- lines.push(` status: isCancellation ? 'CANCELLED' : 'FAILED',`);
292
- lines.push(` });`);
293
- lines.push(` if (!isCancellation) {`);
294
- lines.push(` scopedCtx.sendLogErrorEvent({`);
295
- lines.push(` nodeTypeName: '${child.nodeType}',`);
296
- lines.push(` id: '${child.id}',`);
297
- lines.push(` executionIndex: ${safeChildId}Idx,`);
298
- lines.push(` error: error instanceof Error ? error.message : String(error),`);
299
- lines.push(` });`);
300
- lines.push(` }`);
301
- lines.push(` throw error;`);
302
- lines.push(` }`);
295
+ lines.push(`${tryIndent}${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
296
+ lines.push(`${tryIndent} nodeTypeName: '${child.nodeType}',`);
297
+ lines.push(`${tryIndent} id: '${child.id}',`);
298
+ lines.push(`${tryIndent} executionIndex: ${safeChildId}Idx,`);
299
+ lines.push(`${tryIndent} status: 'SUCCEEDED',`);
300
+ lines.push(`${tryIndent}});`);
301
+ // Debug controller: afterNode hook for scoped children
302
+ if (emitDebugHooks) {
303
+ const awaitHook = isAsync ? 'await ' : '';
304
+ lines.push(`${tryIndent}${awaitHook}__ctrl__.afterNode('${child.id}', scopedCtx);`);
305
+ }
306
+ lines.push(`${childIndent}} catch (error: unknown) {`);
307
+ lines.push(`${tryIndent}const isCancellation = CancellationError.isCancellationError(error);`);
308
+ lines.push(`${tryIndent}${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
309
+ lines.push(`${tryIndent} nodeTypeName: '${child.nodeType}',`);
310
+ lines.push(`${tryIndent} id: '${child.id}',`);
311
+ lines.push(`${tryIndent} executionIndex: ${safeChildId}Idx,`);
312
+ lines.push(`${tryIndent} status: isCancellation ? 'CANCELLED' : 'FAILED',`);
313
+ lines.push(`${tryIndent}});`);
314
+ lines.push(`${tryIndent}if (!isCancellation) {`);
315
+ lines.push(`${tryIndent} scopedCtx.sendLogErrorEvent({`);
316
+ lines.push(`${tryIndent} nodeTypeName: '${child.nodeType}',`);
317
+ lines.push(`${tryIndent} id: '${child.id}',`);
318
+ lines.push(`${tryIndent} executionIndex: ${safeChildId}Idx,`);
319
+ lines.push(`${tryIndent} error: error instanceof Error ? error.message : String(error),`);
320
+ lines.push(`${tryIndent} });`);
321
+ lines.push(`${tryIndent}}`);
322
+ lines.push(`${tryIndent}throw error;`);
323
+ lines.push(`${childIndent}}`);
324
+ // Close debug controller beforeNode if-block
325
+ if (emitDebugHooks) {
326
+ lines.push(` }`);
327
+ }
303
328
  });
304
329
  lines.push(``);
305
330
  }
package/dist/parser.js CHANGED
@@ -1653,6 +1653,10 @@ export class AnnotationParser {
1653
1653
  for (const [inputName] of Object.entries(nextInputs)) {
1654
1654
  if (isControlFlowPort(inputName))
1655
1655
  continue;
1656
+ // Skip auto-wiring if a manual @connect already targets this input port
1657
+ const alreadyConnected = connections.some(c => c.to.node === nextId && c.to.port === inputName);
1658
+ if (alreadyConnected)
1659
+ continue;
1656
1660
  // Walk backward through path steps to find nearest ancestor with same-name output
1657
1661
  for (let j = i; j >= 0; j--) {
1658
1662
  const ancestorId = steps[j].node;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.27.4",
4
- "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
3
+ "version": "0.28.0",
4
+ "description": "Flow Weaver: deterministic TypeScript workflow compiler. Define workflows with JSDoc annotations, compile to standalone functions with zero runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -138,12 +138,18 @@
138
138
  "keywords": [
139
139
  "flow-weaver",
140
140
  "workflow",
141
+ "workflow-engine",
142
+ "workflow-automation",
143
+ "orchestration",
141
144
  "ai-agent",
142
145
  "llm",
143
146
  "mcp",
144
- "deterministic",
145
147
  "typescript",
146
148
  "compiler",
149
+ "code-generation",
150
+ "visual-programming",
151
+ "node-graph",
152
+ "durable-execution",
147
153
  "inngest",
148
154
  "serverless"
149
155
  ],