@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
221 lines • 7.41 kB
JavaScript
/**
* Inter-agent messaging system
*/
import { SystemEvents } from "../utils/types.js";
import { generateId } from "../utils/helpers.js";
/**
* Message router for inter-agent communication
*/
export class MessageRouter {
config;
eventBus;
logger;
queues = new Map(); // agentId -> queue
pendingResponses = new Map();
messageCount = 0;
constructor(config, eventBus, logger) {
this.config = config;
this.eventBus = eventBus;
this.logger = logger;
}
initialize() {
this.logger.info("Initializing message router");
// Set up periodic cleanup
setInterval(() => this.cleanup(), 60000); // Every minute
}
shutdown() {
this.logger.info("Shutting down message router");
// Reject all pending responses
for (const [_id, pending] of this.pendingResponses) {
pending.reject(new Error("Message router shutdown"));
clearTimeout(pending.timeout);
}
this.queues.clear();
this.pendingResponses.clear();
}
async send(from, to, payload) {
const message = {
id: generateId("msg"),
type: "agent-message",
payload,
timestamp: new Date(),
priority: 0,
};
await this.sendMessage(from, to, message);
}
async sendWithResponse(from, to, payload, timeoutMs) {
const message = {
id: generateId("msg"),
type: "agent-request",
payload,
timestamp: new Date(),
priority: 1,
};
// Create response promise
const responsePromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pendingResponses.delete(message.id);
reject(new Error(`Message response timeout: ${message.id}`));
}, timeoutMs || this.config.messageTimeout);
this.pendingResponses.set(message.id, {
resolve: resolve,
reject,
timeout: timeout,
});
});
// Send message
await this.sendMessage(from, to, message);
// Wait for response
return await responsePromise;
}
async broadcast(from, payload) {
const message = {
id: generateId("broadcast"),
type: "broadcast",
payload,
timestamp: new Date(),
priority: 0,
};
// Send to all agents
const agents = Array.from(this.queues.keys()).filter(id => id !== from);
await Promise.all(agents.map(to => this.sendMessage(from, to, message)));
}
subscribe(agentId, handler) {
const queue = this.ensureQueue(agentId);
queue.handlers.set(generateId("handler"), handler);
}
unsubscribe(agentId, handlerId) {
const queue = this.queues.get(agentId);
if (queue) {
queue.handlers.delete(handlerId);
}
}
sendResponse(originalMessageId, response) {
const pending = this.pendingResponses.get(originalMessageId);
if (!pending) {
this.logger.warn("No pending response found", { messageId: originalMessageId });
return;
}
clearTimeout(pending.timeout);
this.pendingResponses.delete(originalMessageId);
pending.resolve(response);
}
getHealthStatus() {
const totalQueues = this.queues.size;
let totalMessages = 0;
let totalHandlers = 0;
for (const queue of this.queues.values()) {
totalMessages += queue.messages.length;
totalHandlers += queue.handlers.size;
}
return {
healthy: true,
metrics: {
activeQueues: totalQueues,
pendingMessages: totalMessages,
registeredHandlers: totalHandlers,
pendingResponses: this.pendingResponses.size,
totalMessagesSent: this.messageCount,
},
};
}
async sendMessage(from, to, message) {
this.logger.debug("Sending message", {
from,
to,
messageId: message.id,
type: message.type,
});
// Ensure destination queue exists
const queue = this.ensureQueue(to);
// Add to queue
queue.messages.push(message);
this.messageCount++;
// Emit event
this.eventBus.emit(SystemEvents.MESSAGE_SENT, { from, to, message });
// Process message immediately if handlers exist
if (queue.handlers.size > 0) {
await this.processMessage(to, message);
}
}
async processMessage(agentId, message) {
const queue = this.queues.get(agentId);
if (!queue) {
return;
}
// Remove message from queue
const index = queue.messages.indexOf(message);
if (index !== -1) {
queue.messages.splice(index, 1);
}
// Call all handlers
const handlers = Array.from(queue.handlers.values());
await Promise.all(handlers.map(handler => {
try {
handler(message);
}
catch (error) {
this.logger.error("Message handler error", {
agentId,
messageId: message.id,
error,
});
}
}));
// Emit received event
this.eventBus.emit(SystemEvents.MESSAGE_RECEIVED, {
from: "", // Would need to track this
to: agentId,
message,
});
}
ensureQueue(agentId) {
let queue = this.queues.get(agentId);
if (!queue) {
queue = {
messages: [],
handlers: new Map(),
};
this.queues.set(agentId, queue);
}
return queue;
}
performMaintenance() {
this.logger.debug("Performing message router maintenance");
this.cleanup();
}
cleanup() {
const now = Date.now();
// Clean up old messages
for (const [agentId, queue] of this.queues) {
const filtered = queue.messages.filter(msg => {
const age = now - msg.timestamp.getTime();
const maxAge = msg.expiry
? msg.expiry.getTime() - msg.timestamp.getTime()
: this.config.messageTimeout;
if (age > maxAge) {
this.logger.warn("Dropping expired message", {
agentId,
messageId: msg.id,
age,
});
return false;
}
return true;
});
queue.messages = filtered;
// Remove empty queues
if (queue.messages.length === 0 && queue.handlers.size === 0) {
this.queues.delete(agentId);
}
}
// Clean up timed out responses
for (const [_id, pending] of this.pendingResponses) {
// This is handled by the timeout, but double-check
clearTimeout(pending.timeout);
pending.reject(new Error("Response timeout during cleanup"));
}
this.pendingResponses.clear();
}
}
//# sourceMappingURL=messaging.js.map