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.

753 lines (752 loc) 31.8 kB
import path from 'path'; import { EventEmitter } from 'events'; import * as fs from 'fs-extra'; import logger from '../../../logger.js'; import { FileUtils } from '../utils/file-utils.js'; import { createErrorContext } from '../utils/enhanced-errors.js'; import { getVibeTaskManagerOutputDir } from '../utils/config-loader.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 }; } export function resolveWorkflowId(data) { try { const metadata = data.metadata; const resolvedId = (metadata?.jobId || metadata?.sessionId || data.taskId || null); if (!resolvedId || resolvedId.trim().length === 0) { return createFailure('No valid ID found in progress event data'); } const workflowId = createWorkflowId(resolvedId.trim()); return createSuccess(workflowId); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error during ID resolution'; return createFailure(`Failed to resolve workflow ID: ${errorMessage}`); } } export function mapSubtaskToParentWorkflowId(taskId) { const rddSubtaskPattern = /^(task-\d+)-(?:atomic|plan|impl)-\d+$/; const match = taskId.match(rddSubtaskPattern); if (match) { const parentId = match[1]; logger.debug({ originalTaskId: taskId, parentTaskId: parentId, pattern: 'rdd_subtask', matchType: 'RDD engine subtask' }, 'Mapped RDD subtask to parent task ID'); return parentId; } const genericSubtaskPattern = /^(.+)-[a-zA-Z]+-\d+$/; const genericMatch = taskId.match(genericSubtaskPattern); if (genericMatch) { const parentId = genericMatch[1]; logger.debug({ originalTaskId: taskId, parentTaskId: parentId, pattern: 'generic_subtask', matchType: 'Generic subtask pattern' }, 'Mapped generic subtask to parent task ID'); return parentId; } logger.debug({ taskId: taskId, pattern: 'no_mapping', matchType: 'Direct task ID (no mapping needed)' }, 'No subtask pattern detected, using original task ID'); return taskId; } export function resolveWorkflowIdWithMapping(data) { try { logger.debug({ dataKeys: Object.keys(data), taskId: data.taskId, metadataJobId: data.metadata?.jobId, metadataSessionId: data.metadata?.sessionId }, 'Starting enhanced workflow ID resolution'); const standardResult = resolveWorkflowId(data); if (standardResult.success) { const workflowId = standardResult.data; const mappedId = mapSubtaskToParentWorkflowId(workflowId); if (mappedId !== workflowId) { logger.debug({ originalWorkflowId: workflowId, mappedWorkflowId: mappedId, resolution: 'subtask_mapped_to_parent' }, 'Successfully mapped subtask ID to parent workflow ID'); return createSuccess(createWorkflowId(mappedId)); } logger.debug({ workflowId: workflowId, resolution: 'direct_workflow_id' }, 'Successfully resolved direct workflow ID'); return standardResult; } logger.debug({ error: standardResult.error, resolution: 'failed' }, 'Enhanced workflow ID resolution failed'); return standardResult; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error during enhanced ID resolution'; logger.error({ err: error, dataKeys: Object.keys(data), taskId: data.taskId }, 'Exception during enhanced workflow ID resolution'); return createFailure(`Failed to resolve workflow ID with mapping: ${errorMessage}`); } } export var WorkflowPhase; (function (WorkflowPhase) { WorkflowPhase["INITIALIZATION"] = "initialization"; WorkflowPhase["DECOMPOSITION"] = "decomposition"; WorkflowPhase["ORCHESTRATION"] = "orchestration"; WorkflowPhase["EXECUTION"] = "execution"; WorkflowPhase["COMPLETED"] = "completed"; WorkflowPhase["FAILED"] = "failed"; WorkflowPhase["CANCELLED"] = "cancelled"; })(WorkflowPhase || (WorkflowPhase = {})); export var WorkflowState; (function (WorkflowState) { WorkflowState["PENDING"] = "pending"; WorkflowState["IN_PROGRESS"] = "in_progress"; WorkflowState["COMPLETED"] = "completed"; WorkflowState["FAILED"] = "failed"; WorkflowState["CANCELLED"] = "cancelled"; WorkflowState["BLOCKED"] = "blocked"; WorkflowState["RETRYING"] = "retrying"; })(WorkflowState || (WorkflowState = {})); export const SUB_PHASES = { [WorkflowPhase.INITIALIZATION]: [ { name: 'setup', weight: 0.4, order: 1 }, { name: 'validation', weight: 0.3, order: 2 }, { name: 'preparation', weight: 0.3, order: 3 } ], [WorkflowPhase.DECOMPOSITION]: [ { name: 'research', weight: 0.2, order: 1 }, { name: 'context_gathering', weight: 0.25, order: 2 }, { name: 'decomposition', weight: 0.3, order: 3 }, { name: 'validation', weight: 0.15, order: 4 }, { name: 'dependency_detection', weight: 0.1, order: 5 } ], [WorkflowPhase.ORCHESTRATION]: [ { name: 'task_preparation', weight: 0.4, order: 1 }, { name: 'dependency_resolution', weight: 0.3, order: 2 }, { name: 'agent_assignment', weight: 0.3, order: 3 } ], [WorkflowPhase.EXECUTION]: [ { name: 'task_execution', weight: 0.7, order: 1 }, { name: 'monitoring', weight: 0.2, order: 2 }, { name: 'completion_verification', weight: 0.1, order: 3 } ], [WorkflowPhase.COMPLETED]: [], [WorkflowPhase.FAILED]: [], [WorkflowPhase.CANCELLED]: [] }; const VALID_TRANSITIONS = new Map([ [`${WorkflowPhase.INITIALIZATION}:${WorkflowState.PENDING}`, new Set([ `${WorkflowPhase.INITIALIZATION}:${WorkflowState.IN_PROGRESS}`, `${WorkflowPhase.INITIALIZATION}:${WorkflowState.FAILED}`, `${WorkflowPhase.INITIALIZATION}:${WorkflowState.CANCELLED}` ])], [`${WorkflowPhase.INITIALIZATION}:${WorkflowState.IN_PROGRESS}`, new Set([ `${WorkflowPhase.INITIALIZATION}:${WorkflowState.COMPLETED}`, `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.PENDING}`, `${WorkflowPhase.INITIALIZATION}:${WorkflowState.FAILED}`, `${WorkflowPhase.INITIALIZATION}:${WorkflowState.CANCELLED}` ])], [`${WorkflowPhase.INITIALIZATION}:${WorkflowState.COMPLETED}`, new Set([ `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.PENDING}` ])], [`${WorkflowPhase.DECOMPOSITION}:${WorkflowState.PENDING}`, new Set([ `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.IN_PROGRESS}`, `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.FAILED}`, `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.CANCELLED}` ])], [`${WorkflowPhase.DECOMPOSITION}:${WorkflowState.IN_PROGRESS}`, new Set([ `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.COMPLETED}`, `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.FAILED}`, `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.CANCELLED}`, `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.RETRYING}` ])], [`${WorkflowPhase.DECOMPOSITION}:${WorkflowState.COMPLETED}`, new Set([ `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.PENDING}` ])], [`${WorkflowPhase.DECOMPOSITION}:${WorkflowState.RETRYING}`, new Set([ `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.IN_PROGRESS}`, `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.FAILED}`, `${WorkflowPhase.DECOMPOSITION}:${WorkflowState.CANCELLED}` ])], [`${WorkflowPhase.ORCHESTRATION}:${WorkflowState.PENDING}`, new Set([ `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.IN_PROGRESS}`, `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.FAILED}`, `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.CANCELLED}` ])], [`${WorkflowPhase.ORCHESTRATION}:${WorkflowState.IN_PROGRESS}`, new Set([ `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.COMPLETED}`, `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.FAILED}`, `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.CANCELLED}`, `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.RETRYING}` ])], [`${WorkflowPhase.ORCHESTRATION}:${WorkflowState.COMPLETED}`, new Set([ `${WorkflowPhase.EXECUTION}:${WorkflowState.PENDING}` ])], [`${WorkflowPhase.ORCHESTRATION}:${WorkflowState.RETRYING}`, new Set([ `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.IN_PROGRESS}`, `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.FAILED}`, `${WorkflowPhase.ORCHESTRATION}:${WorkflowState.CANCELLED}` ])], [`${WorkflowPhase.EXECUTION}:${WorkflowState.PENDING}`, new Set([ `${WorkflowPhase.EXECUTION}:${WorkflowState.IN_PROGRESS}`, `${WorkflowPhase.EXECUTION}:${WorkflowState.FAILED}`, `${WorkflowPhase.EXECUTION}:${WorkflowState.CANCELLED}` ])], [`${WorkflowPhase.EXECUTION}:${WorkflowState.IN_PROGRESS}`, new Set([ `${WorkflowPhase.EXECUTION}:${WorkflowState.COMPLETED}`, `${WorkflowPhase.EXECUTION}:${WorkflowState.FAILED}`, `${WorkflowPhase.EXECUTION}:${WorkflowState.CANCELLED}`, `${WorkflowPhase.EXECUTION}:${WorkflowState.RETRYING}` ])], [`${WorkflowPhase.EXECUTION}:${WorkflowState.COMPLETED}`, new Set([ `${WorkflowPhase.COMPLETED}:${WorkflowState.COMPLETED}` ])], [`${WorkflowPhase.EXECUTION}:${WorkflowState.RETRYING}`, new Set([ `${WorkflowPhase.EXECUTION}:${WorkflowState.IN_PROGRESS}`, `${WorkflowPhase.EXECUTION}:${WorkflowState.FAILED}`, `${WorkflowPhase.EXECUTION}:${WorkflowState.CANCELLED}` ])] ]); export class WorkflowStateManager extends EventEmitter { static instance = null; workflows = new Map(); persistenceEnabled = true; persistenceDirectory; version = '1.0.0'; constructor(persistenceDirectory) { super(); this.persistenceDirectory = persistenceDirectory || path.join(getVibeTaskManagerOutputDir(), 'workflow-states'); } static getInstance(persistenceDirectory) { if (!WorkflowStateManager.instance) { WorkflowStateManager.instance = new WorkflowStateManager(persistenceDirectory); } return WorkflowStateManager.instance; } async initializeWorkflow(workflowId, sessionId, projectId, metadata = {}) { const context = createErrorContext('WorkflowStateManager', 'initializeWorkflow') .sessionId(sessionId) .projectId(projectId) .metadata({ workflowId }) .build(); try { const now = new Date(); const initialPhase = { phase: WorkflowPhase.INITIALIZATION, state: WorkflowState.PENDING, startTime: now, progress: 0, metadata: {}, retryCount: 0, maxRetries: 3 }; const workflow = { workflowId, sessionId, projectId, currentPhase: WorkflowPhase.INITIALIZATION, currentState: WorkflowState.PENDING, overallProgress: 0, startTime: now, phases: new Map([[WorkflowPhase.INITIALIZATION, initialPhase]]), transitions: [], metadata, persistedAt: now, version: this.version }; this.workflows.set(workflowId, workflow); if (this.persistenceEnabled) { await this.persistWorkflow(workflow); } logger.info({ workflowId, sessionId, projectId, phase: WorkflowPhase.INITIALIZATION, state: WorkflowState.PENDING }, 'Workflow initialized'); this.emit('workflow:initialized', { workflowId, sessionId, projectId, snapshot: workflow }); return workflow; } catch (error) { logger.error({ err: error, ...context }, 'Failed to initialize workflow'); throw error; } } async transitionWorkflow(workflowId, toPhase, toState, options = {}) { const context = createErrorContext('WorkflowStateManager', 'transitionWorkflow') .metadata({ workflowId, toPhase, toState, ...options }) .build(); try { const workflow = this.workflows.get(workflowId); if (!workflow) { throw new Error(`Workflow ${workflowId} not found`); } const fromPhase = workflow.currentPhase; const fromState = workflow.currentState; const isValidTransition = this.validateTransition(fromPhase, fromState, toPhase, toState); if (!isValidTransition) { throw new Error(`Invalid transition from ${fromPhase}:${fromState} to ${toPhase}:${toState}`); } const now = new Date(); const transition = { fromPhase, fromState, toPhase, toState, timestamp: now, reason: options.reason, metadata: options.metadata, triggeredBy: options.triggeredBy }; if (workflow.phases.has(fromPhase)) { const currentPhaseExecution = workflow.phases.get(fromPhase); if (toState === WorkflowState.COMPLETED || toState === WorkflowState.FAILED) { currentPhaseExecution.endTime = now; currentPhaseExecution.duration = now.getTime() - currentPhaseExecution.startTime.getTime(); currentPhaseExecution.state = toState; if (options.progress !== undefined) { currentPhaseExecution.progress = options.progress; } } } if (toPhase !== fromPhase) { const newPhaseExecution = { phase: toPhase, state: toState, startTime: now, progress: options.progress || 0, metadata: options.metadata || {}, retryCount: 0, maxRetries: 3 }; workflow.phases.set(toPhase, newPhaseExecution); } else { const phaseExecution = workflow.phases.get(toPhase); phaseExecution.state = toState; if (options.progress !== undefined) { phaseExecution.progress = options.progress; } if (options.metadata) { phaseExecution.metadata = { ...phaseExecution.metadata, ...options.metadata }; } } workflow.currentPhase = toPhase; workflow.currentState = toState; workflow.transitions.push(transition); workflow.persistedAt = now; workflow.overallProgress = this.calculateOverallProgress(workflow); if (toPhase === WorkflowPhase.COMPLETED || toPhase === WorkflowPhase.FAILED) { workflow.endTime = now; workflow.totalDuration = now.getTime() - workflow.startTime.getTime(); } if (this.persistenceEnabled) { await this.persistWorkflow(workflow); } logger.info({ workflowId, fromPhase, fromState, toPhase, toState, progress: workflow.overallProgress, reason: options.reason }, 'Workflow transitioned'); const stateChangeEvent = { workflowId, sessionId: workflow.sessionId, projectId: workflow.projectId, transition, snapshot: workflow }; this.emit('workflow:state-changed', stateChangeEvent); this.emit(`workflow:${toPhase}:${toState}`, stateChangeEvent); return workflow; } catch (error) { logger.error({ err: error, ...context }, 'Failed to transition workflow'); throw error; } } async updatePhaseProgress(workflowId, phase, progress, metadata) { const workflow = this.workflows.get(workflowId); if (!workflow) { throw new Error(`Workflow ${workflowId} not found`); } const phaseExecution = workflow.phases.get(phase); if (!phaseExecution) { throw new Error(`Phase ${phase} not found in workflow ${workflowId}`); } phaseExecution.progress = Math.max(0, Math.min(100, progress)); if (metadata) { phaseExecution.metadata = { ...phaseExecution.metadata, ...metadata }; } workflow.overallProgress = this.calculateOverallProgress(workflow); workflow.persistedAt = new Date(); if (this.persistenceEnabled) { await this.persistWorkflow(workflow); } logger.debug({ workflowId, phase, progress, overallProgress: workflow.overallProgress }, 'Phase progress updated'); this.emit('workflow:progress-updated', { workflowId, sessionId: workflow.sessionId, projectId: workflow.projectId, phase, progress, overallProgress: workflow.overallProgress }); } initializeSubPhases(workflowId, phase) { const workflow = this.workflows.get(workflowId); if (!workflow) { throw new Error(`Workflow ${workflowId} not found`); } const phaseExecution = workflow.phases.get(phase); if (!phaseExecution) { throw new Error(`Phase ${phase} not found in workflow ${workflowId}`); } if (!phaseExecution.subPhases) { phaseExecution.subPhases = new Map(); } const subPhaseDefinitions = SUB_PHASES[phase]; for (const subPhaseDef of subPhaseDefinitions) { if (!phaseExecution.subPhases.has(subPhaseDef.name)) { phaseExecution.subPhases.set(subPhaseDef.name, { subPhase: subPhaseDef.name, parentPhase: phase, state: WorkflowState.PENDING, startTime: new Date(), progress: 0, weight: subPhaseDef.weight, order: subPhaseDef.order, metadata: {} }); } } logger.debug({ workflowId, phase, subPhaseCount: subPhaseDefinitions.length }, 'Sub-phases initialized for phase'); } async updateSubPhaseProgress(workflowId, phase, subPhase, progress, state, metadata) { try { const workflow = this.workflows.get(workflowId); if (!workflow) { return createFailure(`Workflow ${workflowId} not found`); } const phaseExecution = workflow.phases.get(phase); if (!phaseExecution) { return createFailure(`Phase ${phase} not found in workflow ${workflowId}`); } this.initializeSubPhases(workflowId, phase); const subPhaseExecution = phaseExecution.subPhases.get(subPhase); if (!subPhaseExecution) { return createFailure(`Sub-phase ${subPhase} not found in phase ${phase} for workflow ${workflowId}`); } subPhaseExecution.progress = Math.max(0, Math.min(100, progress)); if (state) { subPhaseExecution.state = state; } if (metadata) { subPhaseExecution.metadata = { ...subPhaseExecution.metadata, ...metadata }; } if (progress >= 100 && subPhaseExecution.state !== WorkflowState.COMPLETED) { subPhaseExecution.state = WorkflowState.COMPLETED; subPhaseExecution.endTime = new Date(); subPhaseExecution.duration = subPhaseExecution.endTime.getTime() - subPhaseExecution.startTime.getTime(); } const phaseProgress = this.calculatePhaseProgressFromSubPhases(phaseExecution); phaseExecution.progress = phaseProgress; workflow.overallProgress = this.calculateOverallProgress(workflow); workflow.persistedAt = new Date(); if (this.persistenceEnabled) { await this.persistWorkflow(workflow); } logger.debug({ workflowId, phase, subPhase, subPhaseProgress: progress, phaseProgress, overallProgress: workflow.overallProgress }, 'Sub-phase progress updated'); this.emit('workflow:subphase-updated', { workflowId, sessionId: workflow.sessionId, projectId: workflow.projectId, phase, subPhase, progress, phaseProgress, overallProgress: workflow.overallProgress }); return createSuccess(undefined); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error during sub-phase progress update'; logger.error({ err: error, workflowId, phase, subPhase, progress }, 'Failed to update sub-phase progress'); return createFailure(`Failed to update sub-phase progress: ${errorMessage}`); } } calculatePhaseProgressFromSubPhases(phaseExecution) { if (!phaseExecution.subPhases || phaseExecution.subPhases.size === 0) { return phaseExecution.progress; } let weightedProgress = 0; let totalWeight = 0; for (const subPhase of phaseExecution.subPhases.values()) { weightedProgress += subPhase.progress * subPhase.weight; totalWeight += subPhase.weight; } return totalWeight > 0 ? Math.round(weightedProgress / totalWeight) : 0; } getSubPhaseStatus(workflowId, phase) { const workflow = this.workflows.get(workflowId); if (!workflow) { return null; } const phaseExecution = workflow.phases.get(phase); if (!phaseExecution || !phaseExecution.subPhases) { return null; } return new Map(phaseExecution.subPhases); } async startSubPhase(workflowId, phase, subPhase, metadata) { await this.updateSubPhaseProgress(workflowId, phase, subPhase, 0, WorkflowState.IN_PROGRESS, metadata); logger.info({ workflowId, phase, subPhase }, 'Sub-phase started'); } async completeSubPhase(workflowId, phase, subPhase, metadata) { await this.updateSubPhaseProgress(workflowId, phase, subPhase, 100, WorkflowState.COMPLETED, metadata); logger.info({ workflowId, phase, subPhase }, 'Sub-phase completed'); } getWorkflow(workflowId) { return this.workflows.get(workflowId); } getProjectWorkflows(projectId) { return Array.from(this.workflows.values()).filter(w => w.projectId === projectId); } getSessionWorkflows(sessionId) { return Array.from(this.workflows.values()).filter(w => w.sessionId === sessionId); } hasWorkflow(workflowId) { return this.workflows.has(workflowId); } hasPhase(workflowId, phase) { const workflow = this.workflows.get(workflowId); return workflow ? workflow.phases.has(phase) : false; } validateTransition(fromPhase, fromState, toPhase, toState) { const fromKey = `${fromPhase}:${fromState}`; const toKey = `${toPhase}:${toState}`; const validTransitions = VALID_TRANSITIONS.get(fromKey); return validTransitions ? validTransitions.has(toKey) : false; } calculateOverallProgress(workflow) { const phaseWeights = { [WorkflowPhase.INITIALIZATION]: 5, [WorkflowPhase.DECOMPOSITION]: 30, [WorkflowPhase.ORCHESTRATION]: 15, [WorkflowPhase.EXECUTION]: 45, [WorkflowPhase.COMPLETED]: 5, [WorkflowPhase.FAILED]: 0, [WorkflowPhase.CANCELLED]: 0 }; let totalWeight = 0; let completedWeight = 0; for (const [phase, execution] of workflow.phases) { const weight = phaseWeights[phase] || 0; totalWeight += weight; if (execution.state === WorkflowState.COMPLETED) { completedWeight += weight; } else if (execution.state === WorkflowState.IN_PROGRESS) { completedWeight += (weight * execution.progress) / 100; } } return totalWeight > 0 ? Math.round((completedWeight / totalWeight) * 100) : 0; } async persistWorkflow(workflow) { try { await fs.ensureDir(this.persistenceDirectory); const workflowToSave = { ...workflow, phases: Object.fromEntries(workflow.phases), persistedAt: new Date() }; const filePath = `${this.persistenceDirectory}/${workflow.workflowId}.json`; const saveResult = await FileUtils.writeJsonFile(filePath, workflowToSave); if (!saveResult.success) { logger.warn({ workflowId: workflow.workflowId, error: saveResult.error }, 'Failed to persist workflow state'); } } catch (error) { logger.error({ err: error, workflowId: workflow.workflowId }, 'Error persisting workflow state'); } } async loadWorkflow(workflowId) { try { const filePath = `${this.persistenceDirectory}/${workflowId}.json`; const loadResult = await FileUtils.readJsonFile(filePath); if (!loadResult.success) { return null; } const workflowData = loadResult.data; if (!workflowData || typeof workflowData !== 'object') { logger.warn({ workflowId }, 'Invalid workflow data structure'); return null; } const phases = workflowData.phases && typeof workflowData.phases === 'object' ? new Map(Object.entries(workflowData.phases)) : new Map(); const startTime = typeof workflowData.startTime === 'string' || typeof workflowData.startTime === 'number' ? new Date(workflowData.startTime) : new Date(); const endTime = workflowData.endTime && (typeof workflowData.endTime === 'string' || typeof workflowData.endTime === 'number') ? new Date(workflowData.endTime) : undefined; const persistedAt = typeof workflowData.persistedAt === 'string' || typeof workflowData.persistedAt === 'number' ? new Date(workflowData.persistedAt) : new Date(); const transitions = Array.isArray(workflowData.transitions) ? workflowData.transitions.map((t) => { const transition = t; return { ...transition, timestamp: typeof transition.timestamp === 'string' || typeof transition.timestamp === 'number' ? new Date(transition.timestamp) : new Date() }; }) : []; const workflow = { ...workflowData, phases, startTime, endTime, persistedAt, transitions }; this.workflows.set(workflowId, workflow); return workflow; } catch (error) { logger.error({ err: error, workflowId }, 'Failed to load workflow from persistence'); return null; } } async cleanupOldWorkflows(olderThanDays = 30) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - olderThanDays); let cleanedCount = 0; for (const [workflowId, workflow] of this.workflows) { if (workflow.endTime && workflow.endTime < cutoffDate) { this.workflows.delete(workflowId); try { const filePath = `${this.persistenceDirectory}/${workflowId}.json`; await fs.remove(filePath); cleanedCount++; } catch (error) { logger.warn({ err: error, workflowId }, 'Failed to remove persisted workflow file'); } } } logger.info({ cleanedCount, olderThanDays }, 'Workflow cleanup completed'); return cleanedCount; } getWorkflowStats() { const workflows = Array.from(this.workflows.values()); const total = workflows.length; const byPhase = {}; const byState = {}; let totalDuration = 0; let completedCount = 0; let durationCount = 0; for (const workflow of workflows) { byPhase[workflow.currentPhase] = (byPhase[workflow.currentPhase] || 0) + 1; byState[workflow.currentState] = (byState[workflow.currentState] || 0) + 1; if (workflow.totalDuration) { totalDuration += workflow.totalDuration; durationCount++; } if (workflow.currentPhase === WorkflowPhase.COMPLETED) { completedCount++; } } return { total, byPhase, byState, averageDuration: durationCount > 0 ? totalDuration / durationCount : 0, completionRate: total > 0 ? (completedCount / total) * 100 : 0 }; } }