@synergenius/flow-weaver 0.10.12 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/generate-in-place.js +5 -4
- package/dist/api/inline-runtime.js +42 -0
- package/dist/cli/commands/context.d.ts +13 -0
- package/dist/cli/commands/context.js +53 -0
- package/dist/cli/commands/run.d.ts +8 -0
- package/dist/cli/commands/run.js +396 -4
- package/dist/cli/flow-weaver.mjs +2115 -499
- package/dist/cli/index.js +24 -0
- package/dist/context/index.d.ts +30 -0
- package/dist/context/index.js +182 -0
- package/dist/doc-metadata/extractors/mcp-tools.js +229 -0
- package/dist/doc-metadata/types.d.ts +1 -1
- package/dist/generator/unified.js +112 -35
- package/dist/mcp/debug-session.d.ts +30 -0
- package/dist/mcp/debug-session.js +25 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/server.js +4 -0
- package/dist/mcp/tools-context.d.ts +3 -0
- package/dist/mcp/tools-context.js +51 -0
- package/dist/mcp/tools-debug.d.ts +3 -0
- package/dist/mcp/tools-debug.js +451 -0
- package/dist/mcp/workflow-executor.d.ts +2 -0
- package/dist/mcp/workflow-executor.js +12 -2
- package/dist/runtime/ExecutionContext.d.ts +19 -0
- package/dist/runtime/ExecutionContext.js +43 -0
- package/dist/runtime/checkpoint.d.ts +84 -0
- package/dist/runtime/checkpoint.js +225 -0
- package/dist/runtime/debug-controller.d.ts +110 -0
- package/dist/runtime/debug-controller.js +247 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +2 -0
- package/docs/reference/cli-reference.md +42 -2
- package/docs/reference/debugging.md +152 -5
- package/package.json +1 -1
|
@@ -109,6 +109,18 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
109
109
|
lines.push(` const ctx = new GeneratedExecutionContext(${asyncArg}, __effectiveDebugger__, __abortSignal__);`);
|
|
110
110
|
}
|
|
111
111
|
lines.push('');
|
|
112
|
+
// Debug controller: step-through debugging and checkpoint/resume
|
|
113
|
+
// In production mode, no controller is emitted. In dev mode, the controller
|
|
114
|
+
// resolves from globalThis (injected by executor) or falls back to a no-op.
|
|
115
|
+
if (!production) {
|
|
116
|
+
lines.push(` // Debug controller for step-through debugging and checkpoint/resume`);
|
|
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__`);
|
|
120
|
+
lines.push(` : { beforeNode: () => true, afterNode: () => {} }`);
|
|
121
|
+
lines.push(` );`);
|
|
122
|
+
lines.push('');
|
|
123
|
+
}
|
|
112
124
|
lines.push(` const startIdx = ctx.addExecution('${RESERVED_NODE_NAMES.START}');`);
|
|
113
125
|
Object.keys(extractStartPorts(workflow)).forEach((portName) => {
|
|
114
126
|
const setCall = isAsync ? `await ctx.setVariable` : `ctx.setVariable`;
|
|
@@ -335,7 +347,7 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
335
347
|
const group = parallelGroupOf.get(instanceId);
|
|
336
348
|
const ungeneratedGroup = group.filter((id) => !generatedNodes.has(id));
|
|
337
349
|
if (ungeneratedGroup.length >= 2) {
|
|
338
|
-
generateParallelGroupWithContext(ungeneratedGroup, workflow, nodeTypes, availableVars, lines, generatedNodes, ' ', isAsync, 'ctx', bundleMode, branchingNodes);
|
|
350
|
+
generateParallelGroupWithContext(ungeneratedGroup, workflow, nodeTypes, availableVars, lines, generatedNodes, ' ', isAsync, 'ctx', bundleMode, branchingNodes, production);
|
|
339
351
|
// Generate scoped children for each parallel node
|
|
340
352
|
for (const parallelNodeId of ungeneratedGroup) {
|
|
341
353
|
const inst = workflow.instances.find((i) => i.id === parallelNodeId);
|
|
@@ -344,7 +356,7 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
344
356
|
const nt = nodeTypes.find((n) => n.name === inst.nodeType || n.functionName === inst.nodeType);
|
|
345
357
|
if (!nt)
|
|
346
358
|
continue;
|
|
347
|
-
generateScopedChildrenExecution(inst, nt, workflow, nodeTypes, generatedNodes, availableVars, lines, ' ', branchingNodes, branchRegions, isAsync, bundleMode);
|
|
359
|
+
generateScopedChildrenExecution(inst, nt, workflow, nodeTypes, generatedNodes, availableVars, lines, ' ', branchingNodes, branchRegions, isAsync, bundleMode, production);
|
|
348
360
|
}
|
|
349
361
|
return;
|
|
350
362
|
}
|
|
@@ -377,7 +389,7 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
377
389
|
chainNeedsClose = true;
|
|
378
390
|
}
|
|
379
391
|
}
|
|
380
|
-
generateBranchingChainCode(branchingChains.get(instanceId), workflow, nodeTypes, branchingNodes, branchRegions, availableVars, generatedNodes, lines, chainIndent, isAsync, 'ctx', bundleMode, branchingNodesNeedingSuccessFlag);
|
|
392
|
+
generateBranchingChainCode(branchingChains.get(instanceId), workflow, nodeTypes, branchingNodes, branchRegions, availableVars, generatedNodes, lines, chainIndent, isAsync, 'ctx', bundleMode, branchingNodesNeedingSuccessFlag, production);
|
|
381
393
|
if (chainNeedsClose) {
|
|
382
394
|
lines.push(` }`);
|
|
383
395
|
}
|
|
@@ -404,7 +416,7 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
404
416
|
branchNeedsClose = true;
|
|
405
417
|
}
|
|
406
418
|
}
|
|
407
|
-
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, branchRegions.get(instanceId), availableVars, generatedNodes, lines, branchIndent, false, branchingNodes, branchRegions, isAsync, 'ctx', bundleMode, new Set(), branchingNodesNeedingSuccessFlag.has(instanceId));
|
|
419
|
+
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, branchRegions.get(instanceId), availableVars, generatedNodes, lines, branchIndent, false, branchingNodes, branchRegions, isAsync, 'ctx', bundleMode, new Set(), branchingNodesNeedingSuccessFlag.has(instanceId), production);
|
|
408
420
|
if (branchNeedsClose) {
|
|
409
421
|
lines.push(` }`);
|
|
410
422
|
}
|
|
@@ -412,7 +424,7 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
412
424
|
region.successNodes.forEach((n) => generatedNodes.add(n));
|
|
413
425
|
region.failureNodes.forEach((n) => generatedNodes.add(n));
|
|
414
426
|
// Check if this node creates a scope and generate scoped children
|
|
415
|
-
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, ' ', branchingNodes, branchRegions, isAsync, bundleMode);
|
|
427
|
+
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, ' ', branchingNodes, branchRegions, isAsync, bundleMode, production);
|
|
416
428
|
}
|
|
417
429
|
else {
|
|
418
430
|
const belongsToBranch = Array.from(branchRegions.values()).some((region) => region.successNodes.has(instanceId) || region.failureNodes.has(instanceId));
|
|
@@ -425,11 +437,11 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
425
437
|
generateNodeCallWithContext(instance, nodeType, workflow, availableVars, lines, nodeTypes, ' ', isAsync, nodeUseConst, undefined, // instanceParent
|
|
426
438
|
'ctx', // ctxVar
|
|
427
439
|
bundleMode, false, // skipExecuteGuard
|
|
428
|
-
branchingNodes // for port-aware STEP guards
|
|
429
|
-
);
|
|
440
|
+
branchingNodes, // for port-aware STEP guards
|
|
441
|
+
production);
|
|
430
442
|
generatedNodes.add(instanceId);
|
|
431
443
|
// Check if this node creates a scope and generate scoped children
|
|
432
|
-
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, ' ', branchingNodes, branchRegions, isAsync, bundleMode);
|
|
444
|
+
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, ' ', branchingNodes, branchRegions, isAsync, bundleMode, production);
|
|
433
445
|
}
|
|
434
446
|
}
|
|
435
447
|
});
|
|
@@ -578,7 +590,7 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
|
|
|
578
590
|
/**
|
|
579
591
|
* Helper function to generate scoped children execution for nodes that create scopes
|
|
580
592
|
*/
|
|
581
|
-
function generateScopedChildrenExecution(parentInstance, parentNodeType, workflow, allNodeTypes, generatedNodes, availableVars, lines, indent, branchingNodes, branchRegions, isAsync, bundleMode = false) {
|
|
593
|
+
function generateScopedChildrenExecution(parentInstance, parentNodeType, workflow, allNodeTypes, generatedNodes, availableVars, lines, indent, branchingNodes, branchRegions, isAsync, bundleMode = false, production = false) {
|
|
582
594
|
// Check if this node creates a scope
|
|
583
595
|
if (!parentNodeType.scope)
|
|
584
596
|
return;
|
|
@@ -631,7 +643,7 @@ function generateScopedChildrenExecution(parentInstance, parentNodeType, workflo
|
|
|
631
643
|
// Check if this child is a branching node
|
|
632
644
|
if (branchingNodes.has(childInstanceId)) {
|
|
633
645
|
generateBranchingNodeCode(childInstance, childNodeType, workflow, allNodeTypes, branchRegions.get(childInstanceId), availableVars, generatedNodes, lines, indent, false, branchingNodes, branchRegions, isAsync, scopedCtxVar, // Pass scoped context name
|
|
634
|
-
bundleMode);
|
|
646
|
+
bundleMode, new Set(), false, production);
|
|
635
647
|
const region = branchRegions.get(childInstanceId);
|
|
636
648
|
region.successNodes.forEach((n) => generatedNodes.add(n));
|
|
637
649
|
region.failureNodes.forEach((n) => generatedNodes.add(n));
|
|
@@ -640,7 +652,9 @@ function generateScopedChildrenExecution(parentInstance, parentNodeType, workflo
|
|
|
640
652
|
generateNodeCallWithContext(childInstance, childNodeType, workflow, availableVars, lines, allNodeTypes, indent, isAsync, false, // useConst = false - scoped children need let (referenced outside scope block)
|
|
641
653
|
parentInstance.id, // instanceParent - parent node is const, no ! needed when referencing it
|
|
642
654
|
scopedCtxVar, // Pass scoped context name
|
|
643
|
-
bundleMode
|
|
655
|
+
bundleMode, false, // skipExecuteGuard
|
|
656
|
+
new Set(), // branchingNodes
|
|
657
|
+
production);
|
|
644
658
|
}
|
|
645
659
|
generatedNodes.add(childInstanceId);
|
|
646
660
|
});
|
|
@@ -655,7 +669,7 @@ function generateScopedChildrenExecution(parentInstance, parentNodeType, workflo
|
|
|
655
669
|
* Each node's execution code is wrapped in an async IIFE inside Promise.all.
|
|
656
670
|
* The outer `let` variables for execution indices are assigned inside the IIFEs.
|
|
657
671
|
*/
|
|
658
|
-
function generateParallelGroupWithContext(nodeIds, workflow, nodeTypes, availableVars, lines, generatedNodes, indent, isAsync, ctxVar, bundleMode, branchingNodes) {
|
|
672
|
+
function generateParallelGroupWithContext(nodeIds, workflow, nodeTypes, availableVars, lines, generatedNodes, indent, isAsync, ctxVar, bundleMode, branchingNodes, production = false) {
|
|
659
673
|
// Collect code buffers for each node
|
|
660
674
|
const nodeBuffers = [];
|
|
661
675
|
for (const nodeId of nodeIds) {
|
|
@@ -668,7 +682,7 @@ function generateParallelGroupWithContext(nodeIds, workflow, nodeTypes, availabl
|
|
|
668
682
|
const nodeLines = [];
|
|
669
683
|
generateNodeCallWithContext(instance, nodeType, workflow, availableVars, nodeLines, nodeTypes, `${indent} `, // indent for inside the async IIFE
|
|
670
684
|
isAsync, false, // useConst = false — outer let declarations
|
|
671
|
-
undefined, ctxVar, bundleMode, false, branchingNodes);
|
|
685
|
+
undefined, ctxVar, bundleMode, false, branchingNodes, production);
|
|
672
686
|
nodeBuffers.push({ id: nodeId, lines: nodeLines });
|
|
673
687
|
}
|
|
674
688
|
// Fallback: if only 0-1 nodes remain, emit directly without Promise.all
|
|
@@ -816,7 +830,7 @@ function buildStepSourceCondition(sourceNode, sourcePort, branchingNodes) {
|
|
|
816
830
|
* if (A_success) { B code } else { CANCELLED for B,C and regions }
|
|
817
831
|
* if (A_success && B_success) { C code } else { CANCELLED for C and regions }
|
|
818
832
|
*/
|
|
819
|
-
function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes, branchRegions, availableVars, generatedNodes, lines, indent, isAsync, ctxVar, bundleMode, forceTrackSuccessNodes = new Set()) {
|
|
833
|
+
function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes, branchRegions, availableVars, generatedNodes, lines, indent, isAsync, ctxVar, bundleMode, forceTrackSuccessNodes = new Set(), production = false) {
|
|
820
834
|
// Pre-declare success flags for all non-last chain nodes so they're
|
|
821
835
|
// accessible across guard blocks (avoiding let-in-block scoping issues).
|
|
822
836
|
// Also pre-declare for the last node if promoted nodes depend on its _success flag.
|
|
@@ -860,10 +874,10 @@ function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes,
|
|
|
860
874
|
lines.push(`${indent}if (${guardCondition}) {`);
|
|
861
875
|
}
|
|
862
876
|
const nodeIndent = hasGuard ? indent + ' ' : indent;
|
|
863
|
-
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, effectiveRegion, availableVars, generatedNodes, lines, nodeIndent, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, preDeclaredFlags, !isLast || forceTrackSuccessNodes.has(chain[i]) // forceTrackSuccess for non-last chain nodes or nodes with promoted dependents
|
|
864
|
-
);
|
|
877
|
+
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, effectiveRegion, availableVars, generatedNodes, lines, nodeIndent, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, preDeclaredFlags, !isLast || forceTrackSuccessNodes.has(chain[i]), // forceTrackSuccess for non-last chain nodes or nodes with promoted dependents
|
|
878
|
+
production);
|
|
865
879
|
// Generate scoped children for this chain node
|
|
866
|
-
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, nodeIndent, branchingNodes, branchRegions, isAsync, bundleMode);
|
|
880
|
+
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, nodeIndent, branchingNodes, branchRegions, isAsync, bundleMode, production);
|
|
867
881
|
if (hasGuard) {
|
|
868
882
|
lines.push(`${indent}} else {`);
|
|
869
883
|
// Emit CANCELLED for this node and all remaining chain nodes + their regions
|
|
@@ -890,10 +904,29 @@ function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes,
|
|
|
890
904
|
}
|
|
891
905
|
function generateBranchingNodeCode(instance, branchNode, workflow, allNodeTypes, region, availableVars, generatedNodes, lines, indent, _generateReturns = true, // DEPRECATED: always false, kept for signature compat
|
|
892
906
|
branchingNodes, branchRegions, isAsync, ctxVar = 'ctx', // Context variable name (for scoped contexts)
|
|
893
|
-
bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = false) {
|
|
907
|
+
bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = false, production = false) {
|
|
894
908
|
const instanceId = instance.id;
|
|
895
909
|
const safeId = toValidIdentifier(instanceId);
|
|
896
910
|
const functionName = branchNode.functionName;
|
|
911
|
+
// Debug controller: beforeNode hook for branching nodes
|
|
912
|
+
const emitDebugHooks = !production;
|
|
913
|
+
const outerIndent = indent;
|
|
914
|
+
// Only declare success flag if there are downstream nodes
|
|
915
|
+
const hasSuccessDownstream = region.successNodes.size > 0;
|
|
916
|
+
const hasFailureDownstream = region.failureNodes.size > 0;
|
|
917
|
+
const hasDownstream = hasSuccessDownstream || hasFailureDownstream;
|
|
918
|
+
// Track success flag when there are downstream nodes OR when chain code needs it
|
|
919
|
+
const trackSuccess = hasDownstream || forceTrackSuccess;
|
|
920
|
+
if (emitDebugHooks) {
|
|
921
|
+
// Hoist success flag declaration before the if block so it remains in scope
|
|
922
|
+
// for downstream branching code that runs after the if/else.
|
|
923
|
+
if (trackSuccess && !preDeclaredSuccessFlags.has(safeId)) {
|
|
924
|
+
lines.push(`${indent}let ${safeId}_success = false;`);
|
|
925
|
+
}
|
|
926
|
+
const awaitHook = isAsync ? 'await ' : '';
|
|
927
|
+
lines.push(`${indent}if (${awaitHook}__ctrl__.beforeNode('${instanceId}', ${ctxVar})) {`);
|
|
928
|
+
indent = `${indent} `;
|
|
929
|
+
}
|
|
897
930
|
lines.push(`${indent}${ctxVar}.checkAborted('${instanceId}');`);
|
|
898
931
|
lines.push(`${indent}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
899
932
|
lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${instanceId}';`);
|
|
@@ -904,15 +937,9 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
904
937
|
lines.push(`${indent} status: 'RUNNING',`);
|
|
905
938
|
lines.push(`${indent}});`);
|
|
906
939
|
lines.push('');
|
|
907
|
-
// Only declare success flag if there are downstream nodes
|
|
908
|
-
const hasSuccessDownstream = region.successNodes.size > 0;
|
|
909
|
-
const hasFailureDownstream = region.failureNodes.size > 0;
|
|
910
|
-
const hasDownstream = hasSuccessDownstream || hasFailureDownstream;
|
|
911
|
-
// Track success flag when there are downstream nodes OR when chain code needs it
|
|
912
|
-
const trackSuccess = hasDownstream || forceTrackSuccess;
|
|
913
940
|
if (trackSuccess) {
|
|
914
|
-
if (preDeclaredSuccessFlags.has(safeId)) {
|
|
915
|
-
// Flag was pre-declared by chain code —
|
|
941
|
+
if (preDeclaredSuccessFlags.has(safeId) || emitDebugHooks) {
|
|
942
|
+
// Flag was pre-declared (by chain code or hoisted for debug hooks) — assignment only
|
|
916
943
|
lines.push(`${indent}${safeId}_success = false;`);
|
|
917
944
|
}
|
|
918
945
|
else {
|
|
@@ -1051,6 +1078,11 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
1051
1078
|
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1052
1079
|
lines.push(`${indent} status: 'SUCCEEDED',`);
|
|
1053
1080
|
lines.push(`${indent} });`);
|
|
1081
|
+
// Debug controller: afterNode hook for branching nodes
|
|
1082
|
+
if (emitDebugHooks) {
|
|
1083
|
+
const awaitHook = isAsync ? 'await ' : '';
|
|
1084
|
+
lines.push(`${indent} ${awaitHook}__ctrl__.afterNode('${instanceId}', ${ctxVar});`);
|
|
1085
|
+
}
|
|
1054
1086
|
// Use onSuccess from result to determine control flow
|
|
1055
1087
|
// For expression nodes, onSuccess is always true here (catch handles failure)
|
|
1056
1088
|
if (trackSuccess) {
|
|
@@ -1087,6 +1119,20 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
1087
1119
|
// Re-throw the error to propagate it up (important for recursive workflows)
|
|
1088
1120
|
lines.push(`${indent} throw error;`);
|
|
1089
1121
|
lines.push(`${indent}}`);
|
|
1122
|
+
// Close the debug controller beforeNode if block for the node's own execution.
|
|
1123
|
+
// The downstream branching must be outside the if block so it runs even when
|
|
1124
|
+
// the node is skipped (checkpoint resume).
|
|
1125
|
+
if (emitDebugHooks) {
|
|
1126
|
+
lines.push(`${outerIndent}} else {`);
|
|
1127
|
+
// Node was skipped (checkpoint resume). Controller already restored outputs
|
|
1128
|
+
// into ctx, but we need local vars for downstream branching.
|
|
1129
|
+
lines.push(`${outerIndent} ${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
1130
|
+
if (trackSuccess) {
|
|
1131
|
+
lines.push(`${outerIndent} ${safeId}_success = true;`);
|
|
1132
|
+
}
|
|
1133
|
+
lines.push(`${outerIndent}}`);
|
|
1134
|
+
indent = outerIndent; // Restore indent level for downstream code
|
|
1135
|
+
}
|
|
1090
1136
|
lines.push('');
|
|
1091
1137
|
// Only generate if/else if there are downstream nodes
|
|
1092
1138
|
if (hasDownstream) {
|
|
@@ -1112,7 +1158,7 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
1112
1158
|
return;
|
|
1113
1159
|
if (branchingNodes.has(instanceId)) {
|
|
1114
1160
|
const nestedRegion = branchRegions.get(instanceId);
|
|
1115
|
-
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, successVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode);
|
|
1161
|
+
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, successVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, new Set(), false, production);
|
|
1116
1162
|
successExecutedNodes.push(instanceId);
|
|
1117
1163
|
nestedRegion.successNodes.forEach((n) => successExecutedNodes.push(n));
|
|
1118
1164
|
nestedRegion.failureNodes.forEach((n) => successExecutedNodes.push(n));
|
|
@@ -1120,8 +1166,8 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
1120
1166
|
else {
|
|
1121
1167
|
generateNodeCallWithContext(inst, nodeType, workflow, successVars, lines, allNodeTypes, `${indent} `, isAsync, false, // useConst
|
|
1122
1168
|
undefined, // instanceParent
|
|
1123
|
-
ctxVar, bundleMode, true // skipExecuteGuard — inside branch, execute is guaranteed by if/else
|
|
1124
|
-
);
|
|
1169
|
+
ctxVar, bundleMode, true, // skipExecuteGuard — inside branch, execute is guaranteed by if/else
|
|
1170
|
+
branchingNodes, production);
|
|
1125
1171
|
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
1126
1172
|
successVars.set(`${instanceId}.${portName}`, `${toValidIdentifier(instanceId)}Result.${portName}`);
|
|
1127
1173
|
});
|
|
@@ -1153,7 +1199,7 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
1153
1199
|
return;
|
|
1154
1200
|
if (branchingNodes.has(instanceId)) {
|
|
1155
1201
|
const nestedRegion = branchRegions.get(instanceId);
|
|
1156
|
-
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, failureVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode);
|
|
1202
|
+
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, failureVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, new Set(), false, production);
|
|
1157
1203
|
failureExecutedNodes.push(instanceId);
|
|
1158
1204
|
nestedRegion.successNodes.forEach((n) => failureExecutedNodes.push(n));
|
|
1159
1205
|
nestedRegion.failureNodes.forEach((n) => failureExecutedNodes.push(n));
|
|
@@ -1161,8 +1207,8 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
|
|
|
1161
1207
|
else {
|
|
1162
1208
|
generateNodeCallWithContext(inst, nodeType, workflow, failureVars, lines, allNodeTypes, `${indent} `, isAsync, false, // useConst
|
|
1163
1209
|
undefined, // instanceParent
|
|
1164
|
-
ctxVar, bundleMode, true // skipExecuteGuard — inside branch, execute is guaranteed by if/else
|
|
1165
|
-
);
|
|
1210
|
+
ctxVar, bundleMode, true, // skipExecuteGuard — inside branch, execute is guaranteed by if/else
|
|
1211
|
+
branchingNodes, production);
|
|
1166
1212
|
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
1167
1213
|
failureVars.set(`${instanceId}.${portName}`, `${toValidIdentifier(instanceId)}Result.${portName}`);
|
|
1168
1214
|
});
|
|
@@ -1318,7 +1364,8 @@ instanceParent, // Parent node ID for scope children (parent is const, no ! need
|
|
|
1318
1364
|
ctxVar = 'ctx', // Context variable name (for scoped contexts)
|
|
1319
1365
|
bundleMode = false, // Bundle mode uses params object pattern for wrapper functions
|
|
1320
1366
|
skipExecuteGuard = false, // Skip execute port STEP guard (for nodes inside branch blocks)
|
|
1321
|
-
branchingNodes = new Set() // Branching nodes set for port-aware STEP guards
|
|
1367
|
+
branchingNodes = new Set(), // Branching nodes set for port-aware STEP guards
|
|
1368
|
+
production = false // When false, emit debug controller hooks (beforeNode/afterNode)
|
|
1322
1369
|
) {
|
|
1323
1370
|
const instanceId = instance.id;
|
|
1324
1371
|
const safeId = toValidIdentifier(instanceId);
|
|
@@ -1458,7 +1505,24 @@ branchingNodes = new Set() // Branching nodes set for port-aware STEP guards
|
|
|
1458
1505
|
}
|
|
1459
1506
|
}
|
|
1460
1507
|
}
|
|
1461
|
-
|
|
1508
|
+
// Debug controller: beforeNode hook (step-through / checkpoint resume)
|
|
1509
|
+
// When enabled, wraps the entire node execution in if(await __ctrl__.beforeNode(...))
|
|
1510
|
+
// so checkpoint resume can skip already-completed nodes.
|
|
1511
|
+
const emitDebugHooks = !production;
|
|
1512
|
+
const outerIndent = indent; // preserve for closing brace
|
|
1513
|
+
if (emitDebugHooks) {
|
|
1514
|
+
// When useConst=true, the Idx variable would normally be declared with const
|
|
1515
|
+
// inside the node execution block. Since debug hooks wrap that in if/else,
|
|
1516
|
+
// we must hoist the declaration before the if block to keep it in scope.
|
|
1517
|
+
if (useConst) {
|
|
1518
|
+
lines.push(`${indent}let ${safeId}Idx: number;`);
|
|
1519
|
+
}
|
|
1520
|
+
const awaitHook = isAsync ? 'await ' : '';
|
|
1521
|
+
lines.push(`${indent}if (${awaitHook}__ctrl__.beforeNode('${instanceId}', ${ctxVar})) {`);
|
|
1522
|
+
indent = `${indent} `;
|
|
1523
|
+
}
|
|
1524
|
+
// When debug hooks hoist the declaration, we use assignment only inside the block.
|
|
1525
|
+
const varDecl = (useConst && !emitDebugHooks) ? 'const ' : '';
|
|
1462
1526
|
lines.push(`${indent}${ctxVar}.checkAborted('${instanceId}');`);
|
|
1463
1527
|
lines.push(`${indent}${varDecl}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
1464
1528
|
lines.push(`${indent}if (typeof globalThis !== 'undefined') (globalThis as any).__fw_current_node_id__ = '${instanceId}';`);
|
|
@@ -1628,6 +1692,11 @@ branchingNodes = new Set() // Branching nodes set for port-aware STEP guards
|
|
|
1628
1692
|
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1629
1693
|
lines.push(`${indent} status: 'SUCCEEDED',`);
|
|
1630
1694
|
lines.push(`${indent} });`);
|
|
1695
|
+
// Debug controller: afterNode hook (checkpoint write, step pause)
|
|
1696
|
+
if (emitDebugHooks) {
|
|
1697
|
+
const awaitHook = isAsync ? 'await ' : '';
|
|
1698
|
+
lines.push(`${indent} ${awaitHook}__ctrl__.afterNode('${instanceId}', ${ctxVar});`);
|
|
1699
|
+
}
|
|
1631
1700
|
lines.push(`${indent}} catch (error: unknown) {`);
|
|
1632
1701
|
lines.push(`${indent} const isCancellation = CancellationError.isCancellationError(error);`);
|
|
1633
1702
|
lines.push(`${indent} ${ctxVar}.sendStatusChangedEvent({`);
|
|
@@ -1651,8 +1720,16 @@ branchingNodes = new Set() // Branching nodes set for port-aware STEP guards
|
|
|
1651
1720
|
lines.push(`${indent} }`);
|
|
1652
1721
|
lines.push(`${indent} throw error;`);
|
|
1653
1722
|
lines.push(`${indent}}`);
|
|
1723
|
+
// Close the debug controller beforeNode if block
|
|
1724
|
+
if (emitDebugHooks) {
|
|
1725
|
+
// Else block: node was skipped (checkpoint resume).
|
|
1726
|
+
// Register execution so downstream nodes can reference {safeId}Idx.
|
|
1727
|
+
lines.push(`${outerIndent}} else {`);
|
|
1728
|
+
lines.push(`${outerIndent} ${varDecl}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
1729
|
+
lines.push(`${outerIndent}}`);
|
|
1730
|
+
}
|
|
1654
1731
|
if (shouldIndent) {
|
|
1655
|
-
const originalIndent =
|
|
1732
|
+
const originalIndent = outerIndent.slice(0, -2);
|
|
1656
1733
|
lines.push(`${originalIndent}}`);
|
|
1657
1734
|
}
|
|
1658
1735
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory registry for active debug sessions.
|
|
3
|
+
* Mirrors the pattern of run-registry.ts but tracks DebugController state
|
|
4
|
+
* instead of AgentChannel state.
|
|
5
|
+
*/
|
|
6
|
+
import type { DebugController, DebugPauseState } from '../runtime/debug-controller.js';
|
|
7
|
+
export interface DebugSession {
|
|
8
|
+
debugId: string;
|
|
9
|
+
filePath: string;
|
|
10
|
+
workflowName?: string;
|
|
11
|
+
controller: DebugController;
|
|
12
|
+
/** The still-pending execution promise. Resolves when workflow completes. */
|
|
13
|
+
executionPromise: Promise<unknown>;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
/** Temp files to clean up when the session ends. */
|
|
16
|
+
tmpFiles: string[];
|
|
17
|
+
/** Most recent pause state (updated on each pause) */
|
|
18
|
+
lastPauseState?: DebugPauseState;
|
|
19
|
+
}
|
|
20
|
+
export declare function storeDebugSession(session: DebugSession): void;
|
|
21
|
+
export declare function getDebugSession(debugId: string): DebugSession | undefined;
|
|
22
|
+
export declare function removeDebugSession(debugId: string): void;
|
|
23
|
+
export declare function listDebugSessions(): Array<{
|
|
24
|
+
debugId: string;
|
|
25
|
+
filePath: string;
|
|
26
|
+
workflowName?: string;
|
|
27
|
+
createdAt: number;
|
|
28
|
+
lastPauseState?: DebugPauseState;
|
|
29
|
+
}>;
|
|
30
|
+
//# sourceMappingURL=debug-session.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory registry for active debug sessions.
|
|
3
|
+
* Mirrors the pattern of run-registry.ts but tracks DebugController state
|
|
4
|
+
* instead of AgentChannel state.
|
|
5
|
+
*/
|
|
6
|
+
const debugSessions = new Map();
|
|
7
|
+
export function storeDebugSession(session) {
|
|
8
|
+
debugSessions.set(session.debugId, session);
|
|
9
|
+
}
|
|
10
|
+
export function getDebugSession(debugId) {
|
|
11
|
+
return debugSessions.get(debugId);
|
|
12
|
+
}
|
|
13
|
+
export function removeDebugSession(debugId) {
|
|
14
|
+
debugSessions.delete(debugId);
|
|
15
|
+
}
|
|
16
|
+
export function listDebugSessions() {
|
|
17
|
+
return Array.from(debugSessions.values()).map((session) => ({
|
|
18
|
+
debugId: session.debugId,
|
|
19
|
+
filePath: session.filePath,
|
|
20
|
+
workflowName: session.workflowName,
|
|
21
|
+
createdAt: session.createdAt,
|
|
22
|
+
lastPauseState: session.lastPauseState,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=debug-session.js.map
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { registerEditorTools } from './tools-editor.js';
|
|
|
7
7
|
export { registerQueryTools } from './tools-query.js';
|
|
8
8
|
export { registerTemplateTools } from './tools-template.js';
|
|
9
9
|
export { registerPatternTools } from './tools-pattern.js';
|
|
10
|
+
export { registerDebugTools } from './tools-debug.js';
|
|
10
11
|
export { registerResources } from './resources.js';
|
|
11
12
|
export { registerPrompts } from './prompts.js';
|
|
12
13
|
export { startMcpServer, mcpServerCommand } from './server.js';
|
package/dist/mcp/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { registerEditorTools } from './tools-editor.js';
|
|
|
6
6
|
export { registerQueryTools } from './tools-query.js';
|
|
7
7
|
export { registerTemplateTools } from './tools-template.js';
|
|
8
8
|
export { registerPatternTools } from './tools-pattern.js';
|
|
9
|
+
export { registerDebugTools } from './tools-debug.js';
|
|
9
10
|
export { registerResources } from './resources.js';
|
|
10
11
|
export { registerPrompts } from './prompts.js';
|
|
11
12
|
export { startMcpServer, mcpServerCommand } from './server.js';
|
package/dist/mcp/server.js
CHANGED
|
@@ -12,6 +12,8 @@ import { registerMarketplaceTools } from './tools-marketplace.js';
|
|
|
12
12
|
import { registerDiagramTools } from './tools-diagram.js';
|
|
13
13
|
import { registerDocsTools } from './tools-docs.js';
|
|
14
14
|
import { registerModelTools } from './tools-model.js';
|
|
15
|
+
import { registerDebugTools } from './tools-debug.js';
|
|
16
|
+
import { registerContextTools } from './tools-context.js';
|
|
15
17
|
import { registerResources } from './resources.js';
|
|
16
18
|
import { registerPrompts } from './prompts.js';
|
|
17
19
|
function parseEventFilterFromEnv() {
|
|
@@ -74,6 +76,8 @@ export async function startMcpServer(options) {
|
|
|
74
76
|
registerDiagramTools(mcp);
|
|
75
77
|
registerDocsTools(mcp);
|
|
76
78
|
registerModelTools(mcp);
|
|
79
|
+
registerDebugTools(mcp);
|
|
80
|
+
registerContextTools(mcp);
|
|
77
81
|
registerResources(mcp, connection, buffer);
|
|
78
82
|
registerPrompts(mcp);
|
|
79
83
|
// Connect transport (only in stdio MCP mode)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildContext } from '../context/index.js';
|
|
3
|
+
import { makeToolResult, makeErrorResult } from './response-utils.js';
|
|
4
|
+
export function registerContextTools(mcp) {
|
|
5
|
+
mcp.tool('fw_context', 'Generate a self-contained LLM context bundle with Flow Weaver documentation, grammar, and conventions. Use preset="core" for basics, "authoring" for writing workflows, "full" for everything, "ops" for CLI/deployment reference.', {
|
|
6
|
+
preset: z
|
|
7
|
+
.enum(['core', 'authoring', 'full', 'ops'])
|
|
8
|
+
.optional()
|
|
9
|
+
.default('core')
|
|
10
|
+
.describe('Topic preset'),
|
|
11
|
+
profile: z
|
|
12
|
+
.enum(['standalone', 'assistant'])
|
|
13
|
+
.optional()
|
|
14
|
+
.default('assistant')
|
|
15
|
+
.describe('standalone = full self-contained dump, assistant = assumes MCP tools available'),
|
|
16
|
+
topics: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Comma-separated topic slugs (overrides preset)'),
|
|
20
|
+
addTopics: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Comma-separated slugs to add to preset'),
|
|
24
|
+
includeGrammar: z
|
|
25
|
+
.boolean()
|
|
26
|
+
.optional()
|
|
27
|
+
.default(true)
|
|
28
|
+
.describe('Include EBNF annotation grammar section'),
|
|
29
|
+
}, async (args) => {
|
|
30
|
+
try {
|
|
31
|
+
const result = buildContext({
|
|
32
|
+
preset: args.preset,
|
|
33
|
+
profile: args.profile,
|
|
34
|
+
topics: args.topics ? args.topics.split(',').map((s) => s.trim()) : undefined,
|
|
35
|
+
addTopics: args.addTopics ? args.addTopics.split(',').map((s) => s.trim()) : undefined,
|
|
36
|
+
includeGrammar: args.includeGrammar,
|
|
37
|
+
});
|
|
38
|
+
return makeToolResult({
|
|
39
|
+
profile: result.profile,
|
|
40
|
+
topicCount: result.topicCount,
|
|
41
|
+
lineCount: result.lineCount,
|
|
42
|
+
topicSlugs: result.topicSlugs,
|
|
43
|
+
content: result.content,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return makeErrorResult('CONTEXT_ERROR', `fw_context failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=tools-context.js.map
|