@synergenius/flow-weaver 0.21.21 → 0.22.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.
@@ -8,7 +8,7 @@
8
8
  * - Function body (between markers)
9
9
  */
10
10
  import { bodyGenerator } from '../body-generator.js';
11
- import { generateInlineRuntime, generateInlineDebugClient } from './inline-runtime.js';
11
+ import { generateInlineRuntime } from './inline-runtime.js';
12
12
  import { isExecutePort, isSuccessPort, isFailurePort, isControlFlowPort } from '../constants.js';
13
13
  import { generateJSDocPortTag, assignPortOrders, generateNodeInstanceTag, } from '../annotation-generator.js';
14
14
  import { shouldWorkflowBeAsync } from '../generator/async-detection.js';
@@ -112,9 +112,11 @@ export function generateInPlace(sourceCode, ast, options = {}) {
112
112
  }
113
113
  // Step 5: Detect async from node composition + source signature
114
114
  // If any node is async, force async (even if source isn't marked async)
115
+ // In dev mode (!production), always force async so the debugger can pause execution
116
+ // at breakpoints (sendStatusChangedEvent must be awaited).
115
117
  const nodesRequireAsync = shouldWorkflowBeAsync(ast, ast.nodeTypes);
116
118
  const sourceIsAsync = detectFunctionIsAsync(result, ast.functionName);
117
- const forceAsync = nodesRequireAsync;
119
+ const forceAsync = nodesRequireAsync || !production;
118
120
  const isAsync = forceAsync || sourceIsAsync;
119
121
  // Add async keyword to source if nodes or debug hooks require it
120
122
  const asyncSigResult = ensureAsyncKeyword(result, ast.functionName, forceAsync);
@@ -158,19 +160,11 @@ function generateRuntimeSection(functionName, production, moduleFormat = 'esm',
158
160
  lines.push(`import type { TDebugger } from '${externalRuntimePath}';`);
159
161
  // Declare __flowWeaverDebugger__ so body code can reference it
160
162
  lines.push('declare const __flowWeaverDebugger__: TDebugger | undefined;');
161
- // Include inline debug client (createFlowWeaverDebugClient is not exported from runtime)
162
- lines.push('');
163
- lines.push(generateInlineDebugClient(moduleFormat));
164
163
  }
165
164
  }
166
165
  else {
167
166
  // Inline runtime: embed all types and classes directly
168
167
  lines.push(generateInlineRuntime(production));
169
- // Add debug client (dev mode only)
170
- if (!production) {
171
- lines.push('');
172
- lines.push(generateInlineDebugClient(moduleFormat));
173
- }
174
168
  }
175
169
  return lines.join('\n');
176
170
  }
@@ -3,7 +3,7 @@ import { extractExitPorts, extractStartPorts, hasBranching } from '../ast/workfl
3
3
  import { isExecutePort } from '../constants.js';
4
4
  import { mapToTypeScript } from '../type-mappings.js';
5
5
  import { SourceMapGenerator } from 'source-map';
6
- import { generateInlineRuntime, generateInlineDebugClient, stripTypeScript } from './inline-runtime.js';
6
+ import { generateInlineRuntime, stripTypeScript } from './inline-runtime.js';
7
7
  import { validateWorkflowAsync } from '../generator/async-detection.js';
8
8
  import { extractTypeDeclarationsFromFile } from './extract-types.js';
9
9
  import * as path from 'node:path';
@@ -100,13 +100,6 @@ export function generateCode(ast, options) {
100
100
  ? `const { TDebugger } = require('${externalRuntimePath}');`
101
101
  : `import type { TDebugger } from '${externalRuntimePath}';`);
102
102
  addLine();
103
- // Include inline debug client (createFlowWeaverDebugClient is not exported from runtime)
104
- const inlineDebugClient = generateInlineDebugClient(moduleFormat);
105
- const debugClientLines = inlineDebugClient.split('\n');
106
- debugClientLines.forEach((line) => {
107
- lines.push(line);
108
- addLine();
109
- });
110
103
  }
111
104
  lines.push('');
112
105
  addLine();
@@ -121,15 +114,6 @@ export function generateCode(ast, options) {
121
114
  });
122
115
  lines.push('');
123
116
  addLine();
124
- // Include inline debug client (dev mode only)
125
- if (!production) {
126
- const inlineDebugClient = generateInlineDebugClient(moduleFormat);
127
- const debugClientLines = inlineDebugClient.split('\n');
128
- debugClientLines.forEach((line) => {
129
- lines.push(line);
130
- addLine();
131
- });
132
- }
133
117
  }
134
118
  // Extract and include type declarations from source file (interfaces, type aliases)
135
119
  if (ast.sourceFile) {
@@ -374,7 +358,8 @@ export function generateCode(ast, options) {
374
358
  }
375
359
  }
376
360
  // Generate conditional async keyword and export keyword based on module format
377
- const asyncKeyword = shouldBeAsync ? 'async ' : '';
361
+ // In dev mode, always async so the debugger can pause at breakpoints
362
+ const asyncKeyword = (shouldBeAsync || !production) ? 'async ' : '';
378
363
  const exportKeyword = generateFunctionExportKeyword(moduleFormat);
379
364
  lines.push(`${exportKeyword}${asyncKeyword}function ${ast.functionName}(`);
380
365
  addLine();
@@ -488,8 +473,8 @@ function generateWorkflowFunction(workflow, production, allWorkflows, generatedW
488
473
  // Generate function signature
489
474
  const startPorts = extractStartPorts(workflow);
490
475
  const exitPorts = extractExitPorts(workflow);
491
- const asyncKeyword = shouldBeAsync ? 'async ' : '';
492
- lines.push(`${asyncKeyword}function ${workflow.functionName}(`);
476
+ const asyncKeyword2 = (shouldBeAsync || !production) ? 'async ' : '';
477
+ lines.push(`${asyncKeyword2}function ${workflow.functionName}(`);
493
478
  // STEP Port Architecture: execute is first parameter
494
479
  lines.push(` execute: boolean = true,`);
495
480
  // Collect param names and types for destructuring
@@ -18,14 +18,6 @@ export declare function stripTypeScript(code: string): string;
18
18
  */
19
19
  export declare function generateInlineRuntime(production: boolean, exportClasses?: boolean, outputFormat?: TOutputFormat): string;
20
20
  import type { TModuleFormat } from '../ast/types.js';
21
- /**
22
- * Generates inline WebSocket debug client for auto-detection from env var
23
- * Only included in development mode builds
24
- *
25
- * @param moduleFormat - The module format to use for imports ('esm' or 'cjs')
26
- * @param outputFormat - Output format: 'typescript' (default) or 'javascript' (strips types)
27
- */
28
- export declare function generateInlineDebugClient(moduleFormat?: TModuleFormat, outputFormat?: TOutputFormat): string;
29
21
  /**
30
22
  * Generates a standalone runtime module file for multi-workflow bundles.
31
23
  * This exports all runtime types and classes so individual workflow files can import them.
@@ -303,12 +303,13 @@ export function generateInlineRuntime(production, exportClasses = false, outputF
303
303
  lines.push(' }');
304
304
  lines.push('');
305
305
  // createScope
306
- lines.push(' createScope(_parentNodeName: string, _parentIndex: number, _scopeName: string, cleanScope: boolean = false): GeneratedExecutionContext {');
306
+ lines.push(' createScope(_parentNodeName: string, _parentIndex: number, _scopeName: string, cleanScope: boolean = false, isAsyncOverride?: boolean): GeneratedExecutionContext {');
307
+ lines.push(' const effectiveIsAsync = isAsyncOverride !== undefined ? isAsyncOverride : this.isAsync;');
307
308
  if (production) {
308
- lines.push(' const scopedContext = new GeneratedExecutionContext(this.isAsync, this.abortSignal);');
309
+ lines.push(' const scopedContext = new GeneratedExecutionContext(effectiveIsAsync, this.abortSignal);');
309
310
  }
310
311
  else {
311
- lines.push(' const scopedContext = new GeneratedExecutionContext(this.isAsync, this.flowWeaverDebugger, this.abortSignal);');
312
+ lines.push(' const scopedContext = new GeneratedExecutionContext(effectiveIsAsync, this.flowWeaverDebugger, this.abortSignal);');
312
313
  }
313
314
  lines.push(' // For per-port function scopes (cleanScope=true), start with empty variables');
314
315
  lines.push(' // For node-level scopes (cleanScope=false), inherit parent variables');
@@ -388,12 +389,12 @@ export function generateInlineRuntime(production, exportClasses = false, outputF
388
389
  lines.push(' }');
389
390
  lines.push(' }');
390
391
  lines.push('');
391
- lines.push(' private sendVariableSetEvent(args: {');
392
+ lines.push(' private async sendVariableSetEvent(args: {');
392
393
  lines.push(' identifier: TVariableIdentification;');
393
394
  lines.push(' value: unknown;');
394
- lines.push(' }): void {');
395
+ lines.push(' }): Promise<void> {');
395
396
  lines.push(' if (this.flowWeaverDebugger) {');
396
- lines.push(' this.flowWeaverDebugger.sendEvent({');
397
+ lines.push(' await this.flowWeaverDebugger.sendEvent({');
397
398
  lines.push(' type: "VARIABLE_SET",');
398
399
  lines.push(' ...args,');
399
400
  lines.push(' innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,');
@@ -401,16 +402,16 @@ export function generateInlineRuntime(production, exportClasses = false, outputF
401
402
  lines.push(' }');
402
403
  lines.push(' }');
403
404
  lines.push('');
404
- lines.push(' sendLogErrorEvent(args: {');
405
+ lines.push(' async sendLogErrorEvent(args: {');
405
406
  lines.push(' nodeTypeName: string;');
406
407
  lines.push(' id: string;');
407
408
  lines.push(' scope?: string;');
408
409
  lines.push(' side?: "start" | "exit";');
409
410
  lines.push(' executionIndex: number;');
410
411
  lines.push(' error: string;');
411
- lines.push(' }): void {');
412
+ lines.push(' }): Promise<void> {');
412
413
  lines.push(' if (this.flowWeaverDebugger) {');
413
- lines.push(' this.flowWeaverDebugger.sendEvent({');
414
+ lines.push(' await this.flowWeaverDebugger.sendEvent({');
414
415
  lines.push(' type: "LOG_ERROR",');
415
416
  lines.push(' ...args,');
416
417
  lines.push(' innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,');
@@ -418,13 +419,13 @@ export function generateInlineRuntime(production, exportClasses = false, outputF
418
419
  lines.push(' }');
419
420
  lines.push(' }');
420
421
  lines.push('');
421
- lines.push(' sendWorkflowCompletedEvent(args: {');
422
+ lines.push(' async sendWorkflowCompletedEvent(args: {');
422
423
  lines.push(' executionIndex: number;');
423
424
  lines.push(' status: "SUCCEEDED" | "FAILED" | "CANCELLED";');
424
425
  lines.push(' result?: unknown;');
425
- lines.push(' }): void {');
426
+ lines.push(' }): Promise<void> {');
426
427
  lines.push(' if (this.flowWeaverDebugger) {');
427
- lines.push(' this.flowWeaverDebugger.sendEvent({');
428
+ lines.push(' await this.flowWeaverDebugger.sendEvent({');
428
429
  lines.push(' type: "WORKFLOW_COMPLETED",');
429
430
  lines.push(' ...args,');
430
431
  lines.push(' innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,');
@@ -490,92 +491,6 @@ export function generateInlineRuntime(production, exportClasses = false, outputF
490
491
  }
491
492
  return output;
492
493
  }
493
- /**
494
- * Generates inline WebSocket debug client for auto-detection from env var
495
- * Only included in development mode builds
496
- *
497
- * @param moduleFormat - The module format to use for imports ('esm' or 'cjs')
498
- * @param outputFormat - Output format: 'typescript' (default) or 'javascript' (strips types)
499
- */
500
- export function generateInlineDebugClient(moduleFormat = 'esm', outputFormat = 'typescript') {
501
- const lines = [];
502
- lines.push('// ============================================================================');
503
- lines.push('// Inline Debug Client (auto-created from FLOW_WEAVER_DEBUG env var)');
504
- lines.push('// ============================================================================');
505
- lines.push('');
506
- lines.push('/* eslint-disable @typescript-eslint/no-explicit-any, no-console */');
507
- lines.push('function createFlowWeaverDebugClient(url: string, workflowExportName: string): any {');
508
- lines.push(' let ws: any = null;');
509
- lines.push(' let connected = false;');
510
- lines.push(' let queue: string[] = [];');
511
- lines.push(' const sessionId = Math.random().toString(36).substring(2, 15);');
512
- lines.push('');
513
- lines.push(' const connect = async () => {');
514
- lines.push(' try {');
515
- lines.push(" // Node.js environment - dynamically load 'ws' package");
516
- // Generate format-appropriate import for 'ws' package
517
- if (moduleFormat === 'cjs') {
518
- // CommonJS: use require() directly
519
- lines.push(" const wsModule = require('ws');");
520
- }
521
- else {
522
- // ESM: use dynamic import
523
- lines.push(" const wsModule = await import('ws');");
524
- }
525
- lines.push(' const WS: any = wsModule.default || wsModule;');
526
- lines.push(' ws = new WS(url);');
527
- lines.push('');
528
- lines.push(" ws.on('open', () => {");
529
- lines.push(' connected = true;');
530
- lines.push(' // Send connect message');
531
- lines.push(' ws.send(JSON.stringify({');
532
- lines.push(" type: 'connect',");
533
- lines.push(' sessionId,');
534
- lines.push(' workflowExportName,');
535
- lines.push(' clientInfo: {');
536
- lines.push(' platform: process.platform,');
537
- lines.push(' nodeVersion: process.version,');
538
- lines.push(' pid: process.pid');
539
- lines.push(' }');
540
- lines.push(' }));');
541
- lines.push('');
542
- lines.push(' // Flush queued events');
543
- lines.push(' while (queue.length > 0) {');
544
- lines.push(' const msg = queue.shift();');
545
- lines.push(' if (ws.readyState === 1) ws.send(msg);');
546
- lines.push(' }');
547
- lines.push(' });');
548
- lines.push('');
549
- lines.push(" ws.on('error', () => { connected = false; });");
550
- lines.push(" ws.on('close', () => { connected = false; });");
551
- lines.push(' } catch (err: unknown) {');
552
- lines.push(" // Silently fail if 'ws' package not available");
553
- lines.push(" console.warn('[Flow Weaver] Debug client failed to connect:', err instanceof Error ? err.message : String(err));");
554
- lines.push(' }');
555
- lines.push(' };');
556
- lines.push('');
557
- lines.push(' return {');
558
- lines.push(' sendEvent: (event: unknown) => {');
559
- lines.push(" const message = JSON.stringify({ type: 'event', sessionId, event });");
560
- lines.push(' if (!ws) connect().catch(() => {});');
561
- lines.push(' if (connected && ws.readyState === 1) {');
562
- lines.push(' ws.send(message);');
563
- lines.push(' } else {');
564
- lines.push(' queue.push(message);');
565
- lines.push(' }');
566
- lines.push(' },');
567
- lines.push(' innerFlowInvocation: false,');
568
- lines.push(' sessionId');
569
- lines.push(' };');
570
- lines.push('}');
571
- lines.push('/* eslint-enable @typescript-eslint/no-explicit-any, no-console */');
572
- lines.push('');
573
- const output = lines.join('\n');
574
- if (outputFormat === 'javascript') {
575
- return stripTypeScript(output);
576
- }
577
- return output;
578
- }
579
494
  /**
580
495
  * Generates a standalone runtime module file for multi-workflow bundles.
581
496
  * This exports all runtime types and classes so individual workflow files can import them.
@@ -595,14 +510,6 @@ export function generateStandaloneRuntimeModule(production, moduleFormat = 'esm'
595
510
  const inlineRuntime = generateInlineRuntime(production, true);
596
511
  lines.push(inlineRuntime);
597
512
  lines.push('');
598
- // Include debug client in development mode
599
- if (!production) {
600
- const debugClient = generateInlineDebugClient(moduleFormat);
601
- // Add export to the createFlowWeaverDebugClient function
602
- const exportedDebugClient = debugClient.replace('function createFlowWeaverDebugClient', 'export function createFlowWeaverDebugClient');
603
- lines.push(exportedDebugClient);
604
- lines.push('');
605
- }
606
513
  if (moduleFormat === 'cjs') {
607
514
  // CommonJS exports
608
515
  lines.push('// ============================================================================');
@@ -610,9 +517,6 @@ export function generateStandaloneRuntimeModule(production, moduleFormat = 'esm'
610
517
  lines.push('// ============================================================================');
611
518
  lines.push('');
612
519
  const exports = ['GeneratedExecutionContext', 'CancellationError'];
613
- if (!production) {
614
- exports.push('createFlowWeaverDebugClient', 'TDebugger');
615
- }
616
520
  lines.push(`module.exports = { ${exports.join(', ')} };`);
617
521
  }
618
522
  // For ESM, exports are added via 'export' keyword in the generated code
@@ -9671,7 +9671,7 @@ var VERSION;
9671
9671
  var init_generated_version = __esm({
9672
9672
  "src/generated-version.ts"() {
9673
9673
  "use strict";
9674
- VERSION = "0.21.21";
9674
+ VERSION = "0.22.0";
9675
9675
  }
9676
9676
  });
9677
9677
 
@@ -10261,8 +10261,9 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
10261
10261
  lines.push(``);
10262
10262
  const safeParentId = toValidIdentifier(parentNodeId);
10263
10263
  lines.push(` // Create scoped context for child nodes`);
10264
+ const isAsyncOverrideArg = isAsync2 ? "" : ", false";
10264
10265
  lines.push(
10265
- ` const scopedCtx = ctx.createScope('${parentNodeId}', ${safeParentId}Idx!, '${scopeName}', true);`
10266
+ ` const scopedCtx = ctx.createScope('${parentNodeId}', ${safeParentId}Idx!, '${scopeName}', true${isAsyncOverrideArg});`
10266
10267
  );
10267
10268
  lines.push(``);
10268
10269
  if (scopedOutputPorts.length > 0) {
@@ -10329,7 +10330,7 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
10329
10330
  lines.push(` // Execute: ${child.id} (${child.nodeType})`);
10330
10331
  lines.push(` scopedCtx.checkAborted('${child.id}');`);
10331
10332
  lines.push(` const ${safeChildId}Idx = scopedCtx.addExecution('${child.id}');`);
10332
- lines.push(` if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${child.id}';`);
10333
+ lines.push(` if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${child.id}';`);
10333
10334
  lines.push(` ${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
10334
10335
  lines.push(` nodeTypeName: '${child.nodeType}',`);
10335
10336
  lines.push(` id: '${child.id}',`);
@@ -10373,7 +10374,8 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
10373
10374
  skipPorts: preHandledPorts,
10374
10375
  emitInputEvents: true,
10375
10376
  setCall: childSetCall,
10376
- nodeTypeName: child.nodeType
10377
+ nodeTypeName: child.nodeType,
10378
+ production
10377
10379
  });
10378
10380
  argLines.forEach((line) => lines.push(line));
10379
10381
  if (childNodeType.expression) {
@@ -10441,7 +10443,7 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
10441
10443
  lines.push(``);
10442
10444
  lines.push(` // Extract return values from child outputs`);
10443
10445
  const returnObj = [];
10444
- const getCallAfterMerge = isAsync2 ? "await ctx.getVariable" : "ctx.getVariable";
10446
+ const getCallAfterMerge = isAsync2 ? "await scopedCtx.getVariable" : "scopedCtx.getVariable";
10445
10447
  lines.push(` const scopeExitIdx = ctx.addExecution('${parentNodeId}_scope_exit');`);
10446
10448
  scopedInputPorts.forEach((portName) => {
10447
10449
  const connection = workflow.connections.find((conn) => {
@@ -10460,7 +10462,7 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
10460
10462
  const defaultValue = portName === "success" ? "true" : portName === "failure" ? "false" : "undefined";
10461
10463
  if (isStepPort2) {
10462
10464
  lines.push(
10463
- ` const ${varName} = ctx.hasVariable(${varAddr}) ? ${getCallAfterMerge}(${varAddr}) as ${portType} : ${defaultValue};`
10465
+ ` const ${varName} = scopedCtx.hasVariable(${varAddr}) ? ${getCallAfterMerge}(${varAddr}) as ${portType} : ${defaultValue};`
10464
10466
  );
10465
10467
  } else {
10466
10468
  lines.push(` const ${varName} = ${getCallAfterMerge}(${varAddr}) as ${portType};`);
@@ -10550,7 +10552,8 @@ function buildNodeArgumentsWithContext(opts) {
10550
10552
  emitInputEvents = false,
10551
10553
  setCall = "await ctx.setVariable",
10552
10554
  nodeTypeName,
10553
- bundleMode = false
10555
+ bundleMode = false,
10556
+ production = false
10554
10557
  } = opts;
10555
10558
  const safeId = toValidIdentifier(id);
10556
10559
  const inputConnections = workflow.connections.filter((conn) => conn.to.node === id);
@@ -10648,13 +10651,14 @@ function buildNodeArgumentsWithContext(opts) {
10648
10651
  const portType = mapToTypeScript(portConfig.dataType, portConfig.tsType);
10649
10652
  const needsGuard = portConfig.optional && !isConstSource;
10650
10653
  if (portConfig.dataType === "FUNCTION") {
10654
+ lines.push(`${indent}const __resolveFunction = typeof resolveFunction === 'function' ? resolveFunction : (p: unknown) => ({ fn: typeof p === 'function' ? p : () => { throw new Error('Cannot resolve function reference'); }, source: 'direct' as const });`);
10651
10655
  const rawVarName = `${varName}_raw`;
10652
10656
  if (needsGuard) {
10653
10657
  lines.push(
10654
10658
  `${indent}const ${rawVarName} = ${sourceIdx} !== undefined ? ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) : undefined;`
10655
10659
  );
10656
10660
  lines.push(
10657
- `${indent}const ${varName}_resolved = ${rawVarName} !== undefined ? resolveFunction(${rawVarName}) : undefined;`
10661
+ `${indent}const ${varName}_resolved = ${rawVarName} !== undefined ? __resolveFunction(${rawVarName}) : undefined;`
10658
10662
  );
10659
10663
  lines.push(
10660
10664
  `${indent}const ${varName} = ${varName}_resolved?.fn as ${portType};`
@@ -10664,7 +10668,7 @@ function buildNodeArgumentsWithContext(opts) {
10664
10668
  `${indent}const ${rawVarName} = ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx}${nonNullAssert} });`
10665
10669
  );
10666
10670
  lines.push(
10667
- `${indent}const ${varName}_resolved = resolveFunction(${rawVarName});`
10671
+ `${indent}const ${varName}_resolved = __resolveFunction(${rawVarName});`
10668
10672
  );
10669
10673
  lines.push(
10670
10674
  `${indent}const ${varName} = ${varName}_resolved.fn as ${portType};`
@@ -10721,10 +10725,11 @@ function buildNodeArgumentsWithContext(opts) {
10721
10725
  const ternary = attempts.join(" ?? ");
10722
10726
  const portType = mapToTypeScript(portConfig.dataType, portConfig.tsType);
10723
10727
  if (portConfig.dataType === "FUNCTION") {
10728
+ lines.push(`${indent}const __resolveFunction = typeof resolveFunction === 'function' ? resolveFunction : (p: unknown) => ({ fn: typeof p === 'function' ? p : () => { throw new Error('Cannot resolve function reference'); }, source: 'direct' as const });`);
10724
10729
  const rawVarName = `${varName}_raw`;
10725
10730
  lines.push(`${indent}const ${rawVarName} = ${ternary};`);
10726
10731
  lines.push(
10727
- `${indent}const ${varName}_resolved = ${rawVarName} !== undefined ? resolveFunction(${rawVarName}) : undefined;`
10732
+ `${indent}const ${varName}_resolved = ${rawVarName} !== undefined ? __resolveFunction(${rawVarName}) : undefined;`
10728
10733
  );
10729
10734
  lines.push(
10730
10735
  `${indent}const ${varName} = ${varName}_resolved?.fn as ${portType};`
@@ -10758,7 +10763,7 @@ function buildNodeArgumentsWithContext(opts) {
10758
10763
  } else {
10759
10764
  const portType = mapToTypeScript(portConfig.dataType, portConfig.tsType);
10760
10765
  lines.push(
10761
- `${indent}let ${varName}!: ${portType}; // Required port '${portName}' has no connection`
10766
+ `${indent}let ${varName}: ${portType} = undefined as unknown as ${portType}; // Required port '${portName}' has no connection`
10762
10767
  );
10763
10768
  args.push(varName);
10764
10769
  emitSetEvent();
@@ -10783,7 +10788,7 @@ function buildNodeArgumentsWithContext(opts) {
10783
10788
  );
10784
10789
  return childNodeType?.isAsync === true;
10785
10790
  });
10786
- const scopeIsAsync = isAsync2 || node.isAsync || hasAsyncChild;
10791
+ const scopeIsAsync = node.isAsync || hasAsyncChild;
10787
10792
  const scopeFunctionCode = generateScopeFunctionClosure(
10788
10793
  scopeName,
10789
10794
  id,
@@ -10791,8 +10796,7 @@ function buildNodeArgumentsWithContext(opts) {
10791
10796
  workflow,
10792
10797
  childInstances,
10793
10798
  scopeIsAsync,
10794
- false
10795
- // production mode
10799
+ production
10796
10800
  );
10797
10801
  lines.push(`${indent}const ${scopeFunctionVar} = ${scopeFunctionCode};`);
10798
10802
  args.push(scopeFunctionVar);
@@ -10843,15 +10847,7 @@ function getPullExecutionConfig(instance, nodeType) {
10843
10847
  function generateControlFlowWithExecutionContext(workflow, nodeTypes, isAsync2, production = false, bundleMode = false) {
10844
10848
  const lines = [];
10845
10849
  if (!production) {
10846
- lines.push(` // Use passed debugger or auto-detect from environment variable`);
10847
- lines.push(` const __effectiveDebugger__ = (`);
10848
- lines.push(` typeof __flowWeaverDebugger__ !== 'undefined' ? __flowWeaverDebugger__ :`);
10849
- lines.push(` typeof process !== 'undefined' && process.env.FLOW_WEAVER_DEBUG`);
10850
- lines.push(
10851
- ` ? createFlowWeaverDebugClient(process.env.FLOW_WEAVER_DEBUG, '${workflow.functionName}')`
10852
- );
10853
- lines.push(` : undefined`);
10854
- lines.push(` );`);
10850
+ lines.push(` const __effectiveDebugger__ = typeof __flowWeaverDebugger__ !== 'undefined' ? __flowWeaverDebugger__ : undefined;`);
10855
10851
  lines.push("");
10856
10852
  }
10857
10853
  lines.push(` // Recursion depth protection`);
@@ -10862,6 +10858,9 @@ function generateControlFlowWithExecutionContext(workflow, nodeTypes, isAsync2,
10862
10858
  );
10863
10859
  lines.push(` }`);
10864
10860
  lines.push("");
10861
+ if (!production) {
10862
+ isAsync2 = true;
10863
+ }
10865
10864
  const asyncArg = isAsync2 ? "true" : "false";
10866
10865
  if (production) {
10867
10866
  lines.push(` const ctx = new GeneratedExecutionContext(${asyncArg}, __abortSignal__);`);
@@ -10874,8 +10873,8 @@ function generateControlFlowWithExecutionContext(workflow, nodeTypes, isAsync2,
10874
10873
  if (!production) {
10875
10874
  lines.push(` // Debug controller for step-through debugging and checkpoint/resume`);
10876
10875
  lines.push(` const __ctrl__: TDebugController = (`);
10877
- lines.push(` typeof globalThis !== 'undefined' && (globalThis as any).__fw_debug_controller__`);
10878
- lines.push(` ? (globalThis as any).__fw_debug_controller__`);
10876
+ lines.push(` typeof globalThis !== 'undefined' && (globalThis as unknown as { __fw_debug_controller__?: TDebugController }).__fw_debug_controller__`);
10877
+ lines.push(` ? (globalThis as unknown as { __fw_debug_controller__?: TDebugController }).__fw_debug_controller__`);
10879
10878
  lines.push(` : { beforeNode: () => true, afterNode: () => {} }`);
10880
10879
  lines.push(` );`);
10881
10880
  lines.push("");
@@ -11751,7 +11750,7 @@ function generateBranchingNodeCode(instance, branchNode, workflow, allNodeTypes,
11751
11750
  }
11752
11751
  lines.push(`${indent}${ctxVar}.checkAborted('${instanceId}');`);
11753
11752
  lines.push(`${indent}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
11754
- lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${instanceId}';`);
11753
+ lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${instanceId}';`);
11755
11754
  lines.push(`${indent}${awaitPrefix}${ctxVar}.sendStatusChangedEvent({`);
11756
11755
  lines.push(`${indent} nodeTypeName: '${functionName}',`);
11757
11756
  lines.push(`${indent} id: '${instanceId}',`);
@@ -11781,7 +11780,8 @@ function generateBranchingNodeCode(instance, branchNode, workflow, allNodeTypes,
11781
11780
  emitInputEvents: true,
11782
11781
  setCall,
11783
11782
  nodeTypeName: functionName,
11784
- bundleMode
11783
+ bundleMode,
11784
+ production
11785
11785
  });
11786
11786
  const awaitKeyword = branchNode.isAsync ? "await " : "";
11787
11787
  if (branchNode.expression) {
@@ -12129,7 +12129,7 @@ function generateBranchingNodeCode(instance, branchNode, workflow, allNodeTypes,
12129
12129
  }
12130
12130
  generatedNodes.add(instanceId);
12131
12131
  }
12132
- function generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync2, ctxVar = "ctx", bundleMode = false) {
12132
+ function generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync2, ctxVar = "ctx", bundleMode = false, production = false) {
12133
12133
  const instanceId = instance.id;
12134
12134
  const safeId = toValidIdentifier(instanceId);
12135
12135
  const functionName = nodeType.functionName;
@@ -12144,7 +12144,7 @@ function generatePullNodeWithContext(instance, nodeType, workflow, lines, indent
12144
12144
  lines.push(`${indent} }`);
12145
12145
  lines.push(`${indent} ${ctxVar}.checkAborted('${instanceId}');`);
12146
12146
  lines.push(`${indent} ${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
12147
- lines.push(`${indent} if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${instanceId}';`);
12147
+ lines.push(`${indent} if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${instanceId}';`);
12148
12148
  lines.push(`${indent} ${awaitPrefix}${ctxVar}.sendStatusChangedEvent({`);
12149
12149
  lines.push(`${indent} nodeTypeName: '${functionName}',`);
12150
12150
  lines.push(`${indent} id: '${instanceId}',`);
@@ -12165,7 +12165,8 @@ function generatePullNodeWithContext(instance, nodeType, workflow, lines, indent
12165
12165
  emitInputEvents: true,
12166
12166
  setCall,
12167
12167
  nodeTypeName: functionName,
12168
- bundleMode
12168
+ bundleMode,
12169
+ production
12169
12170
  });
12170
12171
  const resultVar = `${safeId}Result`;
12171
12172
  if (nodeType.variant === "MAP_ITERATOR") {
@@ -12247,7 +12248,7 @@ function generateNodeCallWithContext(instance, nodeType, workflow, _availableVar
12247
12248
  const fullInstance = workflow.instances.find((i) => i.id === instanceId);
12248
12249
  const pullConfig = fullInstance ? getPullExecutionConfig(fullInstance, nodeType) : { enabled: false, triggerPort: "execute" };
12249
12250
  if (pullConfig.enabled) {
12250
- generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync2, ctxVar, bundleMode);
12251
+ generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync2, ctxVar, bundleMode, production);
12251
12252
  return;
12252
12253
  }
12253
12254
  const stepInputs = Object.entries(nodeType.inputs).filter(([portName, portConfig]) => {
@@ -12373,7 +12374,7 @@ function generateNodeCallWithContext(instance, nodeType, workflow, _availableVar
12373
12374
  const awaitPrefix = isAsync2 ? "await " : "";
12374
12375
  lines.push(`${indent}${ctxVar}.checkAborted('${instanceId}');`);
12375
12376
  lines.push(`${indent}${varDecl}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
12376
- lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${instanceId}';`);
12377
+ lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${instanceId}';`);
12377
12378
  lines.push(`${indent}${awaitPrefix}${ctxVar}.sendStatusChangedEvent({`);
12378
12379
  lines.push(`${indent} nodeTypeName: '${functionName}',`);
12379
12380
  lines.push(`${indent} id: '${instanceId}',`);
@@ -12395,7 +12396,8 @@ function generateNodeCallWithContext(instance, nodeType, workflow, _availableVar
12395
12396
  emitInputEvents: true,
12396
12397
  setCall,
12397
12398
  nodeTypeName: functionName,
12398
- bundleMode
12399
+ bundleMode,
12400
+ production
12399
12401
  });
12400
12402
  const resultVar = `${safeId}Result`;
12401
12403
  const awaitKeyword = nodeType.isAsync ? "await " : "";
@@ -17227,15 +17229,18 @@ function generateInlineRuntime(production, exportClasses = false, outputFormat =
17227
17229
  lines.push(" }");
17228
17230
  lines.push("");
17229
17231
  lines.push(
17230
- " createScope(_parentNodeName: string, _parentIndex: number, _scopeName: string, cleanScope: boolean = false): GeneratedExecutionContext {"
17232
+ " createScope(_parentNodeName: string, _parentIndex: number, _scopeName: string, cleanScope: boolean = false, isAsyncOverride?: boolean): GeneratedExecutionContext {"
17233
+ );
17234
+ lines.push(
17235
+ " const effectiveIsAsync = isAsyncOverride !== undefined ? isAsyncOverride : this.isAsync;"
17231
17236
  );
17232
17237
  if (production) {
17233
17238
  lines.push(
17234
- " const scopedContext = new GeneratedExecutionContext(this.isAsync, this.abortSignal);"
17239
+ " const scopedContext = new GeneratedExecutionContext(effectiveIsAsync, this.abortSignal);"
17235
17240
  );
17236
17241
  } else {
17237
17242
  lines.push(
17238
- " const scopedContext = new GeneratedExecutionContext(this.isAsync, this.flowWeaverDebugger, this.abortSignal);"
17243
+ " const scopedContext = new GeneratedExecutionContext(effectiveIsAsync, this.flowWeaverDebugger, this.abortSignal);"
17239
17244
  );
17240
17245
  }
17241
17246
  lines.push(" // For per-port function scopes (cleanScope=true), start with empty variables");
@@ -17314,12 +17319,12 @@ function generateInlineRuntime(production, exportClasses = false, outputFormat =
17314
17319
  lines.push(" }");
17315
17320
  lines.push(" }");
17316
17321
  lines.push("");
17317
- lines.push(" private sendVariableSetEvent(args: {");
17322
+ lines.push(" private async sendVariableSetEvent(args: {");
17318
17323
  lines.push(" identifier: TVariableIdentification;");
17319
17324
  lines.push(" value: unknown;");
17320
- lines.push(" }): void {");
17325
+ lines.push(" }): Promise<void> {");
17321
17326
  lines.push(" if (this.flowWeaverDebugger) {");
17322
- lines.push(" this.flowWeaverDebugger.sendEvent({");
17327
+ lines.push(" await this.flowWeaverDebugger.sendEvent({");
17323
17328
  lines.push(' type: "VARIABLE_SET",');
17324
17329
  lines.push(" ...args,");
17325
17330
  lines.push(" innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,");
@@ -17327,16 +17332,16 @@ function generateInlineRuntime(production, exportClasses = false, outputFormat =
17327
17332
  lines.push(" }");
17328
17333
  lines.push(" }");
17329
17334
  lines.push("");
17330
- lines.push(" sendLogErrorEvent(args: {");
17335
+ lines.push(" async sendLogErrorEvent(args: {");
17331
17336
  lines.push(" nodeTypeName: string;");
17332
17337
  lines.push(" id: string;");
17333
17338
  lines.push(" scope?: string;");
17334
17339
  lines.push(' side?: "start" | "exit";');
17335
17340
  lines.push(" executionIndex: number;");
17336
17341
  lines.push(" error: string;");
17337
- lines.push(" }): void {");
17342
+ lines.push(" }): Promise<void> {");
17338
17343
  lines.push(" if (this.flowWeaverDebugger) {");
17339
- lines.push(" this.flowWeaverDebugger.sendEvent({");
17344
+ lines.push(" await this.flowWeaverDebugger.sendEvent({");
17340
17345
  lines.push(' type: "LOG_ERROR",');
17341
17346
  lines.push(" ...args,");
17342
17347
  lines.push(" innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,");
@@ -17344,13 +17349,13 @@ function generateInlineRuntime(production, exportClasses = false, outputFormat =
17344
17349
  lines.push(" }");
17345
17350
  lines.push(" }");
17346
17351
  lines.push("");
17347
- lines.push(" sendWorkflowCompletedEvent(args: {");
17352
+ lines.push(" async sendWorkflowCompletedEvent(args: {");
17348
17353
  lines.push(" executionIndex: number;");
17349
17354
  lines.push(' status: "SUCCEEDED" | "FAILED" | "CANCELLED";');
17350
17355
  lines.push(" result?: unknown;");
17351
- lines.push(" }): void {");
17356
+ lines.push(" }): Promise<void> {");
17352
17357
  lines.push(" if (this.flowWeaverDebugger) {");
17353
- lines.push(" this.flowWeaverDebugger.sendEvent({");
17358
+ lines.push(" await this.flowWeaverDebugger.sendEvent({");
17354
17359
  lines.push(' type: "WORKFLOW_COMPLETED",');
17355
17360
  lines.push(" ...args,");
17356
17361
  lines.push(" innerFlowInvocation: this.flowWeaverDebugger.innerFlowInvocation,");
@@ -17413,85 +17418,6 @@ function generateInlineRuntime(production, exportClasses = false, outputFormat =
17413
17418
  }
17414
17419
  return output;
17415
17420
  }
17416
- function generateInlineDebugClient(moduleFormat = "esm", outputFormat = "typescript") {
17417
- const lines = [];
17418
- lines.push("// ============================================================================");
17419
- lines.push("// Inline Debug Client (auto-created from FLOW_WEAVER_DEBUG env var)");
17420
- lines.push("// ============================================================================");
17421
- lines.push("");
17422
- lines.push("/* eslint-disable @typescript-eslint/no-explicit-any, no-console */");
17423
- lines.push(
17424
- "function createFlowWeaverDebugClient(url: string, workflowExportName: string): any {"
17425
- );
17426
- lines.push(" let ws: any = null;");
17427
- lines.push(" let connected = false;");
17428
- lines.push(" let queue: string[] = [];");
17429
- lines.push(" const sessionId = Math.random().toString(36).substring(2, 15);");
17430
- lines.push("");
17431
- lines.push(" const connect = async () => {");
17432
- lines.push(" try {");
17433
- lines.push(" // Node.js environment - dynamically load 'ws' package");
17434
- if (moduleFormat === "cjs") {
17435
- lines.push(" const wsModule = require('ws');");
17436
- } else {
17437
- lines.push(" const wsModule = await import('ws');");
17438
- }
17439
- lines.push(" const WS: any = wsModule.default || wsModule;");
17440
- lines.push(" ws = new WS(url);");
17441
- lines.push("");
17442
- lines.push(" ws.on('open', () => {");
17443
- lines.push(" connected = true;");
17444
- lines.push(" // Send connect message");
17445
- lines.push(" ws.send(JSON.stringify({");
17446
- lines.push(" type: 'connect',");
17447
- lines.push(" sessionId,");
17448
- lines.push(" workflowExportName,");
17449
- lines.push(" clientInfo: {");
17450
- lines.push(" platform: process.platform,");
17451
- lines.push(" nodeVersion: process.version,");
17452
- lines.push(" pid: process.pid");
17453
- lines.push(" }");
17454
- lines.push(" }));");
17455
- lines.push("");
17456
- lines.push(" // Flush queued events");
17457
- lines.push(" while (queue.length > 0) {");
17458
- lines.push(" const msg = queue.shift();");
17459
- lines.push(" if (ws.readyState === 1) ws.send(msg);");
17460
- lines.push(" }");
17461
- lines.push(" });");
17462
- lines.push("");
17463
- lines.push(" ws.on('error', () => { connected = false; });");
17464
- lines.push(" ws.on('close', () => { connected = false; });");
17465
- lines.push(" } catch (err: unknown) {");
17466
- lines.push(" // Silently fail if 'ws' package not available");
17467
- lines.push(
17468
- " console.warn('[Flow Weaver] Debug client failed to connect:', err instanceof Error ? err.message : String(err));"
17469
- );
17470
- lines.push(" }");
17471
- lines.push(" };");
17472
- lines.push("");
17473
- lines.push(" return {");
17474
- lines.push(" sendEvent: (event: unknown) => {");
17475
- lines.push(" const message = JSON.stringify({ type: 'event', sessionId, event });");
17476
- lines.push(" if (!ws) connect().catch(() => {});");
17477
- lines.push(" if (connected && ws.readyState === 1) {");
17478
- lines.push(" ws.send(message);");
17479
- lines.push(" } else {");
17480
- lines.push(" queue.push(message);");
17481
- lines.push(" }");
17482
- lines.push(" },");
17483
- lines.push(" innerFlowInvocation: false,");
17484
- lines.push(" sessionId");
17485
- lines.push(" };");
17486
- lines.push("}");
17487
- lines.push("/* eslint-enable @typescript-eslint/no-explicit-any, no-console */");
17488
- lines.push("");
17489
- const output = lines.join("\n");
17490
- if (outputFormat === "javascript") {
17491
- return stripTypeScript(output);
17492
- }
17493
- return output;
17494
- }
17495
17421
  function generateStandaloneRuntimeModule(production, moduleFormat = "esm") {
17496
17422
  const lines = [];
17497
17423
  lines.push("// ============================================================================");
@@ -17502,24 +17428,12 @@ function generateStandaloneRuntimeModule(production, moduleFormat = "esm") {
17502
17428
  const inlineRuntime = generateInlineRuntime(production, true);
17503
17429
  lines.push(inlineRuntime);
17504
17430
  lines.push("");
17505
- if (!production) {
17506
- const debugClient = generateInlineDebugClient(moduleFormat);
17507
- const exportedDebugClient = debugClient.replace(
17508
- "function createFlowWeaverDebugClient",
17509
- "export function createFlowWeaverDebugClient"
17510
- );
17511
- lines.push(exportedDebugClient);
17512
- lines.push("");
17513
- }
17514
17431
  if (moduleFormat === "cjs") {
17515
17432
  lines.push("// ============================================================================");
17516
17433
  lines.push("// Exports");
17517
17434
  lines.push("// ============================================================================");
17518
17435
  lines.push("");
17519
17436
  const exports2 = ["GeneratedExecutionContext", "CancellationError"];
17520
- if (!production) {
17521
- exports2.push("createFlowWeaverDebugClient", "TDebugger");
17522
- }
17523
17437
  lines.push(`module.exports = { ${exports2.join(", ")} };`);
17524
17438
  }
17525
17439
  lines.push("");
@@ -17708,12 +17622,6 @@ function generateCode(ast, options) {
17708
17622
  moduleFormat === "cjs" ? `const { TDebugger } = require('${externalRuntimePath}');` : `import type { TDebugger } from '${externalRuntimePath}';`
17709
17623
  );
17710
17624
  addLine();
17711
- const inlineDebugClient = generateInlineDebugClient(moduleFormat);
17712
- const debugClientLines = inlineDebugClient.split("\n");
17713
- debugClientLines.forEach((line) => {
17714
- lines.push(line);
17715
- addLine();
17716
- });
17717
17625
  }
17718
17626
  lines.push("");
17719
17627
  addLine();
@@ -17726,14 +17634,6 @@ function generateCode(ast, options) {
17726
17634
  });
17727
17635
  lines.push("");
17728
17636
  addLine();
17729
- if (!production) {
17730
- const inlineDebugClient = generateInlineDebugClient(moduleFormat);
17731
- const debugClientLines = inlineDebugClient.split("\n");
17732
- debugClientLines.forEach((line) => {
17733
- lines.push(line);
17734
- addLine();
17735
- });
17736
- }
17737
17637
  }
17738
17638
  if (ast.sourceFile) {
17739
17639
  const extractedTypes = extractTypeDeclarationsFromFile(ast.sourceFile);
@@ -17943,7 +17843,7 @@ function generateCode(ast, options) {
17943
17843
  } catch {
17944
17844
  }
17945
17845
  }
17946
- const asyncKeyword = shouldBeAsync ? "async " : "";
17846
+ const asyncKeyword = shouldBeAsync || !production ? "async " : "";
17947
17847
  const exportKeyword = generateFunctionExportKeyword(moduleFormat);
17948
17848
  lines.push(`${exportKeyword}${asyncKeyword}function ${ast.functionName}(`);
17949
17849
  addLine();
@@ -18040,8 +17940,8 @@ function generateWorkflowFunction(workflow, production, allWorkflows, generatedW
18040
17940
  );
18041
17941
  const startPorts = extractStartPorts(workflow);
18042
17942
  const exitPorts = extractExitPorts(workflow);
18043
- const asyncKeyword = shouldBeAsync ? "async " : "";
18044
- lines.push(`${asyncKeyword}function ${workflow.functionName}(`);
17943
+ const asyncKeyword2 = shouldBeAsync || !production ? "async " : "";
17944
+ lines.push(`${asyncKeyword2}function ${workflow.functionName}(`);
18045
17945
  lines.push(` execute: boolean = true,`);
18046
17946
  const paramEntries = [];
18047
17947
  Object.entries(startPorts).forEach(([portName, portDef]) => {
@@ -19055,7 +18955,7 @@ function generateInPlace(sourceCode, ast, options = {}) {
19055
18955
  }
19056
18956
  const nodesRequireAsync = shouldWorkflowBeAsync(ast, ast.nodeTypes);
19057
18957
  const sourceIsAsync = detectFunctionIsAsync(result, ast.functionName);
19058
- const forceAsync = nodesRequireAsync;
18958
+ const forceAsync = nodesRequireAsync || !production;
19059
18959
  const isAsync2 = forceAsync || sourceIsAsync;
19060
18960
  const asyncSigResult = ensureAsyncKeyword(result, ast.functionName, forceAsync);
19061
18961
  if (asyncSigResult.changed) {
@@ -19089,15 +18989,9 @@ function generateRuntimeSection(functionName, production, moduleFormat = "esm",
19089
18989
  if (!production) {
19090
18990
  lines.push(`import type { TDebugger } from '${externalRuntimePath}';`);
19091
18991
  lines.push("declare const __flowWeaverDebugger__: TDebugger | undefined;");
19092
- lines.push("");
19093
- lines.push(generateInlineDebugClient(moduleFormat));
19094
18992
  }
19095
18993
  } else {
19096
18994
  lines.push(generateInlineRuntime(production));
19097
- if (!production) {
19098
- lines.push("");
19099
- lines.push(generateInlineDebugClient(moduleFormat));
19100
- }
19101
18995
  }
19102
18996
  return lines.join("\n");
19103
18997
  }
@@ -93281,7 +93175,7 @@ function displayInstalledPackage(pkg) {
93281
93175
  // src/cli/index.ts
93282
93176
  init_logger();
93283
93177
  init_error_utils();
93284
- var version2 = true ? "0.21.21" : "0.0.0-dev";
93178
+ var version2 = true ? "0.22.0" : "0.0.0-dev";
93285
93179
  var program2 = new Command();
93286
93180
  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", () => {
93287
93181
  logger.banner(version2);
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.21.21";
1
+ export declare const VERSION = "0.22.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.21.21';
2
+ export const VERSION = '0.22.0';
3
3
  //# sourceMappingURL=generated-version.js.map
@@ -40,6 +40,7 @@ export type TBuildNodeArgsOptions = {
40
40
  setCall?: string;
41
41
  nodeTypeName?: string;
42
42
  bundleMode?: boolean;
43
+ production?: boolean;
43
44
  };
44
45
  /**
45
46
  * Builds the argument list for a node function call by resolving input port values.
@@ -147,7 +147,7 @@ export function buildMergeExpression(sources, strategy) {
147
147
  * @returns Array of argument expressions to pass to the node function
148
148
  */
149
149
  export function buildNodeArgumentsWithContext(opts) {
150
- const { node, workflow, id, lines, indent = ' ', getCall = 'await ctx.getVariable', isAsync = true, instanceParent, skipPorts, emitInputEvents = false, setCall = 'await ctx.setVariable', nodeTypeName, bundleMode = false, } = opts;
150
+ const { node, workflow, id, lines, indent = ' ', getCall = 'await ctx.getVariable', isAsync = true, instanceParent, skipPorts, emitInputEvents = false, setCall = 'await ctx.setVariable', nodeTypeName, bundleMode = false, production = false, } = opts;
151
151
  const safeId = toValidIdentifier(id);
152
152
  const inputConnections = workflow.connections.filter((conn) => conn.to.node === id);
153
153
  const args = [];
@@ -260,15 +260,18 @@ export function buildNodeArgumentsWithContext(opts) {
260
260
  const needsGuard = portConfig.optional && !isConstSource;
261
261
  // For FUNCTION type ports, add resolution step to handle registry IDs
262
262
  if (portConfig.dataType === 'FUNCTION') {
263
+ // Emit inline resolveFunction stub if not already declared in scope
264
+ // This avoids a ReferenceError in self-contained generated code
265
+ lines.push(`${indent}const __resolveFunction = typeof resolveFunction === 'function' ? resolveFunction : (p: unknown) => ({ fn: typeof p === 'function' ? p : () => { throw new Error('Cannot resolve function reference'); }, source: 'direct' as const });`);
263
266
  const rawVarName = `${varName}_raw`;
264
267
  if (needsGuard) {
265
268
  lines.push(`${indent}const ${rawVarName} = ${sourceIdx} !== undefined ? ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) : undefined;`);
266
- lines.push(`${indent}const ${varName}_resolved = ${rawVarName} !== undefined ? resolveFunction(${rawVarName}) : undefined;`);
269
+ lines.push(`${indent}const ${varName}_resolved = ${rawVarName} !== undefined ? __resolveFunction(${rawVarName}) : undefined;`);
267
270
  lines.push(`${indent}const ${varName} = ${varName}_resolved?.fn as ${portType};`);
268
271
  }
269
272
  else {
270
273
  lines.push(`${indent}const ${rawVarName} = ${getCall}({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx}${nonNullAssert} });`);
271
- lines.push(`${indent}const ${varName}_resolved = resolveFunction(${rawVarName});`);
274
+ lines.push(`${indent}const ${varName}_resolved = __resolveFunction(${rawVarName});`);
272
275
  lines.push(`${indent}const ${varName} = ${varName}_resolved.fn as ${portType};`);
273
276
  }
274
277
  }
@@ -326,9 +329,10 @@ export function buildNodeArgumentsWithContext(opts) {
326
329
  const portType = mapToTypeScript(portConfig.dataType, portConfig.tsType);
327
330
  // For FUNCTION type ports, add resolution step to handle registry IDs
328
331
  if (portConfig.dataType === 'FUNCTION') {
332
+ lines.push(`${indent}const __resolveFunction = typeof resolveFunction === 'function' ? resolveFunction : (p: unknown) => ({ fn: typeof p === 'function' ? p : () => { throw new Error('Cannot resolve function reference'); }, source: 'direct' as const });`);
329
333
  const rawVarName = `${varName}_raw`;
330
334
  lines.push(`${indent}const ${rawVarName} = ${ternary};`);
331
- lines.push(`${indent}const ${varName}_resolved = ${rawVarName} !== undefined ? resolveFunction(${rawVarName}) : undefined;`);
335
+ lines.push(`${indent}const ${varName}_resolved = ${rawVarName} !== undefined ? __resolveFunction(${rawVarName}) : undefined;`);
332
336
  lines.push(`${indent}const ${varName} = ${varName}_resolved?.fn as ${portType};`);
333
337
  }
334
338
  else {
@@ -365,7 +369,7 @@ export function buildNodeArgumentsWithContext(opts) {
365
369
  else {
366
370
  // Required port has no connection, expression, or default - use typed undefined fallback
367
371
  const portType = mapToTypeScript(portConfig.dataType, portConfig.tsType);
368
- lines.push(`${indent}let ${varName}!: ${portType}; // Required port '${portName}' has no connection`);
372
+ lines.push(`${indent}let ${varName}: ${portType} = undefined as unknown as ${portType}; // Required port '${portName}' has no connection`);
369
373
  args.push(varName);
370
374
  emitSetEvent();
371
375
  }
@@ -392,18 +396,17 @@ export function buildNodeArgumentsWithContext(opts) {
392
396
  return inst.parent.id === id && inst.parent.scope === scopeName;
393
397
  });
394
398
  // Generate scope function closure
395
- // The scope function's async/sync nature should match the workflow context (isAsync parameter).
396
- // If the workflow is async, the scope function must be async to use await for context operations.
397
- // We also consider node.isAsync for cases where the node type explicitly expects async callbacks.
398
- // PHASE 9 FIX: Also check if any child node in the scope is async - if so, the scope
399
- // function must be async to properly await those child node calls.
399
+ // Scope function async/sync must match what the PARENT NODE expects from its callback.
400
+ // A sync parent node (e.g., forEach) calls the callback synchronously if the scope
401
+ // function is async it returns a Promise, causing `.field` accesses to yield `undefined`.
402
+ // Only make the scope function async if the parent node itself is async or a child is async.
403
+ // Do NOT inherit the workflow-level isAsync flag (which is true in dev mode for debugging).
400
404
  const hasAsyncChild = childInstances.some((child) => {
401
405
  const childNodeType = workflow.nodeTypes?.find((nt) => nt.name === child.nodeType || nt.functionName === child.nodeType);
402
406
  return childNodeType?.isAsync === true;
403
407
  });
404
- const scopeIsAsync = isAsync || node.isAsync || hasAsyncChild;
405
- const scopeFunctionCode = generateScopeFunctionClosure(scopeName, id, node, workflow, childInstances, scopeIsAsync, false // production mode
406
- );
408
+ const scopeIsAsync = node.isAsync || hasAsyncChild;
409
+ const scopeFunctionCode = generateScopeFunctionClosure(scopeName, id, node, workflow, childInstances, scopeIsAsync, production);
407
410
  lines.push(`${indent}const ${scopeFunctionVar} = ${scopeFunctionCode};`);
408
411
  args.push(scopeFunctionVar);
409
412
  });
@@ -416,7 +419,7 @@ export function generateNodeWithExecutionContext(node, workflow, lines, isAsync,
416
419
  const getCall = isAsync ? 'await ctx.getVariable' : 'ctx.getVariable';
417
420
  const setCall = isAsync ? 'await ctx.setVariable' : 'ctx.setVariable';
418
421
  lines.push(`${indent}const ${safeNodeName}Idx = ctx.addExecution('${nodeName}');`);
419
- lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${nodeName}';`);
422
+ lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${nodeName}';`);
420
423
  lines.push(`${indent}${awaitPrefix}ctx.sendStatusChangedEvent({`);
421
424
  lines.push(`${indent} nodeTypeName: '${nodeName}',`);
422
425
  lines.push(`${indent} id: '${nodeName}',`);
@@ -79,6 +79,10 @@ import { mapToTypeScript } from '../type-mappings.js';
79
79
  */
80
80
  export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, workflow, childInstances, isAsync, production) {
81
81
  const lines = [];
82
+ // NOTE: Scope function async/sync is determined by the caller (buildNodeArgumentsWithContext)
83
+ // based on whether the parent node or its children are async. We do NOT force async in dev
84
+ // mode here because the parent node function calls this callback synchronously — if we
85
+ // return a Promise from an async closure, the parent gets Promise objects instead of values.
82
86
  // Extract scoped ports for this scope
83
87
  const scopedOutputPorts = []; // Parameters to the scope function
84
88
  const scopedInputPorts = []; // Return values from the scope function
@@ -112,9 +116,11 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
112
116
  lines.push(``);
113
117
  // Create scoped execution context for isolation
114
118
  // Pass cleanScope=true for per-port function scopes (each call gets fresh variables)
119
+ // When the scope function is sync, override isAsync to false so context ops return values directly
115
120
  const safeParentId = toValidIdentifier(parentNodeId);
116
121
  lines.push(` // Create scoped context for child nodes`);
117
- lines.push(` const scopedCtx = ctx.createScope('${parentNodeId}', ${safeParentId}Idx!, '${scopeName}', true);`);
122
+ const isAsyncOverrideArg = isAsync ? '' : ', false';
123
+ lines.push(` const scopedCtx = ctx.createScope('${parentNodeId}', ${safeParentId}Idx!, '${scopeName}', true${isAsyncOverrideArg});`);
118
124
  lines.push(``);
119
125
  // Set scope parameter values in execution context
120
126
  // These become available to child nodes as outputs from the parent node
@@ -185,7 +191,7 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
185
191
  lines.push(` // Execute: ${child.id} (${child.nodeType})`);
186
192
  lines.push(` scopedCtx.checkAborted('${child.id}');`);
187
193
  lines.push(` const ${safeChildId}Idx = scopedCtx.addExecution('${child.id}');`);
188
- lines.push(` if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${child.id}';`);
194
+ lines.push(` if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${child.id}';`);
189
195
  lines.push(` ${awaitPrefix}scopedCtx.sendStatusChangedEvent({`);
190
196
  lines.push(` nodeTypeName: '${child.nodeType}',`);
191
197
  lines.push(` id: '${child.id}',`);
@@ -232,6 +238,7 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
232
238
  emitInputEvents: true,
233
239
  setCall: childSetCall,
234
240
  nodeTypeName: child.nodeType,
241
+ production,
235
242
  });
236
243
  // Add argument building lines
237
244
  argLines.forEach((line) => lines.push(line));
@@ -304,8 +311,11 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
304
311
  // These are the outputs of child nodes that become the scope function's return value
305
312
  lines.push(` // Extract return values from child outputs`);
306
313
  const returnObj = [];
307
- // After mergeScope, use ctx (parent context) not scopedCtx for all variable access
308
- const getCallAfterMerge = isAsync ? 'await ctx.getVariable' : 'ctx.getVariable';
314
+ // Read return values from scopedCtx (not ctx) because:
315
+ // 1. scopedCtx still has all variables after mergeScope (merge copies, doesn't move)
316
+ // 2. scopedCtx.isAsync matches the scope function's sync/async nature, so getVariable
317
+ // returns values directly (not Promises) when the scope function is sync
318
+ const getCallAfterMerge = isAsync ? 'await scopedCtx.getVariable' : 'scopedCtx.getVariable';
309
319
  // Create per-iteration execution index for scoped exit ports (so each iteration shows separately in UI)
310
320
  // Use ctx (parent context) not scopedCtx so indices accumulate across iterations
311
321
  lines.push(` const scopeExitIdx = ctx.addExecution('${parentNodeId}_scope_exit');`);
@@ -330,7 +340,7 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
330
340
  const isStepPort = portName === 'success' || portName === 'failure';
331
341
  const defaultValue = portName === 'success' ? 'true' : portName === 'failure' ? 'false' : 'undefined';
332
342
  if (isStepPort) {
333
- lines.push(` const ${varName} = ctx.hasVariable(${varAddr}) ? ${getCallAfterMerge}(${varAddr}) as ${portType} : ${defaultValue};`);
343
+ lines.push(` const ${varName} = scopedCtx.hasVariable(${varAddr}) ? ${getCallAfterMerge}(${varAddr}) as ${portType} : ${defaultValue};`);
334
344
  }
335
345
  else {
336
346
  lines.push(` const ${varName} = ${getCallAfterMerge}(${varAddr}) as ${portType};`);
@@ -80,15 +80,9 @@ function getPullExecutionConfig(instance, nodeType) {
80
80
  */
81
81
  export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isAsync, production = false, bundleMode = false) {
82
82
  const lines = [];
83
- // In development mode, determine effective debugger (from parameter or environment)
83
+ // In development mode, use the injected debugger if available
84
84
  if (!production) {
85
- lines.push(` // Use passed debugger or auto-detect from environment variable`);
86
- lines.push(` const __effectiveDebugger__ = (`);
87
- lines.push(` typeof __flowWeaverDebugger__ !== 'undefined' ? __flowWeaverDebugger__ :`);
88
- lines.push(` typeof process !== 'undefined' && process.env.FLOW_WEAVER_DEBUG`);
89
- lines.push(` ? createFlowWeaverDebugClient(process.env.FLOW_WEAVER_DEBUG, '${workflow.functionName}')`);
90
- lines.push(` : undefined`);
91
- lines.push(` );`);
85
+ lines.push(` const __effectiveDebugger__ = typeof __flowWeaverDebugger__ !== 'undefined' ? __flowWeaverDebugger__ : undefined;`);
92
86
  lines.push('');
93
87
  }
94
88
  // Recursion depth protection: prevent infinite recursion in workflows
@@ -101,6 +95,12 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
101
95
  // In development mode, pass the effective debugger (from parameter or environment)
102
96
  // In production mode, omit the debugger parameter
103
97
  // Always pass abort signal for cancellation support
98
+ // In dev mode, always treat as async so the debugger can pause execution
99
+ // at breakpoints (sendStatusChangedEvent must be awaited for this to work).
100
+ // Production mode respects the original isAsync to avoid overhead.
101
+ if (!production) {
102
+ isAsync = true; // eslint-disable-line no-param-reassign
103
+ }
104
104
  const asyncArg = isAsync ? 'true' : 'false';
105
105
  if (production) {
106
106
  lines.push(` const ctx = new GeneratedExecutionContext(${asyncArg}, __abortSignal__);`);
@@ -115,8 +115,8 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
115
115
  if (!production) {
116
116
  lines.push(` // Debug controller for step-through debugging and checkpoint/resume`);
117
117
  lines.push(` const __ctrl__: TDebugController = (`);
118
- lines.push(` typeof globalThis !== 'undefined' && (globalThis as any).__fw_debug_controller__`);
119
- lines.push(` ? (globalThis as any).__fw_debug_controller__`);
118
+ lines.push(` typeof globalThis !== 'undefined' && (globalThis as unknown as { __fw_debug_controller__?: TDebugController }).__fw_debug_controller__`);
119
+ lines.push(` ? (globalThis as unknown as { __fw_debug_controller__?: TDebugController }).__fw_debug_controller__`);
120
120
  lines.push(` : { beforeNode: () => true, afterNode: () => {} }`);
121
121
  lines.push(` );`);
122
122
  lines.push('');
@@ -958,7 +958,7 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
958
958
  }
959
959
  lines.push(`${indent}${ctxVar}.checkAborted('${instanceId}');`);
960
960
  lines.push(`${indent}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
961
- lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${instanceId}';`);
961
+ lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${instanceId}';`);
962
962
  lines.push(`${indent}${awaitPrefix}${ctxVar}.sendStatusChangedEvent({`);
963
963
  lines.push(`${indent} nodeTypeName: '${functionName}',`);
964
964
  lines.push(`${indent} id: '${instanceId}',`);
@@ -991,6 +991,7 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
991
991
  setCall,
992
992
  nodeTypeName: functionName,
993
993
  bundleMode,
994
+ production,
994
995
  });
995
996
  const awaitKeyword = branchNode.isAsync ? 'await ' : '';
996
997
  if (branchNode.expression) {
@@ -1262,7 +1263,7 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
1262
1263
  generatedNodes.add(instanceId);
1263
1264
  }
1264
1265
  function generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync, ctxVar = 'ctx', // Context variable name (for scoped contexts)
1265
- bundleMode = false) {
1266
+ bundleMode = false, production = false) {
1266
1267
  const instanceId = instance.id;
1267
1268
  const safeId = toValidIdentifier(instanceId);
1268
1269
  const functionName = nodeType.functionName;
@@ -1283,7 +1284,7 @@ bundleMode = false) {
1283
1284
  lines.push(`${indent} }`);
1284
1285
  lines.push(`${indent} ${ctxVar}.checkAborted('${instanceId}');`);
1285
1286
  lines.push(`${indent} ${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
1286
- lines.push(`${indent} if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${instanceId}';`);
1287
+ lines.push(`${indent} if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${instanceId}';`);
1287
1288
  lines.push(`${indent} ${awaitPrefix}${ctxVar}.sendStatusChangedEvent({`);
1288
1289
  lines.push(`${indent} nodeTypeName: '${functionName}',`);
1289
1290
  lines.push(`${indent} id: '${instanceId}',`);
@@ -1306,6 +1307,7 @@ bundleMode = false) {
1306
1307
  setCall,
1307
1308
  nodeTypeName: functionName,
1308
1309
  bundleMode,
1310
+ production,
1309
1311
  });
1310
1312
  const resultVar = `${safeId}Result`;
1311
1313
  // Check if this is a workflow call (IMPORTED_WORKFLOW or WORKFLOW variant)
@@ -1407,7 +1409,7 @@ production = false // When false, emit debug controller hooks (beforeNode/afterN
1407
1409
  : { enabled: false, triggerPort: 'execute' };
1408
1410
  // If this is a pull execution node, wrap it in a lazy function
1409
1411
  if (pullConfig.enabled) {
1410
- generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync, ctxVar, bundleMode);
1412
+ generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync, ctxVar, bundleMode, production);
1411
1413
  return;
1412
1414
  }
1413
1415
  const stepInputs = Object.entries(nodeType.inputs).filter(([portName, portConfig]) => {
@@ -1556,7 +1558,7 @@ production = false // When false, emit debug controller hooks (beforeNode/afterN
1556
1558
  const awaitPrefix = isAsync ? 'await ' : '';
1557
1559
  lines.push(`${indent}${ctxVar}.checkAborted('${instanceId}');`);
1558
1560
  lines.push(`${indent}${varDecl}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
1559
- lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${instanceId}';`);
1561
+ lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as unknown as { __fw_current_node_id__?: string }).__fw_current_node_id__ = '${instanceId}';`);
1560
1562
  lines.push(`${indent}${awaitPrefix}${ctxVar}.sendStatusChangedEvent({`);
1561
1563
  lines.push(`${indent} nodeTypeName: '${functionName}',`);
1562
1564
  lines.push(`${indent} id: '${instanceId}',`);
@@ -1579,6 +1581,7 @@ production = false // When false, emit debug controller hooks (beforeNode/afterN
1579
1581
  setCall,
1580
1582
  nodeTypeName: functionName,
1581
1583
  bundleMode,
1584
+ production,
1582
1585
  });
1583
1586
  const resultVar = `${safeId}Result`;
1584
1587
  const awaitKeyword = nodeType.isAsync ? 'await ' : '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.21.21",
3
+ "version": "0.22.0",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",