UNPKG

shipdeck

Version:

Ship MVPs in 48 hours. Fix bugs in 30 seconds. The command deck for developers who ship.

550 lines (469 loc) 14.7 kB
/** * Task Orchestrator for Shipdeck Ultimate * Routes tasks to appropriate agents and manages execution */ const { AgentExecutor, AGENT_ROLES } = require('../anthropic/agent-executor'); const EventEmitter = require('events'); const { logger } = require('./logger'); /** * Task type to agent mapping * Maps high-level task types to specific agents */ const TASK_AGENT_MAPPING = { // Backend tasks 'api': 'backend-architect', 'database': 'backend-architect', 'auth': 'backend-architect', 'security': 'backend-architect', 'server': 'backend-architect', 'backend': 'backend-architect', 'microservice': 'backend-architect', // Frontend tasks 'ui': 'frontend-developer', 'component': 'frontend-developer', 'react': 'frontend-developer', 'frontend': 'frontend-developer', 'interface': 'frontend-developer', 'dashboard': 'frontend-developer', 'form': 'frontend-developer', // AI/ML tasks 'ai': 'ai-engineer', 'ml': 'ai-engineer', 'llm': 'ai-engineer', 'prompt': 'ai-engineer', 'intelligence': 'ai-engineer', 'chatbot': 'ai-engineer', // Testing tasks 'test': 'test-writer-fixer', 'testing': 'test-writer-fixer', 'unittest': 'test-writer-fixer', 'integration': 'test-writer-fixer', 'coverage': 'test-writer-fixer', // DevOps tasks 'deploy': 'devops-automator', 'deployment': 'devops-automator', 'ci': 'devops-automator', 'cd': 'devops-automator', 'docker': 'devops-automator', 'infrastructure': 'devops-automator', 'pipeline': 'devops-automator' }; /** * Task complexity estimation */ const COMPLEXITY_INDICATORS = { simple: ['button', 'link', 'text', 'basic', 'simple', 'quick'], medium: ['form', 'table', 'chart', 'integration', 'auth', 'crud'], complex: ['dashboard', 'analytics', 'workflow', 'payment', 'system', 'architecture'] }; class TaskOrchestrator extends EventEmitter { constructor(options = {}) { super(); this.agentExecutor = new AgentExecutor(options.agentExecutor); this.config = { defaultAgent: 'backend-architect', maxRetries: 2, retryDelay: 1000, timeout: 300000, // 5 minutes default enableLogging: true, ...options.config }; this.executionHistory = new Map(); this.activeExecutions = new Map(); if (this.config.enableLogging) { this.setupLogging(); } } /** * Setup execution logging */ setupLogging() { this.on('task:started', (data) => { logger.info(`Task started: ${data.taskType} (Agent: ${data.agent})`); }); this.on('task:completed', (data) => { logger.info(`Task completed: ${data.taskType} in ${data.duration}ms`); }); this.on('task:failed', (data) => { console.error(`❌ Task failed: ${data.taskType} - ${data.error}`); }); this.on('task:retry', (data) => { logger.info(`Retrying task: ${data.taskType} (Attempt ${data.attempt}/${data.maxRetries})`); }); } /** * Execute a task by routing it to the appropriate agent * @param {string} taskType - Type of task (e.g., 'api', 'ui', 'test') * @param {Object} params - Task parameters * @param {string} params.description - Task description/prompt * @param {Object} params.context - Additional context * @param {Object} options - Execution options * @returns {Object} Execution result */ async executeTask(taskType, params = {}, options = {}) { const startTime = Date.now(); const executionId = this.generateExecutionId(); // Validate inputs if (!taskType || typeof taskType !== 'string') { throw new Error('Task type is required and must be a string'); } if (!params.description) { throw new Error('Task description is required'); } // Determine agent const agent = this.selectAgent(taskType, params); const complexity = this.estimateComplexity(params.description); // Prepare execution context const executionContext = { id: executionId, taskType, agent, complexity, params, options: { ...this.config, ...options }, startTime, attempts: 0 }; this.activeExecutions.set(executionId, executionContext); try { this.emit('task:started', { id: executionId, taskType, agent, complexity, description: params.description }); const result = await this.executeWithRetry(executionContext); const duration = Date.now() - startTime; const executionResult = { success: true, result: result.content?.[0]?.text || result, agent, taskType, complexity, duration, metadata: { ...result.metadata, executionId, hasTests: result.hasTests || false }, tests: result.tests }; // Store in history this.executionHistory.set(executionId, { ...executionResult, params, timestamp: new Date().toISOString() }); this.emit('task:completed', { id: executionId, taskType, agent, duration, hasTests: executionResult.metadata.hasTests }); return executionResult; } catch (error) { const duration = Date.now() - startTime; const executionResult = { success: false, error: error.message, agent, taskType, complexity, duration, metadata: { executionId, attempts: executionContext.attempts } }; // Store in history this.executionHistory.set(executionId, { ...executionResult, params, timestamp: new Date().toISOString() }); this.emit('task:failed', { id: executionId, taskType, agent, error: error.message, duration }); return executionResult; } finally { this.activeExecutions.delete(executionId); } } /** * Execute task with retry logic */ async executeWithRetry(executionContext) { const { taskType, agent, params, options } = executionContext; let lastError; for (let attempt = 1; attempt <= options.maxRetries + 1; attempt++) { executionContext.attempts = attempt; try { // Add retry context to prompt if this is a retry let prompt = params.description; if (attempt > 1) { prompt = `${prompt}\n\nNote: This is retry attempt ${attempt}. Previous attempt failed with: ${lastError?.message}`; } // Add context if provided if (params.context) { prompt = `Context: ${JSON.stringify(params.context, null, 2)}\n\nTask: ${prompt}`; } // Execute with timeout const result = await this.executeWithTimeout( () => this.agentExecutor.executeAgent(agent, prompt, { sessionId: executionContext.id, ...options.agentOptions }), options.timeout ); return result; } catch (error) { lastError = error; if (attempt <= options.maxRetries) { this.emit('task:retry', { id: executionContext.id, taskType, agent, attempt, maxRetries: options.maxRetries, error: error.message }); // Wait before retry await this.sleep(options.retryDelay * attempt); } } } throw lastError; } /** * Execute with timeout wrapper */ async executeWithTimeout(promise, timeoutMs) { const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error(`Task timed out after ${timeoutMs}ms`)), timeoutMs); }); return Promise.race([promise(), timeout]); } /** * Select appropriate agent based on task type and parameters * @param {string} taskType - The task type * @param {Object} params - Task parameters * @returns {string} Agent type */ selectAgent(taskType, params) { // Direct mapping first if (TASK_AGENT_MAPPING[taskType.toLowerCase()]) { return TASK_AGENT_MAPPING[taskType.toLowerCase()]; } // Content-based detection const description = params.description.toLowerCase(); // Check for keywords in description for (const [keywords, agent] of Object.entries(TASK_AGENT_MAPPING)) { if (description.includes(keywords)) { return agent; } } // Pattern-based detection if (description.includes('test') || description.includes('spec')) { return 'test-writer-fixer'; } if (description.includes('deploy') || description.includes('build')) { return 'devops-automator'; } if (description.includes('component') || description.includes('ui') || description.includes('interface')) { return 'frontend-developer'; } if (description.includes('api') || description.includes('server') || description.includes('database')) { return 'backend-architect'; } if (description.includes('ai') || description.includes('intelligent') || description.includes('llm')) { return 'ai-engineer'; } // Default fallback return this.config.defaultAgent; } /** * Estimate task complexity * @param {string} description - Task description * @returns {string} Complexity level */ estimateComplexity(description) { const desc = description.toLowerCase(); // Check for complexity indicators for (const [level, indicators] of Object.entries(COMPLEXITY_INDICATORS)) { if (indicators.some(indicator => desc.includes(indicator))) { return level; } } // Length-based estimation if (desc.length < 50) return 'simple'; if (desc.length < 200) return 'medium'; return 'complex'; } /** * Execute multiple tasks sequentially * @param {Array} tasks - Array of task definitions * @returns {Array} Array of execution results */ async executeTasks(tasks) { const results = []; for (const task of tasks) { try { const result = await this.executeTask( task.type, task.params, task.options ); results.push(result); } catch (error) { results.push({ success: false, error: error.message, taskType: task.type }); } } return results; } /** * Get available agents * @returns {Array} List of available agents */ getAvailableAgents() { return Object.keys(AGENT_ROLES).map(agentType => ({ type: agentType, description: AGENT_ROLES[agentType].systemPrompt.split('\n')[0], config: { temperature: AGENT_ROLES[agentType].temperature, maxTokens: AGENT_ROLES[agentType].maxTokens } })); } /** * Get task execution history * @param {Object} filters - Optional filters * @returns {Array} Execution history */ getExecutionHistory(filters = {}) { let history = Array.from(this.executionHistory.values()); if (filters.agent) { history = history.filter(h => h.agent === filters.agent); } if (filters.taskType) { history = history.filter(h => h.taskType === filters.taskType); } if (filters.success !== undefined) { history = history.filter(h => h.success === filters.success); } if (filters.limit) { history = history.slice(-filters.limit); } return history.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); } /** * Get active executions * @returns {Array} Currently running executions */ getActiveExecutions() { return Array.from(this.activeExecutions.values()).map(ctx => ({ id: ctx.id, taskType: ctx.taskType, agent: ctx.agent, complexity: ctx.complexity, attempts: ctx.attempts, duration: Date.now() - ctx.startTime })); } /** * Get execution statistics * @returns {Object} Statistics */ getStats() { const history = Array.from(this.executionHistory.values()); const successful = history.filter(h => h.success); const failed = history.filter(h => !h.success); const agentStats = {}; const taskTypeStats = {}; for (const execution of history) { // Agent stats if (!agentStats[execution.agent]) { agentStats[execution.agent] = { total: 0, successful: 0, failed: 0 }; } agentStats[execution.agent].total++; if (execution.success) { agentStats[execution.agent].successful++; } else { agentStats[execution.agent].failed++; } // Task type stats if (!taskTypeStats[execution.taskType]) { taskTypeStats[execution.taskType] = { total: 0, successful: 0, failed: 0 }; } taskTypeStats[execution.taskType].total++; if (execution.success) { taskTypeStats[execution.taskType].successful++; } else { taskTypeStats[execution.taskType].failed++; } } return { total: history.length, successful: successful.length, failed: failed.length, successRate: history.length > 0 ? (successful.length / history.length) * 100 : 0, averageDuration: history.length > 0 ? history.reduce((sum, h) => sum + h.duration, 0) / history.length : 0, agentStats, taskTypeStats, activeExecutions: this.activeExecutions.size }; } /** * Clear execution history */ clearHistory() { this.executionHistory.clear(); } /** * Generate unique execution ID */ generateExecutionId() { return `exec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Sleep utility for retry delays */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Shutdown orchestrator */ async shutdown() { logger.info('Shutting down task orchestrator...'); // Wait for active executions to complete (with timeout) const activeIds = Array.from(this.activeExecutions.keys()); if (activeIds.length > 0) { logger.info(`Waiting for ${activeIds.length} active executions to complete...`); // Wait up to 30 seconds for executions to complete let waited = 0; while (this.activeExecutions.size > 0 && waited < 30000) { await this.sleep(1000); waited += 1000; } if (this.activeExecutions.size > 0) { console.log(`⚠️ ${this.activeExecutions.size} executions did not complete in time`); } } console.log('✅ Task orchestrator shutdown complete'); } } module.exports = { TaskOrchestrator, TASK_AGENT_MAPPING, COMPLEXITY_INDICATORS };