context-forge
Version:
AI orchestration platform with autonomous teams, enhancement planning, migration tools, 25+ slash commands, checkpoints & hooks. Multi-IDE: Claude, Cursor, Windsurf, Cline, Copilot
270 lines (267 loc) • 9.72 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentCommunicationService = void 0;
const events_1 = require("events");
const uuid_1 = require("uuid");
const chalk_1 = __importDefault(require("chalk"));
class AgentCommunicationService extends events_1.EventEmitter {
constructor(communicationModel = 'hub-and-spoke') {
super();
this.messages = [];
this.messageHandlers = new Map();
this.responseTimeTracker = new Map();
this.stats = {
totalMessages: 0,
messagesByType: {},
messagesByAgent: {},
averageResponseTime: 0,
blockedMessages: 0,
escalations: 0,
};
this.agentHierarchy = new Map(); // agentId -> supervisorId
this.communicationModel = communicationModel;
}
/**
* Set the communication model
*/
setCommunicationModel(model) {
this.communicationModel = model;
console.log(chalk_1.default.blue(`Communication model set to: ${model}`));
}
/**
* Register agent hierarchy
*/
registerAgentHierarchy(agentId, supervisorId) {
if (supervisorId) {
this.agentHierarchy.set(agentId, supervisorId);
}
}
/**
* Subscribe to messages for a specific agent
*/
subscribe(agentId, handler) {
if (!this.messageHandlers.has(agentId)) {
this.messageHandlers.set(agentId, []);
}
this.messageHandlers.get(agentId).push(handler);
}
/**
* Send a message between agents
*/
async sendMessage(fromAgent, toAgent, type, content, metadata, requiresResponse = false) {
// Validate communication path
if (!this.isValidCommunicationPath(fromAgent, toAgent)) {
this.stats.blockedMessages++;
throw new Error(`Communication blocked: ${fromAgent} cannot directly communicate with ${toAgent} in ${this.communicationModel} model`);
}
const message = {
id: (0, uuid_1.v4)(),
timestamp: new Date(),
fromAgent,
toAgent,
type,
content,
metadata,
requiresResponse,
parentMessageId: undefined,
};
// Track message
this.messages.push(message);
this.updateStats(message);
// Emit event for monitoring
this.emit('message', message);
// Handle escalations
if (type === 'escalation') {
this.stats.escalations++;
this.emit('escalation', message);
}
// Track response time if needed
if (requiresResponse) {
this.responseTimeTracker.set(message.id, Date.now());
}
// Deliver message
await this.deliverMessage(message);
return message;
}
/**
* Validate communication path based on model
*/
isValidCommunicationPath(fromAgent, toAgent) {
if (this.communicationModel === 'mesh') {
// Anyone can talk to anyone
return true;
}
if (this.communicationModel === 'hub-and-spoke') {
// Only through supervisors
const fromSupervisor = this.agentHierarchy.get(fromAgent);
const toSupervisor = this.agentHierarchy.get(toAgent);
// Direct communication allowed if:
// 1. One is the supervisor of the other
// 2. Both have same supervisor (peers)
// 3. One is orchestrator (can talk to anyone)
return (fromAgent === toSupervisor ||
toAgent === fromSupervisor ||
(fromSupervisor === toSupervisor && fromSupervisor !== undefined) ||
this.isOrchestrator(fromAgent) ||
this.isOrchestrator(toAgent));
}
if (this.communicationModel === 'hierarchical') {
// Can only communicate up/down the hierarchy
const fromSupervisor = this.agentHierarchy.get(fromAgent);
const toSupervisor = this.agentHierarchy.get(toAgent);
return (fromAgent === toSupervisor || toAgent === fromSupervisor || this.isOrchestrator(fromAgent));
}
return false;
}
/**
* Check if agent is orchestrator (has no supervisor)
*/
isOrchestrator(agentId) {
return !this.agentHierarchy.has(agentId);
}
/**
* Deliver message to recipient
*/
async deliverMessage(message) {
const handlers = this.messageHandlers.get(message.toAgent) || [];
if (handlers.length === 0) {
console.log(chalk_1.default.yellow(`No handlers for agent ${message.toAgent}, message queued`));
return;
}
// Deliver to all handlers
await Promise.all(handlers.map((handler) => handler(message)));
}
/**
* Send a response to a message
*/
async sendResponse(originalMessageId, fromAgent, content, metadata) {
// Find original message
const originalMessage = this.messages.find((m) => m.id === originalMessageId);
if (!originalMessage) {
throw new Error(`Original message ${originalMessageId} not found`);
}
// Track response time
const requestTime = this.responseTimeTracker.get(originalMessageId);
if (requestTime) {
const responseTime = Date.now() - requestTime;
this.updateAverageResponseTime(responseTime);
this.responseTimeTracker.delete(originalMessageId);
}
// Send response
await this.sendMessage(fromAgent, originalMessage.fromAgent, 'status', content, {
...metadata,
inResponseTo: originalMessageId,
});
}
/**
* Broadcast message to multiple agents
*/
async broadcast(fromAgent, toAgents, type, content, metadata) {
const validRecipients = toAgents.filter((agent) => this.isValidCommunicationPath(fromAgent, agent));
if (validRecipients.length < toAgents.length) {
console.log(chalk_1.default.yellow(`Broadcast restricted: ${toAgents.length - validRecipients.length} agents filtered by communication model`));
}
await Promise.all(validRecipients.map((agent) => this.sendMessage(fromAgent, agent, type, content, metadata)));
}
/**
* Get pending messages for an agent
*/
getPendingMessages(agentId) {
return this.messages.filter((m) => m.toAgent === agentId && m.requiresResponse && !this.hasResponse(m.id));
}
/**
* Check if message has response
*/
hasResponse(messageId) {
return this.messages.some((m) => m.metadata?.inResponseTo === messageId);
}
/**
* Update statistics
*/
updateStats(message) {
this.stats.totalMessages++;
// By type
this.stats.messagesByType[message.type] = (this.stats.messagesByType[message.type] || 0) + 1;
// By agent
this.stats.messagesByAgent[message.fromAgent] =
(this.stats.messagesByAgent[message.fromAgent] || 0) + 1;
}
/**
* Update average response time
*/
updateAverageResponseTime(responseTime) {
const currentAvg = this.stats.averageResponseTime;
const totalResponses = Object.values(this.stats.messagesByType).reduce((sum, count) => sum + count, 0);
this.stats.averageResponseTime =
(currentAvg * (totalResponses - 1) + responseTime) / totalResponses;
}
/**
* Get communication statistics
*/
getStats() {
return { ...this.stats };
}
/**
* Get recent messages
*/
getRecentMessages(limit = 10) {
return this.messages
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
.slice(0, limit);
}
/**
* Get message history
*/
getMessageHistory(limit) {
const messages = [...this.messages].reverse();
return limit ? messages.slice(0, limit) : messages;
}
/**
* Get conversation between two agents
*/
getConversation(agent1, agent2) {
return this.messages
.filter((m) => (m.fromAgent === agent1 && m.toAgent === agent2) ||
(m.fromAgent === agent2 && m.toAgent === agent1))
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
}
/**
* Clear old messages
*/
clearOldMessages(olderThan) {
const before = this.messages.length;
this.messages = this.messages.filter((m) => m.timestamp > olderThan);
return before - this.messages.length;
}
/**
* Create standard message templates
*/
createStatusUpdate(fromAgent, completed, current, blocked, eta) {
return `STATUS UPDATE
Completed:
${completed.map((task) => `- ${task}`).join('\n')}
Current: ${current}
Blocked: ${blocked || 'None'}
ETA: ${eta || 'Unknown'}`;
}
createTaskAssignment(taskId, taskName, description, priority, criteria) {
return `TASK ${taskId}: ${taskName}
Priority: ${priority.toUpperCase()}
Description: ${description}
Success Criteria:
${criteria.map((c) => `- ${c}`).join('\n')}`;
}
createEscalation(issue, impact, attemptedSolutions, recommendation) {
return `ESCALATION REQUIRED
Issue: ${issue}
Impact: ${impact}
Attempted Solutions:
${attemptedSolutions.map((s) => `- ${s}`).join('\n')}
${recommendation ? `Recommendation: ${recommendation}` : ''}`;
}
}
exports.AgentCommunicationService = AgentCommunicationService;
//# sourceMappingURL=agentCommunication.js.map