claude-flow-multilang
Version:
Revolutionary multilingual AI orchestration framework with cultural awareness and DDD architecture
720 lines (622 loc) • 21.5 kB
text/typescript
/**
* Task Engine Core - Comprehensive task management with orchestration features
* Integrates with TodoWrite/TodoRead for coordination and Memory for persistence
*/
import { EventEmitter } from 'events';
import type { Task, TaskStatus, AgentProfile, Resource } from '../utils/types.js';
import type { TaskMetadata } from './types.js';
import { generateId } from '../utils/helpers.js';
export interface TaskDependency {
taskId: string;
type: 'finish-to-start' | 'start-to-start' | 'finish-to-finish' | 'start-to-finish';
lag?: number; // delay in milliseconds
}
export interface ResourceRequirement {
resourceId: string;
type: 'cpu' | 'memory' | 'disk' | 'network' | 'custom';
amount: number;
unit: string;
exclusive?: boolean;
priority?: number;
}
export interface TaskSchedule {
startTime?: Date;
endTime?: Date;
deadline?: Date;
recurring?: {
interval: 'daily' | 'weekly' | 'monthly';
count?: number;
until?: Date;
};
timezone?: string;
}
export interface WorkflowTask extends Omit<Task, 'dependencies' | 'metadata'> {
dependencies: TaskDependency[];
resourceRequirements: ResourceRequirement[];
schedule?: TaskSchedule;
retryPolicy?: {
maxAttempts: number;
backoffMs: number;
backoffMultiplier: number;
};
timeout?: number;
tags: string[];
estimatedDurationMs?: number;
actualDurationMs?: number;
progressPercentage: number;
checkpoints: TaskCheckpoint[];
rollbackStrategy?: 'previous-checkpoint' | 'initial-state' | 'custom';
customRollbackHandler?: string;
metadata: TaskMetadata;
}
export interface TaskCheckpoint {
id: string;
timestamp: Date;
description: string;
state: Record<string, unknown>;
artifacts: string[];
}
export interface TaskExecution {
id: string;
taskId: string;
agentId: string;
startedAt: Date;
completedAt?: Date;
status: TaskStatus;
progress: number;
metrics: TaskMetrics;
logs: TaskLog[];
}
export interface TaskMetrics {
cpuUsage: number;
memoryUsage: number;
diskIO: number;
networkIO: number;
customMetrics: Record<string, number>;
}
export interface TaskLog {
timestamp: Date;
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
metadata?: Record<string, unknown>;
}
export interface Workflow {
id: string;
name: string;
description: string;
version: string;
tasks: WorkflowTask[];
variables: Record<string, unknown>;
parallelism: {
maxConcurrent: number;
strategy: 'breadth-first' | 'depth-first' | 'priority-based';
};
errorHandling: {
strategy: 'fail-fast' | 'continue-on-error' | 'retry-failed';
maxRetries: number;
};
createdAt: Date;
updatedAt: Date;
createdBy: string;
}
export interface TaskFilter {
status?: TaskStatus[];
assignedAgent?: string[];
priority?: { min?: number; max?: number };
tags?: string[];
createdAfter?: Date;
createdBefore?: Date;
dueBefore?: Date;
search?: string;
}
export interface TaskSort {
field: 'createdAt' | 'priority' | 'deadline' | 'status' | 'estimatedDuration';
direction: 'asc' | 'desc';
}
export class TaskEngine extends EventEmitter {
private tasks = new Map<string, WorkflowTask>();
private executions = new Map<string, TaskExecution>();
private workflows = new Map<string, Workflow>();
private resources = new Map<string, Resource>();
private dependencyGraph = new Map<string, Set<string>>();
private readyQueue: string[] = [];
private runningTasks = new Set<string>();
private cancelledTasks = new Set<string>();
private taskState = new Map<string, Record<string, unknown>>();
constructor(
private maxConcurrent: number = 10,
private memoryManager?: any, // Memory interface for persistence
) {
super();
this.setupEventHandlers();
}
private setupEventHandlers(): void {
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: Partial<WorkflowTask>): Promise<WorkflowTask> {
const task: WorkflowTask = {
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?: TaskFilter,
sort?: TaskSort,
limit?: number,
offset?: number,
): Promise<{ tasks: WorkflowTask[]; total: number; hasMore: boolean }> {
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: string): Promise<{
task: WorkflowTask;
execution?: TaskExecution;
dependencies: { task: WorkflowTask; satisfied: boolean }[];
dependents: WorkflowTask[];
resourceStatus: { required: ResourceRequirement; available: boolean; allocated: boolean }[];
} | null> {
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: string,
reason: string = 'User requested',
rollback: boolean = true,
): Promise<void> {
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: Workflow): Promise<void> {
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: Partial<Workflow>): Promise<Workflow> {
const workflow: 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(): { nodes: any[]; edges: any[] } {
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: any[] = [];
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
private updateDependencyGraph(task: WorkflowTask): void {
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);
}
}
private scheduleTask(task: WorkflowTask): void {
if (this.areTaskDependenciesSatisfied(task)) {
this.readyQueue.push(task.id);
this.processReadyQueue();
}
}
private areTaskDependenciesSatisfied(task: WorkflowTask): boolean {
return task.dependencies.every((dep) => {
const depTask = this.tasks.get(dep.taskId);
return depTask && this.isDependencySatisfied(dep, depTask);
});
}
private isDependencySatisfied(dependency: TaskDependency, depTask: WorkflowTask): boolean {
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';
}
}
private async processReadyQueue(): Promise<void> {
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);
}
}
private async executeTask(task: WorkflowTask): Promise<void> {
if (!(await this.acquireTaskResources(task))) {
// Resources not available, put back in queue
this.readyQueue.unshift(task.id);
return;
}
const execution: TaskExecution = {
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 as 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);
}
}
}
private async simulateTaskExecution(task: WorkflowTask, execution: TaskExecution): Promise<void> {
// 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() };
}
private async createCheckpoint(task: WorkflowTask, description: string): Promise<void> {
const checkpoint: TaskCheckpoint = {
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);
}
}
private async rollbackTask(task: WorkflowTask): Promise<void> {
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);
}
private async acquireTaskResources(task: WorkflowTask): Promise<boolean> {
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;
}
private async releaseTaskResources(taskId: string): Promise<void> {
for (const resource of Array.from(this.resources.values())) {
if (resource.lockedBy === taskId) {
resource.locked = false;
resource.lockedBy = undefined;
resource.lockedAt = undefined;
}
}
}
private matchesSearch(task: WorkflowTask, search: string): boolean {
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)
);
}
private async processWorkflow(workflow: Workflow): Promise<void> {
// Implementation would manage workflow execution based on parallelism settings
// This is a simplified version
for (const task of workflow.tasks) {
this.scheduleTask(task);
}
}
private handleTaskCreated(data: { task: WorkflowTask }): void {
// Handle task creation events
}
private handleTaskCompleted(data: { taskId: string; result: unknown }): void {
// 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();
}
private handleTaskFailed(data: { taskId: string; error: Error }): void {
// 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),
);
}
}
private handleTaskCancelled(data: { taskId: string; reason: string }): void {
// Handle task cancellation
}
}