UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

441 lines (379 loc) 14.1 kB
/** * Autonomous Task Manager - Orchestrates multi-agent task execution * Handles task planning, agent coordination, and autonomous execution */ import { EventEmitter } from 'events'; import { logger } from './logger.js'; import { taskMemoryDB, TaskState, AgentAssignment } from './task-memory-db.js'; import { promises as fs } from 'fs'; import { join } from 'path'; import chalk from 'chalk'; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export interface TaskPlan { title: string; description: string; priority: 'low' | 'medium' | 'high' | 'critical'; phases: TaskPhase[]; success_criteria: string[]; rollback_strategy: string; } export interface TaskPhase { name: string; description: string; steps: TaskStep[]; agents_required: string[]; estimated_duration: string; dependencies: string[]; } export interface TaskStep { id: string; description: string; action_type: 'code' | 'test' | 'analysis' | 'cleanup' | 'validation'; command?: string; files_affected?: string[]; validation_criteria?: string; rollback_action?: string; } export class AutonomousTaskManager extends EventEmitter { private agents: Map<string, AgentWorker> = new Map(); private initialized = false; private taskPlanPath: string; constructor() { super(); this.taskPlanPath = join(process.cwd(), 'task.md'); } async initialize(): Promise<void> { await taskMemoryDB.initialize(); // Initialize specialized agents this.agents.set('primary-fixer', new AgentWorker('primary-fixer', 'Development and Fixes')); this.agents.set('test-runner', new AgentWorker('test-runner', 'Testing and Validation')); this.agents.set('cleaner', new AgentWorker('cleaner', 'Cleanup and Optimization')); this.agents.set('coordinator', new AgentWorker('coordinator', 'Task Coordination')); this.initialized = true; logger.info('AutonomousTaskManager initialized with 4 agents'); } async planAndExecuteTask(taskDescription: string): Promise<TaskState> { if (!this.initialized) { await this.initialize(); } // Generate comprehensive task plan const plan = await this.generateTaskPlan(taskDescription); // Create task in memory DB const task = await taskMemoryDB.createTask({ title: plan.title, priority: plan.priority, estimated_duration: this.estimateTaskDuration(plan), total_phases: plan.phases.length, context_data: { description: plan.description, success_criteria: plan.success_criteria, rollback_strategy: plan.rollback_strategy, }, }); // Update task.md with detailed plan await this.updateTaskFile(task, plan); // Assign agents to phases await this.assignAgentsToTask(task.task_id, plan); // Start autonomous execution await this.executeTaskAutonomously(task.task_id); return task; } private async generateTaskPlan(description: string): Promise<TaskPlan> { // For now, use the current CodeCrucible fix plan // In production, this would use AI to generate plans return { title: 'Fix CodeCrucible Synth Critical Issues', description, priority: 'critical', phases: [ { name: 'Critical Fixes', description: 'Fix immediate blocking issues', steps: [ { id: 'fix_context_window', description: 'Fix Ollama context window size (1024 → 8192)', action_type: 'code', files_affected: ['src/providers/ollama.ts'], command: 'npm run build', validation_criteria: 'Test with long prompt succeeds', }, { id: 'fix_versions', description: 'Standardize version numbers to v3.8.9', action_type: 'code', files_affected: ['src/index.ts', 'src/core/cli.ts', 'bin/*.js'], }, { id: 'fix_memory_leaks', description: 'Fix EventEmitter memory leak warnings', action_type: 'code', files_affected: ['src/index.ts'], }, ], agents_required: ['primary-fixer', 'test-runner'], estimated_duration: '30 minutes', dependencies: [], }, { name: 'Test Infrastructure', description: 'Fix failing tests and validation', steps: [ { id: 'fix_unit_tests', description: 'Fix 36 failing unit tests', action_type: 'test', command: 'npm test', validation_criteria: 'All tests pass', }, { id: 'clean_emergency_scripts', description: 'Archive emergency fix scripts', action_type: 'cleanup', command: 'mkdir emergency-archive && mv *fix*.* emergency-archive/', }, ], agents_required: ['test-runner', 'cleaner'], estimated_duration: '45 minutes', dependencies: ['Critical Fixes'], }, { name: 'Autonomous Systems', description: 'Implement full autonomous workflow', steps: [ { id: 'validate_core_functionality', description: 'Test core codebase analysis works', action_type: 'validation', command: 'node dist/bin/crucible.js "analyze this codebase"', validation_criteria: 'Returns actual project analysis, not generic response', }, ], agents_required: ['coordinator'], estimated_duration: '30 minutes', dependencies: ['Critical Fixes', 'Test Infrastructure'], }, ], success_criteria: [ 'Core codebase analysis functionality works', 'All tests pass', 'No memory leaks', 'Version consistency', 'Clean codebase', ], rollback_strategy: 'Git reset to last working commit, restore from backup', }; } private async assignAgentsToTask(task_id: string, plan: TaskPlan): Promise<void> { for (const phase of plan.phases) { for (const agentId of phase.agents_required) { const steps = phase.steps .filter(step => this.isAgentSuitableForStep(agentId, step)) .map(step => step.id); if (steps.length > 0) { await taskMemoryDB.assignAgent(task_id, agentId, this.getAgentRole(agentId), steps); } } } } private async executeTaskAutonomously(task_id: string): Promise<void> { const task = await taskMemoryDB.getTask(task_id); if (!task) { logger.error(`Task not found: ${task_id}`); return; } console.log(chalk.blue('🤖 Starting Autonomous Task Execution')); console.log(chalk.gray(`Task: ${task.title}`)); await taskMemoryDB.updateTask(task_id, { status: 'in_progress' }); // Create initial checkpoint await taskMemoryDB.addCheckpoint(task_id, { phase: 0, description: 'Task execution started', }); try { // Execute each agent's assigned steps for (const assignment of task.agent_assignments) { await this.executeAgentAssignment(task_id, assignment); } // Validate success criteria const success = await this.validateTaskCompletion(task_id); if (success) { await taskMemoryDB.updateTask(task_id, { status: 'completed' }); console.log(chalk.green('✅ Task completed successfully!')); } else { await taskMemoryDB.updateTask(task_id, { status: 'failed' }); console.log(chalk.red('❌ Task failed validation')); } } catch (error) { await taskMemoryDB.recordFailedAttempt( task_id, 'autonomous_execution', error instanceof Error ? error.message : String(error) ); await taskMemoryDB.updateTask(task_id, { status: 'failed' }); console.error(chalk.red('❌ Task execution failed:'), error); } } private async executeAgentAssignment( task_id: string, assignment: AgentAssignment ): Promise<void> { const agent = this.agents.get(assignment.agent_id); if (!agent) { logger.error(`Agent not found: ${assignment.agent_id}`); return; } console.log(chalk.cyan(`🤖 Agent ${assignment.agent_id} starting work`)); for (const stepId of assignment.assigned_steps) { try { const result = await agent.executeStep(stepId); await taskMemoryDB.markStepCompleted(task_id, stepId, result); console.log(chalk.green(` ✅ ${stepId} completed`)); // Create checkpoint after important steps if (stepId.includes('fix_context') || stepId.includes('test')) { await taskMemoryDB.addCheckpoint(task_id, { phase: 1, description: `Completed ${stepId}`, git_commit: await this.createGitCommit(`Automated: ${stepId}`), }); } } catch (error) { await taskMemoryDB.recordFailedAttempt( task_id, stepId, error instanceof Error ? error.message : String(error), assignment.agent_id ); console.error( chalk.red(` ❌ ${stepId} failed:`), error instanceof Error ? error.message : String(error) ); // Try alternative approach or continue to next step console.log(chalk.yellow(` 🔄 Continuing to next step...`)); } } } private async validateTaskCompletion(_task_id: string): Promise<boolean> { console.log(chalk.blue('🔍 Validating task completion...')); const validations = [ async () => { console.log(' • Testing core functionality...'); try { const { stdout } = await execAsync('timeout 30 node dist/bin/crucible.js "test prompt"'); return !stdout.includes('I need to see the codebase'); } catch { return false; } }, async () => { console.log(' • Running tests...'); try { const { stdout } = await execAsync('npm test'); return !stdout.includes('FAIL'); } catch { return false; } }, async () => { console.log(' • Checking build...'); try { await execAsync('npm run build'); return true; } catch { return false; } }, ]; const results = await Promise.all(validations.map(async v => v())); const passCount = results.filter(r => r).length; console.log(chalk.blue(` Validation: ${passCount}/${results.length} checks passed`)); return passCount >= 2; // Allow 1 failure } private async updateTaskFile(task: TaskState, plan: TaskPlan): Promise<void> { const taskContent = await this.generateTaskMarkdown(task, plan); await fs.writeFile(this.taskPlanPath, taskContent); logger.info('Updated task.md with current plan'); } private async generateTaskMarkdown(task: TaskState, plan: TaskPlan): Promise<string> { const progress = await taskMemoryDB.getTaskProgress(task.task_id); return `# 🎯 AUTONOMOUS TASK: ${task.title} **Task ID:** ${task.task_id} **Status:** ${task.status.toUpperCase()} **Progress:** ${progress?.progress_percentage.toFixed(1)}% **Updated:** ${new Date().toISOString()} ## 📊 OVERVIEW ${plan.description} ## 🎯 SUCCESS CRITERIA ${plan.success_criteria.map(c => `- [ ] ${c}`).join('\n')} ## 🤖 AGENT ASSIGNMENTS ${task.agent_assignments .map(a => `- **${a.agent_id}** (${a.role}): ${a.status.toUpperCase()}`) .join('\n')} ## 📋 PROGRESS ### Completed Steps: ${task.completed_steps.map(s => `- ✅ ${s}`).join('\n') || '- None yet'} ### Failed Attempts: ${task.failed_attempts.map(f => `- ❌ ${f.step}: ${f.error} (attempts: ${f.retry_count})`).join('\n') || '- None'} ### Next Steps: ${progress?.next_steps.map(s => `- ⏳ ${s}`).join('\n') || '- Calculating...'} ## 🔄 CHECKPOINTS ${task.checkpoints.map(c => `- **${c.description}** (${c.timestamp})`).join('\n') || '- None yet'} --- *Auto-generated by AutonomousTaskManager*`; } private isAgentSuitableForStep(agentId: string, step: TaskStep): boolean { const agentCapabilities: Record<string, string[]> = { 'primary-fixer': ['code', 'analysis'], 'test-runner': ['test', 'validation'], cleaner: ['cleanup'], coordinator: ['validation', 'analysis'], }; return agentCapabilities[agentId]?.includes(step.action_type) || false; } private getAgentRole(agentId: string): string { const roles: Record<string, string> = { 'primary-fixer': 'Development and Fixes', 'test-runner': 'Testing and Validation', cleaner: 'Cleanup and Optimization', coordinator: 'Task Coordination', }; return roles[agentId] || 'General'; } private estimateTaskDuration(plan: TaskPlan): string { const totalMinutes = plan.phases.reduce((sum, phase) => { const phaseMinutes = parseInt(phase.estimated_duration) || 30; return sum + phaseMinutes; }, 0); return `${Math.ceil(totalMinutes / 60)} hours`; } private async createGitCommit(message: string): Promise<string> { try { await execAsync('git add .'); const { stdout } = await execAsync(`git commit -m "${message}"`); return stdout.split(' ')[0]; // Return commit hash } catch { return 'no-commit'; } } } class AgentWorker { constructor( private id: string, private role: string ) {} async executeStep(stepId: string): Promise<any> { console.log(chalk.gray(` 🔧 ${this.id}: Executing ${stepId}`)); // Simulate work - in production this would call actual tools await new Promise(resolve => setTimeout(resolve, 1000)); // Return mock result return { stepId, agent: this.id, timestamp: new Date().toISOString(), result: 'completed', }; } } export const autonomousTaskManager = new AutonomousTaskManager();