UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

455 lines (454 loc) 19 kB
import { EventEmitter } from 'events'; import path from 'path'; import * as fs from 'fs-extra'; import { getTaskOperations } from './operations/task-operations.js'; import { createErrorContext, ErrorFactory } from '../utils/enhanced-errors.js'; import { getVibeTaskManagerOutputDir } from '../utils/config-loader.js'; import logger from '../../../logger.js'; export function createWorkflowId(id) { if (!id || id.trim().length === 0) { throw new Error('Workflow ID cannot be empty'); } return id.trim(); } export function createSessionId(id) { if (!id || id.trim().length === 0) { throw new Error('Session ID cannot be empty'); } return id.trim(); } export function createTaskId(id) { if (!id || id.trim().length === 0) { throw new Error('Task ID cannot be empty'); } return id.trim(); } export function createProjectId(id) { if (!id || id.trim().length === 0) { throw new Error('Project ID cannot be empty'); } return id.trim(); } export function createSuccess(data) { return { success: true, data }; } export function createFailure(error) { return { success: false, error }; } const DEFAULT_CONFIG = { enableTaskAutomation: true, taskTransitionTimeout: 30000, maxTaskRetries: 3, enableStateHistory: true, enableDependencyTracking: true, serviceStartupTimeout: 60000, serviceShutdownTimeout: 30000, enableServiceHealthChecks: true, enableWorkflowPersistence: true, workflowStateBackupInterval: 300000, maxWorkflowHistory: 100, maxConcurrentExecutions: 10, executionTimeout: 300000, enableExecutionMetrics: true }; const VALID_TRANSITIONS = new Map([ ['pending', ['in_progress', 'cancelled', 'blocked']], ['in_progress', ['completed', 'failed', 'blocked', 'cancelled']], ['blocked', ['in_progress', 'cancelled', 'failed']], ['completed', ['cancelled']], ['failed', ['pending', 'cancelled']], ['cancelled', ['pending']] ]); export class UnifiedLifecycleManager extends EventEmitter { static instance = null; config; taskTransitions = new Map(); taskAutomationInterval; services = new Map(); serviceDependencies = []; startupInProgress = false; shutdownInProgress = false; workflows = new Map(); workflowBackupInterval; executions = new Map(); executionQueue = []; runningExecutions = new Set(); constructor(config = {}) { super(); this.config = { ...DEFAULT_CONFIG, ...config }; this.setupAutomation(); } static getInstance(config) { if (!UnifiedLifecycleManager.instance) { UnifiedLifecycleManager.instance = new UnifiedLifecycleManager(config); } return UnifiedLifecycleManager.instance; } static resetInstance() { if (UnifiedLifecycleManager.instance) { UnifiedLifecycleManager.instance.dispose(); } UnifiedLifecycleManager.instance = null; } async transitionTask(taskId, toStatus, options = {}) { try { const taskOps = getTaskOperations(); const taskResult = await taskOps.getTask(taskId); if (!taskResult.success || !taskResult.data) { return createFailure(ErrorFactory.createError('task', `Task not found: ${taskId}`, createErrorContext('UnifiedLifecycleManager', 'transitionTask') .taskId(taskId) .build())); } const task = taskResult.data; const fromStatus = task.status; const validTransitions = VALID_TRANSITIONS.get(fromStatus) || []; if (!validTransitions.includes(toStatus)) { return createFailure(ErrorFactory.createError('validation', `Invalid transition from ${fromStatus} to ${toStatus}`, createErrorContext('UnifiedLifecycleManager', 'transitionTask') .taskId(taskId) .metadata({ fromStatus, toStatus, validTransitions }) .build())); } const transition = { taskId, fromStatus, toStatus, timestamp: new Date(), reason: options.reason, triggeredBy: options.triggeredBy, metadata: options.metadata, isAutomated: options.isAutomated ?? false }; const updatedTask = { ...task, status: toStatus }; await taskOps.updateTask(taskId, updatedTask); if (this.config.enableStateHistory) { const transitions = this.taskTransitions.get(taskId) || []; transitions.push(transition); this.taskTransitions.set(taskId, transitions); } this.emit('taskTransition', transition); logger.info(`Task ${taskId} transitioned from ${fromStatus} to ${toStatus}`, { taskId, fromStatus, toStatus, reason: options.reason, isAutomated: options.isAutomated }); return createSuccess(transition); } catch (error) { return createFailure(ErrorFactory.createError('task', `Failed to transition task: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedLifecycleManager', 'transitionTask') .taskId(taskId) .build(), { cause: error instanceof Error ? error : undefined })); } } getTaskTransitions(taskId) { return this.taskTransitions.get(taskId) || []; } registerService(config) { const service = { ...config, isStarted: false, isDisposed: false }; this.services.set(config.name, service); logger.debug(`Service registered: ${config.name}`); this.emit('serviceRegistered', service); } registerServiceDependency(service, dependsOn) { this.serviceDependencies.push({ service, dependsOn }); logger.debug(`Dependencies registered for ${service}: ${dependsOn.join(', ')}`); } async startAllServices() { if (this.startupInProgress) { return createFailure(ErrorFactory.createError('system', 'Service startup already in progress', createErrorContext('UnifiedLifecycleManager', 'startAllServices').build())); } if (this.shutdownInProgress) { return createFailure(ErrorFactory.createError('system', 'Service shutdown in progress, cannot start services', createErrorContext('UnifiedLifecycleManager', 'startAllServices').build())); } this.startupInProgress = true; try { const startOrder = this.calculateStartupOrder(); logger.info(`Starting services in order: ${startOrder.join(' -> ')}`); for (const serviceName of startOrder) { const result = await this.startService(serviceName); if (!result.success) { return result; } } this.emit('allServicesStarted'); return createSuccess(undefined); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to start services: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedLifecycleManager', 'startAllServices').build(), { cause: error instanceof Error ? error : undefined })); } finally { this.startupInProgress = false; } } async startService(serviceName) { const service = this.services.get(serviceName); if (!service) { return createFailure(ErrorFactory.createError('system', `Service not found: ${serviceName}`, createErrorContext('UnifiedLifecycleManager', 'startService') .metadata({ serviceName }) .build())); } if (service.isStarted) { return createSuccess(undefined); } try { if (service.startMethod && typeof service.instance[service.startMethod] === 'function') { await service.instance[service.startMethod](); } service.isStarted = true; logger.info(`Service started: ${serviceName}`); this.emit('serviceStarted', service); return createSuccess(undefined); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to start service ${serviceName}: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedLifecycleManager', 'startService') .metadata({ serviceName }) .build(), { cause: error instanceof Error ? error : undefined })); } } calculateStartupOrder() { const visited = new Set(); const visiting = new Set(); const order = []; const visit = (serviceName) => { if (visiting.has(serviceName)) { throw new Error(`Circular dependency detected involving service: ${serviceName}`); } if (visited.has(serviceName)) { return; } visiting.add(serviceName); const deps = this.serviceDependencies.find(d => d.service === serviceName); if (deps) { for (const dep of deps.dependsOn) { if (this.services.has(dep)) { visit(dep); } } } visiting.delete(serviceName); visited.add(serviceName); order.push(serviceName); }; const serviceNames = Array.from(this.services.keys()).sort(); const servicesWithDeps = new Set(this.serviceDependencies.map(d => d.service)); const servicesWithoutDeps = serviceNames.filter(name => !servicesWithDeps.has(name)); for (const serviceName of servicesWithoutDeps) { if (!visited.has(serviceName)) { visit(serviceName); } } for (const serviceName of serviceNames) { if (!visited.has(serviceName)) { visit(serviceName); } } return order; } async createWorkflow(workflowId, sessionId, metadata = {}) { try { if (this.workflows.has(workflowId)) { return createFailure(ErrorFactory.createError('validation', `Workflow already exists: ${workflowId}`, createErrorContext('UnifiedLifecycleManager', 'createWorkflow') .metadata({ workflowId }) .build())); } const workflow = { workflowId, sessionId, status: 'initializing', phase: 'decomposition', startTime: new Date(), metadata, tasks: [], dependencies: {} }; this.workflows.set(workflowId, workflow); if (this.config.enableWorkflowPersistence) { this.persistWorkflowState(workflow); } this.emit('workflowCreated', workflow); logger.info(`Workflow created: ${workflowId}`); return createSuccess(workflow); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to create workflow: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedLifecycleManager', 'createWorkflow') .metadata({ workflowId }) .build(), { cause: error instanceof Error ? error : undefined })); } } async updateWorkflowState(workflowId, updates) { try { const workflow = this.workflows.get(workflowId); if (!workflow) { return createFailure(ErrorFactory.createError('validation', `Workflow not found: ${workflowId}`, createErrorContext('UnifiedLifecycleManager', 'updateWorkflowState') .metadata({ workflowId }) .build())); } const updatedWorkflow = { ...workflow, ...updates }; this.workflows.set(workflowId, updatedWorkflow); if (this.config.enableWorkflowPersistence) { this.persistWorkflowState(updatedWorkflow); } this.emit('workflowUpdated', updatedWorkflow); logger.debug(`Workflow updated: ${workflowId}`); return createSuccess(updatedWorkflow); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to update workflow: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedLifecycleManager', 'updateWorkflowState') .metadata({ workflowId }) .build(), { cause: error instanceof Error ? error : undefined })); } } getWorkflowState(workflowId) { return this.workflows.get(workflowId) || null; } async persistWorkflowState(workflow) { try { const outputDir = getVibeTaskManagerOutputDir(); const workflowDir = path.join(outputDir, 'workflows'); await fs.ensureDir(workflowDir); const filePath = path.join(workflowDir, `${workflow.workflowId}.json`); await fs.writeJson(filePath, workflow, { spaces: 2 }); } catch (error) { logger.error(`Failed to persist workflow state: ${workflow.workflowId}`, error); } } async queueTaskExecution(taskId, workflowId, _metadata = {}) { try { if (this.executions.has(taskId)) { return createFailure(ErrorFactory.createError('validation', `Task execution already exists: ${taskId}`, createErrorContext('UnifiedLifecycleManager', 'queueTaskExecution') .taskId(taskId) .build())); } const execution = { taskId, workflowId, status: 'queued', startTime: new Date(), metadata: { retryCount: 0, timeoutCount: 0, executionId: `exec-${Date.now()}-${Math.random().toString(36).substring(2, 11)}` } }; this.executions.set(taskId, execution); this.executionQueue.push(taskId); this.emit('taskQueued', execution); logger.debug(`Task queued for execution: ${taskId}`); if (process.env.NODE_ENV !== 'test') { void this.processExecutionQueue(); } return createSuccess(execution); } catch (error) { return createFailure(ErrorFactory.createError('system', `Failed to queue task execution: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedLifecycleManager', 'queueTaskExecution') .taskId(taskId) .build(), { cause: error instanceof Error ? error : undefined })); } } async processExecutionQueue() { while (this.executionQueue.length > 0 && this.runningExecutions.size < this.config.maxConcurrentExecutions) { const taskId = this.executionQueue.shift(); if (taskId) { this.executeTask(taskId); } } } async executeTask(taskId) { const execution = this.executions.get(taskId); if (!execution) { logger.error(`Execution not found for task: ${taskId}`); return; } this.runningExecutions.add(taskId); execution.status = 'running'; execution.startTime = new Date(); this.emit('taskExecutionStarted', execution); try { await this.transitionTask(taskId, 'in_progress', { reason: 'Task execution started', triggeredBy: 'UnifiedLifecycleManager', isAutomated: true }); await new Promise(resolve => setTimeout(resolve, 1000)); execution.status = 'completed'; execution.endTime = new Date(); execution.actualDuration = (execution.endTime.getTime() - execution.startTime.getTime()) / 1000 / 3600; execution.result = { success: true, output: 'Task completed successfully' }; await this.transitionTask(taskId, 'completed', { reason: 'Task execution completed', triggeredBy: 'UnifiedLifecycleManager', isAutomated: true }); this.emit('taskExecutionCompleted', execution); logger.info(`Task execution completed: ${taskId}`); } catch (error) { execution.status = 'failed'; execution.endTime = new Date(); execution.result = { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; await this.transitionTask(taskId, 'failed', { reason: 'Task execution failed', triggeredBy: 'UnifiedLifecycleManager', isAutomated: true, metadata: { error: execution.result.error } }); this.emit('taskExecutionFailed', execution); logger.error(`Task execution failed: ${taskId}`, error); } finally { this.runningExecutions.delete(taskId); this.processExecutionQueue(); } } setupAutomation() { if (this.config.enableTaskAutomation) { this.taskAutomationInterval = setInterval(() => { this.processTaskAutomation(); }, 5000); } if (this.config.enableWorkflowPersistence) { this.workflowBackupInterval = setInterval(() => { this.backupWorkflowStates(); }, this.config.workflowStateBackupInterval); } } async processTaskAutomation() { } async backupWorkflowStates() { for (const workflow of this.workflows.values()) { await this.persistWorkflowState(workflow); } } dispose() { if (this.taskAutomationInterval) { clearInterval(this.taskAutomationInterval); } if (this.workflowBackupInterval) { clearInterval(this.workflowBackupInterval); } this.removeAllListeners(); this.taskTransitions.clear(); this.services.clear(); this.workflows.clear(); this.executions.clear(); this.executionQueue.length = 0; this.runningExecutions.clear(); logger.debug('UnifiedLifecycleManager disposed'); } } export function getUnifiedLifecycleManager(config) { return UnifiedLifecycleManager.getInstance(config); }