agentic-qe 1.9.3 → 2.0.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/.claude/agents/qe-api-contract-validator.md +95 -1336
- package/.claude/agents/qe-chaos-engineer.md +152 -1211
- package/.claude/agents/qe-code-complexity.md +144 -707
- package/.claude/agents/qe-coverage-analyzer.md +147 -743
- package/.claude/agents/qe-deployment-readiness.md +143 -1496
- package/.claude/agents/qe-flaky-test-hunter.md +132 -1529
- package/.claude/agents/qe-fleet-commander.md +12 -12
- package/.claude/agents/qe-performance-tester.md +150 -886
- package/.claude/agents/qe-production-intelligence.md +155 -1396
- package/.claude/agents/qe-quality-analyzer.md +6 -6
- package/.claude/agents/qe-quality-gate.md +151 -648
- package/.claude/agents/qe-regression-risk-analyzer.md +132 -1150
- package/.claude/agents/qe-requirements-validator.md +149 -932
- package/.claude/agents/qe-security-scanner.md +157 -797
- package/.claude/agents/qe-test-data-architect.md +96 -1365
- package/.claude/agents/qe-test-executor.md +8 -8
- package/.claude/agents/qe-test-generator.md +145 -1540
- package/.claude/agents/qe-visual-tester.md +153 -1257
- package/.claude/agents/qx-partner.md +235 -0
- package/.claude/agents/subagents/qe-code-reviewer.md +40 -136
- package/.claude/agents/subagents/qe-coverage-gap-analyzer.md +40 -480
- package/.claude/agents/subagents/qe-data-generator.md +41 -125
- package/.claude/agents/subagents/qe-flaky-investigator.md +55 -411
- package/.claude/agents/subagents/qe-integration-tester.md +53 -141
- package/.claude/agents/subagents/qe-performance-validator.md +54 -130
- package/.claude/agents/subagents/qe-security-auditor.md +56 -114
- package/.claude/agents/subagents/qe-test-data-architect-sub.md +57 -548
- package/.claude/agents/subagents/qe-test-implementer.md +58 -551
- package/.claude/agents/subagents/qe-test-refactorer.md +65 -722
- package/.claude/agents/subagents/qe-test-writer.md +63 -726
- package/.claude/skills/skills-manifest.json +632 -0
- package/.claude/skills/testability-scoring/README.md +71 -0
- package/.claude/skills/testability-scoring/SKILL.md +611 -0
- package/.claude/skills/testability-scoring/resources/templates/config.template.js +84 -0
- package/.claude/skills/testability-scoring/resources/templates/testability-scoring.spec.template.js +532 -0
- package/.claude/skills/testability-scoring/scripts/generate-html-report.js +1007 -0
- package/.claude/skills/testability-scoring/scripts/run-assessment.sh +70 -0
- package/CHANGELOG.md +116 -0
- package/README.md +59 -7
- package/config/.env.otel.example +25 -0
- package/config/OTEL-QUICK-REFERENCE.md +137 -0
- package/config/README-OTEL.md +222 -0
- package/config/alerting-rules.yml +518 -0
- package/config/docker-compose.otel.yml +187 -0
- package/config/grafana/dashboards/agentic-qe-overview.json +286 -0
- package/config/grafana/provisioning/dashboards/dashboards.yml +19 -0
- package/config/grafana/provisioning/datasources/datasources.yml +53 -0
- package/config/otel-collector-config.yaml.example +145 -0
- package/config/prometheus.yml.example +106 -0
- package/dist/agents/QXPartnerAgent.d.ts +139 -0
- package/dist/agents/QXPartnerAgent.d.ts.map +1 -0
- package/dist/agents/QXPartnerAgent.js +769 -0
- package/dist/agents/QXPartnerAgent.js.map +1 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +82 -2
- package/dist/agents/index.js.map +1 -1
- package/dist/alerting/AlertManager.d.ts +120 -0
- package/dist/alerting/AlertManager.d.ts.map +1 -0
- package/dist/alerting/AlertManager.js +345 -0
- package/dist/alerting/AlertManager.js.map +1 -0
- package/dist/alerting/FeedbackRouter.d.ts +98 -0
- package/dist/alerting/FeedbackRouter.d.ts.map +1 -0
- package/dist/alerting/FeedbackRouter.js +331 -0
- package/dist/alerting/FeedbackRouter.js.map +1 -0
- package/dist/alerting/StrategyApplicator.d.ts +120 -0
- package/dist/alerting/StrategyApplicator.d.ts.map +1 -0
- package/dist/alerting/StrategyApplicator.js +299 -0
- package/dist/alerting/StrategyApplicator.js.map +1 -0
- package/dist/alerting/index.d.ts +68 -0
- package/dist/alerting/index.d.ts.map +1 -0
- package/dist/alerting/index.js +112 -0
- package/dist/alerting/index.js.map +1 -0
- package/dist/alerting/types.d.ts +118 -0
- package/dist/alerting/types.d.ts.map +1 -0
- package/dist/alerting/types.js +11 -0
- package/dist/alerting/types.js.map +1 -0
- package/dist/cli/commands/debug/agent.d.ts.map +1 -1
- package/dist/cli/commands/debug/agent.js +19 -6
- package/dist/cli/commands/debug/agent.js.map +1 -1
- package/dist/cli/commands/debug/health-check.js +20 -7
- package/dist/cli/commands/debug/health-check.js.map +1 -1
- package/dist/cli/commands/init-claude-md-template.d.ts +1 -0
- package/dist/cli/commands/init-claude-md-template.d.ts.map +1 -1
- package/dist/cli/commands/init-claude-md-template.js +4 -3
- package/dist/cli/commands/init-claude-md-template.js.map +1 -1
- package/dist/cli/commands/workflow/cancel.d.ts.map +1 -1
- package/dist/cli/commands/workflow/cancel.js +4 -3
- package/dist/cli/commands/workflow/cancel.js.map +1 -1
- package/dist/cli/commands/workflow/list.d.ts.map +1 -1
- package/dist/cli/commands/workflow/list.js +4 -3
- package/dist/cli/commands/workflow/list.js.map +1 -1
- package/dist/cli/commands/workflow/pause.d.ts.map +1 -1
- package/dist/cli/commands/workflow/pause.js +4 -3
- package/dist/cli/commands/workflow/pause.js.map +1 -1
- package/dist/cli/init/claude-config.d.ts.map +1 -1
- package/dist/cli/init/claude-config.js +13 -13
- package/dist/cli/init/claude-config.js.map +1 -1
- package/dist/cli/init/claude-md.d.ts.map +1 -1
- package/dist/cli/init/claude-md.js +44 -2
- package/dist/cli/init/claude-md.js.map +1 -1
- package/dist/cli/init/database-init.js +1 -1
- package/dist/cli/init/index.d.ts.map +1 -1
- package/dist/cli/init/index.js +13 -6
- package/dist/cli/init/index.js.map +1 -1
- package/dist/cli/init/skills.d.ts.map +1 -1
- package/dist/cli/init/skills.js +2 -1
- package/dist/cli/init/skills.js.map +1 -1
- package/dist/core/memory/AgentDBIntegration.d.ts +24 -6
- package/dist/core/memory/AgentDBIntegration.d.ts.map +1 -1
- package/dist/core/memory/AgentDBIntegration.js +66 -10
- package/dist/core/memory/AgentDBIntegration.js.map +1 -1
- package/dist/core/memory/IPatternStore.d.ts +209 -0
- package/dist/core/memory/IPatternStore.d.ts.map +1 -0
- package/dist/core/memory/IPatternStore.js +15 -0
- package/dist/core/memory/IPatternStore.js.map +1 -0
- package/dist/core/memory/MigrationTools.d.ts +192 -0
- package/dist/core/memory/MigrationTools.d.ts.map +1 -0
- package/dist/core/memory/MigrationTools.js +615 -0
- package/dist/core/memory/MigrationTools.js.map +1 -0
- package/dist/core/memory/NeuralEnhancement.d.ts +154 -0
- package/dist/core/memory/NeuralEnhancement.d.ts.map +1 -0
- package/dist/core/memory/NeuralEnhancement.js +598 -0
- package/dist/core/memory/NeuralEnhancement.js.map +1 -0
- package/dist/core/memory/PatternStoreFactory.d.ts +143 -0
- package/dist/core/memory/PatternStoreFactory.d.ts.map +1 -0
- package/dist/core/memory/PatternStoreFactory.js +370 -0
- package/dist/core/memory/PatternStoreFactory.js.map +1 -0
- package/dist/core/memory/RealAgentDBAdapter.d.ts +1 -0
- package/dist/core/memory/RealAgentDBAdapter.d.ts.map +1 -1
- package/dist/core/memory/RealAgentDBAdapter.js +28 -20
- package/dist/core/memory/RealAgentDBAdapter.js.map +1 -1
- package/dist/core/memory/RuVectorPatternStore.d.ts +198 -0
- package/dist/core/memory/RuVectorPatternStore.d.ts.map +1 -0
- package/dist/core/memory/RuVectorPatternStore.js +605 -0
- package/dist/core/memory/RuVectorPatternStore.js.map +1 -0
- package/dist/core/memory/SelfHealingMonitor.d.ts +186 -0
- package/dist/core/memory/SelfHealingMonitor.d.ts.map +1 -0
- package/dist/core/memory/SelfHealingMonitor.js +451 -0
- package/dist/core/memory/SelfHealingMonitor.js.map +1 -0
- package/dist/core/memory/SwarmMemoryManager.d.ts +62 -0
- package/dist/core/memory/SwarmMemoryManager.d.ts.map +1 -1
- package/dist/core/memory/SwarmMemoryManager.js +97 -0
- package/dist/core/memory/SwarmMemoryManager.js.map +1 -1
- package/dist/core/memory/UnifiedMemoryCoordinator.d.ts +341 -0
- package/dist/core/memory/UnifiedMemoryCoordinator.d.ts.map +1 -0
- package/dist/core/memory/UnifiedMemoryCoordinator.js +986 -0
- package/dist/core/memory/UnifiedMemoryCoordinator.js.map +1 -0
- package/dist/core/memory/index.d.ts +16 -0
- package/dist/core/memory/index.d.ts.map +1 -1
- package/dist/core/memory/index.js +58 -1
- package/dist/core/memory/index.js.map +1 -1
- package/dist/core/optimization/SwarmOptimizer.d.ts +185 -0
- package/dist/core/optimization/SwarmOptimizer.d.ts.map +1 -0
- package/dist/core/optimization/SwarmOptimizer.js +631 -0
- package/dist/core/optimization/SwarmOptimizer.js.map +1 -0
- package/dist/core/optimization/index.d.ts +9 -0
- package/dist/core/optimization/index.d.ts.map +1 -0
- package/dist/core/optimization/index.js +25 -0
- package/dist/core/optimization/index.js.map +1 -0
- package/dist/core/optimization/types.d.ts +53 -0
- package/dist/core/optimization/types.d.ts.map +1 -0
- package/dist/core/optimization/types.js +6 -0
- package/dist/core/optimization/types.js.map +1 -0
- package/dist/core/orchestration/PriorityQueue.d.ts +54 -0
- package/dist/core/orchestration/PriorityQueue.d.ts.map +1 -0
- package/dist/core/orchestration/PriorityQueue.js +122 -0
- package/dist/core/orchestration/PriorityQueue.js.map +1 -0
- package/dist/core/orchestration/WorkflowOrchestrator.d.ts +176 -0
- package/dist/core/orchestration/WorkflowOrchestrator.d.ts.map +1 -0
- package/dist/core/orchestration/WorkflowOrchestrator.js +813 -0
- package/dist/core/orchestration/WorkflowOrchestrator.js.map +1 -0
- package/dist/core/orchestration/index.d.ts +7 -0
- package/dist/core/orchestration/index.d.ts.map +1 -0
- package/dist/core/orchestration/index.js +11 -0
- package/dist/core/orchestration/index.js.map +1 -0
- package/dist/core/orchestration/types.d.ts +96 -0
- package/dist/core/orchestration/types.d.ts.map +1 -0
- package/dist/core/orchestration/types.js +6 -0
- package/dist/core/orchestration/types.js.map +1 -0
- package/dist/core/skills/DynamicSkillLoader.d.ts +96 -0
- package/dist/core/skills/DynamicSkillLoader.d.ts.map +1 -0
- package/dist/core/skills/DynamicSkillLoader.js +353 -0
- package/dist/core/skills/DynamicSkillLoader.js.map +1 -0
- package/dist/core/skills/types.d.ts +118 -0
- package/dist/core/skills/types.d.ts.map +1 -0
- package/dist/core/skills/types.js +7 -0
- package/dist/core/skills/types.js.map +1 -0
- package/dist/core/transport/QUICTransport.d.ts +320 -0
- package/dist/core/transport/QUICTransport.d.ts.map +1 -0
- package/dist/core/transport/QUICTransport.js +711 -0
- package/dist/core/transport/QUICTransport.js.map +1 -0
- package/dist/core/transport/index.d.ts +40 -0
- package/dist/core/transport/index.d.ts.map +1 -0
- package/dist/core/transport/index.js +46 -0
- package/dist/core/transport/index.js.map +1 -0
- package/dist/core/transport/quic-loader.d.ts +123 -0
- package/dist/core/transport/quic-loader.d.ts.map +1 -0
- package/dist/core/transport/quic-loader.js +293 -0
- package/dist/core/transport/quic-loader.js.map +1 -0
- package/dist/core/transport/quic.d.ts +154 -0
- package/dist/core/transport/quic.d.ts.map +1 -0
- package/dist/core/transport/quic.js +214 -0
- package/dist/core/transport/quic.js.map +1 -0
- package/dist/mcp/services/AgentRegistry.d.ts.map +1 -1
- package/dist/mcp/services/AgentRegistry.js +4 -1
- package/dist/mcp/services/AgentRegistry.js.map +1 -1
- package/dist/reasoning/RuVectorReasoningAdapter.d.ts +232 -0
- package/dist/reasoning/RuVectorReasoningAdapter.d.ts.map +1 -0
- package/dist/reasoning/RuVectorReasoningAdapter.js +585 -0
- package/dist/reasoning/RuVectorReasoningAdapter.js.map +1 -0
- package/dist/reasoning/index.d.ts +2 -0
- package/dist/reasoning/index.d.ts.map +1 -1
- package/dist/reasoning/index.js +6 -1
- package/dist/reasoning/index.js.map +1 -1
- package/dist/reporting/ResultAggregator.d.ts +107 -0
- package/dist/reporting/ResultAggregator.d.ts.map +1 -0
- package/dist/reporting/ResultAggregator.js +435 -0
- package/dist/reporting/ResultAggregator.js.map +1 -0
- package/dist/reporting/index.d.ts +48 -0
- package/dist/reporting/index.d.ts.map +1 -0
- package/dist/reporting/index.js +154 -0
- package/dist/reporting/index.js.map +1 -0
- package/dist/reporting/reporters/ControlLoopReporter.d.ts +128 -0
- package/dist/reporting/reporters/ControlLoopReporter.d.ts.map +1 -0
- package/dist/reporting/reporters/ControlLoopReporter.js +417 -0
- package/dist/reporting/reporters/ControlLoopReporter.js.map +1 -0
- package/dist/reporting/reporters/HumanReadableReporter.d.ts +140 -0
- package/dist/reporting/reporters/HumanReadableReporter.d.ts.map +1 -0
- package/dist/reporting/reporters/HumanReadableReporter.js +524 -0
- package/dist/reporting/reporters/HumanReadableReporter.js.map +1 -0
- package/dist/reporting/reporters/JSONReporter.d.ts +193 -0
- package/dist/reporting/reporters/JSONReporter.d.ts.map +1 -0
- package/dist/reporting/reporters/JSONReporter.js +324 -0
- package/dist/reporting/reporters/JSONReporter.js.map +1 -0
- package/dist/reporting/reporters/index.d.ts +14 -0
- package/dist/reporting/reporters/index.d.ts.map +1 -0
- package/dist/reporting/reporters/index.js +19 -0
- package/dist/reporting/reporters/index.js.map +1 -0
- package/dist/reporting/types.d.ts +427 -0
- package/dist/reporting/types.d.ts.map +1 -0
- package/dist/reporting/types.js +12 -0
- package/dist/reporting/types.js.map +1 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/qx.d.ts +397 -0
- package/dist/types/qx.d.ts.map +1 -0
- package/dist/types/qx.js +71 -0
- package/dist/types/qx.js.map +1 -0
- package/dist/visualization/api/RestEndpoints.js +1 -1
- package/dist/visualization/api/RestEndpoints.js.map +1 -1
- package/dist/visualization/api/WebSocketServer.d.ts +44 -0
- package/dist/visualization/api/WebSocketServer.d.ts.map +1 -1
- package/dist/visualization/api/WebSocketServer.js +144 -23
- package/dist/visualization/api/WebSocketServer.js.map +1 -1
- package/dist/visualization/core/DataTransformer.d.ts +10 -0
- package/dist/visualization/core/DataTransformer.d.ts.map +1 -1
- package/dist/visualization/core/DataTransformer.js +60 -5
- package/dist/visualization/core/DataTransformer.js.map +1 -1
- package/dist/visualization/emit-event.d.ts +75 -0
- package/dist/visualization/emit-event.d.ts.map +1 -0
- package/dist/visualization/emit-event.js +213 -0
- package/dist/visualization/emit-event.js.map +1 -0
- package/dist/visualization/index.d.ts +1 -0
- package/dist/visualization/index.d.ts.map +1 -1
- package/dist/visualization/index.js +7 -1
- package/dist/visualization/index.js.map +1 -1
- package/docs/reference/skills.md +63 -1
- package/package.json +12 -4
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WorkflowOrchestrator - Adaptive workflow execution for QE agent swarms
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Adaptive strategy selection (parallel/sequential/hybrid)
|
|
7
|
+
* - Priority-based task queue with dependency resolution
|
|
8
|
+
* - Workflow checkpointing for recovery
|
|
9
|
+
* - Integration with SwarmOptimizer and FleetManager
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WorkflowOrchestrator = void 0;
|
|
13
|
+
const Logger_1 = require("../../utils/Logger");
|
|
14
|
+
const PriorityQueue_1 = require("./PriorityQueue");
|
|
15
|
+
class WorkflowOrchestrator {
|
|
16
|
+
constructor(memoryStore, eventBus, optimizer) {
|
|
17
|
+
this.isShutdown = false;
|
|
18
|
+
// Priority mapping for tasks
|
|
19
|
+
this.PRIORITY_VALUES = {
|
|
20
|
+
critical: 100,
|
|
21
|
+
high: 75,
|
|
22
|
+
medium: 50,
|
|
23
|
+
low: 25
|
|
24
|
+
};
|
|
25
|
+
this.logger = Logger_1.Logger.getInstance();
|
|
26
|
+
this.memoryStore = memoryStore;
|
|
27
|
+
this.eventBus = eventBus;
|
|
28
|
+
this.optimizer = optimizer;
|
|
29
|
+
this.workflows = new Map();
|
|
30
|
+
this.executions = new Map();
|
|
31
|
+
this.taskQueue = new PriorityQueue_1.PriorityQueue();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Initialize the orchestrator
|
|
35
|
+
*/
|
|
36
|
+
async initialize() {
|
|
37
|
+
this.logger.info('Initializing WorkflowOrchestrator');
|
|
38
|
+
// Load workflows from memory
|
|
39
|
+
await this.loadWorkflowsFromMemory();
|
|
40
|
+
// Subscribe to events
|
|
41
|
+
this.eventBus.subscribe('workflow:step:completed', this.handleStepCompleted.bind(this));
|
|
42
|
+
this.eventBus.subscribe('workflow:step:failed', this.handleStepFailed.bind(this));
|
|
43
|
+
this.eventBus.subscribe('agent:available', this.handleAgentAvailable.bind(this));
|
|
44
|
+
this.logger.info('WorkflowOrchestrator initialized successfully');
|
|
45
|
+
}
|
|
46
|
+
// ============= WORKFLOW MANAGEMENT =============
|
|
47
|
+
/**
|
|
48
|
+
* Register a new workflow
|
|
49
|
+
*/
|
|
50
|
+
registerWorkflow(workflow) {
|
|
51
|
+
this.logger.info(`Registering workflow: ${workflow.id} - ${workflow.name}`);
|
|
52
|
+
// Validate workflow
|
|
53
|
+
this.validateWorkflow(workflow);
|
|
54
|
+
this.workflows.set(workflow.id, workflow);
|
|
55
|
+
// Persist to memory
|
|
56
|
+
this.memoryStore.store(`workflows:registry:${workflow.id}`, workflow, { partition: 'workflows', ttl: 2592000 } // 30 days
|
|
57
|
+
).catch(err => this.logger.error('Failed to persist workflow:', err));
|
|
58
|
+
this.eventBus.emitAsync('workflow:registered', { workflowId: workflow.id });
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get a workflow by ID
|
|
62
|
+
*/
|
|
63
|
+
getWorkflow(id) {
|
|
64
|
+
return this.workflows.get(id);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* List all registered workflows
|
|
68
|
+
*/
|
|
69
|
+
listWorkflows() {
|
|
70
|
+
return Array.from(this.workflows.values());
|
|
71
|
+
}
|
|
72
|
+
// ============= EXECUTION =============
|
|
73
|
+
/**
|
|
74
|
+
* Execute a workflow
|
|
75
|
+
*/
|
|
76
|
+
async executeWorkflow(workflowId, inputs = {}) {
|
|
77
|
+
this.logger.info(`Executing workflow: ${workflowId}`);
|
|
78
|
+
const workflow = this.workflows.get(workflowId);
|
|
79
|
+
if (!workflow) {
|
|
80
|
+
throw new Error(`Workflow not found: ${workflowId}`);
|
|
81
|
+
}
|
|
82
|
+
// Create execution
|
|
83
|
+
const execution = {
|
|
84
|
+
id: this.generateExecutionId(),
|
|
85
|
+
workflowId,
|
|
86
|
+
status: 'running',
|
|
87
|
+
startedAt: new Date(),
|
|
88
|
+
completedSteps: [],
|
|
89
|
+
failedSteps: [],
|
|
90
|
+
results: new Map(),
|
|
91
|
+
metrics: {
|
|
92
|
+
totalDuration: 0,
|
|
93
|
+
stepDurations: new Map(),
|
|
94
|
+
retryCount: 0,
|
|
95
|
+
parallelization: 0
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
this.executions.set(execution.id, execution);
|
|
99
|
+
// Emit start event
|
|
100
|
+
await this.eventBus.emitAsync('workflow:started', {
|
|
101
|
+
executionId: execution.id,
|
|
102
|
+
workflowId
|
|
103
|
+
});
|
|
104
|
+
try {
|
|
105
|
+
// Create execution context
|
|
106
|
+
const context = {
|
|
107
|
+
executionId: execution.id,
|
|
108
|
+
workflowId,
|
|
109
|
+
inputs,
|
|
110
|
+
stepResults: new Map(),
|
|
111
|
+
startTime: Date.now()
|
|
112
|
+
};
|
|
113
|
+
// Resolve dependencies and create execution plan
|
|
114
|
+
const plan = this.resolveDependencies(workflow.steps);
|
|
115
|
+
// Select execution strategy
|
|
116
|
+
const strategy = workflow.strategy === 'adaptive'
|
|
117
|
+
? await this.selectStrategy(workflow, plan)
|
|
118
|
+
: workflow.strategy;
|
|
119
|
+
this.logger.info(`Using ${strategy} execution strategy`);
|
|
120
|
+
// Execute based on strategy
|
|
121
|
+
let results;
|
|
122
|
+
if (strategy === 'parallel') {
|
|
123
|
+
results = await this.executeParallel(workflow.steps, context);
|
|
124
|
+
}
|
|
125
|
+
else if (strategy === 'sequential') {
|
|
126
|
+
results = await this.executeSequential(workflow.steps, context);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Hybrid: execute phases in sequence, steps within phases in parallel
|
|
130
|
+
results = await this.executeHybrid(plan, context);
|
|
131
|
+
}
|
|
132
|
+
// Update execution with results
|
|
133
|
+
execution.status = 'completed';
|
|
134
|
+
execution.completedAt = new Date();
|
|
135
|
+
execution.results = results;
|
|
136
|
+
execution.metrics.totalDuration = Date.now() - context.startTime;
|
|
137
|
+
// Calculate parallelization metric
|
|
138
|
+
const totalStepTime = Array.from(results.values())
|
|
139
|
+
.reduce((sum, r) => sum + r.duration, 0);
|
|
140
|
+
execution.metrics.parallelization = totalStepTime / execution.metrics.totalDuration;
|
|
141
|
+
// Emit completion event
|
|
142
|
+
await this.eventBus.emitAsync('workflow:completed', {
|
|
143
|
+
executionId: execution.id,
|
|
144
|
+
workflowId,
|
|
145
|
+
duration: execution.metrics.totalDuration
|
|
146
|
+
});
|
|
147
|
+
this.logger.info(`Workflow ${workflowId} completed in ${execution.metrics.totalDuration}ms`);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
execution.status = 'failed';
|
|
151
|
+
execution.completedAt = new Date();
|
|
152
|
+
await this.eventBus.emitAsync('workflow:failed', {
|
|
153
|
+
executionId: execution.id,
|
|
154
|
+
workflowId,
|
|
155
|
+
error: error instanceof Error ? error.message : String(error)
|
|
156
|
+
});
|
|
157
|
+
this.logger.error(`Workflow ${workflowId} failed:`, error);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
// Persist execution
|
|
161
|
+
await this.persistExecution(execution);
|
|
162
|
+
}
|
|
163
|
+
return execution;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Pause a running execution
|
|
167
|
+
*/
|
|
168
|
+
async pauseExecution(executionId) {
|
|
169
|
+
const execution = this.executions.get(executionId);
|
|
170
|
+
if (!execution) {
|
|
171
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
172
|
+
}
|
|
173
|
+
if (execution.status !== 'running') {
|
|
174
|
+
throw new Error(`Cannot pause execution in ${execution.status} state`);
|
|
175
|
+
}
|
|
176
|
+
// Create checkpoint
|
|
177
|
+
const checkpoint = await this.createCheckpoint(executionId);
|
|
178
|
+
execution.checkpoint = checkpoint;
|
|
179
|
+
execution.status = 'paused';
|
|
180
|
+
await this.eventBus.emitAsync('workflow:paused', { executionId });
|
|
181
|
+
await this.persistExecution(execution);
|
|
182
|
+
this.logger.info(`Execution ${executionId} paused`);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Resume a paused execution
|
|
186
|
+
*/
|
|
187
|
+
async resumeExecution(executionId) {
|
|
188
|
+
const execution = this.executions.get(executionId);
|
|
189
|
+
if (!execution) {
|
|
190
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
191
|
+
}
|
|
192
|
+
if (execution.status !== 'paused') {
|
|
193
|
+
throw new Error(`Cannot resume execution in ${execution.status} state`);
|
|
194
|
+
}
|
|
195
|
+
if (!execution.checkpoint) {
|
|
196
|
+
throw new Error(`No checkpoint found for execution ${executionId}`);
|
|
197
|
+
}
|
|
198
|
+
execution.status = 'running';
|
|
199
|
+
await this.eventBus.emitAsync('workflow:resumed', { executionId });
|
|
200
|
+
this.logger.info(`Execution ${executionId} resumed`);
|
|
201
|
+
// Continue execution from checkpoint
|
|
202
|
+
// This would be implemented based on specific requirements
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Cancel a running or paused execution
|
|
206
|
+
*/
|
|
207
|
+
async cancelExecution(executionId) {
|
|
208
|
+
const execution = this.executions.get(executionId);
|
|
209
|
+
if (!execution) {
|
|
210
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
211
|
+
}
|
|
212
|
+
if (execution.status === 'completed' || execution.status === 'failed') {
|
|
213
|
+
throw new Error(`Cannot cancel execution in ${execution.status} state`);
|
|
214
|
+
}
|
|
215
|
+
execution.status = 'failed';
|
|
216
|
+
execution.completedAt = new Date();
|
|
217
|
+
await this.eventBus.emitAsync('workflow:cancelled', { executionId });
|
|
218
|
+
await this.persistExecution(execution);
|
|
219
|
+
this.logger.info(`Execution ${executionId} cancelled`);
|
|
220
|
+
}
|
|
221
|
+
// ============= STRATEGY SELECTION =============
|
|
222
|
+
/**
|
|
223
|
+
* Select optimal execution strategy based on workflow characteristics
|
|
224
|
+
*/
|
|
225
|
+
async selectStrategy(workflow, plan) {
|
|
226
|
+
const workload = this.analyzeWorkloadCharacteristics(workflow.steps);
|
|
227
|
+
// Decision logic:
|
|
228
|
+
// - High parallelizability + low interdependencies → parallel
|
|
229
|
+
// - Low parallelizability or high interdependencies → sequential
|
|
230
|
+
// - Mixed characteristics → hybrid
|
|
231
|
+
if (workload.parallelizability > 0.7 && workload.interdependencies < 0.3) {
|
|
232
|
+
return 'parallel';
|
|
233
|
+
}
|
|
234
|
+
if (workload.parallelizability < 0.3 || workload.interdependencies > 0.7) {
|
|
235
|
+
return 'sequential';
|
|
236
|
+
}
|
|
237
|
+
return 'hybrid';
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Analyze workload characteristics
|
|
241
|
+
*/
|
|
242
|
+
analyzeWorkloadCharacteristics(steps) {
|
|
243
|
+
const totalSteps = steps.length;
|
|
244
|
+
// Calculate average complexity (based on timeout as proxy)
|
|
245
|
+
const avgComplexity = steps.reduce((sum, s) => sum + (s.timeout / 60000), 0) / totalSteps;
|
|
246
|
+
// Calculate parallelizability (steps with no dependencies)
|
|
247
|
+
const parallelSteps = steps.filter(s => s.dependencies.length === 0).length;
|
|
248
|
+
const parallelizability = parallelSteps / totalSteps;
|
|
249
|
+
// Calculate interdependencies
|
|
250
|
+
const totalDependencies = steps.reduce((sum, s) => sum + s.dependencies.length, 0);
|
|
251
|
+
const interdependencies = totalDependencies / (totalSteps * totalSteps);
|
|
252
|
+
// Estimate resource intensity from timeout and priority
|
|
253
|
+
const criticalSteps = steps.filter(s => s.priority === 'critical').length;
|
|
254
|
+
const resourceIntensity = (criticalSteps / totalSteps + avgComplexity) / 2;
|
|
255
|
+
return {
|
|
256
|
+
stepCount: totalSteps,
|
|
257
|
+
averageComplexity: Math.min(1, avgComplexity / 10), // normalize to 0-1
|
|
258
|
+
parallelizability,
|
|
259
|
+
resourceIntensity: Math.min(1, resourceIntensity),
|
|
260
|
+
interdependencies: Math.min(1, interdependencies)
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// ============= DEPENDENCY RESOLUTION =============
|
|
264
|
+
/**
|
|
265
|
+
* Resolve dependencies and create execution plan
|
|
266
|
+
*/
|
|
267
|
+
resolveDependencies(steps) {
|
|
268
|
+
// Check for cycles
|
|
269
|
+
if (this.detectCycles(steps)) {
|
|
270
|
+
throw new Error('Workflow contains circular dependencies');
|
|
271
|
+
}
|
|
272
|
+
// Perform topological sort
|
|
273
|
+
const sortedStepIds = this.topologicalSort(steps);
|
|
274
|
+
// Group steps into phases (steps that can run in parallel)
|
|
275
|
+
const phases = this.groupIntoPhases(steps, sortedStepIds);
|
|
276
|
+
// Calculate critical path (longest dependency chain)
|
|
277
|
+
const criticalPath = this.calculateCriticalPath(steps);
|
|
278
|
+
// Estimate total duration based on critical path
|
|
279
|
+
const estimatedDuration = criticalPath
|
|
280
|
+
.map(stepId => steps.find(s => s.id === stepId))
|
|
281
|
+
.filter(s => s !== undefined)
|
|
282
|
+
.reduce((sum, step) => sum + step.timeout, 0);
|
|
283
|
+
return {
|
|
284
|
+
phases,
|
|
285
|
+
criticalPath,
|
|
286
|
+
estimatedDuration
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Topological sort using Kahn's algorithm
|
|
291
|
+
*/
|
|
292
|
+
topologicalSort(steps) {
|
|
293
|
+
const sorted = [];
|
|
294
|
+
const inDegree = new Map();
|
|
295
|
+
const adjList = new Map();
|
|
296
|
+
// Build adjacency list and in-degree map
|
|
297
|
+
for (const step of steps) {
|
|
298
|
+
inDegree.set(step.id, step.dependencies.length);
|
|
299
|
+
adjList.set(step.id, []);
|
|
300
|
+
}
|
|
301
|
+
for (const step of steps) {
|
|
302
|
+
for (const dep of step.dependencies) {
|
|
303
|
+
const neighbors = adjList.get(dep) || [];
|
|
304
|
+
neighbors.push(step.id);
|
|
305
|
+
adjList.set(dep, neighbors);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Find all steps with no dependencies
|
|
309
|
+
const queue = [];
|
|
310
|
+
for (const [stepId, degree] of inDegree.entries()) {
|
|
311
|
+
if (degree === 0) {
|
|
312
|
+
queue.push(stepId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Process queue
|
|
316
|
+
while (queue.length > 0) {
|
|
317
|
+
const stepId = queue.shift();
|
|
318
|
+
sorted.push(stepId);
|
|
319
|
+
const neighbors = adjList.get(stepId) || [];
|
|
320
|
+
for (const neighbor of neighbors) {
|
|
321
|
+
const degree = inDegree.get(neighbor) - 1;
|
|
322
|
+
inDegree.set(neighbor, degree);
|
|
323
|
+
if (degree === 0) {
|
|
324
|
+
queue.push(neighbor);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return sorted;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Detect circular dependencies using DFS
|
|
332
|
+
*/
|
|
333
|
+
detectCycles(steps) {
|
|
334
|
+
const visited = new Set();
|
|
335
|
+
const recursionStack = new Set();
|
|
336
|
+
const hasCycle = (stepId) => {
|
|
337
|
+
visited.add(stepId);
|
|
338
|
+
recursionStack.add(stepId);
|
|
339
|
+
const step = steps.find(s => s.id === stepId);
|
|
340
|
+
if (!step)
|
|
341
|
+
return false;
|
|
342
|
+
for (const dep of step.dependencies) {
|
|
343
|
+
if (!visited.has(dep)) {
|
|
344
|
+
if (hasCycle(dep)) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else if (recursionStack.has(dep)) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
recursionStack.delete(stepId);
|
|
353
|
+
return false;
|
|
354
|
+
};
|
|
355
|
+
for (const step of steps) {
|
|
356
|
+
if (!visited.has(step.id)) {
|
|
357
|
+
if (hasCycle(step.id)) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Group steps into parallel execution phases
|
|
366
|
+
*/
|
|
367
|
+
groupIntoPhases(steps, sortedStepIds) {
|
|
368
|
+
const phases = [];
|
|
369
|
+
const processedSteps = new Set();
|
|
370
|
+
let phaseId = 0;
|
|
371
|
+
while (processedSteps.size < steps.length) {
|
|
372
|
+
// Find steps whose dependencies are all processed
|
|
373
|
+
const readySteps = steps.filter(step => {
|
|
374
|
+
if (processedSteps.has(step.id))
|
|
375
|
+
return false;
|
|
376
|
+
return step.dependencies.every(dep => processedSteps.has(dep));
|
|
377
|
+
});
|
|
378
|
+
if (readySteps.length === 0)
|
|
379
|
+
break;
|
|
380
|
+
phases.push({
|
|
381
|
+
id: `phase-${phaseId++}`,
|
|
382
|
+
steps: readySteps,
|
|
383
|
+
isParallel: readySteps.length > 1,
|
|
384
|
+
dependencies: readySteps.flatMap(s => s.dependencies).filter(d => !processedSteps.has(d))
|
|
385
|
+
});
|
|
386
|
+
readySteps.forEach(step => processedSteps.add(step.id));
|
|
387
|
+
}
|
|
388
|
+
return phases;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Calculate critical path (longest dependency chain)
|
|
392
|
+
*/
|
|
393
|
+
calculateCriticalPath(steps) {
|
|
394
|
+
const memo = new Map();
|
|
395
|
+
const findLongestPath = (stepId) => {
|
|
396
|
+
if (memo.has(stepId)) {
|
|
397
|
+
return memo.get(stepId);
|
|
398
|
+
}
|
|
399
|
+
const step = steps.find(s => s.id === stepId);
|
|
400
|
+
if (!step || step.dependencies.length === 0) {
|
|
401
|
+
memo.set(stepId, [stepId]);
|
|
402
|
+
return [stepId];
|
|
403
|
+
}
|
|
404
|
+
let longestPath = [];
|
|
405
|
+
for (const dep of step.dependencies) {
|
|
406
|
+
const path = findLongestPath(dep);
|
|
407
|
+
if (path.length > longestPath.length) {
|
|
408
|
+
longestPath = path;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const result = [...longestPath, stepId];
|
|
412
|
+
memo.set(stepId, result);
|
|
413
|
+
return result;
|
|
414
|
+
};
|
|
415
|
+
// Find longest path among all terminal steps
|
|
416
|
+
const terminalSteps = steps.filter(step => !steps.some(s => s.dependencies.includes(step.id)));
|
|
417
|
+
let criticalPath = [];
|
|
418
|
+
for (const step of terminalSteps) {
|
|
419
|
+
const path = findLongestPath(step.id);
|
|
420
|
+
if (path.length > criticalPath.length) {
|
|
421
|
+
criticalPath = path;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return criticalPath;
|
|
425
|
+
}
|
|
426
|
+
// ============= STEP EXECUTION =============
|
|
427
|
+
/**
|
|
428
|
+
* Execute a single step
|
|
429
|
+
*/
|
|
430
|
+
async executeStep(step, context) {
|
|
431
|
+
const startTime = Date.now();
|
|
432
|
+
let retryCount = 0;
|
|
433
|
+
let lastError;
|
|
434
|
+
this.logger.debug(`Executing step: ${step.id} - ${step.name}`);
|
|
435
|
+
// Emit start event
|
|
436
|
+
await this.eventBus.emitAsync('workflow:step:started', {
|
|
437
|
+
executionId: context.executionId,
|
|
438
|
+
stepId: step.id
|
|
439
|
+
});
|
|
440
|
+
while (retryCount <= step.retries) {
|
|
441
|
+
try {
|
|
442
|
+
// Resolve inputs from previous step results
|
|
443
|
+
const resolvedInputs = this.resolveStepInputs(step, context);
|
|
444
|
+
// Execute step with timeout
|
|
445
|
+
const output = await this.executeStepWithTimeout(step, resolvedInputs, context);
|
|
446
|
+
const duration = Date.now() - startTime;
|
|
447
|
+
const result = {
|
|
448
|
+
stepId: step.id,
|
|
449
|
+
status: 'success',
|
|
450
|
+
output,
|
|
451
|
+
duration,
|
|
452
|
+
retryCount
|
|
453
|
+
};
|
|
454
|
+
// Update execution
|
|
455
|
+
const execution = this.executions.get(context.executionId);
|
|
456
|
+
if (execution) {
|
|
457
|
+
execution.completedSteps.push(step.id);
|
|
458
|
+
execution.metrics.stepDurations.set(step.id, duration);
|
|
459
|
+
}
|
|
460
|
+
// Emit success event
|
|
461
|
+
await this.eventBus.emitAsync('workflow:step:completed', {
|
|
462
|
+
executionId: context.executionId,
|
|
463
|
+
stepId: step.id,
|
|
464
|
+
duration
|
|
465
|
+
});
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
470
|
+
retryCount++;
|
|
471
|
+
if (retryCount <= step.retries) {
|
|
472
|
+
this.logger.warn(`Step ${step.id} failed, retrying (${retryCount}/${step.retries})`);
|
|
473
|
+
await this.delay(1000 * retryCount); // Exponential backoff
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// All retries exhausted
|
|
478
|
+
const duration = Date.now() - startTime;
|
|
479
|
+
const result = {
|
|
480
|
+
stepId: step.id,
|
|
481
|
+
status: 'failed',
|
|
482
|
+
output: null,
|
|
483
|
+
error: lastError,
|
|
484
|
+
duration,
|
|
485
|
+
retryCount: retryCount - 1
|
|
486
|
+
};
|
|
487
|
+
// Update execution
|
|
488
|
+
const execution = this.executions.get(context.executionId);
|
|
489
|
+
if (execution) {
|
|
490
|
+
execution.failedSteps.push(step.id);
|
|
491
|
+
execution.metrics.retryCount += retryCount - 1;
|
|
492
|
+
}
|
|
493
|
+
// Emit failure event
|
|
494
|
+
await this.eventBus.emitAsync('workflow:step:failed', {
|
|
495
|
+
executionId: context.executionId,
|
|
496
|
+
stepId: step.id,
|
|
497
|
+
error: lastError?.message
|
|
498
|
+
});
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Execute step with timeout
|
|
503
|
+
*/
|
|
504
|
+
async executeStepWithTimeout(step, inputs, context) {
|
|
505
|
+
return new Promise((resolve, reject) => {
|
|
506
|
+
const timer = setTimeout(() => {
|
|
507
|
+
reject(new Error(`Step ${step.id} timed out after ${step.timeout}ms`));
|
|
508
|
+
}, step.timeout);
|
|
509
|
+
// Simulate step execution (in real implementation, this would call agent)
|
|
510
|
+
this.executeStepAction(step, inputs, context)
|
|
511
|
+
.then(result => {
|
|
512
|
+
clearTimeout(timer);
|
|
513
|
+
resolve(result);
|
|
514
|
+
})
|
|
515
|
+
.catch(error => {
|
|
516
|
+
clearTimeout(timer);
|
|
517
|
+
reject(error);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Execute step action (placeholder for agent invocation)
|
|
523
|
+
*/
|
|
524
|
+
async executeStepAction(step, inputs, context) {
|
|
525
|
+
// In real implementation, this would:
|
|
526
|
+
// 1. Allocate agent using optimizer
|
|
527
|
+
// 2. Invoke agent with step action and inputs
|
|
528
|
+
// 3. Return agent's output
|
|
529
|
+
this.logger.debug(`Executing action: ${step.action} with inputs:`, inputs);
|
|
530
|
+
// Placeholder: return mock result
|
|
531
|
+
return {
|
|
532
|
+
stepId: step.id,
|
|
533
|
+
agentType: step.agentType,
|
|
534
|
+
action: step.action,
|
|
535
|
+
inputs,
|
|
536
|
+
timestamp: Date.now()
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Resolve step inputs from previous results
|
|
541
|
+
*/
|
|
542
|
+
resolveStepInputs(step, context) {
|
|
543
|
+
const resolved = { ...step.inputs };
|
|
544
|
+
// Replace references to previous step outputs
|
|
545
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
546
|
+
if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) {
|
|
547
|
+
const ref = value.slice(2, -1);
|
|
548
|
+
const [stepId, outputKey] = ref.split('.');
|
|
549
|
+
const stepResult = context.stepResults.get(stepId);
|
|
550
|
+
if (stepResult && stepResult.output) {
|
|
551
|
+
resolved[key] = outputKey
|
|
552
|
+
? stepResult.output[outputKey]
|
|
553
|
+
: stepResult.output;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return resolved;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Execute steps in parallel
|
|
561
|
+
*/
|
|
562
|
+
async executeParallel(steps, context) {
|
|
563
|
+
this.logger.info(`Executing ${steps.length} steps in parallel`);
|
|
564
|
+
const results = new Map();
|
|
565
|
+
// Execute all steps concurrently
|
|
566
|
+
const promises = steps.map(step => this.executeStep(step, context));
|
|
567
|
+
const stepResults = await Promise.allSettled(promises);
|
|
568
|
+
// Collect results
|
|
569
|
+
for (let i = 0; i < steps.length; i++) {
|
|
570
|
+
const step = steps[i];
|
|
571
|
+
const result = stepResults[i];
|
|
572
|
+
if (result.status === 'fulfilled') {
|
|
573
|
+
results.set(step.id, result.value);
|
|
574
|
+
context.stepResults.set(step.id, result.value);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
results.set(step.id, {
|
|
578
|
+
stepId: step.id,
|
|
579
|
+
status: 'failed',
|
|
580
|
+
output: null,
|
|
581
|
+
error: result.reason,
|
|
582
|
+
duration: 0,
|
|
583
|
+
retryCount: 0
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return results;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Execute steps sequentially
|
|
591
|
+
*/
|
|
592
|
+
async executeSequential(steps, context) {
|
|
593
|
+
this.logger.info(`Executing ${steps.length} steps sequentially`);
|
|
594
|
+
const results = new Map();
|
|
595
|
+
for (const step of steps) {
|
|
596
|
+
const result = await this.executeStep(step, context);
|
|
597
|
+
results.set(step.id, result);
|
|
598
|
+
context.stepResults.set(step.id, result);
|
|
599
|
+
// Stop on failure if step is critical
|
|
600
|
+
if (result.status === 'failed' && step.priority === 'critical') {
|
|
601
|
+
throw new Error(`Critical step ${step.id} failed`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return results;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Execute in hybrid mode (phases sequential, steps within phase parallel)
|
|
608
|
+
*/
|
|
609
|
+
async executeHybrid(plan, context) {
|
|
610
|
+
this.logger.info(`Executing ${plan.phases.length} phases in hybrid mode`);
|
|
611
|
+
const results = new Map();
|
|
612
|
+
for (const phase of plan.phases) {
|
|
613
|
+
this.logger.debug(`Executing phase ${phase.id} with ${phase.steps.length} steps`);
|
|
614
|
+
if (phase.isParallel) {
|
|
615
|
+
const phaseResults = await this.executeParallel(phase.steps, context);
|
|
616
|
+
phaseResults.forEach((result, stepId) => results.set(stepId, result));
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
const phaseResults = await this.executeSequential(phase.steps, context);
|
|
620
|
+
phaseResults.forEach((result, stepId) => results.set(stepId, result));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return results;
|
|
624
|
+
}
|
|
625
|
+
// ============= CHECKPOINTING =============
|
|
626
|
+
/**
|
|
627
|
+
* Create a checkpoint for an execution
|
|
628
|
+
*/
|
|
629
|
+
async createCheckpoint(executionId) {
|
|
630
|
+
const execution = this.executions.get(executionId);
|
|
631
|
+
if (!execution) {
|
|
632
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
633
|
+
}
|
|
634
|
+
const checkpoint = {
|
|
635
|
+
executionId,
|
|
636
|
+
timestamp: new Date(),
|
|
637
|
+
completedSteps: [...execution.completedSteps],
|
|
638
|
+
stepResults: new Map(execution.results),
|
|
639
|
+
state: {
|
|
640
|
+
status: execution.status,
|
|
641
|
+
currentStep: execution.currentStep
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
// Persist checkpoint
|
|
645
|
+
await this.memoryStore.store(`workflows:checkpoints:${executionId}:${Date.now()}`, this.serializeCheckpoint(checkpoint), { partition: 'workflows', ttl: 604800 } // 7 days
|
|
646
|
+
);
|
|
647
|
+
this.logger.info(`Created checkpoint for execution ${executionId}`);
|
|
648
|
+
return checkpoint;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Restore execution from checkpoint
|
|
652
|
+
*/
|
|
653
|
+
async restoreFromCheckpoint(checkpoint) {
|
|
654
|
+
this.logger.info(`Restoring execution from checkpoint: ${checkpoint.executionId}`);
|
|
655
|
+
const execution = this.executions.get(checkpoint.executionId);
|
|
656
|
+
if (!execution) {
|
|
657
|
+
throw new Error(`Execution not found: ${checkpoint.executionId}`);
|
|
658
|
+
}
|
|
659
|
+
execution.completedSteps = [...checkpoint.completedSteps];
|
|
660
|
+
execution.results = new Map(checkpoint.stepResults);
|
|
661
|
+
execution.checkpoint = checkpoint;
|
|
662
|
+
execution.status = checkpoint.state.status;
|
|
663
|
+
return execution;
|
|
664
|
+
}
|
|
665
|
+
// ============= QUEUE MANAGEMENT =============
|
|
666
|
+
/**
|
|
667
|
+
* Enqueue a task with priority
|
|
668
|
+
*/
|
|
669
|
+
enqueueTask(task) {
|
|
670
|
+
this.taskQueue.enqueue(task, task.priority);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Dequeue highest priority task
|
|
674
|
+
*/
|
|
675
|
+
dequeueTask() {
|
|
676
|
+
return this.taskQueue.dequeue();
|
|
677
|
+
}
|
|
678
|
+
// ============= METRICS =============
|
|
679
|
+
/**
|
|
680
|
+
* Get execution metrics
|
|
681
|
+
*/
|
|
682
|
+
getExecutionMetrics(executionId) {
|
|
683
|
+
const execution = this.executions.get(executionId);
|
|
684
|
+
if (!execution) {
|
|
685
|
+
throw new Error(`Execution not found: ${executionId}`);
|
|
686
|
+
}
|
|
687
|
+
return execution.metrics;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Get execution by ID
|
|
691
|
+
*/
|
|
692
|
+
getExecution(executionId) {
|
|
693
|
+
return this.executions.get(executionId);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* List all executions
|
|
697
|
+
*/
|
|
698
|
+
listExecutions() {
|
|
699
|
+
return Array.from(this.executions.values());
|
|
700
|
+
}
|
|
701
|
+
// ============= HELPERS =============
|
|
702
|
+
/**
|
|
703
|
+
* Validate workflow structure
|
|
704
|
+
*/
|
|
705
|
+
validateWorkflow(workflow) {
|
|
706
|
+
if (!workflow.id || !workflow.name) {
|
|
707
|
+
throw new Error('Workflow must have id and name');
|
|
708
|
+
}
|
|
709
|
+
if (!workflow.steps || workflow.steps.length === 0) {
|
|
710
|
+
throw new Error('Workflow must have at least one step');
|
|
711
|
+
}
|
|
712
|
+
// Validate step dependencies exist
|
|
713
|
+
const stepIds = new Set(workflow.steps.map(s => s.id));
|
|
714
|
+
for (const step of workflow.steps) {
|
|
715
|
+
for (const dep of step.dependencies) {
|
|
716
|
+
if (!stepIds.has(dep)) {
|
|
717
|
+
throw new Error(`Step ${step.id} depends on non-existent step: ${dep}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Generate unique execution ID
|
|
724
|
+
*/
|
|
725
|
+
generateExecutionId() {
|
|
726
|
+
return `exec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Serialize checkpoint for storage
|
|
730
|
+
*/
|
|
731
|
+
serializeCheckpoint(checkpoint) {
|
|
732
|
+
return {
|
|
733
|
+
...checkpoint,
|
|
734
|
+
stepResults: Array.from(checkpoint.stepResults.entries())
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Persist execution to memory
|
|
739
|
+
*/
|
|
740
|
+
async persistExecution(execution) {
|
|
741
|
+
const serialized = {
|
|
742
|
+
...execution,
|
|
743
|
+
results: Array.from(execution.results.entries()),
|
|
744
|
+
metrics: {
|
|
745
|
+
...execution.metrics,
|
|
746
|
+
stepDurations: Array.from(execution.metrics.stepDurations.entries())
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
await this.memoryStore.store(`workflows:executions:${execution.id}`, serialized, { partition: 'workflows', ttl: 604800 } // 7 days
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Load workflows from memory
|
|
754
|
+
*/
|
|
755
|
+
async loadWorkflowsFromMemory() {
|
|
756
|
+
try {
|
|
757
|
+
const entries = await this.memoryStore.query('workflows:registry:%', {
|
|
758
|
+
partition: 'workflows'
|
|
759
|
+
});
|
|
760
|
+
for (const entry of entries) {
|
|
761
|
+
const workflow = entry.value;
|
|
762
|
+
this.workflows.set(workflow.id, workflow);
|
|
763
|
+
}
|
|
764
|
+
this.logger.info(`Loaded ${this.workflows.size} workflows from memory`);
|
|
765
|
+
}
|
|
766
|
+
catch (error) {
|
|
767
|
+
this.logger.warn('Failed to load workflows from memory:', error);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Delay helper
|
|
772
|
+
*/
|
|
773
|
+
delay(ms) {
|
|
774
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Event handlers
|
|
778
|
+
*/
|
|
779
|
+
async handleStepCompleted(data) {
|
|
780
|
+
this.logger.debug(`Step completed: ${data.stepId}`);
|
|
781
|
+
}
|
|
782
|
+
async handleStepFailed(data) {
|
|
783
|
+
this.logger.error(`Step failed: ${data.stepId} - ${data.error}`);
|
|
784
|
+
}
|
|
785
|
+
async handleAgentAvailable(data) {
|
|
786
|
+
// Process queued tasks when agent becomes available
|
|
787
|
+
if (!this.taskQueue.isEmpty()) {
|
|
788
|
+
const task = this.dequeueTask();
|
|
789
|
+
if (task) {
|
|
790
|
+
this.logger.debug(`Assigning queued task ${task.id} to available agent`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// ============= CLEANUP =============
|
|
795
|
+
/**
|
|
796
|
+
* Shutdown orchestrator
|
|
797
|
+
*/
|
|
798
|
+
async shutdown() {
|
|
799
|
+
this.logger.info('Shutting down WorkflowOrchestrator');
|
|
800
|
+
this.isShutdown = true;
|
|
801
|
+
// Cancel all running executions
|
|
802
|
+
for (const execution of this.executions.values()) {
|
|
803
|
+
if (execution.status === 'running' || execution.status === 'paused') {
|
|
804
|
+
await this.cancelExecution(execution.id);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// Clear queue
|
|
808
|
+
this.taskQueue.clear();
|
|
809
|
+
this.logger.info('WorkflowOrchestrator shutdown complete');
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
exports.WorkflowOrchestrator = WorkflowOrchestrator;
|
|
813
|
+
//# sourceMappingURL=WorkflowOrchestrator.js.map
|