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
JavaScript
/**
* 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
};