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
JavaScript
/**
* 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;