UNPKG

@sethdouglasford/claude-flow

Version:

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

492 lines 19.5 kB
/** * 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 */ 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 Promise.resolve({ 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(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 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); void 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); } } rollbackTask(task) { if (task.checkpoints.length === 0) return Promise.resolve(); 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); return Promise.resolve(); } acquireTaskResources(task) { for (const requirement of task.resourceRequirements) { const resource = this.resources.get(requirement.resourceId); if (!resource) return Promise.resolve(false); if (resource.locked && requirement.exclusive) return Promise.resolve(false); resource.locked = true; resource.lockedBy = task.id; resource.lockedAt = new Date(); } return Promise.resolve(true); } releaseTaskResources(taskId) { for (const resource of this.resources.values()) { if (resource.lockedBy === taskId) { resource.locked = false; resource.lockedBy = undefined; resource.lockedAt = undefined; } } return Promise.resolve(); } 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)) || Boolean(task.assignedAgent?.toLowerCase().includes(searchLower))); } 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); } return Promise.resolve(); } 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); } } void 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 const currentRetryCount = typeof task.metadata?.retryCount === "number" ? task.metadata.retryCount : 0; if (task.retryPolicy && currentRetryCount < task.retryPolicy.maxAttempts) { task.metadata = { ...task.metadata, retryCount: currentRetryCount + 1 }; 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