UNPKG

claude-expert-workflow-mcp

Version:

Production-ready MCP server for AI-powered product development consultation through specialized expert roles. Enterprise-grade with memory management, monitoring, and Claude Code integration.

374 lines 13.4 kB
import { logger } from '../utils/logger'; /** * Persistent workflow engine that extends the basic workflow engine * with file-based persistence capabilities */ export class PersistentWorkflowEngine { constructor(storage, autoSave = true) { this.workflows = new Map(); // Default linear workflow: PM -> UX -> Architect this.DEFAULT_LINEAR_QUEUE = [ 'product_manager', 'ux_designer', 'software_architect' ]; this.storage = storage; this.autoSave = autoSave; } /** * Initialize by loading all workflows from storage */ async initialize() { try { const workflowIds = await this.storage.listWorkflows(); for (const id of workflowIds) { const workflow = await this.storage.loadWorkflow(id); if (workflow) { // Restore Date objects from JSON workflow.createdAt = new Date(workflow.createdAt); workflow.updatedAt = new Date(workflow.updatedAt); if (workflow.completedAt) { workflow.completedAt = new Date(workflow.completedAt); } // Restore Date objects in outputs workflow.outputs.forEach(output => { output.completedAt = new Date(output.completedAt); }); this.workflows.set(id, workflow); } } logger.info(`Loaded ${workflowIds.length} workflows from storage`); } catch (error) { logger.error('Failed to initialize workflow engine:', error); throw error; } } /** * Generate a unique workflow session ID */ generateWorkflowId() { return `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Start a new workflow session */ async startWorkflow(projectDescription, options = {}) { const workflowId = this.generateWorkflowId(); const { workflowType = 'linear', customExpertQueue } = options; let expertQueue; switch (workflowType) { case 'linear': expertQueue = [...this.DEFAULT_LINEAR_QUEUE]; break; case 'parallel': // For parallel, all experts work simultaneously expertQueue = [...this.DEFAULT_LINEAR_QUEUE]; break; case 'custom': expertQueue = customExpertQueue || [...this.DEFAULT_LINEAR_QUEUE]; break; default: expertQueue = [...this.DEFAULT_LINEAR_QUEUE]; } const workflow = { id: workflowId, projectDescription, workflowType, expertQueue, currentExpert: null, state: 'initialized', outputs: [], createdAt: new Date(), updatedAt: new Date() }; this.workflows.set(workflowId, workflow); if (this.autoSave) { await this.storage.saveWorkflow(workflow); } logger.debug(`Started workflow ${workflowId} with type ${workflowType}`); // Automatically start with the first expert for linear workflows if (workflowType === 'linear' && expertQueue.length > 0) { await this.progressWorkflow(workflowId); } return workflowId; } /** * Progress the workflow to the next step */ async progressWorkflow(workflowId) { let workflow = this.workflows.get(workflowId); // Try to load from storage if not in memory if (!workflow) { workflow = await this.loadWorkflow(workflowId); if (!workflow) { logger.error(`Workflow ${workflowId} not found`); return false; } } try { let result = false; switch (workflow.state) { case 'initialized': result = await this._initializeWorkflow(workflow); break; case 'in_progress': result = await this._continueWorkflow(workflow); break; case 'expert_consultation': // Wait for current expert consultation to complete result = true; break; case 'completed': case 'failed': logger.info(`Workflow ${workflowId} already in final state: ${workflow.state}`); result = false; break; default: logger.error(`Unknown workflow state: ${workflow.state}`); result = false; } if (result && this.autoSave) { await this.storage.saveWorkflow(workflow); } return result; } catch (error) { return await this._handleWorkflowError(workflow, error); } } /** * Get current workflow status */ getWorkflowStatus(workflowId) { const workflow = this.workflows.get(workflowId); if (!workflow) { return undefined; } const completedExperts = workflow.outputs.map(output => output.expertType); const currentStep = completedExperts.length + (workflow.currentExpert ? 1 : 0); return { sessionId: workflow.id, currentStep, totalSteps: workflow.expertQueue.length, currentExpert: workflow.currentExpert, completedExperts, state: workflow.state, lastActivity: workflow.updatedAt }; } /** * Add expert output and continue workflow */ async addExpertOutput(workflowId, expertType, conversationId, output, topics) { let workflow = this.workflows.get(workflowId); // Try to load from storage if not in memory if (!workflow) { workflow = await this.loadWorkflow(workflowId); if (!workflow) { logger.error(`Workflow ${workflowId} not found`); return false; } } if (workflow.currentExpert !== expertType) { logger.error(`Expected expert ${workflow.currentExpert}, got ${expertType}`); return false; } const expertOutput = { expertType, conversationId, output, completedAt: new Date(), topics }; workflow.outputs.push(expertOutput); workflow.currentExpert = null; workflow.state = 'in_progress'; workflow.updatedAt = new Date(); logger.debug(`Added output from ${expertType} to workflow ${workflowId}`); if (this.autoSave) { await this.storage.saveWorkflow(workflow); } // Continue to next expert or complete workflow return this.progressWorkflow(workflowId); } /** * Load workflow from storage */ async loadWorkflow(workflowId) { // First check in-memory cache const cached = this.workflows.get(workflowId); if (cached) { return cached; } // Load from storage try { const workflow = await this.storage.loadWorkflow(workflowId); if (workflow) { // Restore Date objects from JSON workflow.createdAt = new Date(workflow.createdAt); workflow.updatedAt = new Date(workflow.updatedAt); if (workflow.completedAt) { workflow.completedAt = new Date(workflow.completedAt); } // Restore Date objects in outputs workflow.outputs.forEach(output => { output.completedAt = new Date(output.completedAt); }); this.workflows.set(workflowId, workflow); return workflow; } return undefined; } catch (error) { logger.error(`Failed to load workflow ${workflowId}:`, error); return undefined; } } /** * Get all workflow outputs */ getWorkflowOutputs(workflowId) { const workflow = this.workflows.get(workflowId); return workflow ? [...workflow.outputs] : []; } /** * Get workflow session */ getWorkflowSession(workflowId) { const workflow = this.workflows.get(workflowId); return workflow ? { ...workflow } : null; } /** * List all active workflows */ getActiveWorkflows() { const activeWorkflows = []; for (const workflow of this.workflows.values()) { if (workflow.state !== 'completed' && workflow.state !== 'failed') { const status = this.getWorkflowStatus(workflow.id); if (status) { activeWorkflows.push(status); } } } return activeWorkflows; } async deleteWorkflow(workflowId) { this.workflows.delete(workflowId); try { return await this.storage.deleteWorkflow(workflowId); } catch (error) { logger.error(`Failed to delete workflow ${workflowId}:`, error); return false; } } async listWorkflows() { try { return await this.storage.listWorkflows(); } catch (error) { logger.error('Failed to list workflows:', error); return []; } } async saveWorkflow(workflowId) { const workflow = this.workflows.get(workflowId); if (workflow) { await this.storage.saveWorkflow(workflow); logger.debug(`Manually saved workflow: ${workflowId}`); } else { throw new Error(`Workflow ${workflowId} not found in memory`); } } async saveAllWorkflows() { const savePromises = Array.from(this.workflows.values()).map(workflow => this.storage.saveWorkflow(workflow)); await Promise.all(savePromises); logger.info(`Saved ${savePromises.length} workflows to storage`); } async getWorkflowStats() { const storedCount = (await this.storage.listWorkflows()).length; const inMemoryCount = this.workflows.size; let completed = 0; let active = 0; let failed = 0; for (const workflow of this.workflows.values()) { switch (workflow.state) { case 'completed': completed++; break; case 'failed': failed++; break; case 'initialized': case 'in_progress': case 'expert_consultation': active++; break; } } return { total: storedCount, inMemory: inMemoryCount, completed, active, failed }; } setAutoSave(enabled) { this.autoSave = enabled; logger.debug(`Auto-save ${enabled ? 'enabled' : 'disabled'}`); } // Private helper methods async _initializeWorkflow(workflow) { if (workflow.expertQueue.length === 0) { workflow.state = 'completed'; workflow.completedAt = new Date(); workflow.updatedAt = new Date(); return true; } const firstExpert = workflow.expertQueue[0]; workflow.currentExpert = firstExpert; workflow.state = 'expert_consultation'; workflow.updatedAt = new Date(); logger.info(`Workflow ${workflow.id} starting with expert: ${firstExpert}`); return true; } async _continueWorkflow(workflow) { const completedCount = workflow.outputs.length; if (completedCount >= workflow.expertQueue.length) { // All experts completed workflow.state = 'completed'; workflow.completedAt = new Date(); workflow.updatedAt = new Date(); logger.info(`Workflow ${workflow.id} completed with ${completedCount} expert outputs`); return true; } // Move to next expert const nextExpert = workflow.expertQueue[completedCount]; workflow.currentExpert = nextExpert; workflow.state = 'expert_consultation'; workflow.updatedAt = new Date(); logger.info(`Workflow ${workflow.id} progressing to expert: ${nextExpert}`); return true; } async _handleWorkflowError(workflow, error) { workflow.state = 'failed'; workflow.error = error.message; workflow.currentExpert = null; workflow.updatedAt = new Date(); if (this.autoSave) { try { await this.storage.saveWorkflow(workflow); } catch (saveError) { logger.error(`Failed to save workflow after error: ${saveError}`); } } logger.error(`Workflow ${workflow.id} failed: ${error.message}`); return false; } } //# sourceMappingURL=persistentWorkflowEngine.js.map