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
JavaScript
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);
}