UNPKG

sf-agent-framework

Version:

AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction

413 lines (346 loc) 11.5 kB
/** * Simulation Engine for SF-Agent Framework * Provides realistic work simulation parallel to actual agent execution * Ensures actual work is done while showing progress simulation */ const EventEmitter = require('events'); const path = require('path'); const fs = require('fs').promises; const yaml = require('js-yaml'); class SimulationEngine extends EventEmitter { constructor(options = {}) { super(); this.options = { enabled: options.enabled !== false, // Default enabled, can be toggled visualizationType: options.visualizationType || 'ascii', // ascii, text, or both realTimeSync: true, // Sync with actual work progress showIntermediateUpdates: false, // No fake progress, only real status ...options, }; this.activeSimulations = new Map(); this.actualWorkStatus = new Map(); this.teamMembers = new Map(); this.visualizationEngine = null; this.personaEngine = null; this.progressTracker = null; } /** * Initialize simulation components */ async initialize() { if (!this.options.enabled) { console.log('🔄 Simulation Engine: Disabled'); return; } // Load sub-components const PersonaEngine = require('./persona-engine'); const ProgressTracker = require('./progress-tracker'); const VisualizationEngine = require('./visualization-engine'); this.personaEngine = new PersonaEngine(); this.progressTracker = new ProgressTracker({ realTimeSync: this.options.realTimeSync, }); this.visualizationEngine = new VisualizationEngine({ type: this.options.visualizationType, }); await this.personaEngine.initialize(); console.log('✅ Simulation Engine: Initialized'); } /** * Start a new simulation for a workflow * This runs parallel to actual work */ async startSimulation(workflowId, workflowData, teamDefinition) { if (!this.options.enabled) { return { id: workflowId, status: 'simulation_disabled' }; } const simulation = { id: workflowId, startTime: Date.now(), workflow: workflowData, team: await this.createVirtualTeam(teamDefinition), status: 'initializing', actualWork: { tasks: [], completedTasks: [], currentPhase: null, progress: 0, }, visualization: { lastUpdate: null, refreshInterval: 5000, // 5 seconds }, }; this.activeSimulations.set(workflowId, simulation); // Start monitoring actual work this.monitorActualWork(workflowId); // Initialize visualization await this.showInitialVisualization(simulation); return { id: workflowId, status: 'simulation_started', team: simulation.team, }; } /** * Create virtual team based on actual agents being used * Dynamically generates personas from agent definitions */ async createVirtualTeam(teamDefinition) { const team = []; // If team definition provided, use it if (teamDefinition && teamDefinition.agents) { for (const agentId of teamDefinition.agents) { const member = await this.createTeamMember(agentId); if (member) team.push(member); } } // Store team for reference this.teamMembers.set(teamDefinition.name || 'default', team); return team; } /** * Create a team member from an agent definition */ async createTeamMember(agentId) { try { // Load agent definition - fix path resolution const basePath = path.resolve(__dirname, '..', '..'); // Go up to sf-agent-framework const agentPath = path.join(basePath, 'sf-core', 'agents', `${agentId}.md`); const agentContent = await fs.readFile(agentPath, 'utf8'); // Extract persona from agent YAML const yamlMatch = agentContent.match(/```yaml([\s\S]*?)```/); if (!yamlMatch) return null; const agentConfig = yaml.load(yamlMatch[1]); // Generate virtual team member const persona = await this.personaEngine.generatePersona(agentConfig); return { id: agentId, name: persona.name || agentConfig.agent?.name || agentId, role: persona.role || agentConfig.agent?.title, status: 'available', currentTask: null, progress: 0, personality: persona.personality, icon: agentConfig.agent?.icon || '👤', workStyle: persona.workStyle, }; } catch (error) { console.error(`Error loading agent ${agentId}:`, error.message); return null; } } /** * Monitor actual work progress and sync simulation * This ensures simulation reflects real work, not fake progress */ monitorActualWork(workflowId) { const simulation = this.activeSimulations.get(workflowId); if (!simulation) return; // Set up real work monitoring const monitorInterval = setInterval(() => { const actualProgress = this.getActualWorkProgress(workflowId); if (actualProgress) { // Update simulation based on ACTUAL progress simulation.actualWork = actualProgress; // Update team member statuses based on real work this.updateTeamStatus(simulation, actualProgress); // Refresh visualization with real data this.updateVisualization(simulation); // Check if work is complete if (actualProgress.progress >= 100 || actualProgress.status === 'complete') { clearInterval(monitorInterval); this.completeSimulation(workflowId); } } }, 2000); // Check every 2 seconds simulation.monitorInterval = monitorInterval; } /** * Get actual work progress from the real agents * This connects to the actual workflow execution */ getActualWorkProgress(workflowId) { // This will be connected to the actual workflow planner // For now, return tracked status return ( this.actualWorkStatus.get(workflowId) || { progress: 0, currentPhase: 'initializing', tasks: [], completedTasks: [], } ); } /** * Update actual work progress (called by orchestrator) * This is the key method that ensures simulation shows real work */ updateActualProgress(workflowId, progressData) { const current = this.actualWorkStatus.get(workflowId) || {}; const updated = { ...current, ...progressData, lastUpdate: Date.now(), }; this.actualWorkStatus.set(workflowId, updated); // Emit event for real-time updates this.emit('progress', { workflowId, progress: updated, }); // If error occurred, stop simulation immediately if (progressData.error) { this.handleError(workflowId, progressData.error); } } /** * Handle errors - stop simulation immediately and show error */ handleError(workflowId, error) { const simulation = this.activeSimulations.get(workflowId); if (!simulation) return; // Clear monitor interval if (simulation.monitorInterval) { clearInterval(simulation.monitorInterval); } // Show error in visualization this.visualizationEngine.showError(error); // Update simulation status simulation.status = 'error'; simulation.error = error; // Clean up this.activeSimulations.delete(workflowId); } /** * Update team member status based on actual work */ updateTeamStatus(simulation, actualProgress) { if (!simulation.team) return; // Distribute actual tasks among team members const activeTasks = actualProgress.tasks || []; const completedTasks = actualProgress.completedTasks || []; simulation.team.forEach((member, index) => { // Assign tasks to team members based on actual work if (index < activeTasks.length) { const task = activeTasks[index]; member.status = 'working'; member.currentTask = task.name || task; member.progress = task.progress || 50; } else if (index < completedTasks.length) { member.status = 'complete'; member.currentTask = null; member.progress = 100; } else { member.status = 'available'; member.currentTask = null; member.progress = 0; } }); } /** * Show initial visualization when simulation starts */ async showInitialVisualization(simulation) { if (this.options.visualizationType === 'none') return; const visual = this.visualizationEngine.renderDashboard({ workflow: simulation.workflow.name, phase: 'Initializing', team: simulation.team, progress: 0, status: 'Starting workflow...', }); console.log(visual); } /** * Update visualization with current state */ updateVisualization(simulation) { if (this.options.visualizationType === 'none') return; const now = Date.now(); if ( simulation.visualization.lastUpdate && now - simulation.visualization.lastUpdate < simulation.visualization.refreshInterval ) { return; // Don't update too frequently } const visual = this.visualizationEngine.renderDashboard({ workflow: simulation.workflow.name, phase: simulation.actualWork.currentPhase, team: simulation.team, progress: simulation.actualWork.progress, status: this.generateStatusMessage(simulation), tasks: simulation.actualWork.tasks, completedTasks: simulation.actualWork.completedTasks, }); console.log('\x1Bc'); // Clear console console.log(visual); simulation.visualization.lastUpdate = now; } /** * Generate status message based on actual work */ generateStatusMessage(simulation) { const { actualWork } = simulation; if (actualWork.error) { return `❌ Error: ${actualWork.error}`; } if (actualWork.progress >= 100) { return '✅ Workflow complete!'; } if (actualWork.currentPhase) { return `Phase: ${actualWork.currentPhase} (${actualWork.progress}% complete)`; } return 'Processing...'; } /** * Complete simulation when actual work is done */ completeSimulation(workflowId) { const simulation = this.activeSimulations.get(workflowId); if (!simulation) return; // Clear monitor interval if (simulation.monitorInterval) { clearInterval(simulation.monitorInterval); } // Show final visualization const summary = this.visualizationEngine.renderSummary({ workflow: simulation.workflow.name, duration: Date.now() - simulation.startTime, team: simulation.team, completedTasks: simulation.actualWork.completedTasks, result: simulation.actualWork.result || 'Success', }); console.log(summary); // Clean up this.activeSimulations.delete(workflowId); this.actualWorkStatus.delete(workflowId); } /** * Toggle simulation on/off via flag */ toggleSimulation(enabled) { this.options.enabled = enabled; console.log(`🔄 Simulation Engine: ${enabled ? 'Enabled' : 'Disabled'}`); } /** * Check if simulation is enabled */ isEnabled() { return this.options.enabled; } /** * Clean up all active simulations */ cleanup() { this.activeSimulations.forEach((simulation, id) => { if (simulation.monitorInterval) { clearInterval(simulation.monitorInterval); } }); this.activeSimulations.clear(); this.actualWorkStatus.clear(); this.teamMembers.clear(); } } module.exports = SimulationEngine;