UNPKG

sf-agent-framework

Version:

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

354 lines (309 loc) 9.53 kB
/** * Progress Tracker for SF-Agent Framework * Tracks ACTUAL work progress, no fake simulation * Ensures simulation reflects real agent work */ class ProgressTracker { constructor(options = {}) { this.options = { realTimeSync: true, // Always sync with actual work updateInterval: 2000, // 2 seconds ...options, }; this.workflows = new Map(); this.phases = new Map(); this.tasks = new Map(); } /** * Initialize a workflow for tracking */ initializeWorkflow(workflowId, workflowData) { const workflow = { id: workflowId, name: workflowData.name, startTime: Date.now(), phases: this.extractPhases(workflowData), currentPhaseIndex: 0, tasks: [], completedTasks: [], progress: 0, status: 'initialized', actualResults: [], }; this.workflows.set(workflowId, workflow); return workflow; } /** * Extract phases from workflow data */ extractPhases(workflowData) { const phases = []; if (workflowData.sequence) { // Sequential workflow with phases workflowData.sequence.forEach((phase) => { phases.push({ id: phase.phase, name: phase.phase, duration: phase.duration, steps: phase.steps || [], status: 'pending', progress: 0, }); }); } else if (workflowData.phases) { // Interactive workflow with phases workflowData.phases.forEach((phase) => { phases.push({ id: phase.name, name: phase.name, description: phase.description, status: 'pending', progress: 0, }); }); } else { // Simple workflow - create a single phase phases.push({ id: 'execution', name: 'Execution', status: 'pending', progress: 0, }); } return phases; } /** * Update progress based on ACTUAL work completion * Called by agents when they complete real tasks */ updateActualProgress(workflowId, progressData) { const workflow = this.workflows.get(workflowId); if (!workflow) return null; // Update based on actual data if (progressData.phase !== undefined) { this.updatePhase(workflowId, progressData.phase); } if (progressData.task) { this.updateTask(workflowId, progressData.task); } if (progressData.completedTask) { this.completeTask(workflowId, progressData.completedTask); } if (progressData.result) { workflow.actualResults.push(progressData.result); } // Calculate overall progress based on actual completion workflow.progress = this.calculateRealProgress(workflow); // Update status if (progressData.status) { workflow.status = progressData.status; } // Check for errors - stop immediately if error occurs if (progressData.error) { workflow.status = 'error'; workflow.error = progressData.error; return { ...workflow, shouldStop: true, }; } return workflow; } /** * Update current phase based on actual work */ updatePhase(workflowId, phaseName) { const workflow = this.workflows.get(workflowId); if (!workflow) return; const phaseIndex = workflow.phases.findIndex((p) => p.name === phaseName || p.id === phaseName); if (phaseIndex !== -1) { // Mark previous phases as complete for (let i = 0; i < phaseIndex; i++) { workflow.phases[i].status = 'complete'; workflow.phases[i].progress = 100; } // Set current phase workflow.currentPhaseIndex = phaseIndex; workflow.phases[phaseIndex].status = 'in_progress'; workflow.phases[phaseIndex].startTime = Date.now(); } } /** * Update task based on actual agent work */ updateTask(workflowId, taskData) { const workflow = this.workflows.get(workflowId); if (!workflow) return; const existingTask = workflow.tasks.find( (t) => t.id === taskData.id || t.name === taskData.name ); if (existingTask) { // Update existing task with actual progress Object.assign(existingTask, { ...taskData, lastUpdate: Date.now(), }); } else { // Add new task workflow.tasks.push({ id: taskData.id || `task-${Date.now()}`, name: taskData.name, agent: taskData.agent, status: taskData.status || 'in_progress', progress: taskData.progress || 0, startTime: Date.now(), ...taskData, }); } } /** * Mark task as complete based on actual completion */ completeTask(workflowId, taskData) { const workflow = this.workflows.get(workflowId); if (!workflow) return; // Move from active to completed const taskIndex = workflow.tasks.findIndex( (t) => t.id === taskData.id || t.name === taskData.name ); if (taskIndex !== -1) { const task = workflow.tasks.splice(taskIndex, 1)[0]; task.status = 'complete'; task.progress = 100; task.completionTime = Date.now(); task.duration = task.completionTime - task.startTime; // Add actual result if provided if (taskData.result) { task.result = taskData.result; } workflow.completedTasks.push(task); } else { // Task wasn't tracked, add directly to completed workflow.completedTasks.push({ ...taskData, status: 'complete', progress: 100, completionTime: Date.now(), }); } } /** * Calculate real progress based on actual task completion */ calculateRealProgress(workflow) { // Check if progress was explicitly set if (workflow.progress !== undefined && workflow.progress !== null) { return workflow.progress; } if (!workflow.phases || workflow.phases.length === 0) { // No phases, calculate based on tasks const totalTasks = workflow.tasks.length + workflow.completedTasks.length; if (totalTasks === 0) return 0; return Math.round((workflow.completedTasks.length / totalTasks) * 100); } // Calculate based on phase completion const totalPhases = workflow.phases.length; let completedPhases = 0; let currentPhaseProgress = 0; workflow.phases.forEach((phase, index) => { if (phase.status === 'complete') { completedPhases++; } else if (phase.status === 'in_progress') { // Calculate current phase progress based on its tasks const phaseTasks = workflow.tasks.filter((t) => t.phase === phase.id); const phaseCompleted = workflow.completedTasks.filter((t) => t.phase === phase.id); const totalPhaseTasks = phaseTasks.length + phaseCompleted.length; if (totalPhaseTasks > 0) { currentPhaseProgress = (phaseCompleted.length / totalPhaseTasks) * 0.5; // Partial credit } else { currentPhaseProgress = 0.25; // Phase started } } }); const progress = ((completedPhases + currentPhaseProgress) / totalPhases) * 100; return Math.round(progress); } /** * Get current state for a workflow */ getWorkflowState(workflowId) { const workflow = this.workflows.get(workflowId); if (!workflow) return null; const currentPhase = workflow.phases[workflow.currentPhaseIndex]; return { id: workflowId, name: workflow.name, progress: workflow.progress, status: workflow.status, currentPhase: currentPhase ? currentPhase.name : null, activeTasks: workflow.tasks, completedTasks: workflow.completedTasks, duration: Date.now() - workflow.startTime, actualResults: workflow.actualResults, error: workflow.error, }; } /** * Get progress report for visualization */ getProgressReport(workflowId) { const state = this.getWorkflowState(workflowId); if (!state) return null; return { workflow: state.name, overall: { progress: state.progress, status: state.status, duration: this.formatDuration(state.duration), }, phase: { current: state.currentPhase, tasksActive: state.activeTasks.length, tasksComplete: state.completedTasks.length, }, tasks: { active: state.activeTasks.map((t) => ({ name: t.name, agent: t.agent, progress: t.progress, })), completed: state.completedTasks.slice(-5).map((t) => ({ name: t.name, duration: this.formatDuration(t.duration), })), }, }; } /** * Format duration for display */ formatDuration(ms) { if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${Math.round(ms / 1000)}s`; if (ms < 3600000) return `${Math.round(ms / 60000)}m`; return `${Math.round(ms / 3600000)}h`; } /** * Check if workflow is complete */ isComplete(workflowId) { const workflow = this.workflows.get(workflowId); return workflow && (workflow.progress >= 100 || workflow.status === 'complete'); } /** * Check if workflow has error */ hasError(workflowId) { const workflow = this.workflows.get(workflowId); return workflow && workflow.status === 'error'; } /** * Clean up workflow tracking */ cleanup(workflowId) { this.workflows.delete(workflowId); this.phases.delete(workflowId); this.tasks.delete(workflowId); } } module.exports = ProgressTracker;