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
JavaScript
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);
}