Skip to main content
Glama
unified-lifecycle-manager.ts26.1 kB
/** * Unified Lifecycle Manager for Vibe Task Manager * * Consolidates task lifecycle, service lifecycle, workflow state management, * and execution coordination into a single, cohesive service. * * Replaces: * - task-lifecycle.ts * - service-lifecycle-manager.ts * - workflow-state-manager.ts * - execution-coordinator.ts (lifecycle aspects) */ import { EventEmitter } from 'events'; import path from 'path'; import * as fs from 'fs-extra'; import { TaskStatus } from '../types/task.js'; import { getTaskOperations } from './operations/task-operations.js'; import { createErrorContext, EnhancedError, ErrorFactory } from '../utils/enhanced-errors.js'; import { getVibeTaskManagerOutputDir } from '../utils/config-loader.js'; import logger from '../../../logger.js'; /** * Branded types for domain-specific type safety */ export type WorkflowId = string & { readonly __brand: 'WorkflowId' }; export type SessionId = string & { readonly __brand: 'SessionId' }; export type TaskId = string & { readonly __brand: 'TaskId' }; export type ProjectId = string & { readonly __brand: 'ProjectId' }; /** * Factory functions for branded type construction */ export function createWorkflowId(id: string): WorkflowId { if (!id || id.trim().length === 0) { throw new Error('Workflow ID cannot be empty'); } return id.trim() as WorkflowId; } export function createSessionId(id: string): SessionId { if (!id || id.trim().length === 0) { throw new Error('Session ID cannot be empty'); } return id.trim() as SessionId; } export function createTaskId(id: string): TaskId { if (!id || id.trim().length === 0) { throw new Error('Task ID cannot be empty'); } return id.trim() as TaskId; } export function createProjectId(id: string): ProjectId { if (!id || id.trim().length === 0) { throw new Error('Project ID cannot be empty'); } return id.trim() as ProjectId; } /** * Result type for explicit error handling */ export type Result<T, E = Error> = { readonly success: true; readonly data: T; } | { readonly success: false; readonly error: E; }; export function createSuccess<T, E = Error>(data: T): Result<T, E> { return { success: true, data }; } export function createFailure<E>(error: E): Result<never, E> { return { success: false, error }; } /** * Task transition metadata */ export interface TaskTransition { taskId: string; fromStatus: TaskStatus; toStatus: TaskStatus; timestamp: Date; reason?: string; triggeredBy?: string; metadata?: Record<string, unknown>; isAutomated: boolean; } /** * Service instance for lifecycle management */ export interface ServiceInstance { name: string; instance: unknown; isStarted: boolean; isDisposed: boolean; startMethod?: string; stopMethod?: string; disposeMethod?: string; resetStaticMethod?: string; } /** * Service dependency configuration */ export interface ServiceDependency { service: string; dependsOn: string[]; } /** * Workflow state information */ export interface WorkflowState { workflowId: WorkflowId; sessionId: SessionId; status: 'initializing' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled'; phase: 'decomposition' | 'orchestration' | 'execution' | 'monitoring' | 'cleanup'; startTime: Date; endTime?: Date; metadata: Record<string, unknown>; tasks: TaskId[]; dependencies: Record<string, string[]>; } /** * Execution status for tasks */ export type ExecutionStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled' | 'timeout'; /** * Task execution context */ export interface TaskExecution { taskId: TaskId; workflowId: WorkflowId; status: ExecutionStatus; startTime: Date; endTime?: Date; actualDuration?: number; result?: { success: boolean; output?: string; error?: string; exitCode?: number; }; metadata: { retryCount: number; timeoutCount: number; lastRetryAt?: Date; executionId: string; }; } /** * Unified lifecycle configuration */ export interface UnifiedLifecycleConfig { // Task lifecycle settings enableTaskAutomation: boolean; taskTransitionTimeout: number; maxTaskRetries: number; enableStateHistory: boolean; enableDependencyTracking: boolean; // Service lifecycle settings serviceStartupTimeout: number; serviceShutdownTimeout: number; enableServiceHealthChecks: boolean; // Workflow state settings enableWorkflowPersistence: boolean; workflowStateBackupInterval: number; maxWorkflowHistory: number; // Execution settings maxConcurrentExecutions: number; executionTimeout: number; enableExecutionMetrics: boolean; } /** * Default configuration */ const DEFAULT_CONFIG: UnifiedLifecycleConfig = { enableTaskAutomation: true, taskTransitionTimeout: 30000, maxTaskRetries: 3, enableStateHistory: true, enableDependencyTracking: true, serviceStartupTimeout: 60000, serviceShutdownTimeout: 30000, enableServiceHealthChecks: true, enableWorkflowPersistence: true, workflowStateBackupInterval: 300000, // 5 minutes maxWorkflowHistory: 100, maxConcurrentExecutions: 10, executionTimeout: 300000, // 5 minutes enableExecutionMetrics: true }; /** * Valid task status transitions */ const VALID_TRANSITIONS: Map<TaskStatus, TaskStatus[]> = 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']] ]); /** * Unified Lifecycle Manager * * Provides comprehensive lifecycle management for tasks, services, workflows, * and executions in a single, cohesive service. */ export class UnifiedLifecycleManager extends EventEmitter { private static instance: UnifiedLifecycleManager | null = null; private config: UnifiedLifecycleConfig; // Task lifecycle state private taskTransitions = new Map<string, TaskTransition[]>(); private taskAutomationInterval?: NodeJS.Timeout; // Service lifecycle state private services = new Map<string, ServiceInstance>(); private serviceDependencies: ServiceDependency[] = []; private startupInProgress = false; private shutdownInProgress = false; // Workflow state management private workflows = new Map<WorkflowId, WorkflowState>(); private workflowBackupInterval?: NodeJS.Timeout; // Execution management private executions = new Map<TaskId, TaskExecution>(); private executionQueue: TaskId[] = []; private runningExecutions = new Set<TaskId>(); private constructor(config: Partial<UnifiedLifecycleConfig> = {}) { super(); this.config = { ...DEFAULT_CONFIG, ...config }; this.setupAutomation(); } /** * Get singleton instance */ static getInstance(config?: Partial<UnifiedLifecycleConfig>): UnifiedLifecycleManager { if (!UnifiedLifecycleManager.instance) { UnifiedLifecycleManager.instance = new UnifiedLifecycleManager(config); } return UnifiedLifecycleManager.instance; } /** * Reset singleton (for testing) */ static resetInstance(): void { if (UnifiedLifecycleManager.instance) { UnifiedLifecycleManager.instance.dispose(); } UnifiedLifecycleManager.instance = null; } // ============================================================================= // TASK LIFECYCLE MANAGEMENT // ============================================================================= /** * Transition a task to a new status */ async transitionTask( taskId: string, toStatus: TaskStatus, options: { reason?: string; triggeredBy?: string; metadata?: Record<string, unknown>; isAutomated?: boolean; } = {} ): Promise<Result<TaskTransition, EnhancedError>> { try { // Get current task status 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; // Validate transition 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() )); } // Create transition record const transition: TaskTransition = { taskId, fromStatus, toStatus, timestamp: new Date(), reason: options.reason, triggeredBy: options.triggeredBy, metadata: options.metadata, isAutomated: options.isAutomated ?? false }; // Update task status const updatedTask = { ...task, status: toStatus }; await taskOps.updateTask(taskId, updatedTask); // Record transition if (this.config.enableStateHistory) { const transitions = this.taskTransitions.get(taskId) || []; transitions.push(transition); this.taskTransitions.set(taskId, transitions); } // Emit transition event 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 } )); } } /** * Get task transition history */ getTaskTransitions(taskId: string): TaskTransition[] { return this.taskTransitions.get(taskId) || []; } // ============================================================================= // SERVICE LIFECYCLE MANAGEMENT // ============================================================================= /** * Register a service for lifecycle management */ registerService(config: Omit<ServiceInstance, 'isStarted' | 'isDisposed'>): void { const service: ServiceInstance = { ...config, isStarted: false, isDisposed: false }; this.services.set(config.name, service); logger.debug(`Service registered: ${config.name}`); this.emit('serviceRegistered', service); } /** * Register service dependencies */ registerServiceDependency(service: string, dependsOn: string[]): void { this.serviceDependencies.push({ service, dependsOn }); logger.debug(`Dependencies registered for ${service}: ${dependsOn.join(', ')}`); } /** * Start all services in dependency order */ async startAllServices(): Promise<Result<void, EnhancedError>> { 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; } } /** * Start a specific service */ private async startService(serviceName: string): Promise<Result<void, EnhancedError>> { 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 { // Call start method if available if (service.startMethod && typeof (service.instance as Record<string, unknown>)[service.startMethod] === 'function') { await (service.instance as Record<string, () => Promise<void>>)[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 } )); } } /** * Calculate service startup order based on dependencies */ private calculateStartupOrder(): string[] { const visited = new Set<string>(); const visiting = new Set<string>(); const order: string[] = []; const visit = (serviceName: string): void => { if (visiting.has(serviceName)) { throw new Error(`Circular dependency detected involving service: ${serviceName}`); } if (visited.has(serviceName)) { return; } visiting.add(serviceName); // Visit dependencies first const deps = this.serviceDependencies.find(d => d.service === serviceName); if (deps) { for (const dep of deps.dependsOn) { // Only visit if the dependency is a registered service if (this.services.has(dep)) { visit(dep); } } } visiting.delete(serviceName); visited.add(serviceName); order.push(serviceName); }; // Create a sorted list of services to ensure consistent ordering // Services with no dependencies should be visited first const serviceNames = Array.from(this.services.keys()).sort(); // First, visit services that have no dependencies const servicesWithDeps = new Set(this.serviceDependencies.map(d => d.service)); const servicesWithoutDeps = serviceNames.filter(name => !servicesWithDeps.has(name)); // Visit services without dependencies first for (const serviceName of servicesWithoutDeps) { if (!visited.has(serviceName)) { visit(serviceName); } } // Then visit remaining services (those with dependencies) for (const serviceName of serviceNames) { if (!visited.has(serviceName)) { visit(serviceName); } } return order; } // ============================================================================= // WORKFLOW STATE MANAGEMENT // ============================================================================= /** * Create a new workflow */ async createWorkflow( workflowId: WorkflowId, sessionId: SessionId, metadata: Record<string, unknown> = {} ): Promise<Result<WorkflowState, EnhancedError>> { try { if (this.workflows.has(workflowId)) { return createFailure(ErrorFactory.createError( 'validation', `Workflow already exists: ${workflowId}`, createErrorContext('UnifiedLifecycleManager', 'createWorkflow') .metadata({ workflowId }) .build() )); } const workflow: WorkflowState = { 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 } )); } } /** * Update workflow state */ async updateWorkflowState( workflowId: WorkflowId, updates: Partial<Omit<WorkflowState, 'workflowId'>> ): Promise<Result<WorkflowState, EnhancedError>> { 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 } )); } } /** * Get workflow state */ getWorkflowState(workflowId: WorkflowId): WorkflowState | null { return this.workflows.get(workflowId) || null; } /** * Persist workflow state to disk */ private async persistWorkflowState(workflow: WorkflowState): Promise<void> { 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); } } // ============================================================================= // EXECUTION MANAGEMENT // ============================================================================= /** * Queue task for execution */ async queueTaskExecution( taskId: TaskId, workflowId: WorkflowId, _metadata: Record<string, unknown> = {} ): Promise<Result<TaskExecution, EnhancedError>> { try { if (this.executions.has(taskId)) { return createFailure(ErrorFactory.createError( 'validation', `Task execution already exists: ${taskId}`, createErrorContext('UnifiedLifecycleManager', 'queueTaskExecution') .taskId(taskId) .build() )); } const execution: TaskExecution = { 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}`); // Process queue (only if not in test mode) 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 } )); } } /** * Process execution queue */ private async processExecutionQueue(): Promise<void> { while ( this.executionQueue.length > 0 && this.runningExecutions.size < this.config.maxConcurrentExecutions ) { const taskId = this.executionQueue.shift(); if (taskId) { this.executeTask(taskId); } } } /** * Execute a task */ private async executeTask(taskId: TaskId): Promise<void> { 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 { // Transition task to in_progress await this.transitionTask(taskId, 'in_progress', { reason: 'Task execution started', triggeredBy: 'UnifiedLifecycleManager', isAutomated: true }); // TODO: Actual task execution logic would go here // For now, simulate execution await new Promise(resolve => setTimeout(resolve, 1000)); // Mark as completed 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); // Process next tasks in queue this.processExecutionQueue(); } } // ============================================================================= // AUTOMATION AND CLEANUP // ============================================================================= /** * Setup automation intervals */ private setupAutomation(): void { if (this.config.enableTaskAutomation) { this.taskAutomationInterval = setInterval(() => { this.processTaskAutomation(); }, 5000); // Check every 5 seconds } if (this.config.enableWorkflowPersistence) { this.workflowBackupInterval = setInterval(() => { this.backupWorkflowStates(); }, this.config.workflowStateBackupInterval); } } /** * Process task automation */ private async processTaskAutomation(): Promise<void> { // TODO: Implement task automation logic // This would handle automatic task transitions based on dependencies, // timeouts, and other conditions } /** * Backup workflow states */ private async backupWorkflowStates(): Promise<void> { for (const workflow of this.workflows.values()) { await this.persistWorkflowState(workflow); } } /** * Dispose of the lifecycle manager */ dispose(): void { 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'); } } /** * Convenience function to get the unified lifecycle manager instance */ export function getUnifiedLifecycleManager(config?: Partial<UnifiedLifecycleConfig>): UnifiedLifecycleManager { return UnifiedLifecycleManager.getInstance(config); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freshtechbro/vibe-coder-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server