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