UNPKG

shipdeck

Version:

Ship MVPs in 48 hours. Fix bugs in 30 seconds. The command deck for developers who ship.

1,163 lines (987 loc) 31 kB
/** * Agent Coordinator for Shipdeck Ultimate * Manages sequential and parallel agent execution with intelligent coordination */ const EventEmitter = require('events'); const fs = require('fs'); const { logger } = require('./logger'); const path = require('path'); /** * Task execution strategies */ const EXECUTION_STRATEGIES = { SEQUENTIAL: 'sequential', PARALLEL: 'parallel', HYBRID: 'hybrid', DYNAMIC: 'dynamic' }; /** * Resource types for conflict detection */ const RESOURCE_TYPES = { FILE: 'file', DIRECTORY: 'directory', API_ENDPOINT: 'api_endpoint', DATABASE: 'database', SERVICE: 'service', NETWORK_PORT: 'network_port' }; /** * Task priorities */ const TASK_PRIORITIES = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, BACKGROUND: 4 }; /** * Agent execution states */ const AGENT_STATES = { IDLE: 'idle', QUEUED: 'queued', RUNNING: 'running', WAITING: 'waiting', COMPLETED: 'completed', FAILED: 'failed', CANCELLED: 'cancelled' }; class AgentCoordinator extends EventEmitter { constructor(options = {}) { super(); this.config = { maxConcurrentAgents: 4, maxRetries: 3, retryDelay: 1000, taskTimeout: 300000, // 5 minutes dependencyTimeout: 60000, // 1 minute resourceLockTimeout: 30000, // 30 seconds enableMetrics: true, enableLogging: true, defaultStrategy: EXECUTION_STRATEGIES.DYNAMIC, ...options }; // Core state management this.taskQueue = []; this.runningTasks = new Map(); this.completedTasks = new Map(); this.failedTasks = new Map(); this.agentStates = new Map(); this.dependencyGraph = new Map(); this.resourceLocks = new Map(); // Performance tracking this.metrics = { tasksExecuted: 0, tasksSuccessful: 0, tasksFailed: 0, averageExecutionTime: 0, parallelEfficiency: 0, resourceContention: 0, startTime: Date.now() }; // Dependency resolution tracking this.waitingTasks = new Map(); this.resourceWaiters = new Map(); if (this.config.enableLogging) { this.setupLogging(); } } /** * Setup event logging */ setupLogging() { const logEvents = [ 'task:queued', 'task:started', 'task:completed', 'task:failed', 'dependency:resolved', 'resource:locked', 'resource:released', 'strategy:selected', 'parallel:started', 'parallel:completed' ]; logEvents.forEach(event => { this.on(event, (data) => { if (this.config.enableLogging) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [AgentCoordinator] ${event}:`, data); } }); }); } /** * Queue a task for execution * @param {Object} task - Task configuration * @param {string} task.id - Unique task identifier * @param {string} task.type - Task type * @param {string} task.agent - Target agent * @param {Object} task.params - Task parameters * @param {Array<string>} task.dependencies - Task dependencies * @param {Array<Object>} task.resources - Required resources * @param {number} task.priority - Task priority * @param {Object} task.context - Execution context * @returns {string} Task ID */ queueTask(task) { // Validate task this.validateTask(task); // Generate ID if not provided if (!task.id) { task.id = this.generateTaskId(); } // Set default values const queuedTask = { priority: TASK_PRIORITIES.MEDIUM, dependencies: [], resources: [], strategy: this.config.defaultStrategy, maxRetries: this.config.maxRetries, timeout: this.config.taskTimeout, queuedAt: Date.now(), attempts: 0, ...task, state: AGENT_STATES.QUEUED }; // Add to dependency graph this.dependencyGraph.set(task.id, { task: queuedTask, dependencies: new Set(queuedTask.dependencies), dependents: new Set() }); // Update dependents queuedTask.dependencies.forEach(depId => { const depNode = this.dependencyGraph.get(depId); if (depNode) { depNode.dependents.add(task.id); } }); // Insert into queue based on priority this.insertTaskByPriority(queuedTask); this.emit('task:queued', { taskId: task.id, agent: task.agent, priority: queuedTask.priority, dependencies: queuedTask.dependencies, queueSize: this.taskQueue.length }); return task.id; } /** * Queue multiple tasks * @param {Array<Object>} tasks - Array of task configurations * @returns {Array<string>} Array of task IDs */ queueTasks(tasks) { return tasks.map(task => this.queueTask(task)); } /** * Start task execution with automatic strategy selection * @param {Object} options - Execution options * @returns {Promise<Array>} Execution results */ async execute(options = {}) { const strategy = options.strategy || await this.selectOptimalStrategy(); this.emit('strategy:selected', { strategy, queueSize: this.taskQueue.length, runningTasks: this.runningTasks.size }); switch (strategy) { case EXECUTION_STRATEGIES.SEQUENTIAL: return this.executeSequential(options); case EXECUTION_STRATEGIES.PARALLEL: return this.executeParallel(options); case EXECUTION_STRATEGIES.HYBRID: return this.executeHybrid(options); default: return this.executeDynamic(options); } } /** * Execute tasks sequentially */ async executeSequential(options = {}) { const results = []; while (this.taskQueue.length > 0 || this.runningTasks.size > 0) { // Execute next ready task const readyTasks = this.getReadyTasks(); if (readyTasks.length > 0) { const task = readyTasks[0]; const result = await this.executeSingleTask(task); results.push(result); } else if (this.runningTasks.size === 0) { // No ready tasks and no running tasks - check for deadlocks await this.handleDeadlock(); break; } else { // Wait for running tasks to complete await this.sleep(100); } } return results; } /** * Execute tasks in parallel */ async executeParallel(options = {}) { const maxConcurrent = options.maxConcurrent || this.config.maxConcurrentAgents; const results = []; const activeTasks = new Set(); this.emit('parallel:started', { maxConcurrent, queueSize: this.taskQueue.length }); while (this.taskQueue.length > 0 || activeTasks.size > 0) { // Start new tasks up to concurrency limit while (activeTasks.size < maxConcurrent) { const readyTasks = this.getReadyTasks(); if (readyTasks.length === 0) break; const task = readyTasks[0]; // Check resource conflicts with currently running tasks const runningTaskList = Array.from(this.runningTasks.values()); if (this.canExecuteParallel(task, runningTaskList)) { const taskPromise = this.executeSingleTask(task) .then(result => { if (result) results.push(result); activeTasks.delete(taskPromise); return result; }) .catch(error => { const errorResult = { success: false, taskId: task.id, error: error.message }; results.push(errorResult); activeTasks.delete(taskPromise); return errorResult; }); activeTasks.add(taskPromise); } else { break; } } // Wait for at least one task to complete if we have active tasks if (activeTasks.size > 0) { await Promise.race(Array.from(activeTasks)); } else if (this.taskQueue.length > 0) { // No tasks can run in parallel - switch to sequential for remaining tasks const remainingResults = await this.executeSequential(); results.push(...remainingResults); break; } else { break; } } this.emit('parallel:completed', { totalTasks: results.length, successfulTasks: results.filter(r => r.success).length }); return results; } /** * Execute with hybrid strategy (parallel groups, sequential between groups) */ async executeHybrid(options = {}) { const results = []; const taskGroups = this.groupTasksByDependencies(); for (const group of taskGroups) { if (group.length === 1) { // Single task - execute directly const result = await this.executeSingleTask(group[0]); results.push(result); } else { // Multiple tasks - execute in parallel const groupResults = await this.executeParallel({ ...options, tasks: group }); results.push(...groupResults); } } return results; } /** * Dynamic execution with real-time strategy adjustment */ async executeDynamic(options = {}) { const results = []; let currentStrategy = EXECUTION_STRATEGIES.PARALLEL; while (this.taskQueue.length > 0 || this.runningTasks.size > 0) { // Analyze current conditions const analysis = this.analyzeExecutionConditions(); const optimalStrategy = this.selectStrategyFromAnalysis(analysis); if (optimalStrategy !== currentStrategy) { this.emit('strategy:changed', { from: currentStrategy, to: optimalStrategy, reason: analysis.reason }); currentStrategy = optimalStrategy; } // Execute batch with current strategy const batchSize = this.calculateOptimalBatchSize(analysis); const batch = this.getReadyTasks().slice(0, batchSize); if (batch.length === 0) { if (this.runningTasks.size > 0) { await this.sleep(100); continue; } else { break; } } let batchResults; switch (currentStrategy) { case EXECUTION_STRATEGIES.SEQUENTIAL: batchResults = await this.executeSequentialBatch(batch); break; case EXECUTION_STRATEGIES.PARALLEL: batchResults = await this.executeParallelBatch(batch); break; default: batchResults = await this.executeParallelBatch(batch); } results.push(...batchResults); } return results; } /** * Execute a single task with full lifecycle management */ async executeSingleTask(task) { const startTime = Date.now(); try { // Update state task.state = AGENT_STATES.RUNNING; task.startedAt = startTime; this.runningTasks.set(task.id, task); this.agentStates.set(task.agent, AGENT_STATES.RUNNING); // Acquire resource locks await this.acquireResourceLocks(task); // Emit start event this.emit('task:started', { taskId: task.id, agent: task.agent, attempt: task.attempts + 1, resources: task.resources }); // Load agent dynamically const agent = await this.loadAgent(task.agent); // Execute task const result = await Promise.race([ agent.execute(task, task.context), this.createTimeoutPromise(task.timeout, task.id) ]); // Process success const duration = Date.now() - startTime; const executionResult = { success: true, taskId: task.id, agent: task.agent, result, duration, attempts: task.attempts + 1, metadata: { startTime, endTime: Date.now(), resources: task.resources } }; // Update state task.state = AGENT_STATES.COMPLETED; task.completedAt = Date.now(); task.result = executionResult; // Store result this.completedTasks.set(task.id, executionResult); this.runningTasks.delete(task.id); this.agentStates.set(task.agent, AGENT_STATES.IDLE); // Release resources this.releaseResourceLocks(task); // Resolve dependencies this.resolveDependencies(task.id); // Update metrics this.updateMetrics(executionResult); this.emit('task:completed', executionResult); return executionResult; } catch (error) { return await this.handleTaskError(task, error, startTime); } } /** * Handle task execution error with retry logic */ async handleTaskError(task, error, startTime) { const duration = Date.now() - startTime; task.attempts++; // Release resources on error this.releaseResourceLocks(task); if (task.attempts <= task.maxRetries && this.isRetryableError(error)) { // Retry task task.state = AGENT_STATES.QUEUED; this.runningTasks.delete(task.id); this.agentStates.set(task.agent, AGENT_STATES.IDLE); // Add back to queue with delay await this.sleep(this.config.retryDelay * task.attempts); this.insertTaskByPriority(task); this.emit('task:retry', { taskId: task.id, attempt: task.attempts, maxRetries: task.maxRetries, error: error.message }); return null; // Will be retried } else { // Mark as failed const errorResult = { success: false, taskId: task.id, agent: task.agent, error: error.message, duration, attempts: task.attempts, metadata: { startTime, endTime: Date.now(), errorType: error.constructor.name } }; task.state = AGENT_STATES.FAILED; task.failedAt = Date.now(); task.error = errorResult; this.failedTasks.set(task.id, errorResult); this.runningTasks.delete(task.id); this.agentStates.set(task.agent, AGENT_STATES.IDLE); // Handle dependent tasks this.handleFailedDependencies(task.id); this.emit('task:failed', errorResult); return errorResult; } } /** * Get tasks ready for execution (dependencies satisfied) */ getReadyTasks() { return this.taskQueue.filter(task => { if (task.state !== AGENT_STATES.QUEUED) return false; // Check dependencies return task.dependencies.every(depId => this.completedTasks.has(depId) ); }); } /** * Check if task can execute in parallel with others */ canExecuteParallel(task, runningTasks) { // Check resource conflicts with running tasks for (const runningTask of runningTasks) { const runningTaskObj = this.runningTasks.get(runningTask.taskId) || runningTask; if (this.hasResourceConflict(task, runningTaskObj)) { return false; } } // Check agent availability (if agent is single-threaded) if (this.isAgentBusy(task.agent)) { return false; } return true; } /** * Check for resource conflicts between tasks */ hasResourceConflict(task1, task2) { if (!task1.resources || !task2.resources) return false; return task1.resources.some(res1 => task2.resources.some(res2 => res1.type === res2.type && res1.id === res2.id && (res1.access === 'write' || res2.access === 'write') ) ); } /** * Acquire resource locks for a task */ async acquireResourceLocks(task) { if (!task.resources) return; const locksToAcquire = []; for (const resource of task.resources) { const lockKey = `${resource.type}:${resource.id}`; if (resource.access === 'write') { // Wait for exclusive access await this.waitForResourceLock(lockKey); this.resourceLocks.set(lockKey, { taskId: task.id, type: 'exclusive', acquiredAt: Date.now() }); locksToAcquire.push(lockKey); } else { // Shared read access const existing = this.resourceLocks.get(lockKey); if (!existing || existing.type === 'shared') { this.resourceLocks.set(lockKey, { taskId: task.id, type: 'shared', count: (existing?.count || 0) + 1, acquiredAt: Date.now() }); locksToAcquire.push(lockKey); } else { // Wait for exclusive lock to be released await this.waitForResourceLock(lockKey); this.resourceLocks.set(lockKey, { taskId: task.id, type: 'shared', count: 1, acquiredAt: Date.now() }); locksToAcquire.push(lockKey); } } } this.emit('resource:locked', { taskId: task.id, resources: locksToAcquire }); } /** * Release resource locks for a task */ releaseResourceLocks(task) { if (!task.resources) return; const releasedLocks = []; for (const resource of task.resources) { const lockKey = `${resource.type}:${resource.id}`; const lock = this.resourceLocks.get(lockKey); if (lock && lock.taskId === task.id) { if (lock.type === 'shared' && lock.count > 1) { lock.count--; } else { this.resourceLocks.delete(lockKey); releasedLocks.push(lockKey); } } } if (releasedLocks.length > 0) { this.emit('resource:released', { taskId: task.id, resources: releasedLocks }); // Notify waiting tasks this.notifyResourceWaiters(releasedLocks); } } /** * Wait for resource lock to be available */ async waitForResourceLock(lockKey) { const timeout = this.config.resourceLockTimeout; const start = Date.now(); while (this.resourceLocks.has(lockKey)) { if (Date.now() - start > timeout) { throw new Error(`Resource lock timeout for ${lockKey}`); } // Add to waiters if (!this.resourceWaiters.has(lockKey)) { this.resourceWaiters.set(lockKey, []); } await new Promise(resolve => { const waiters = this.resourceWaiters.get(lockKey); waiters.push(resolve); // Auto-resolve after small delay to prevent deadlocks setTimeout(resolve, 100); }); } } /** * Notify tasks waiting for resources */ notifyResourceWaiters(releasedLocks) { for (const lockKey of releasedLocks) { const waiters = this.resourceWaiters.get(lockKey); if (waiters) { waiters.forEach(resolve => resolve()); this.resourceWaiters.delete(lockKey); } } } /** * Resolve dependencies when a task completes */ resolveDependencies(completedTaskId) { const node = this.dependencyGraph.get(completedTaskId); if (!node) return; // Notify dependent tasks for (const dependentId of node.dependents) { this.emit('dependency:resolved', { completedTask: completedTaskId, dependentTask: dependentId }); } } /** * Handle failed task dependencies */ handleFailedDependencies(failedTaskId) { const node = this.dependencyGraph.get(failedTaskId); if (!node) return; // Cancel or mark dependent tasks as failed for (const dependentId of node.dependents) { const dependentTask = this.taskQueue.find(t => t.id === dependentId) || this.runningTasks.get(dependentId); if (dependentTask) { dependentTask.state = AGENT_STATES.CANCELLED; this.emit('task:cancelled', { taskId: dependentId, reason: `Dependency failed: ${failedTaskId}` }); } } } /** * Select optimal execution strategy */ async selectOptimalStrategy() { const analysis = this.analyzeExecutionConditions(); return this.selectStrategyFromAnalysis(analysis); } /** * Analyze current execution conditions */ analyzeExecutionConditions() { const readyTasks = this.getReadyTasks(); const totalTasks = this.taskQueue.length; const parallelizableTasks = this.countParallelizableTasks(readyTasks); const resourceContention = this.calculateResourceContention(); const avgTaskComplexity = this.calculateAverageComplexity(readyTasks); return { readyTasks: readyTasks.length, totalTasks, parallelizableTasks, resourceContention, avgTaskComplexity, parallelEfficiency: parallelizableTasks / Math.max(readyTasks.length, 1), reason: '' }; } /** * Select strategy based on analysis */ selectStrategyFromAnalysis(analysis) { if (analysis.parallelEfficiency > 0.7 && analysis.resourceContention < 0.3) { analysis.reason = 'High parallel efficiency, low contention'; return EXECUTION_STRATEGIES.PARALLEL; } if (analysis.parallelEfficiency < 0.3 || analysis.resourceContention > 0.7) { analysis.reason = 'Low parallel efficiency or high contention'; return EXECUTION_STRATEGIES.SEQUENTIAL; } analysis.reason = 'Mixed conditions, using hybrid approach'; return EXECUTION_STRATEGIES.HYBRID; } /** * Group tasks by dependency levels */ groupTasksByDependencies() { const groups = []; const processed = new Set(); // Build dependency levels const levels = new Map(); function calculateLevel(taskId) { if (levels.has(taskId)) return levels.get(taskId); const node = this.dependencyGraph.get(taskId); if (!node || node.dependencies.size === 0) { levels.set(taskId, 0); return 0; } let maxDepLevel = -1; for (const depId of node.dependencies) { maxDepLevel = Math.max(maxDepLevel, calculateLevel.call(this, depId)); } const level = maxDepLevel + 1; levels.set(taskId, level); return level; } // Calculate levels for all tasks this.taskQueue.forEach(task => calculateLevel.call(this, task.id)); // Group by levels const levelGroups = new Map(); levels.forEach((level, taskId) => { if (!levelGroups.has(level)) { levelGroups.set(level, []); } const task = this.taskQueue.find(t => t.id === taskId); if (task) { levelGroups.get(level).push(task); } }); // Convert to array const sortedLevels = Array.from(levelGroups.keys()).sort((a, b) => a - b); return sortedLevels.map(level => levelGroups.get(level)); } /** * Count tasks that can run in parallel */ countParallelizableTasks(tasks) { let parallelizable = 0; for (let i = 0; i < tasks.length; i++) { let canRunParallel = true; for (let j = i + 1; j < tasks.length; j++) { if (this.hasResourceConflict(tasks[i], tasks[j])) { canRunParallel = false; break; } } if (canRunParallel) parallelizable++; } return parallelizable; } /** * Calculate resource contention level */ calculateResourceContention() { const readyTasks = this.getReadyTasks(); if (readyTasks.length < 2) return 0; let conflicts = 0; let comparisons = 0; for (let i = 0; i < readyTasks.length; i++) { for (let j = i + 1; j < readyTasks.length; j++) { comparisons++; if (this.hasResourceConflict(readyTasks[i], readyTasks[j])) { conflicts++; } } } return comparisons > 0 ? conflicts / comparisons : 0; } /** * Calculate average task complexity */ calculateAverageComplexity(tasks) { if (tasks.length === 0) return 0; const complexityMap = { simple: 1, medium: 2, complex: 3 }; const totalComplexity = tasks.reduce((sum, task) => { return sum + (complexityMap[task.complexity] || 2); }, 0); return totalComplexity / tasks.length; } /** * Update performance metrics */ updateMetrics(result) { if (!this.config.enableMetrics) return; this.metrics.tasksExecuted++; if (result.success) { this.metrics.tasksSuccessful++; } else { this.metrics.tasksFailed++; } // Update average execution time const currentAvg = this.metrics.averageExecutionTime; const newAvg = (currentAvg * (this.metrics.tasksExecuted - 1) + result.duration) / this.metrics.tasksExecuted; this.metrics.averageExecutionTime = newAvg; // Update parallel efficiency (example calculation) const runningCount = this.runningTasks.size; if (runningCount > 1) { this.metrics.parallelEfficiency = Math.min(runningCount / this.config.maxConcurrentAgents, 1.0); } // Update resource contention this.metrics.resourceContention = this.calculateResourceContention(); } /** * Load agent dynamically */ async loadAgent(agentType) { try { const agentPath = path.join(__dirname, `${agentType}.js`); // Check if agent file exists if (!fs.existsSync(agentPath)) { throw new Error(`Agent not found: ${agentType}`); } const AgentClass = require(agentPath); return new AgentClass(); } catch (error) { throw new Error(`Failed to load agent ${agentType}: ${error.message}`); } } /** * Utility functions */ validateTask(task) { if (!task || typeof task !== 'object') { throw new Error('Task must be an object'); } if (!task.type) { throw new Error('Task must have a type'); } if (!task.agent) { throw new Error('Task must specify an agent'); } return true; } generateTaskId() { return `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } insertTaskByPriority(task) { const index = this.taskQueue.findIndex(t => t.priority > task.priority); if (index === -1) { this.taskQueue.push(task); } else { this.taskQueue.splice(index, 0, task); } } isAgentBusy(agentType) { return Array.from(this.runningTasks.values()).some(task => task.agent === agentType); } isRetryableError(error) { // Define which errors are retryable const retryableErrors = [ 'NetworkError', 'TimeoutError', 'RateLimitError', 'ServiceUnavailableError', 'Simulated failure' // For testing ]; return retryableErrors.some(errorType => error.constructor.name === errorType || error.message.includes(errorType) ); } createTimeoutPromise(timeout, taskId) { return new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Task ${taskId} timed out after ${timeout}ms`)); }, timeout); }); } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } calculateOptimalBatchSize(analysis) { if (analysis.parallelEfficiency > 0.7) { return Math.min(this.config.maxConcurrentAgents, analysis.readyTasks); } return 1; // Sequential processing } async executeSequentialBatch(batch) { const results = []; for (const task of batch) { const result = await this.executeSingleTask(task); if (result) results.push(result); } return results; } async executeParallelBatch(batch) { const promises = batch.map(task => this.executeSingleTask(task)); const results = await Promise.allSettled(promises); return results .map(result => result.status === 'fulfilled' ? result.value : result.reason) .filter(Boolean); } async handleDeadlock() { // Simple deadlock resolution: cancel lowest priority waiting tasks const waitingTasks = this.taskQueue.filter(task => task.state === AGENT_STATES.QUEUED && !this.getReadyTasks().includes(task) ); if (waitingTasks.length > 0) { const lowestPriority = Math.max(...waitingTasks.map(t => t.priority)); const tasksToCancel = waitingTasks.filter(t => t.priority === lowestPriority); for (const task of tasksToCancel) { task.state = AGENT_STATES.CANCELLED; this.taskQueue = this.taskQueue.filter(t => t.id !== task.id); this.emit('task:cancelled', { taskId: task.id, reason: 'Deadlock resolution' }); } } } /** * Public API methods */ /** * Get current system status */ getStatus() { return { queueSize: this.taskQueue.length, runningTasks: this.runningTasks.size, completedTasks: this.completedTasks.size, failedTasks: this.failedTasks.size, metrics: { ...this.metrics }, uptime: Date.now() - this.metrics.startTime, resourceLocks: this.resourceLocks.size, agentStates: Object.fromEntries(this.agentStates) }; } /** * Get detailed task information */ getTaskDetails(taskId) { // Check all task stores let task = this.taskQueue.find(t => t.id === taskId); if (task) return { ...task, status: 'queued' }; task = this.runningTasks.get(taskId); if (task) return { ...task, status: 'running' }; task = this.completedTasks.get(taskId); if (task) return { ...task, status: 'completed' }; task = this.failedTasks.get(taskId); if (task) return { ...task, status: 'failed' }; // Check dependency graph for cancelled tasks const node = this.dependencyGraph.get(taskId); if (node && node.task.state === AGENT_STATES.CANCELLED) { return { ...node.task, status: 'cancelled' }; } return null; } /** * Cancel a task */ cancelTask(taskId) { // Remove from queue const queueIndex = this.taskQueue.findIndex(t => t.id === taskId); if (queueIndex !== -1) { const task = this.taskQueue.splice(queueIndex, 1)[0]; task.state = AGENT_STATES.CANCELLED; // Update dependency graph to reflect cancellation const node = this.dependencyGraph.get(taskId); if (node) { node.task.state = AGENT_STATES.CANCELLED; } this.emit('task:cancelled', { taskId, reason: 'User cancellation' }); return true; } // Note: Cannot cancel running tasks in this implementation return false; } /** * Clear completed tasks from memory */ clearCompletedTasks() { const count = this.completedTasks.size; this.completedTasks.clear(); return count; } /** * Shutdown coordinator gracefully */ async shutdown() { console.log('🛑 Shutting down Agent Coordinator...'); // Wait for running tasks to complete while (this.runningTasks.size > 0) { console.log(`⏳ Waiting for ${this.runningTasks.size} running tasks...`); await this.sleep(1000); } // Clear all resources this.taskQueue.length = 0; this.resourceLocks.clear(); this.resourceWaiters.clear(); this.dependencyGraph.clear(); console.log('✅ Agent Coordinator shutdown complete'); } } module.exports = { AgentCoordinator, EXECUTION_STRATEGIES, RESOURCE_TYPES, TASK_PRIORITIES, AGENT_STATES };