UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

544 lines 22.7 kB
/** * Enhanced Swarm Coordinator * Implements hierarchical model strategy with intelligent task decomposition * Provides advanced AI-powered coordination for multi-agent systems */ import { EventEmitter } from "events"; import { logger } from "../core/logger.js"; import { CodeAgent } from "./agents/code-agent.js"; import { StrategyFactory } from "./strategy-factory.js"; /** * Enhanced Swarm Coordinator with AI-powered task management * Features: * - Hierarchical model selection based on task complexity * - Intelligent context window management * - Advanced task decomposition and analysis * - Multi-agent coordination with specialized roles */ export class EnhancedSwarmCoordinator extends EventEmitter { agents = new Map(); taskQueue = []; activeTasksMap = new Map(); modelConfig; contextCache = new Map(); strategy; strategyInstance; constructor(config) { super(); this.modelConfig = config.modelConfig; this.strategy = config.strategy; this.strategyInstance = StrategyFactory.createStrategy(config.strategy); this.initializeAgents(config.maxAgents || 5); this.setupEventHandlers(); logger.info(`Enhanced Swarm Coordinator initialized with strategy: ${config.strategy}`); } setupEventHandlers() { // Setup event handlers for agent communication this.on("taskCompleted", this.handleAgentTaskCompleted.bind(this)); this.on("taskError", this.handleAgentTaskError.bind(this)); logger.info("Event handlers setup complete"); } initializeAgents(maxAgents) { // Create specialized agents with hierarchical model configuration for (let i = 0; i < maxAgents; i++) { const agentId = `enhanced-agent-${i}`; const agent = new CodeAgent(agentId, { models: { primary: this.modelConfig.primary, apply: this.modelConfig.apply, review: this.modelConfig.review, }, threshold: this.modelConfig.threshold, }); // Set up agent event listeners agent.on("taskCompleted", this.handleAgentTaskCompleted.bind(this)); agent.on("taskError", this.handleAgentTaskError.bind(this)); this.agents.set(agentId, agent); logger.info(`Initialized ${agent.getType()} agent: ${agentId}`); } } async addObjective(objective) { logger.info(`Adding objective: ${objective}`); // Decompose objective using primary model (complex reasoning) const tasks = await this.decomposeObjective(objective); // Analyze each task complexity and assign appropriate models const analyzedTasks = await Promise.all(tasks.map(task => this.analyzeTaskComplexity(task))); // Add tasks to queue with enhanced metadata for (const analysis of analyzedTasks) { const enhancedTask = { ...analysis.task, metadata: { ...analysis.task.metadata, customProperties: { complexity: analysis.complexity, suggestedModel: analysis.suggestedModel, estimatedTokens: analysis.estimatedTokens, requiredContext: analysis.requiredContext, }, }, }; this.taskQueue.push(enhancedTask); } const objectiveId = `obj-${Date.now()}`; logger.info(`Created objective ${objectiveId} with ${analyzedTasks.length} tasks`); this.emit("objectiveAdded", { objectiveId, tasks: analyzedTasks.length }); // Start processing tasks asynchronously this.processTasks().catch(error => { logger.error("Error processing tasks:", error); this.emit("error", error); }); return objectiveId; } async decomposeObjective(objective) { // Enhanced task decomposition using the selected strategy logger.info(`Decomposing objective with ${this.strategy} strategy: ${objective}`); // Create SwarmObjective for the strategy const swarmObjective = { id: `objective-${Date.now()}`, name: objective.substring(0, 50), // Truncate for name description: objective, strategy: this.strategy, mode: "centralized", requirements: { minAgents: 1, maxAgents: 5, agentTypes: ["developer", "researcher", "analyzer"], estimatedDuration: 60, maxDuration: 120, qualityThreshold: 0.8, reviewCoverage: 0.5, testCoverage: 0.7, reliabilityTarget: 0.9, }, constraints: { milestones: [], resourceLimits: {}, minQuality: 0.8, requiredApprovals: [], allowedFailures: 1, recoveryTime: 300, }, tasks: [], dependencies: [], status: "planning", progress: { totalTasks: 0, completedTasks: 0, failedTasks: 0, runningTasks: 0, estimatedCompletion: new Date(Date.now() + 60 * 60 * 1000), timeRemaining: 3600, percentComplete: 0, averageQuality: 0, passedReviews: 0, passedTests: 0, resourceUtilization: {}, costSpent: 0, activeAgents: 0, idleAgents: 0, busyAgents: 0, }, metrics: { throughput: 0, latency: 0, efficiency: 0, reliability: 0, averageQuality: 0, defectRate: 0, reworkRate: 0, resourceUtilization: {}, costEfficiency: 0, agentUtilization: 0, agentSatisfaction: 0, collaborationEffectiveness: 0, scheduleVariance: 0, deadlineAdherence: 0, }, createdAt: new Date(), updatedAt: new Date(), }; try { // Use the strategy instance to decompose the objective const decompositionResult = await this.strategyInstance.decomposeObjective(swarmObjective); logger.info(`Strategy decomposed objective into ${decompositionResult.tasks.length} tasks`); logger.info(`Estimated duration: ${decompositionResult.estimatedDuration} minutes`); // Update task dependencies based on the decomposition result decompositionResult.tasks.forEach(task => { const dependencies = decompositionResult.dependencies.get(task.id.id) || []; task.constraints.dependencies = dependencies.map(depId => ({ id: depId, swarmId: task.id.swarmId, sequence: 1, priority: 1, })); }); return decompositionResult.tasks; } catch (error) { logger.error(`Error decomposing objective with ${this.strategy} strategy:`, error); // Fallback to simple task creation return this.createFallbackTasks(objective); } } createFallbackTasks(objective) { const timestamp = Date.now(); return [{ id: { id: `fallback-task-${timestamp}`, swarmId: "enhanced-swarm", sequence: 1, priority: 1, }, name: "Execute Objective", description: `Execute: ${objective}`, status: "created", priority: "high", type: "research", requirements: { capabilities: ["research", "analysis"], tools: ["memory"], permissions: ["read", "write"], }, constraints: { dependencies: [], dependents: [], conflicts: [], }, input: { objective }, instructions: `Execute the objective: ${objective}`, context: {}, createdAt: new Date(), updatedAt: new Date(), attempts: [], statusHistory: [], }]; } async analyzeTaskComplexity(task) { // Analyze task using intelligent complexity detection patterns const description = task.description.toLowerCase(); const name = task.name.toLowerCase(); let complexity = "simple"; let estimatedTokens = 1000; let suggestedModel = this.modelConfig.apply; // Complex task indicators const complexIndicators = [ "architecture", "system design", "integration", "security", "performance optimization", "database schema", "api design", ]; // Medium task indicators const mediumIndicators = [ "implement", "refactor", "optimize", "validate", "test", "function", "method", "class", "component", ]; if (complexIndicators.some(indicator => description.includes(indicator) || name.includes(indicator))) { complexity = "complex"; estimatedTokens = 4000; suggestedModel = this.modelConfig.primary; } else if (mediumIndicators.some(indicator => description.includes(indicator) || name.includes(indicator))) { complexity = "medium"; estimatedTokens = 2000; suggestedModel = this.modelConfig.primary; } // Extract required context files const requiredContext = this.extractContextFiles(`${task.description} ${task.instructions || ""}`); return { task, complexity, estimatedTokens, requiredContext, suggestedModel, }; } extractContextFiles(text) { // Extract file paths and imports from task description const filePatterns = [ /[\w\-\.\/]+\.(ts|js|tsx|jsx|py|java|cpp|h|css|html|md|json|yaml|yml)/g, /from\s+['"]([^'"]+)['"]/g, /import\s+['"]([^'"]+)['"]/g, ]; const files = new Set(); for (const pattern of filePatterns) { const matches = text.match(pattern); if (matches) { matches.forEach(match => files.add(match)); } } return Array.from(files); } determineChunkType(file) { const keywords = file.toLowerCase().split(/[\s\-_\.\/]/); if (keywords.includes("test") || keywords.includes("testing")) { return "test"; } if (keywords.includes("config") || keywords.includes("configuration")) { return "config"; } if (keywords.includes("type") || keywords.includes("interface")) { return "module"; } // Default to module for other files return "module"; } async processTasks() { logger.info("Starting task processing..."); while (this.taskQueue.length > 0 || this.activeTasksMap.size > 0) { const nextTask = this.getNextTask(); if (!nextTask) { // No tasks ready, wait for dependencies to complete await new Promise(resolve => setTimeout(resolve, 1000)); continue; } const availableAgents = Array.from(this.agents.values()).filter(agent => agent.isAvailable()); if (availableAgents.length === 0) { // No agents available, wait logger.info("No agents available, waiting..."); await new Promise(resolve => setTimeout(resolve, 1000)); continue; } const bestAgent = this.findBestAgent(nextTask, availableAgents); if (!bestAgent) { // No suitable agent found - try to make agents more flexible logger.warn(`No suitable agent found for task: ${nextTask.name}`); // Try with any available agent as fallback const fallbackAgent = availableAgents[0]; if (fallbackAgent) { logger.info(`Using fallback agent ${fallbackAgent.getId()} for task: ${nextTask.name}`); // Prepare context for the agent await this.prepareContextWindow(nextTask, fallbackAgent); // Remove task from queue and add to active tasks const taskIndex = this.taskQueue.indexOf(nextTask); this.taskQueue.splice(taskIndex, 1); this.activeTasksMap.set(nextTask.id.id, nextTask); // Assign task to fallback agent logger.info(`Assigning task ${nextTask.id.id} to fallback agent ${fallbackAgent.getId()}`); this.emit("taskStarted", { taskName: nextTask.name, taskId: nextTask.id.id, agentId: fallbackAgent.getId(), }); await fallbackAgent.assignTask(nextTask); } else { // Still no agent, wait and retry await new Promise(resolve => setTimeout(resolve, 2000)); } continue; } // Found suitable agent await this.prepareContextWindow(nextTask, bestAgent); // Remove task from queue and add to active tasks const taskIndex = this.taskQueue.indexOf(nextTask); this.taskQueue.splice(taskIndex, 1); this.activeTasksMap.set(nextTask.id.id, nextTask); // Assign task to agent logger.info(`Assigning task ${nextTask.id.id} to agent ${bestAgent.getId()}`); this.emit("taskStarted", { taskName: nextTask.name, taskId: nextTask.id.id, agentId: bestAgent.getId(), }); await bestAgent.assignTask(nextTask); } } getNextTask() { // Find tasks with all dependencies completed for (const task of this.taskQueue) { const dependenciesMet = task.constraints.dependencies.every(depId => { const depTask = this.activeTasksMap.get(depId.id); return depTask?.status === "completed"; }); if (dependenciesMet) { return task; } } return null; } findBestAgent(task, availableAgents) { let bestAgent = null; let bestScore = 0; for (const agent of availableAgents) { // Use more lenient capability checking if (!this.agentCanHandleTaskLenient(agent, task)) { continue; } const capabilities = agent.getCapabilities(); let score = 0; // Score based on capability match const taskCapabilities = task.requirements?.capabilities || []; const matchingCapabilities = taskCapabilities.filter(capability => { // Check if agent's tools include this capability or related ones return capabilities.tools.some(tool => tool.includes(capability) || capability.includes(tool) || this.areCapabilitiesRelated(capability, tool)); }); score += matchingCapabilities.length * 10; // Score based on agent reliability score += capabilities.reliability * 5; // Score based on agent speed score += capabilities.speed * 3; // Bonus for CodeAgent handling development tasks if (agent.getType() === "developer" && (task.type === "coding" || task.type === "analysis" || task.type === "research")) { score += 20; } if (score > bestScore) { bestScore = score; bestAgent = agent; } } return bestAgent; } agentCanHandleTaskLenient(agent, task) { // First try the agent's own canHandleTask method try { if (agent.canHandleTask(task)) { return true; } } catch (error) { logger.warn(`Error checking if agent ${agent.getId()} can handle task ${task.id.id}:`, error); } // Fallback: CodeAgent can handle most development tasks if (agent.getType() === "developer") { const developmentTaskTypes = ["coding", "analysis", "research", "implementation", "review"]; if (developmentTaskTypes.includes(task.type)) { return true; } } return false; } areCapabilitiesRelated(cap1, cap2) { const relatedCapabilities = { "analysis": ["research", "planning", "architecture", "design"], "coding": ["implementation", "frontend", "backend", "api_development"], "review": ["quality_assurance", "testing"], "research": ["analysis", "web_search"], "planning": ["analysis", "architecture"], "architecture": ["design", "analysis", "planning"], "design": ["architecture", "analysis"], "frontend": ["coding", "implementation"], "backend": ["coding", "implementation", "api_development"], "api_development": ["backend", "coding", "implementation"], }; const related1 = relatedCapabilities[cap1] || []; const related2 = relatedCapabilities[cap2] || []; return related1.includes(cap2) || related2.includes(cap1); } async prepareContextWindow(task, agent) { // Prepare context window based on task requirements const customProps = task.metadata?.customProperties; const requiredContext = customProps?.requiredContext || []; const estimatedTokens = customProps?.estimatedTokens || 1000; const contextWindow = { files: requiredContext, maxTokens: estimatedTokens, priority: this.determineContextPriority(task), chunks: await this.createContextChunks(requiredContext), }; this.contextCache.set(task.id.id, contextWindow); } determineContextPriority(task) { const customProps = task.metadata?.customProperties; const complexity = customProps?.complexity; switch (complexity) { case "simple": return "immediate"; case "medium": return "extended"; case "complex": return "project"; default: return "semantic"; } } async createContextChunks(files) { const chunks = []; for (const file of files) { chunks.push({ content: `// Context for ${file}`, type: this.determineChunkType(file), priority: this.calculateChunkPriority(file), tokens: 100, // Simplified token calculation }); } return chunks; } calculateChunkPriority(file) { // Higher priority for certain file types if (file.includes("test")) return 1; if (file.includes("config")) return 2; if (file.includes(".ts") || file.includes(".js")) return 3; return 4; } handleAgentTaskCompleted(event) { logger.info(`Task ${event.taskId} completed by agent ${event.agentId}`); const task = this.activeTasksMap.get(event.taskId); if (task) { task.status = "completed"; task.completedAt = new Date(); task.result = event.result; this.activeTasksMap.delete(event.taskId); const duration = task.completedAt.getTime() - task.createdAt.getTime(); this.emit("taskCompleted", { taskName: task.name, taskId: event.taskId, agentId: event.agentId, duration, result: event.result, }); } } handleAgentTaskError(event) { logger.error(`Task ${event.taskId} failed on agent ${event.agentId}:`, event.error); const task = this.activeTasksMap.get(event.taskId); if (task) { task.status = "failed"; task.error = { type: "execution_error", message: event.error.message, recoverable: true, retryable: true, context: {}, }; this.activeTasksMap.delete(event.taskId); this.emit("taskError", { taskName: task.name, taskId: event.taskId, agentId: event.agentId, error: event.error.message || event.error, }); } } getStatus() { const busyAgents = Array.from(this.agents.values()).filter(a => !a.isAvailable()).length; const totalTasks = this.taskQueue.length + this.activeTasksMap.size; const completedTasks = this.agents.size > 0 ? Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.getMetrics().tasksCompleted, 0) : 0; return { status: this.taskQueue.length === 0 && this.activeTasksMap.size === 0 ? "completed" : "executing", activeAgents: busyAgents, totalAgents: this.agents.size, activeTasks: this.activeTasksMap.size, totalTasks, completedTasks, failedTasks: 0, // TODO: Track failed tasks queuedTasks: this.taskQueue.length, strategy: this.strategy, }; } async shutdown() { logger.info("Shutting down Enhanced Swarm Coordinator"); // Cleanup all agents await Promise.all(Array.from(this.agents.values()).map(agent => agent.cleanup())); this.agents.clear(); this.taskQueue.length = 0; this.activeTasksMap.clear(); this.contextCache.clear(); this.emit("shutdown"); } } //# sourceMappingURL=enhanced-coordinator.js.map