musubi-sdd 3.10.0 → 5.1.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.
Files changed (44) hide show
  1. package/README.md +24 -19
  2. package/package.json +1 -1
  3. package/src/agents/agent-loop.js +532 -0
  4. package/src/agents/agentic/code-generator.js +767 -0
  5. package/src/agents/agentic/code-reviewer.js +698 -0
  6. package/src/agents/agentic/index.js +43 -0
  7. package/src/agents/function-tool.js +432 -0
  8. package/src/agents/index.js +45 -0
  9. package/src/agents/schema-generator.js +514 -0
  10. package/src/analyzers/ast-extractor.js +870 -0
  11. package/src/analyzers/context-optimizer.js +681 -0
  12. package/src/analyzers/repository-map.js +692 -0
  13. package/src/integrations/index.js +7 -1
  14. package/src/integrations/mcp/index.js +175 -0
  15. package/src/integrations/mcp/mcp-context-provider.js +472 -0
  16. package/src/integrations/mcp/mcp-discovery.js +436 -0
  17. package/src/integrations/mcp/mcp-tool-registry.js +467 -0
  18. package/src/integrations/mcp-connector.js +818 -0
  19. package/src/integrations/tool-discovery.js +589 -0
  20. package/src/managers/index.js +7 -0
  21. package/src/managers/skill-tools.js +565 -0
  22. package/src/monitoring/cost-tracker.js +7 -0
  23. package/src/monitoring/incident-manager.js +10 -0
  24. package/src/monitoring/observability.js +10 -0
  25. package/src/monitoring/quality-dashboard.js +491 -0
  26. package/src/monitoring/release-manager.js +10 -0
  27. package/src/orchestration/agent-skill-binding.js +655 -0
  28. package/src/orchestration/error-handler.js +827 -0
  29. package/src/orchestration/index.js +235 -1
  30. package/src/orchestration/mcp-tool-adapters.js +896 -0
  31. package/src/orchestration/reasoning/index.js +58 -0
  32. package/src/orchestration/reasoning/planning-engine.js +831 -0
  33. package/src/orchestration/reasoning/reasoning-engine.js +710 -0
  34. package/src/orchestration/reasoning/self-correction.js +751 -0
  35. package/src/orchestration/skill-executor.js +665 -0
  36. package/src/orchestration/skill-registry.js +650 -0
  37. package/src/orchestration/workflow-examples.js +1072 -0
  38. package/src/orchestration/workflow-executor.js +779 -0
  39. package/src/phase4-integration.js +248 -0
  40. package/src/phase5-integration.js +402 -0
  41. package/src/steering/steering-auto-update.js +572 -0
  42. package/src/steering/steering-validator.js +547 -0
  43. package/src/templates/template-constraints.js +646 -0
  44. package/src/validators/advanced-validation.js +580 -0
@@ -0,0 +1,779 @@
1
+ /**
2
+ * WorkflowExecutor - End-to-end workflow execution engine
3
+ * Sprint 3.5: Advanced Workflows
4
+ *
5
+ * Provides comprehensive workflow execution with:
6
+ * - Step-by-step execution with state management
7
+ * - Parallel and sequential step execution
8
+ * - Conditional branching and loops
9
+ * - Error handling and recovery
10
+ * - Progress tracking and reporting
11
+ */
12
+
13
+ const EventEmitter = require('events');
14
+
15
+ /**
16
+ * Workflow step types
17
+ */
18
+ const StepType = {
19
+ SKILL: 'skill',
20
+ TOOL: 'tool',
21
+ CONDITION: 'condition',
22
+ PARALLEL: 'parallel',
23
+ LOOP: 'loop',
24
+ CHECKPOINT: 'checkpoint',
25
+ HUMAN_REVIEW: 'human-review'
26
+ };
27
+
28
+ /**
29
+ * Execution states
30
+ */
31
+ const ExecutionState = {
32
+ PENDING: 'pending',
33
+ RUNNING: 'running',
34
+ PAUSED: 'paused',
35
+ COMPLETED: 'completed',
36
+ FAILED: 'failed',
37
+ CANCELLED: 'cancelled',
38
+ WAITING_REVIEW: 'waiting-review'
39
+ };
40
+
41
+ /**
42
+ * Error recovery strategies
43
+ */
44
+ const RecoveryStrategy = {
45
+ RETRY: 'retry',
46
+ SKIP: 'skip',
47
+ FALLBACK: 'fallback',
48
+ ROLLBACK: 'rollback',
49
+ ABORT: 'abort',
50
+ MANUAL: 'manual'
51
+ };
52
+
53
+ /**
54
+ * Step execution result
55
+ */
56
+ class StepResult {
57
+ constructor(stepId, success, output = null, error = null, duration = 0) {
58
+ this.stepId = stepId;
59
+ this.success = success;
60
+ this.output = output;
61
+ this.error = error;
62
+ this.duration = duration;
63
+ this.timestamp = new Date().toISOString();
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Workflow execution context
69
+ */
70
+ class ExecutionContext {
71
+ constructor(workflowId) {
72
+ this.workflowId = workflowId;
73
+ this.executionId = `exec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
74
+ this.state = ExecutionState.PENDING;
75
+ this.variables = new Map();
76
+ this.stepResults = new Map();
77
+ this.currentStep = null;
78
+ this.startTime = null;
79
+ this.endTime = null;
80
+ this.checkpoints = [];
81
+ this.errors = [];
82
+ }
83
+
84
+ setVariable(name, value) {
85
+ this.variables.set(name, value);
86
+ }
87
+
88
+ getVariable(name, defaultValue = null) {
89
+ return this.variables.has(name) ? this.variables.get(name) : defaultValue;
90
+ }
91
+
92
+ addStepResult(stepId, result) {
93
+ this.stepResults.set(stepId, result);
94
+ }
95
+
96
+ createCheckpoint(name) {
97
+ this.checkpoints.push({
98
+ name,
99
+ timestamp: new Date().toISOString(),
100
+ variables: new Map(this.variables),
101
+ currentStep: this.currentStep
102
+ });
103
+ }
104
+
105
+ restoreCheckpoint(name) {
106
+ const checkpoint = this.checkpoints.find(cp => cp.name === name);
107
+ if (checkpoint) {
108
+ this.variables = new Map(checkpoint.variables);
109
+ this.currentStep = checkpoint.currentStep;
110
+ return true;
111
+ }
112
+ return false;
113
+ }
114
+
115
+ getDuration() {
116
+ if (!this.startTime) return 0;
117
+ const end = this.endTime || Date.now();
118
+ return end - this.startTime;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Workflow definition
124
+ */
125
+ class WorkflowDefinition {
126
+ constructor(id, name, steps = [], options = {}) {
127
+ this.id = id;
128
+ this.name = name;
129
+ this.description = options.description || '';
130
+ this.version = options.version || '1.0.0';
131
+ this.steps = steps;
132
+ this.inputs = options.inputs || [];
133
+ this.outputs = options.outputs || [];
134
+ this.errorHandling = options.errorHandling || { strategy: RecoveryStrategy.ABORT };
135
+ this.timeout = options.timeout || 0; // 0 = no timeout
136
+ this.retryPolicy = options.retryPolicy || { maxRetries: 3, backoffMs: 1000 };
137
+ }
138
+
139
+ validate() {
140
+ const errors = [];
141
+
142
+ if (!this.id) {
143
+ errors.push('Workflow ID is required');
144
+ }
145
+
146
+ if (!this.name) {
147
+ errors.push('Workflow name is required');
148
+ }
149
+
150
+ if (!this.steps || this.steps.length === 0) {
151
+ errors.push('Workflow must have at least one step');
152
+ }
153
+
154
+ // Validate each step
155
+ const stepIds = new Set();
156
+ for (const step of this.steps) {
157
+ if (!step.id) {
158
+ errors.push('Each step must have an ID');
159
+ } else if (stepIds.has(step.id)) {
160
+ errors.push(`Duplicate step ID: ${step.id}`);
161
+ } else {
162
+ stepIds.add(step.id);
163
+ }
164
+
165
+ if (!step.type) {
166
+ errors.push(`Step ${step.id} must have a type`);
167
+ }
168
+ }
169
+
170
+ return {
171
+ valid: errors.length === 0,
172
+ errors
173
+ };
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Main workflow executor
179
+ */
180
+ class WorkflowExecutor extends EventEmitter {
181
+ constructor(options = {}) {
182
+ super();
183
+ this.skillRegistry = options.skillRegistry || null;
184
+ this.toolDiscovery = options.toolDiscovery || null;
185
+ this.mcpConnector = options.mcpConnector || null;
186
+ this.executions = new Map();
187
+ this.stepHandlers = new Map();
188
+
189
+ // Register default step handlers
190
+ this._registerDefaultHandlers();
191
+ }
192
+
193
+ /**
194
+ * Register default step type handlers
195
+ */
196
+ _registerDefaultHandlers() {
197
+ // Skill execution handler
198
+ this.stepHandlers.set(StepType.SKILL, async (step, context) => {
199
+ const { skillId, input } = step;
200
+
201
+ if (!this.skillRegistry) {
202
+ throw new Error('Skill registry not configured');
203
+ }
204
+
205
+ const skill = this.skillRegistry.getSkill(skillId);
206
+ if (!skill) {
207
+ throw new Error(`Skill not found: ${skillId}`);
208
+ }
209
+
210
+ // Resolve input variables
211
+ const resolvedInput = this._resolveVariables(input, context);
212
+
213
+ // Execute skill
214
+ const result = await skill.execute(resolvedInput, context);
215
+
216
+ // Store output in context
217
+ if (step.outputVariable) {
218
+ context.setVariable(step.outputVariable, result);
219
+ }
220
+
221
+ return result;
222
+ });
223
+
224
+ // Tool execution handler
225
+ this.stepHandlers.set(StepType.TOOL, async (step, context) => {
226
+ const { toolName, serverName, arguments: args } = step;
227
+
228
+ if (!this.mcpConnector) {
229
+ throw new Error('MCP connector not configured');
230
+ }
231
+
232
+ // Resolve arguments
233
+ const resolvedArgs = this._resolveVariables(args, context);
234
+
235
+ // Call tool
236
+ const result = await this.mcpConnector.callTool(toolName, resolvedArgs, serverName);
237
+
238
+ if (step.outputVariable) {
239
+ context.setVariable(step.outputVariable, result);
240
+ }
241
+
242
+ return result;
243
+ });
244
+
245
+ // Condition handler
246
+ this.stepHandlers.set(StepType.CONDITION, async (step, context) => {
247
+ const { condition, thenSteps, elseSteps } = step;
248
+
249
+ // Evaluate condition
250
+ const conditionResult = this._evaluateCondition(condition, context);
251
+
252
+ // Execute appropriate branch
253
+ const stepsToExecute = conditionResult ? thenSteps : elseSteps;
254
+
255
+ if (stepsToExecute && stepsToExecute.length > 0) {
256
+ for (const subStep of stepsToExecute) {
257
+ await this._executeStep(subStep, context);
258
+ }
259
+ }
260
+
261
+ return conditionResult;
262
+ });
263
+
264
+ // Parallel execution handler
265
+ this.stepHandlers.set(StepType.PARALLEL, async (step, context) => {
266
+ const { steps, maxConcurrency = 5 } = step;
267
+
268
+ const results = [];
269
+ const executing = new Set();
270
+
271
+ for (const subStep of steps) {
272
+ if (executing.size >= maxConcurrency) {
273
+ const completed = await Promise.race([...executing]);
274
+ executing.delete(completed.promise);
275
+ results.push(completed.result);
276
+ }
277
+
278
+ const promise = this._executeStep(subStep, context)
279
+ .then(result => ({ promise, result }));
280
+ executing.add(promise);
281
+ }
282
+
283
+ // Wait for remaining
284
+ const remaining = await Promise.all([...executing]);
285
+ results.push(...remaining.map(r => r.result));
286
+
287
+ if (step.outputVariable) {
288
+ context.setVariable(step.outputVariable, results);
289
+ }
290
+
291
+ return results;
292
+ });
293
+
294
+ // Loop handler
295
+ this.stepHandlers.set(StepType.LOOP, async (step, context) => {
296
+ const { items, itemVariable, indexVariable, steps: loopSteps, maxIterations = 1000 } = step;
297
+
298
+ // Resolve items
299
+ const resolvedItems = this._resolveVariables(items, context);
300
+
301
+ if (!Array.isArray(resolvedItems)) {
302
+ throw new Error('Loop items must be an array');
303
+ }
304
+
305
+ const results = [];
306
+ let iteration = 0;
307
+
308
+ for (const item of resolvedItems) {
309
+ if (iteration >= maxIterations) {
310
+ this.emit('warning', { message: `Loop reached max iterations: ${maxIterations}` });
311
+ break;
312
+ }
313
+
314
+ context.setVariable(itemVariable || 'item', item);
315
+ context.setVariable(indexVariable || 'index', iteration);
316
+
317
+ for (const subStep of loopSteps) {
318
+ const result = await this._executeStep(subStep, context);
319
+ results.push(result);
320
+ }
321
+
322
+ iteration++;
323
+ }
324
+
325
+ if (step.outputVariable) {
326
+ context.setVariable(step.outputVariable, results);
327
+ }
328
+
329
+ return results;
330
+ });
331
+
332
+ // Checkpoint handler
333
+ this.stepHandlers.set(StepType.CHECKPOINT, async (step, context) => {
334
+ const { name } = step;
335
+ context.createCheckpoint(name);
336
+ this.emit('checkpoint', { name, context });
337
+ return { checkpointCreated: name };
338
+ });
339
+
340
+ // Human review handler
341
+ this.stepHandlers.set(StepType.HUMAN_REVIEW, async (step, context) => {
342
+ const { message, options = ['approve', 'reject'] } = step;
343
+
344
+ context.state = ExecutionState.WAITING_REVIEW;
345
+ this.emit('review-required', {
346
+ stepId: step.id,
347
+ message: this._resolveVariables(message, context),
348
+ options,
349
+ context
350
+ });
351
+
352
+ // Wait for review (in real implementation, this would wait for external input)
353
+ return { reviewRequested: true, message };
354
+ });
355
+ }
356
+
357
+ /**
358
+ * Register a custom step handler
359
+ */
360
+ registerStepHandler(type, handler) {
361
+ this.stepHandlers.set(type, handler);
362
+ }
363
+
364
+ /**
365
+ * Execute a workflow
366
+ */
367
+ async execute(workflow, initialVariables = {}) {
368
+ // Validate workflow
369
+ const validation = workflow.validate();
370
+ if (!validation.valid) {
371
+ throw new Error(`Invalid workflow: ${validation.errors.join(', ')}`);
372
+ }
373
+
374
+ // Create execution context
375
+ const context = new ExecutionContext(workflow.id);
376
+ context.state = ExecutionState.RUNNING;
377
+ context.startTime = Date.now();
378
+
379
+ // Set initial variables
380
+ for (const [key, value] of Object.entries(initialVariables)) {
381
+ context.setVariable(key, value);
382
+ }
383
+
384
+ // Store execution
385
+ this.executions.set(context.executionId, context);
386
+
387
+ this.emit('execution-started', {
388
+ executionId: context.executionId,
389
+ workflowId: workflow.id
390
+ });
391
+
392
+ try {
393
+ // Execute steps
394
+ for (const step of workflow.steps) {
395
+ if (context.state === ExecutionState.CANCELLED) {
396
+ break;
397
+ }
398
+
399
+ if (context.state === ExecutionState.PAUSED) {
400
+ await this._waitForResume(context);
401
+ }
402
+
403
+ await this._executeStep(step, context, workflow);
404
+ }
405
+
406
+ context.state = ExecutionState.COMPLETED;
407
+ context.endTime = Date.now();
408
+
409
+ this.emit('execution-completed', {
410
+ executionId: context.executionId,
411
+ duration: context.getDuration(),
412
+ results: Object.fromEntries(context.stepResults)
413
+ });
414
+
415
+ } catch (error) {
416
+ context.state = ExecutionState.FAILED;
417
+ context.endTime = Date.now();
418
+ context.errors.push(error);
419
+
420
+ this.emit('execution-failed', {
421
+ executionId: context.executionId,
422
+ error: error.message,
423
+ step: context.currentStep
424
+ });
425
+
426
+ // Apply error handling strategy
427
+ await this._handleExecutionError(error, context, workflow);
428
+
429
+ throw error;
430
+ }
431
+
432
+ return {
433
+ executionId: context.executionId,
434
+ state: context.state,
435
+ duration: context.getDuration(),
436
+ outputs: this._collectOutputs(context, workflow.outputs),
437
+ stepResults: Object.fromEntries(context.stepResults)
438
+ };
439
+ }
440
+
441
+ /**
442
+ * Execute a single step
443
+ */
444
+ async _executeStep(step, context, workflow = null) {
445
+ context.currentStep = step.id;
446
+ const startTime = Date.now();
447
+
448
+ this.emit('step-started', { stepId: step.id, type: step.type });
449
+
450
+ try {
451
+ // Check condition if present
452
+ if (step.when) {
453
+ const shouldExecute = this._evaluateCondition(step.when, context);
454
+ if (!shouldExecute) {
455
+ this.emit('step-skipped', { stepId: step.id, reason: 'Condition not met' });
456
+ return { skipped: true };
457
+ }
458
+ }
459
+
460
+ // Get handler for step type
461
+ const handler = this.stepHandlers.get(step.type);
462
+ if (!handler) {
463
+ throw new Error(`Unknown step type: ${step.type}`);
464
+ }
465
+
466
+ // Execute with retry logic
467
+ const result = await this._executeWithRetry(
468
+ () => handler(step, context),
469
+ step.retry || (workflow?.retryPolicy),
470
+ step.id
471
+ );
472
+
473
+ const duration = Date.now() - startTime;
474
+ const stepResult = new StepResult(step.id, true, result, null, duration);
475
+ context.addStepResult(step.id, stepResult);
476
+
477
+ this.emit('step-completed', { stepId: step.id, duration, result });
478
+
479
+ return result;
480
+
481
+ } catch (error) {
482
+ const duration = Date.now() - startTime;
483
+ const stepResult = new StepResult(step.id, false, null, error.message, duration);
484
+ context.addStepResult(step.id, stepResult);
485
+
486
+ this.emit('step-failed', { stepId: step.id, error: error.message, duration });
487
+
488
+ // Apply step-level error handling
489
+ if (step.onError) {
490
+ return await this._handleStepError(error, step, context);
491
+ }
492
+
493
+ throw error;
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Execute with retry logic
499
+ */
500
+ async _executeWithRetry(fn, retryPolicy, stepId) {
501
+ const maxRetries = retryPolicy?.maxRetries || 0;
502
+ const backoffMs = retryPolicy?.backoffMs || 1000;
503
+ const backoffMultiplier = retryPolicy?.backoffMultiplier || 2;
504
+
505
+ let lastError;
506
+ let currentBackoff = backoffMs;
507
+
508
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
509
+ try {
510
+ return await fn();
511
+ } catch (error) {
512
+ lastError = error;
513
+
514
+ if (attempt < maxRetries) {
515
+ this.emit('step-retry', {
516
+ stepId,
517
+ attempt: attempt + 1,
518
+ maxRetries,
519
+ nextRetryMs: currentBackoff
520
+ });
521
+
522
+ await this._sleep(currentBackoff);
523
+ currentBackoff *= backoffMultiplier;
524
+ }
525
+ }
526
+ }
527
+
528
+ throw lastError;
529
+ }
530
+
531
+ /**
532
+ * Handle step-level error
533
+ */
534
+ async _handleStepError(error, step, context) {
535
+ const errorConfig = step.onError;
536
+ const strategy = errorConfig.strategy || RecoveryStrategy.ABORT;
537
+
538
+ switch (strategy) {
539
+ case RecoveryStrategy.SKIP:
540
+ this.emit('step-error-skipped', { stepId: step.id, error: error.message });
541
+ return { skipped: true, error: error.message };
542
+
543
+ case RecoveryStrategy.FALLBACK:
544
+ if (errorConfig.fallbackSteps) {
545
+ for (const fallbackStep of errorConfig.fallbackSteps) {
546
+ await this._executeStep(fallbackStep, context);
547
+ }
548
+ }
549
+ return { fallback: true };
550
+
551
+ case RecoveryStrategy.ROLLBACK:
552
+ if (errorConfig.rollbackTo) {
553
+ const restored = context.restoreCheckpoint(errorConfig.rollbackTo);
554
+ if (restored) {
555
+ this.emit('rollback', { stepId: step.id, checkpoint: errorConfig.rollbackTo });
556
+ }
557
+ }
558
+ throw error;
559
+
560
+ case RecoveryStrategy.MANUAL:
561
+ context.state = ExecutionState.PAUSED;
562
+ this.emit('manual-intervention-required', { stepId: step.id, error: error.message });
563
+ return { waitingIntervention: true };
564
+
565
+ default:
566
+ throw error;
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Handle execution-level error
572
+ */
573
+ async _handleExecutionError(error, context, workflow) {
574
+ const strategy = workflow.errorHandling?.strategy || RecoveryStrategy.ABORT;
575
+
576
+ if (strategy === RecoveryStrategy.ROLLBACK && workflow.errorHandling?.rollbackTo) {
577
+ context.restoreCheckpoint(workflow.errorHandling.rollbackTo);
578
+ }
579
+
580
+ // Log error for analysis
581
+ this.emit('error-logged', {
582
+ executionId: context.executionId,
583
+ error: error.message,
584
+ step: context.currentStep,
585
+ strategy
586
+ });
587
+ }
588
+
589
+ /**
590
+ * Resolve variables in a value
591
+ */
592
+ _resolveVariables(value, context) {
593
+ if (typeof value === 'string') {
594
+ // Replace ${variable} patterns
595
+ return value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
596
+ const resolved = context.getVariable(varName);
597
+ return resolved !== null ? String(resolved) : '';
598
+ });
599
+ }
600
+
601
+ if (Array.isArray(value)) {
602
+ return value.map(item => this._resolveVariables(item, context));
603
+ }
604
+
605
+ if (typeof value === 'object' && value !== null) {
606
+ // Check if it's a variable reference
607
+ if (value.$var) {
608
+ return context.getVariable(value.$var, value.default);
609
+ }
610
+
611
+ const resolved = {};
612
+ for (const [key, val] of Object.entries(value)) {
613
+ resolved[key] = this._resolveVariables(val, context);
614
+ }
615
+ return resolved;
616
+ }
617
+
618
+ return value;
619
+ }
620
+
621
+ /**
622
+ * Evaluate a condition expression
623
+ */
624
+ _evaluateCondition(condition, context) {
625
+ if (typeof condition === 'boolean') {
626
+ return condition;
627
+ }
628
+
629
+ if (typeof condition === 'object') {
630
+ // Handle comparison operators
631
+ if (condition.$eq) {
632
+ const [left, right] = condition.$eq;
633
+ return this._resolveVariables(left, context) === this._resolveVariables(right, context);
634
+ }
635
+ if (condition.$ne) {
636
+ const [left, right] = condition.$ne;
637
+ return this._resolveVariables(left, context) !== this._resolveVariables(right, context);
638
+ }
639
+ if (condition.$gt) {
640
+ const [left, right] = condition.$gt;
641
+ return this._resolveVariables(left, context) > this._resolveVariables(right, context);
642
+ }
643
+ if (condition.$lt) {
644
+ const [left, right] = condition.$lt;
645
+ return this._resolveVariables(left, context) < this._resolveVariables(right, context);
646
+ }
647
+ if (condition.$exists) {
648
+ const varName = condition.$exists;
649
+ return context.getVariable(varName) !== null;
650
+ }
651
+ if (condition.$and) {
652
+ return condition.$and.every(c => this._evaluateCondition(c, context));
653
+ }
654
+ if (condition.$or) {
655
+ return condition.$or.some(c => this._evaluateCondition(c, context));
656
+ }
657
+ if (condition.$not) {
658
+ return !this._evaluateCondition(condition.$not, context);
659
+ }
660
+ }
661
+
662
+ // String expression (simple variable truthy check)
663
+ if (typeof condition === 'string') {
664
+ const value = context.getVariable(condition);
665
+ return Boolean(value);
666
+ }
667
+
668
+ return false;
669
+ }
670
+
671
+ /**
672
+ * Collect workflow outputs
673
+ */
674
+ _collectOutputs(context, outputDefs) {
675
+ const outputs = {};
676
+
677
+ for (const outputDef of outputDefs) {
678
+ const name = typeof outputDef === 'string' ? outputDef : outputDef.name;
679
+ const source = typeof outputDef === 'string' ? outputDef : outputDef.from;
680
+ outputs[name] = context.getVariable(source);
681
+ }
682
+
683
+ return outputs;
684
+ }
685
+
686
+ /**
687
+ * Wait for execution to resume
688
+ */
689
+ async _waitForResume(context) {
690
+ return new Promise(resolve => {
691
+ const checkResume = () => {
692
+ if (context.state === ExecutionState.RUNNING) {
693
+ resolve();
694
+ } else {
695
+ setTimeout(checkResume, 100);
696
+ }
697
+ };
698
+ checkResume();
699
+ });
700
+ }
701
+
702
+ /**
703
+ * Pause execution
704
+ */
705
+ pause(executionId) {
706
+ const context = this.executions.get(executionId);
707
+ if (context && context.state === ExecutionState.RUNNING) {
708
+ context.state = ExecutionState.PAUSED;
709
+ this.emit('execution-paused', { executionId });
710
+ return true;
711
+ }
712
+ return false;
713
+ }
714
+
715
+ /**
716
+ * Resume execution
717
+ */
718
+ resume(executionId) {
719
+ const context = this.executions.get(executionId);
720
+ if (context && context.state === ExecutionState.PAUSED) {
721
+ context.state = ExecutionState.RUNNING;
722
+ this.emit('execution-resumed', { executionId });
723
+ return true;
724
+ }
725
+ return false;
726
+ }
727
+
728
+ /**
729
+ * Cancel execution
730
+ */
731
+ cancel(executionId) {
732
+ const context = this.executions.get(executionId);
733
+ if (context && [ExecutionState.RUNNING, ExecutionState.PAUSED].includes(context.state)) {
734
+ context.state = ExecutionState.CANCELLED;
735
+ context.endTime = Date.now();
736
+ this.emit('execution-cancelled', { executionId });
737
+ return true;
738
+ }
739
+ return false;
740
+ }
741
+
742
+ /**
743
+ * Get execution status
744
+ */
745
+ getStatus(executionId) {
746
+ const context = this.executions.get(executionId);
747
+ if (!context) {
748
+ return null;
749
+ }
750
+
751
+ return {
752
+ executionId: context.executionId,
753
+ workflowId: context.workflowId,
754
+ state: context.state,
755
+ currentStep: context.currentStep,
756
+ duration: context.getDuration(),
757
+ stepsCompleted: context.stepResults.size,
758
+ errors: context.errors.map(e => e.message),
759
+ checkpoints: context.checkpoints.map(cp => cp.name)
760
+ };
761
+ }
762
+
763
+ /**
764
+ * Helper sleep function
765
+ */
766
+ _sleep(ms) {
767
+ return new Promise(resolve => setTimeout(resolve, ms));
768
+ }
769
+ }
770
+
771
+ module.exports = {
772
+ WorkflowExecutor,
773
+ WorkflowDefinition,
774
+ ExecutionContext,
775
+ StepResult,
776
+ StepType,
777
+ ExecutionState,
778
+ RecoveryStrategy
779
+ };