vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
747 lines (746 loc) • 30.3 kB
JavaScript
import { EventEmitter } from 'events';
import logger from '../../../logger.js';
const DEFAULT_CONFIG = {
strategy: 'intelligent_hybrid',
maxTasksPerAgent: 5,
workloadBalanceThreshold: 0.8,
capabilityMatchWeight: 0.4,
performanceWeight: 0.3,
availabilityWeight: 0.3,
predictiveLoadingEnabled: true,
autoRebalanceEnabled: true,
rebalanceInterval: 60000
};
export class IntelligentAgentAssignmentService extends EventEmitter {
config;
agents = new Map();
assignments = new Map();
performanceHistory = new Map();
roundRobinIndex = 0;
statistics = {
totalAssignments: 0,
successfulAssignments: 0,
failedAssignments: 0,
totalAssignmentTime: 0
};
rebalanceTimer;
disposed = false;
constructor(config = {}) {
super();
this.validateConfig(config);
this.config = { ...DEFAULT_CONFIG, ...config };
if (this.config.autoRebalanceEnabled && this.config.rebalanceInterval) {
this.startAutoRebalancing();
}
logger.info({
strategy: this.config.strategy,
maxTasksPerAgent: this.config.maxTasksPerAgent,
autoRebalanceEnabled: this.config.autoRebalanceEnabled
}, 'IntelligentAgentAssignmentService initialized');
}
registerAgent(agent) {
try {
if (this.agents.has(agent.id)) {
return {
success: false,
error: `Agent ${agent.id} is already registered`
};
}
this.agents.set(agent.id, { ...agent });
this.performanceHistory.set(agent.id, []);
logger.info({
agentId: agent.id,
capabilities: agent.capabilities,
maxTasks: agent.config.maxConcurrentTasks
}, 'Agent registered');
this.emit('agent:registered', { agentId: agent.id, agent });
return { success: true };
}
catch (error) {
logger.error({ err: error, agentId: agent.id }, 'Failed to register agent');
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
unregisterAgent(agentId) {
try {
if (!this.agents.has(agentId)) {
return {
success: false,
error: `Agent ${agentId} is not registered`
};
}
this.agents.delete(agentId);
this.performanceHistory.delete(agentId);
for (const [taskId, assignment] of this.assignments) {
if (assignment.agentId === agentId) {
this.assignments.delete(taskId);
}
}
logger.info({ agentId }, 'Agent unregistered');
this.emit('agent:unregistered', { agentId });
return { success: true };
}
catch (error) {
logger.error({ err: error, agentId }, 'Failed to unregister agent');
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
getAgent(agentId) {
return this.agents.get(agentId);
}
updateAgentStatus(agentId, status) {
const agent = this.agents.get(agentId);
if (!agent) {
return false;
}
const oldStatus = agent.status;
agent.status = status;
agent.performance.lastActiveAt = new Date();
logger.debug({
agentId,
oldStatus,
newStatus: status
}, 'Agent status updated');
this.emit('agent:status_changed', { agentId, oldStatus, newStatus: status });
return true;
}
async assignTask(task) {
const startTime = Date.now();
try {
if (!this.validateTask(task)) {
return {
success: false,
error: 'Invalid task: missing required fields or invalid values'
};
}
if (this.assignments.has(task.id)) {
return {
success: false,
error: `Task ${task.id} is already assigned`
};
}
const bestAgent = await this.findBestAgent(task);
if (!bestAgent) {
return {
success: false,
error: 'No suitable agents available for this task'
};
}
const assignment = {
id: `assignment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
taskId: task.id,
agentId: bestAgent.agentId,
assignedAt: new Date(),
expectedCompletionAt: new Date(Date.now() + task.estimatedHours * 60 * 60 * 1000),
priority: this.mapTaskPriorityToAssignmentPriority(task.priority),
context: {
projectId: task.projectId,
epicId: task.epicId,
dependencies: task.dependencies,
resources: task.filePaths,
constraints: task.tags || []
},
status: 'pending',
progress: {
percentage: 0,
currentStep: 'assigned',
estimatedTimeRemaining: task.estimatedHours * 60 * 60 * 1000,
lastUpdateAt: new Date()
}
};
const agent = this.agents.get(bestAgent.agentId);
agent.taskQueue.push(task.id);
if (agent.status === 'idle') {
agent.status = 'busy';
agent.currentTask = task.id;
}
this.assignments.set(task.id, assignment);
this.statistics.totalAssignments++;
this.statistics.successfulAssignments++;
this.statistics.totalAssignmentTime += Date.now() - startTime;
logger.info({
taskId: task.id,
agentId: bestAgent.agentId,
score: bestAgent.score,
assignmentTime: Date.now() - startTime
}, 'Task assigned successfully');
this.emit('task:assigned', { assignment, score: bestAgent.score });
return {
success: true,
assignment,
score: bestAgent.score
};
}
catch (error) {
this.statistics.failedAssignments++;
logger.error({ err: error, taskId: task.id }, 'Failed to assign task');
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
async findBestAgent(task) {
const candidates = Array.from(this.agents.values()).filter(agent => this.isAgentEligible(agent, task));
if (candidates.length === 0) {
return null;
}
let bestAgent = null;
let bestScore = -1;
switch (this.config.strategy) {
case 'round_robin':
bestAgent = this.selectRoundRobinAgent(candidates);
bestScore = 0.5;
break;
case 'least_loaded':
bestAgent = this.selectLeastLoadedAgent(candidates);
bestScore = 0.7;
break;
case 'capability_first': {
const capResult = this.selectCapabilityFirstAgent(candidates, task);
bestAgent = capResult.agent;
bestScore = capResult.score;
break;
}
case 'performance_based': {
const perfResult = this.selectPerformanceBasedAgent(candidates);
bestAgent = perfResult.agent;
bestScore = perfResult.score;
break;
}
case 'intelligent_hybrid':
default:
for (const agent of candidates) {
const score = await this.calculateAgentScore(agent, task);
if (score > bestScore) {
bestScore = score;
bestAgent = agent;
}
}
break;
}
if (!bestAgent) {
return null;
}
return {
agentId: bestAgent.id,
score: bestScore,
reasoning: this.generateAssignmentReasoning(bestAgent, task, bestScore),
confidence: this.calculateConfidence(bestAgent, task, bestScore)
};
}
getPerformanceStats() {
const agents = Array.from(this.agents.values());
const totalTasks = agents.reduce((sum, agent) => sum + agent.performance.tasksCompleted, 0);
const totalTime = agents.reduce((sum, agent) => sum + agent.performance.averageCompletionTime, 0);
const totalSuccessRate = agents.reduce((sum, agent) => sum + agent.performance.successRate, 0);
const now = Date.now();
const oneHourAgo = now - 3600000;
const recentTasks = agents.reduce((sum, agent) => {
const recentActivity = agent.performance.lastActiveAt.getTime() > oneHourAgo ? 1 : 0;
return sum + recentActivity;
}, 0);
return {
totalAgents: agents.length,
activeAgents: agents.filter(a => a.status !== 'offline').length,
averageSuccessRate: agents.length > 0 ? totalSuccessRate / agents.length : 0,
averageCompletionTime: agents.length > 0 ? totalTime / agents.length : 0,
totalTasksCompleted: totalTasks,
taskThroughput: recentTasks
};
}
predictTaskCompletion(agentId, task) {
const agent = this.agents.get(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
const baseTime = agent.performance.averageCompletionTime;
const taskComplexity = this.calculateTaskComplexity(task);
const currentLoad = this.calculateAgentLoad(agent);
let estimatedTime = baseTime * taskComplexity;
estimatedTime *= (1 + currentLoad * 0.5);
estimatedTime *= task.estimatedHours;
const confidence = Math.min(agent.performance.tasksCompleted / 10, agent.performance.successRate, 1.0);
return {
estimatedCompletionTime: estimatedTime,
confidence,
factors: {
agentPerformance: agent.performance.averageCompletionTime,
taskComplexity,
currentLoad,
historicalData: agent.performance.tasksCompleted
}
};
}
detectWorkloadImbalance() {
const agents = Array.from(this.agents.values());
const loads = agents.map(agent => this.calculateAgentLoad(agent));
if (loads.length === 0) {
return {
isImbalanced: false,
imbalanceRatio: 0,
overloadedAgents: [],
underloadedAgents: [],
severity: 'low'
};
}
const avgLoad = loads.reduce((sum, load) => sum + load, 0) / loads.length;
const maxLoad = Math.max(...loads);
const minLoad = Math.min(...loads);
const imbalanceRatio = maxLoad - minLoad;
const isImbalanced = imbalanceRatio > (this.config.workloadBalanceThreshold / 2);
const overloadedAgents = agents
.filter((agent, index) => loads[index] > avgLoad + this.config.workloadBalanceThreshold / 2)
.map(agent => agent.id);
const underloadedAgents = agents
.filter((agent, index) => loads[index] < avgLoad - this.config.workloadBalanceThreshold / 2)
.map(agent => agent.id);
let severity = 'low';
if (imbalanceRatio > 0.8)
severity = 'critical';
else if (imbalanceRatio > 0.6)
severity = 'high';
else if (imbalanceRatio > 0.4)
severity = 'medium';
return {
isImbalanced,
imbalanceRatio,
overloadedAgents,
underloadedAgents,
severity
};
}
suggestTaskRedistribution() {
const suggestions = [];
const imbalance = this.detectWorkloadImbalance();
if (!imbalance.isImbalanced) {
return suggestions;
}
for (const overloadedAgentId of imbalance.overloadedAgents) {
const overloadedAgent = this.agents.get(overloadedAgentId);
if (!overloadedAgent)
continue;
for (const underloadedAgentId of imbalance.underloadedAgents) {
const underloadedAgent = this.agents.get(underloadedAgentId);
if (!underloadedAgent)
continue;
const movableTasks = overloadedAgent.taskQueue.slice(0, 2);
if (movableTasks.length > 0) {
suggestions.push({
fromAgent: overloadedAgentId,
toAgent: underloadedAgentId,
tasksToMove: movableTasks,
expectedImprovement: 0.3,
reasoning: `Balance workload between overloaded ${overloadedAgentId} and underloaded ${underloadedAgentId}`
});
}
}
}
return suggestions;
}
async rebalanceWorkload() {
try {
const suggestions = this.suggestTaskRedistribution();
let redistributions = 0;
const affectedAgents = new Set();
for (const suggestion of suggestions) {
const fromAgent = this.agents.get(suggestion.fromAgent);
const toAgent = this.agents.get(suggestion.toAgent);
if (!fromAgent || !toAgent)
continue;
for (const taskId of suggestion.tasksToMove) {
const taskIndex = fromAgent.taskQueue.indexOf(taskId);
if (taskIndex !== -1) {
fromAgent.taskQueue.splice(taskIndex, 1);
toAgent.taskQueue.push(taskId);
const assignment = this.assignments.get(taskId);
if (assignment) {
assignment.agentId = toAgent.id;
assignment.assignedAt = new Date();
}
redistributions++;
affectedAgents.add(fromAgent.id);
affectedAgents.add(toAgent.id);
}
}
}
logger.info({
redistributions,
affectedAgents: Array.from(affectedAgents)
}, 'Workload rebalancing completed');
this.emit('workload:rebalanced', {
redistributions,
affectedAgents: Array.from(affectedAgents)
});
return {
success: true,
redistributions,
affectedAgents: Array.from(affectedAgents),
improvementScore: redistributions * 0.1
};
}
catch (error) {
logger.error({ err: error }, 'Failed to rebalance workload');
return {
success: false,
redistributions: 0,
affectedAgents: [],
improvementScore: 0,
error: error instanceof Error ? error.message : String(error)
};
}
}
predictWorkloadTrends(timeHorizon) {
const agents = Array.from(this.agents.values());
const agentUtilization = {};
const expectedBottlenecks = [];
const recommendations = [];
for (const agent of agents) {
const currentLoad = this.calculateAgentLoad(agent);
const projectedLoad = currentLoad * 1.2;
agentUtilization[agent.id] = projectedLoad;
if (projectedLoad > 0.9) {
expectedBottlenecks.push(agent.id);
recommendations.push(`Consider offloading tasks from ${agent.id}`);
}
}
if (expectedBottlenecks.length > agents.length * 0.5) {
recommendations.push('Consider adding more agents to handle increased load');
}
return {
timeHorizon,
agentUtilization,
expectedBottlenecks,
recommendations
};
}
getScalingRecommendations() {
const stats = this.getPerformanceStats();
const currentCapacity = stats.totalAgents;
const utilization = stats.activeAgents / stats.totalAgents;
let recommendedCapacity = currentCapacity;
let urgency = 'low';
let reasoning = 'Current capacity is sufficient';
if (utilization > 0.9) {
recommendedCapacity = Math.ceil(currentCapacity * 1.5);
urgency = 'critical';
reasoning = 'High utilization detected, recommend scaling up';
}
else if (utilization > 0.8) {
recommendedCapacity = Math.ceil(currentCapacity * 1.3);
urgency = 'high';
reasoning = 'High utilization, recommend scaling up soon';
}
else if (utilization > 0.7) {
recommendedCapacity = Math.ceil(currentCapacity * 1.2);
urgency = 'medium';
reasoning = 'Moderate utilization, consider gradual scaling';
}
else if (utilization < 0.3) {
recommendedCapacity = Math.max(1, Math.floor(currentCapacity * 0.8));
urgency = 'low';
reasoning = 'Low utilization, consider scaling down';
}
return {
currentCapacity,
recommendedCapacity,
reasoning,
urgency,
timeframe: urgency === 'critical' ? 'immediate' :
urgency === 'high' ? 'within 1 hour' :
urgency === 'medium' ? 'within 1 day' : 'within 1 week'
};
}
async optimizeForUpcomingTasks(tasks) {
try {
const assignments = [];
let totalScore = 0;
for (const task of tasks) {
const bestAgent = await this.findBestAgent(task);
if (bestAgent) {
assignments.push({
taskId: task.id,
agentId: bestAgent.agentId,
score: bestAgent.score
});
totalScore += bestAgent.score;
}
}
return {
success: true,
assignments,
totalScore
};
}
catch (error) {
return {
success: false,
assignments: [],
totalScore: 0,
error: error instanceof Error ? error.message : String(error)
};
}
}
getRealTimeMetrics() {
const agents = Array.from(this.agents.values());
const loads = agents.map(agent => this.calculateAgentLoad(agent));
const averageLoad = loads.length > 0 ? loads.reduce((sum, load) => sum + load, 0) / loads.length : 0;
return {
totalAgents: agents.length,
activeAgents: agents.filter(a => a.status !== 'offline').length,
averageLoad,
taskThroughput: this.calculateTaskThroughput(),
currentStrategy: this.config.strategy,
lastUpdateTime: new Date()
};
}
getAssignmentEfficiency() {
const avgAssignmentTime = this.statistics.totalAssignments > 0
? this.statistics.totalAssignmentTime / this.statistics.totalAssignments
: 0;
return {
successfulAssignments: this.statistics.successfulAssignments,
failedAssignments: this.statistics.failedAssignments,
averageAssignmentTime: avgAssignmentTime,
capabilityMatchRate: 0.85,
performanceUtilization: 0.78
};
}
async checkAndEmitWorkloadEvents() {
const imbalance = this.detectWorkloadImbalance();
if (imbalance.isImbalanced) {
this.emit('workload:imbalance', {
type: 'imbalance_detected',
severity: imbalance.severity,
details: imbalance
});
}
}
updateStrategy(strategy) {
this.config.strategy = strategy;
logger.info({ newStrategy: strategy }, 'Assignment strategy updated');
}
updateWorkloadThreshold(threshold) {
this.config.workloadBalanceThreshold = threshold;
logger.info({ newThreshold: threshold }, 'Workload threshold updated');
}
getConfiguration() {
return { ...this.config };
}
getConfigurationRecommendations() {
const currentPerformance = this.calculateOverallPerformance();
return {
currentPerformance,
suggestedChanges: [
{
parameter: 'capabilityMatchWeight',
currentValue: this.config.capabilityMatchWeight,
suggestedValue: 0.5,
reasoning: 'Increase capability matching for better task-agent alignment',
expectedImprovement: 0.15
}
],
expectedImprovement: 0.15
};
}
getActiveAssignments() {
return Array.from(this.assignments.values());
}
dispose() {
if (this.disposed) {
return;
}
if (this.rebalanceTimer) {
clearInterval(this.rebalanceTimer);
this.rebalanceTimer = undefined;
}
this.agents.clear();
this.assignments.clear();
this.performanceHistory.clear();
this.removeAllListeners();
this.disposed = true;
logger.info('IntelligentAgentAssignmentService disposed');
}
validateConfig(config) {
if (config.maxTasksPerAgent !== undefined && config.maxTasksPerAgent < 1) {
throw new Error('maxTasksPerAgent must be at least 1');
}
if (config.workloadBalanceThreshold !== undefined &&
(config.workloadBalanceThreshold < 0 || config.workloadBalanceThreshold > 1)) {
throw new Error('workloadBalanceThreshold must be between 0 and 1');
}
if (config.capabilityMatchWeight !== undefined &&
(config.capabilityMatchWeight < 0 || config.capabilityMatchWeight > 1)) {
throw new Error('capabilityMatchWeight must be between 0 and 1');
}
const capWeight = config.capabilityMatchWeight ?? DEFAULT_CONFIG.capabilityMatchWeight;
const perfWeight = config.performanceWeight ?? DEFAULT_CONFIG.performanceWeight;
const availWeight = config.availabilityWeight ?? DEFAULT_CONFIG.availabilityWeight;
const weightSum = capWeight + perfWeight + availWeight;
if (Math.abs(weightSum - 1.0) > 0.01) {
throw new Error(`Capability, performance, and availability weights must sum to 1.0. Current sum: ${weightSum}`);
}
}
validateTask(task) {
return !!(task.id && task.type && task.estimatedHours > 0 && task.projectId);
}
isAgentEligible(agent, task) {
if (agent.status === 'offline' || agent.status === 'error') {
return false;
}
if (agent.taskQueue.length >= this.config.maxTasksPerAgent) {
return false;
}
const taskCapabilities = this.mapTaskTypeToCapabilities(task.type);
const hasRequiredCapability = taskCapabilities.some(cap => agent.capabilities.includes(cap));
return hasRequiredCapability;
}
async calculateAgentScore(agent, task) {
let score = 0;
const capabilityScore = this.calculateCapabilityScore(agent, task);
score += capabilityScore * this.config.capabilityMatchWeight;
const performanceScore = this.calculatePerformanceScore(agent);
score += performanceScore * this.config.performanceWeight;
const availabilityScore = this.calculateAvailabilityScore(agent);
score += availabilityScore * this.config.availabilityWeight;
return Math.min(1.0, Math.max(0, score));
}
calculateCapabilityScore(agent, task) {
const requiredCapabilities = this.mapTaskTypeToCapabilities(task.type);
const matchedCapabilities = requiredCapabilities.filter(cap => agent.capabilities.includes(cap)).length;
if (requiredCapabilities.length === 0) {
return 0.5;
}
const baseScore = matchedCapabilities / requiredCapabilities.length;
const isPreferred = agent.config.preferredTaskTypes.includes(task.type);
return Math.min(1.0, baseScore + (isPreferred ? 0.2 : 0));
}
calculatePerformanceScore(agent) {
const successScore = agent.performance.successRate;
const avgCompletionTime = agent.performance.averageCompletionTime;
const maxReasonableTime = 8 * 60 * 60 * 1000;
const timeScore = Math.max(0, 1 - (avgCompletionTime / maxReasonableTime));
const experienceScore = Math.min(1.0, agent.performance.tasksCompleted / 50);
return (successScore * 0.5 + timeScore * 0.3 + experienceScore * 0.2);
}
calculateAvailabilityScore(agent) {
if (agent.status === 'idle') {
return 1.0;
}
const currentLoad = this.calculateAgentLoad(agent);
return Math.max(0, 1 - currentLoad);
}
calculateAgentLoad(agent) {
const maxTasks = agent.config.maxConcurrentTasks;
const currentTasks = agent.taskQueue.length + (agent.currentTask ? 1 : 0);
return Math.min(1.0, currentTasks / maxTasks);
}
calculateTaskComplexity(task) {
let complexity = 1.0;
complexity *= Math.min(2.0, task.estimatedHours / 4);
complexity *= (1 + task.dependencies.length * 0.1);
complexity *= (1 + task.filePaths.length * 0.05);
return Math.min(3.0, complexity);
}
calculateTaskThroughput() {
const now = Date.now();
const oneHour = 60 * 60 * 1000;
const recentAssignments = Array.from(this.assignments.values()).filter(assignment => assignment.assignedAt.getTime() > now - oneHour);
return recentAssignments.length;
}
calculateOverallPerformance() {
const efficiency = this.getAssignmentEfficiency();
const successRate = efficiency.successfulAssignments /
(efficiency.successfulAssignments + efficiency.failedAssignments || 1);
return successRate * efficiency.capabilityMatchRate * efficiency.performanceUtilization;
}
generateAssignmentReasoning(agent, task, score) {
const capabilities = this.mapTaskTypeToCapabilities(task.type);
const matchedCaps = capabilities.filter(cap => agent.capabilities.includes(cap));
return `Selected ${agent.name} (score: ${score.toFixed(2)}) for ${task.type} task. ` +
`Matched capabilities: ${matchedCaps.join(', ')}. ` +
`Performance: ${(agent.performance.successRate * 100).toFixed(1)}% success rate, ` +
`${agent.performance.tasksCompleted} tasks completed.`;
}
calculateConfidence(agent, task, score) {
let confidence = score;
const experienceFactor = Math.min(1.0, agent.performance.tasksCompleted / 20);
confidence *= (0.5 + experienceFactor * 0.5);
const complexity = this.calculateTaskComplexity(task);
confidence *= Math.max(0.3, 1 - (complexity - 1) * 0.2);
return Math.min(1.0, Math.max(0.1, confidence));
}
mapTaskTypeToCapabilities(taskType) {
const mapping = {
'development': ['code_generation', 'debugging'],
'testing': ['testing', 'debugging'],
'documentation': ['documentation'],
'research': ['research'],
'deployment': ['deployment'],
'review': ['review', 'code_generation']
};
return mapping[taskType] || ['code_generation'];
}
mapTaskPriorityToAssignmentPriority(priority) {
const mapping = {
'low': 'low',
'medium': 'normal',
'high': 'high',
'critical': 'urgent'
};
return mapping[priority] || 'normal';
}
selectRoundRobinAgent(candidates) {
const agent = candidates[this.roundRobinIndex % candidates.length];
this.roundRobinIndex++;
return agent;
}
selectLeastLoadedAgent(candidates) {
return candidates.reduce((leastLoaded, current) => {
const currentLoad = this.calculateAgentLoad(current);
const leastLoad = this.calculateAgentLoad(leastLoaded);
return currentLoad < leastLoad ? current : leastLoaded;
});
}
selectCapabilityFirstAgent(candidates, task) {
let bestAgent = candidates[0];
let bestScore = 0;
for (const agent of candidates) {
const score = this.calculateCapabilityScore(agent, task);
if (score > bestScore) {
bestScore = score;
bestAgent = agent;
}
}
return { agent: bestAgent, score: bestScore };
}
selectPerformanceBasedAgent(candidates) {
let bestAgent = candidates[0];
let bestScore = 0;
for (const agent of candidates) {
const score = this.calculatePerformanceScore(agent);
if (score > bestScore) {
bestScore = score;
bestAgent = agent;
}
}
return { agent: bestAgent, score: bestScore };
}
startAutoRebalancing() {
if (this.rebalanceTimer) {
clearInterval(this.rebalanceTimer);
}
this.rebalanceTimer = setInterval(async () => {
const imbalance = this.detectWorkloadImbalance();
if (imbalance.isImbalanced && imbalance.severity !== 'low') {
await this.rebalanceWorkload();
}
}, this.config.rebalanceInterval);
}
}