claude-flow-tbowman01
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
500 lines • 19.6 kB
JavaScript
/**
* Task Engine Core - Comprehensive task management with orchestration features
* Integrates with TodoWrite/TodoRead for coordination and Memory for persistence
*/
import { EventEmitter } from 'events';
import { generateId } from '../utils/helpers.js';
export class TaskEngine extends EventEmitter {
maxConcurrent;
memoryManager;
tasks = new Map();
executions = new Map();
workflows = new Map();
resources = new Map();
dependencyGraph = new Map();
readyQueue = [];
runningTasks = new Set();
cancelledTasks = new Set();
taskState = new Map();
constructor(maxConcurrent = 10, memoryManager) {
super();
this.maxConcurrent = maxConcurrent;
this.memoryManager = memoryManager;
this.setupEventHandlers();
}
setupEventHandlers() {
this.on('task:created', this.handleTaskCreated.bind(this));
this.on('task:completed', this.handleTaskCompleted.bind(this));
this.on('task:failed', this.handleTaskFailed.bind(this));
this.on('task:cancelled', this.handleTaskCancelled.bind(this));
}
/**
* Create a new task with comprehensive options
*/
async createTask(taskData) {
const task = {
id: taskData.id || generateId('task'),
type: taskData.type || 'general',
description: taskData.description || '',
priority: taskData.priority || 0,
status: 'pending',
input: taskData.input || {},
createdAt: new Date(),
dependencies: taskData.dependencies || [],
resourceRequirements: taskData.resourceRequirements || [],
schedule: taskData.schedule,
retryPolicy: taskData.retryPolicy || {
maxAttempts: 3,
backoffMs: 1000,
backoffMultiplier: 2,
},
timeout: taskData.timeout || 300000, // 5 minutes default
tags: taskData.tags || [],
estimatedDurationMs: taskData.estimatedDurationMs,
progressPercentage: 0,
checkpoints: [],
rollbackStrategy: taskData.rollbackStrategy || 'previous-checkpoint',
metadata: taskData.metadata || {},
};
this.tasks.set(task.id, task);
this.updateDependencyGraph(task);
// Store in memory if manager available
if (this.memoryManager) {
await this.memoryManager.store(`task:${task.id}`, task);
}
this.emit('task:created', { task });
this.scheduleTask(task);
return task;
}
/**
* List tasks with filtering and sorting
*/
async listTasks(filter, sort, limit, offset) {
let filteredTasks = Array.from(this.tasks.values());
// Apply filters
if (filter) {
filteredTasks = filteredTasks.filter((task) => {
if (filter.status && !filter.status.includes(task.status))
return false;
if (filter.assignedAgent && !filter.assignedAgent.includes(task.assignedAgent || ''))
return false;
if (filter.priority) {
if (filter.priority.min !== undefined && task.priority < filter.priority.min)
return false;
if (filter.priority.max !== undefined && task.priority > filter.priority.max)
return false;
}
if (filter.tags && !filter.tags.some((tag) => task.tags.includes(tag)))
return false;
if (filter.createdAfter && task.createdAt < filter.createdAfter)
return false;
if (filter.createdBefore && task.createdAt > filter.createdBefore)
return false;
if (filter.dueBefore &&
task.schedule?.deadline &&
task.schedule.deadline > filter.dueBefore)
return false;
if (filter.search && !this.matchesSearch(task, filter.search))
return false;
return true;
});
}
// Apply sorting
if (sort) {
filteredTasks.sort((a, b) => {
const direction = sort.direction === 'desc' ? -1 : 1;
switch (sort.field) {
case 'createdAt':
return direction * (a.createdAt.getTime() - b.createdAt.getTime());
case 'priority':
return direction * (a.priority - b.priority);
case 'deadline':
const aDeadline = a.schedule?.deadline?.getTime() || 0;
const bDeadline = b.schedule?.deadline?.getTime() || 0;
return direction * (aDeadline - bDeadline);
case 'estimatedDuration':
return direction * ((a.estimatedDurationMs || 0) - (b.estimatedDurationMs || 0));
default:
return 0;
}
});
}
const total = filteredTasks.length;
const startIndex = offset || 0;
const endIndex = limit ? startIndex + limit : filteredTasks.length;
const tasks = filteredTasks.slice(startIndex, endIndex);
return {
tasks,
total,
hasMore: endIndex < total,
};
}
/**
* Get detailed task status with progress and metrics
*/
async getTaskStatus(taskId) {
const task = this.tasks.get(taskId);
if (!task)
return null;
const execution = this.executions.get(taskId);
// Get dependency status
const dependencies = await Promise.all(task.dependencies.map(async (dep) => {
const depTask = this.tasks.get(dep.taskId);
if (!depTask)
throw new Error(`Dependency task ${dep.taskId} not found`);
const satisfied = this.isDependencySatisfied(dep, depTask);
return { task: depTask, satisfied };
}));
// Get dependent tasks
const dependents = Array.from(this.tasks.values()).filter((t) => t.dependencies.some((dep) => dep.taskId === taskId));
// Get resource status
const resourceStatus = task.resourceRequirements.map((req) => {
const resource = this.resources.get(req.resourceId);
return {
required: req,
available: !!resource,
allocated: resource?.lockedBy === taskId,
};
});
return {
task,
execution,
dependencies,
dependents,
resourceStatus,
};
}
/**
* Cancel task with rollback and cleanup
*/
async cancelTask(taskId, reason = 'User requested', rollback = true) {
const task = this.tasks.get(taskId);
if (!task)
throw new Error(`Task ${taskId} not found`);
if (task.status === 'completed') {
throw new Error(`Cannot cancel completed task ${taskId}`);
}
this.cancelledTasks.add(taskId);
// Stop running execution
if (this.runningTasks.has(taskId)) {
this.runningTasks.delete(taskId);
const execution = this.executions.get(taskId);
if (execution) {
execution.status = 'cancelled';
execution.completedAt = new Date();
}
}
// Release resources
await this.releaseTaskResources(taskId);
// Perform rollback if requested
if (rollback && task.checkpoints.length > 0) {
await this.rollbackTask(task);
}
// Update task status
task.status = 'cancelled';
task.metadata = {
...task.metadata,
cancellationReason: reason,
cancelledAt: new Date(),
};
// Update memory
if (this.memoryManager) {
await this.memoryManager.store(`task:${taskId}`, task);
}
this.emit('task:cancelled', { taskId, reason });
// Cancel dependent tasks if configured
const dependents = Array.from(this.tasks.values()).filter((t) => t.dependencies.some((dep) => dep.taskId === taskId));
for (const dependent of dependents) {
if (dependent.status === 'pending' || dependent.status === 'queued') {
await this.cancelTask(dependent.id, `Dependency ${taskId} was cancelled`);
}
}
}
/**
* Execute workflow with parallel processing
*/
async executeWorkflow(workflow) {
this.workflows.set(workflow.id, workflow);
// Add all workflow tasks
for (const task of workflow.tasks) {
this.tasks.set(task.id, task);
this.updateDependencyGraph(task);
}
// Start execution with parallel processing
await this.processWorkflow(workflow);
}
/**
* Create workflow from tasks
*/
async createWorkflow(workflowData) {
const workflow = {
id: workflowData.id || generateId('workflow'),
name: workflowData.name || 'Unnamed Workflow',
description: workflowData.description || '',
version: workflowData.version || '1.0.0',
tasks: workflowData.tasks || [],
variables: workflowData.variables || {},
parallelism: workflowData.parallelism || {
maxConcurrent: this.maxConcurrent,
strategy: 'priority-based',
},
errorHandling: workflowData.errorHandling || {
strategy: 'fail-fast',
maxRetries: 3,
},
createdAt: new Date(),
updatedAt: new Date(),
createdBy: workflowData.createdBy || 'system',
};
this.workflows.set(workflow.id, workflow);
if (this.memoryManager) {
await this.memoryManager.store(`workflow:${workflow.id}`, workflow);
}
return workflow;
}
/**
* Get dependency visualization
*/
getDependencyGraph() {
const nodes = Array.from(this.tasks.values()).map((task) => ({
id: task.id,
label: task.description,
status: task.status,
priority: task.priority,
progress: task.progressPercentage,
estimatedDuration: task.estimatedDurationMs,
tags: task.tags,
}));
const edges = [];
for (const task of Array.from(this.tasks.values())) {
for (const dep of task.dependencies) {
edges.push({
from: dep.taskId,
to: task.id,
type: dep.type,
lag: dep.lag,
});
}
}
return { nodes, edges };
}
// Private helper methods
updateDependencyGraph(task) {
if (!this.dependencyGraph.has(task.id)) {
this.dependencyGraph.set(task.id, new Set());
}
for (const dep of task.dependencies) {
if (!this.dependencyGraph.has(dep.taskId)) {
this.dependencyGraph.set(dep.taskId, new Set());
}
this.dependencyGraph.get(dep.taskId).add(task.id);
}
}
scheduleTask(task) {
if (this.areTaskDependenciesSatisfied(task)) {
this.readyQueue.push(task.id);
this.processReadyQueue();
}
}
areTaskDependenciesSatisfied(task) {
return task.dependencies.every((dep) => {
const depTask = this.tasks.get(dep.taskId);
return depTask && this.isDependencySatisfied(dep, depTask);
});
}
isDependencySatisfied(dependency, depTask) {
switch (dependency.type) {
case 'finish-to-start':
return depTask.status === 'completed';
case 'start-to-start':
return depTask.status !== 'pending';
case 'finish-to-finish':
return depTask.status === 'completed';
case 'start-to-finish':
return depTask.status !== 'pending';
default:
return depTask.status === 'completed';
}
}
async processReadyQueue() {
while (this.readyQueue.length > 0 && this.runningTasks.size < this.maxConcurrent) {
const taskId = this.readyQueue.shift();
if (this.cancelledTasks.has(taskId))
continue;
const task = this.tasks.get(taskId);
if (!task)
continue;
await this.executeTask(task);
}
}
async executeTask(task) {
if (!(await this.acquireTaskResources(task))) {
// Resources not available, put back in queue
this.readyQueue.unshift(task.id);
return;
}
const execution = {
id: generateId('execution'),
taskId: task.id,
agentId: task.assignedAgent || 'system',
startedAt: new Date(),
status: 'running',
progress: 0,
metrics: {
cpuUsage: 0,
memoryUsage: 0,
diskIO: 0,
networkIO: 0,
customMetrics: {},
},
logs: [],
};
this.executions.set(task.id, execution);
this.runningTasks.add(task.id);
task.status = 'running';
task.startedAt = new Date();
this.emit('task:started', { taskId: task.id, agentId: execution.agentId });
try {
// Simulate task execution - in real implementation, this would delegate to agents
await this.simulateTaskExecution(task, execution);
task.status = 'completed';
task.completedAt = new Date();
task.progressPercentage = 100;
execution.status = 'completed';
execution.completedAt = new Date();
this.emit('task:completed', { taskId: task.id, result: task.output });
}
catch (error) {
task.status = 'failed';
task.error = error;
execution.status = 'failed';
execution.completedAt = new Date();
this.emit('task:failed', { taskId: task.id, error });
}
finally {
this.runningTasks.delete(task.id);
await this.releaseTaskResources(task.id);
if (this.memoryManager) {
await this.memoryManager.store(`task:${task.id}`, task);
await this.memoryManager.store(`execution:${execution.id}`, execution);
}
}
}
async simulateTaskExecution(task, execution) {
// Simulate work with progress updates
const steps = 10;
for (let i = 0; i <= steps; i++) {
if (this.cancelledTasks.has(task.id)) {
throw new Error('Task was cancelled');
}
task.progressPercentage = (i / steps) * 100;
execution.progress = task.progressPercentage;
// Create checkpoint every 25%
if (i % Math.ceil(steps / 4) === 0) {
await this.createCheckpoint(task, `Step ${i} completed`);
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
task.output = { result: 'Task completed successfully', timestamp: new Date() };
}
async createCheckpoint(task, description) {
const checkpoint = {
id: generateId('checkpoint'),
timestamp: new Date(),
description,
state: { ...(this.taskState.get(task.id) || {}) },
artifacts: [],
};
task.checkpoints.push(checkpoint);
if (this.memoryManager) {
await this.memoryManager.store(`checkpoint:${checkpoint.id}`, checkpoint);
}
}
async rollbackTask(task) {
if (task.checkpoints.length === 0)
return;
const targetCheckpoint = task.rollbackStrategy === 'initial-state'
? task.checkpoints[0]
: task.checkpoints[task.checkpoints.length - 1];
// Restore state from checkpoint
this.taskState.set(task.id, { ...targetCheckpoint.state });
// Remove checkpoints after the target
const targetIndex = task.checkpoints.findIndex((cp) => cp.id === targetCheckpoint.id);
task.checkpoints = task.checkpoints.slice(0, targetIndex + 1);
task.progressPercentage = Math.max(0, task.progressPercentage - 25);
}
async acquireTaskResources(task) {
for (const requirement of task.resourceRequirements) {
const resource = this.resources.get(requirement.resourceId);
if (!resource)
return false;
if (resource.locked && requirement.exclusive)
return false;
resource.locked = true;
resource.lockedBy = task.id;
resource.lockedAt = new Date();
}
return true;
}
async releaseTaskResources(taskId) {
for (const resource of Array.from(this.resources.values())) {
if (resource.lockedBy === taskId) {
resource.locked = false;
resource.lockedBy = undefined;
resource.lockedAt = undefined;
}
}
}
matchesSearch(task, search) {
const searchLower = search.toLowerCase();
return (task.description.toLowerCase().includes(searchLower) ||
task.type.toLowerCase().includes(searchLower) ||
task.tags.some((tag) => tag.toLowerCase().includes(searchLower)) ||
(task.assignedAgent ? task.assignedAgent.toLowerCase().includes(searchLower) : false));
}
async processWorkflow(workflow) {
// Implementation would manage workflow execution based on parallelism settings
// This is a simplified version
for (const task of workflow.tasks) {
this.scheduleTask(task);
}
}
handleTaskCreated(data) {
// Handle task creation events
}
handleTaskCompleted(data) {
// Schedule dependent tasks
const dependents = Array.from(this.tasks.values()).filter((task) => task.dependencies.some((dep) => dep.taskId === data.taskId));
for (const dependent of dependents) {
if (this.areTaskDependenciesSatisfied(dependent)) {
this.readyQueue.push(dependent.id);
}
}
this.processReadyQueue();
}
handleTaskFailed(data) {
// Handle task failure, potentially retry or fail dependents
const task = this.tasks.get(data.taskId);
if (!task)
return;
// Implement retry logic based on retryPolicy
if (task.retryPolicy && (task.metadata.retryCount || 0) < task.retryPolicy.maxAttempts) {
const currentRetryCount = task.metadata.retryCount || 0;
task.metadata = {
...task.metadata,
retryCount: currentRetryCount + 1,
lastRetryAt: new Date(),
};
task.status = 'pending';
// Schedule retry with backoff
setTimeout(() => {
this.scheduleTask(task);
}, task.retryPolicy.backoffMs *
Math.pow(task.retryPolicy.backoffMultiplier, currentRetryCount));
}
}
handleTaskCancelled(data) {
// Handle task cancellation
}
}
//# sourceMappingURL=engine.js.map