UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

193 lines (192 loc) 7.37 kB
import { EventEmitter } from 'events'; import { ProgressTracker } from '../services/progress-tracker.js'; import { JobManagerIntegrationService } from './job-manager-integration.js'; import { mapSubtaskToParentWorkflowId } from '../services/workflow-state-manager.js'; import logger from '../../../logger.js'; export class ProgressJobBridge extends EventEmitter { static instance; progressTracker; jobManagerIntegration; config; lastProgressUpdate = new Map(); updateTimers = new Map(); constructor(config) { super(); this.config = { enableProgressMapping: true, enableResourceTracking: true, progressUpdateThreshold: 5, debounceMs: 500, ...config }; this.progressTracker = ProgressTracker.getInstance(); this.jobManagerIntegration = JobManagerIntegrationService.getInstance(); this.initializeEventBridges(); logger.info({ config: this.config }, 'ProgressJobBridge initialized'); } static getInstance(config) { if (!ProgressJobBridge.instance) { ProgressJobBridge.instance = new ProgressJobBridge(config); } return ProgressJobBridge.instance; } initializeEventBridges() { if (!this.config.enableProgressMapping) { logger.debug('Progress mapping disabled in config'); return; } this.progressTracker.addEventListener('decomposition_progress', (data) => { this.handleDecompositionProgress(data); }); this.progressTracker.addEventListener('decomposition_completed', (data) => { this.handleDecompositionCompleted(data); }); this.progressTracker.addEventListener('task_progress_updated', (data) => { this.handleTaskProgressUpdated(data); }); this.progressTracker.addEventListener('task_completed', (data) => { this.handleTaskCompleted(data); }); this.progressTracker.addEventListener('task_failed', (data) => { this.handleTaskFailed(data); }); logger.debug('Progress-Job event bridges initialized'); } handleDecompositionProgress(data) { const jobId = this.extractJobId(data); if (!jobId || !data.progressPercentage) { return; } const lastProgress = this.lastProgressUpdate.get(jobId) || 0; const progressDelta = Math.abs(data.progressPercentage - lastProgress); if (progressDelta < this.config.progressUpdateThreshold) { return; } this.debounceJobUpdate(jobId, () => { this.updateJobProgress(jobId, data.progressPercentage, data.message || `Decomposition: ${data.progressPercentage}%`, this.extractResourceUsage(data)); this.lastProgressUpdate.set(jobId, data.progressPercentage); }); } handleDecompositionCompleted(data) { const jobId = this.extractJobId(data); if (!jobId) { return; } const timer = this.updateTimers.get(jobId); if (timer) { clearTimeout(timer); this.updateTimers.delete(jobId); } this.jobManagerIntegration.completeJob(jobId, { success: true, decompositionResults: data.metadata, completedAt: data.timestamp }, { performanceScore: 100, resourceUsage: { peakMemoryMB: process.memoryUsage().heapUsed / 1024 / 1024, averageCpuUsage: 0 } }).catch(error => { logger.debug({ err: error, jobId }, 'Failed to complete job via bridge'); }); this.lastProgressUpdate.delete(jobId); logger.debug({ jobId, data }, 'Decomposition job completed via bridge'); } handleTaskProgressUpdated(data) { const jobId = this.extractJobId(data); if (!jobId || !data.progressPercentage) { return; } this.debounceJobUpdate(jobId, () => { this.updateJobProgress(jobId, data.progressPercentage, data.message || `Task progress: ${data.progressPercentage}%`, this.extractResourceUsage(data)); }); } handleTaskCompleted(data) { const jobId = this.extractJobId(data); if (!jobId) { return; } this.jobManagerIntegration.completeJob(jobId, { success: true, taskResults: data.metadata, completedAt: data.timestamp }).catch(error => { logger.debug({ err: error, jobId }, 'Failed to complete task job via bridge'); }); } handleTaskFailed(data) { const jobId = this.extractJobId(data); if (!jobId) { return; } this.jobManagerIntegration.failJob(jobId, new Error(data.message || 'Task failed'), true).catch(error => { logger.debug({ err: error, jobId }, 'Failed to mark job as failed via bridge'); }); } debounceJobUpdate(jobId, updateFn) { const existingTimer = this.updateTimers.get(jobId); if (existingTimer) { clearTimeout(existingTimer); } const timer = setTimeout(() => { updateFn(); this.updateTimers.delete(jobId); }, this.config.debounceMs); this.updateTimers.set(jobId, timer); } updateJobProgress(jobId, progress, message, resourceUsage) { this.jobManagerIntegration.updateJobProgress(jobId, progress, message, resourceUsage).catch(error => { logger.debug({ err: error, jobId, progress }, 'Failed to update job progress via bridge'); }); } extractJobId(data) { const rawJobId = (data.metadata?.jobId || data.metadata?.sessionId || data.taskId || null); if (!rawJobId) { return null; } const mappedJobId = mapSubtaskToParentWorkflowId(rawJobId); if (mappedJobId !== rawJobId) { logger.debug({ originalJobId: rawJobId, mappedJobId, eventType: data.event }, 'Mapped subtask ID to parent workflow ID for job resolution'); } return mappedJobId; } extractResourceUsage(data) { if (!this.config.enableResourceTracking) { return undefined; } const metadata = data.metadata; return { peakMemoryMB: metadata?.resourceUsage?.peakMemoryMB || process.memoryUsage().heapUsed / 1024 / 1024, averageCpuUsage: metadata?.resourceUsage?.averageCpuUsage || 0 }; } getBridgeStats() { return { activeJobs: this.lastProgressUpdate.size, pendingUpdates: this.updateTimers.size, totalProgressUpdates: this.lastProgressUpdate.size, config: this.config }; } dispose() { for (const timer of this.updateTimers.values()) { clearTimeout(timer); } this.updateTimers.clear(); this.lastProgressUpdate.clear(); this.removeAllListeners(); logger.info('ProgressJobBridge disposed'); } } export const progressJobBridge = ProgressJobBridge.getInstance(); export function getProgressJobBridge(config) { return ProgressJobBridge.getInstance(config); }