UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

499 lines • 16.6 kB
/** * V3 Message Bus * High-performance inter-agent communication system * Target: 1000+ messages/second throughput */ import { EventEmitter } from 'events'; import { SWARM_CONSTANTS, } from './types.js'; // ============================================================================ // High-Performance Deque Implementation // O(1) push/pop from both ends using circular buffer // ============================================================================ class Deque { buffer; head = 0; tail = 0; count = 0; capacity; constructor(initialCapacity = 16) { this.capacity = initialCapacity; this.buffer = new Array(this.capacity); } get length() { return this.count; } grow() { const newCapacity = this.capacity * 2; const newBuffer = new Array(newCapacity); // Copy elements in order for (let i = 0; i < this.count; i++) { newBuffer[i] = this.buffer[(this.head + i) % this.capacity]; } this.buffer = newBuffer; this.head = 0; this.tail = this.count; this.capacity = newCapacity; } pushBack(item) { if (this.count === this.capacity) { this.grow(); } this.buffer[this.tail] = item; this.tail = (this.tail + 1) % this.capacity; this.count++; } popFront() { if (this.count === 0) { return undefined; } const item = this.buffer[this.head]; this.buffer[this.head] = undefined; // Help GC this.head = (this.head + 1) % this.capacity; this.count--; return item; } peekFront() { if (this.count === 0) { return undefined; } return this.buffer[this.head]; } clear() { this.buffer = new Array(this.capacity); this.head = 0; this.tail = 0; this.count = 0; } // Find and remove first matching element - O(n) but rarely used findAndRemove(predicate) { for (let i = 0; i < this.count; i++) { const idx = (this.head + i) % this.capacity; const item = this.buffer[idx]; if (item !== undefined && predicate(item)) { // Shift remaining elements (O(n) but acceptable for rare operations) for (let j = i; j < this.count - 1; j++) { const currentIdx = (this.head + j) % this.capacity; const nextIdx = (this.head + j + 1) % this.capacity; this.buffer[currentIdx] = this.buffer[nextIdx]; } this.tail = (this.tail - 1 + this.capacity) % this.capacity; this.buffer[this.tail] = undefined; this.count--; return item; } } return undefined; } find(predicate) { for (let i = 0; i < this.count; i++) { const idx = (this.head + i) % this.capacity; const item = this.buffer[idx]; if (item !== undefined && predicate(item)) { return item; } } return undefined; } *[Symbol.iterator]() { for (let i = 0; i < this.count; i++) { const item = this.buffer[(this.head + i) % this.capacity]; if (item !== undefined) { yield item; } } } } const PRIORITY_ORDER = ['urgent', 'high', 'normal', 'low']; class PriorityMessageQueue { queues = new Map(); totalCount = 0; constructor() { for (const priority of PRIORITY_ORDER) { this.queues.set(priority, new Deque()); } } get length() { return this.totalCount; } enqueue(entry) { const priority = entry.message.priority; const queue = this.queues.get(priority); queue.pushBack(entry); this.totalCount++; } dequeue() { // Dequeue from highest priority non-empty queue for (const priority of PRIORITY_ORDER) { const queue = this.queues.get(priority); if (queue.length > 0) { this.totalCount--; return queue.popFront(); } } return undefined; } // Find and remove first low/normal priority entry for overflow handling removeLowestPriority() { // Check low priority first, then normal for (const priority of ['low', 'normal']) { const queue = this.queues.get(priority); if (queue.length > 0) { this.totalCount--; return queue.popFront(); } } // Fall back to any queue for (const priority of PRIORITY_ORDER) { const queue = this.queues.get(priority); if (queue.length > 0) { this.totalCount--; return queue.popFront(); } } return undefined; } clear() { for (const queue of this.queues.values()) { queue.clear(); } this.totalCount = 0; } find(predicate) { for (const queue of this.queues.values()) { const found = queue.find(predicate); if (found) { return found; } } return undefined; } } export class MessageBus extends EventEmitter { config; queues = new Map(); subscriptions = new Map(); pendingAcks = new Map(); processingInterval; statsInterval; messageCounter = 0; stats; startTime = new Date(); // Circular buffer for message history (max 60 entries for 60 seconds) messageHistory = []; messageHistoryIndex = 0; static MAX_HISTORY_SIZE = 60; constructor(config = {}) { super(); this.config = { maxQueueSize: config.maxQueueSize ?? SWARM_CONSTANTS.MAX_QUEUE_SIZE, processingIntervalMs: config.processingIntervalMs ?? 10, ackTimeoutMs: config.ackTimeoutMs ?? 5000, retryAttempts: config.retryAttempts ?? SWARM_CONSTANTS.MAX_RETRIES, enablePersistence: config.enablePersistence ?? false, compressionEnabled: config.compressionEnabled ?? false, }; this.stats = { totalMessages: 0, messagesPerSecond: 0, avgLatencyMs: 0, queueDepth: 0, ackRate: 1.0, errorRate: 0, }; } async initialize(config) { if (config) { this.config = { ...this.config, ...config }; } this.startProcessing(); this.startStatsCollection(); this.emit('initialized'); } async shutdown() { if (this.processingInterval) { clearInterval(this.processingInterval); this.processingInterval = undefined; } if (this.statsInterval) { clearInterval(this.statsInterval); this.statsInterval = undefined; } // Clear all pending acks for (const [, pending] of this.pendingAcks) { clearTimeout(pending.timeout); } this.pendingAcks.clear(); // Clear all queues this.queues.clear(); this.subscriptions.clear(); this.messageHistory = []; this.emit('shutdown'); } generateMessageId() { this.messageCounter++; return `msg_${Date.now()}_${this.messageCounter.toString(36)}`; } async send(message) { const fullMessage = { ...message, id: this.generateMessageId(), timestamp: new Date(), }; return this.enqueue(fullMessage); } async broadcast(message) { const fullMessage = { ...message, id: this.generateMessageId(), timestamp: new Date(), to: 'broadcast', }; return this.enqueue(fullMessage); } async enqueue(message) { const startTime = performance.now(); if (message.to === 'broadcast') { // Broadcast to all subscribed agents for (const [agentId, subscription] of this.subscriptions) { if (agentId !== message.from) { this.addToQueue(agentId, message); } } } else { this.addToQueue(message.to, message); } this.stats.totalMessages++; const latency = performance.now() - startTime; this.updateLatencyStats(latency); this.emit('message.enqueued', { messageId: message.id, to: message.to }); return message.id; } addToQueue(agentId, message) { if (!this.queues.has(agentId)) { this.queues.set(agentId, new PriorityMessageQueue()); } const queue = this.queues.get(agentId); // Check queue size limit - O(1) removal of lowest priority if (queue.length >= this.config.maxQueueSize) { queue.removeLowestPriority(); } // O(1) priority-aware insertion const entry = { message, attempts: 0, enqueuedAt: new Date(), }; queue.enqueue(entry); } subscribe(agentId, callback, filter) { this.subscriptions.set(agentId, { agentId, callback, filter, }); // Initialize queue for this agent if (!this.queues.has(agentId)) { this.queues.set(agentId, new PriorityMessageQueue()); } this.emit('subscription.added', { agentId }); } unsubscribe(agentId) { this.subscriptions.delete(agentId); this.queues.delete(agentId); this.emit('subscription.removed', { agentId }); } async acknowledge(ack) { const pending = this.pendingAcks.get(ack.messageId); if (!pending) { return; } clearTimeout(pending.timeout); this.pendingAcks.delete(ack.messageId); if (!ack.received && ack.error) { this.handleAckFailure(pending.message, ack.error); } this.emit('message.acknowledged', { messageId: ack.messageId, success: ack.received }); } startProcessing() { this.processingInterval = setInterval(() => { this.processQueues(); }, this.config.processingIntervalMs); } processQueues() { const now = Date.now(); for (const [agentId, queue] of this.queues) { const subscription = this.subscriptions.get(agentId); if (!subscription) { continue; } // Process messages in batch for better throughput const batchSize = Math.min(10, queue.length); const batch = []; for (let i = 0; i < batchSize && queue.length > 0; i++) { // O(1) dequeue from highest priority queue const entry = queue.dequeue(); if (!entry) break; // Check TTL if (now - entry.message.timestamp.getTime() > entry.message.ttlMs) { this.emit('message.expired', { messageId: entry.message.id }); continue; } // Check filter if (subscription.filter && !subscription.filter.includes(entry.message.type)) { continue; } batch.push(entry); } // Deliver batch for (const entry of batch) { this.deliverMessage(subscription, entry); } } } deliverMessage(subscription, entry) { const message = entry.message; try { // Set up ack timeout if required if (message.requiresAck) { const timeout = setTimeout(() => { this.handleAckTimeout(message); }, this.config.ackTimeoutMs); this.pendingAcks.set(message.id, { message, timeout }); } // Deliver asynchronously setImmediate(() => { try { subscription.callback(message); this.emit('message.delivered', { messageId: message.id, to: subscription.agentId }); } catch (error) { this.handleDeliveryError(message, entry, error); } }); } catch (error) { this.handleDeliveryError(message, entry, error); } } handleAckTimeout(message) { this.pendingAcks.delete(message.id); this.stats.ackRate = Math.max(0, this.stats.ackRate - 0.01); this.emit('message.ack_timeout', { messageId: message.id }); } handleAckFailure(message, error) { this.stats.errorRate += 0.01; this.emit('message.ack_failed', { messageId: message.id, error }); } handleDeliveryError(message, entry, error) { entry.attempts++; entry.lastAttemptAt = new Date(); if (entry.attempts < this.config.retryAttempts) { // Re-queue for retry this.addToQueue(message.to, message); this.emit('message.retry', { messageId: message.id, attempt: entry.attempts }); } else { this.stats.errorRate += 0.01; this.emit('message.failed', { messageId: message.id, error: error.message }); } } startStatsCollection() { this.statsInterval = setInterval(() => { this.calculateMessagesPerSecond(); }, 1000); } calculateMessagesPerSecond() { const now = Date.now(); const entry = { timestamp: now, count: this.stats.totalMessages }; // Use circular buffer pattern - O(1) instead of O(n) filter if (this.messageHistory.length < MessageBus.MAX_HISTORY_SIZE) { this.messageHistory.push(entry); } else { this.messageHistory[this.messageHistoryIndex] = entry; this.messageHistoryIndex = (this.messageHistoryIndex + 1) % MessageBus.MAX_HISTORY_SIZE; } // Calculate messages per second from history if (this.messageHistory.length >= 2) { // Find oldest valid entry (within last 60 seconds) let oldest = entry; for (const h of this.messageHistory) { if (h.timestamp < oldest.timestamp && now - h.timestamp < 60000) { oldest = h; } } const seconds = (now - oldest.timestamp) / 1000; const messages = entry.count - oldest.count; this.stats.messagesPerSecond = seconds > 0 ? messages / seconds : 0; } // Update queue depth this.stats.queueDepth = this.getQueueDepth(); } updateLatencyStats(latencyMs) { // Exponential moving average const alpha = 0.1; this.stats.avgLatencyMs = alpha * latencyMs + (1 - alpha) * this.stats.avgLatencyMs; } getStats() { return { ...this.stats }; } getQueueDepth() { let total = 0; for (const queue of this.queues.values()) { total += queue.length; } return total; } // Direct message retrieval for agents (pull mode) getMessages(agentId) { const queue = this.queues.get(agentId); if (!queue) { return []; } const now = Date.now(); const messages = []; // O(1) per dequeue operation while (queue.length > 0) { const entry = queue.dequeue(); if (!entry) break; if (now - entry.message.timestamp.getTime() <= entry.message.ttlMs) { messages.push(entry.message); } } return messages; } // Query pending messages for an agent hasPendingMessages(agentId) { const queue = this.queues.get(agentId); return queue !== undefined && queue.length > 0; } // Get message by ID getMessage(messageId) { for (const queue of this.queues.values()) { const entry = queue.find(e => e.message.id === messageId); if (entry) { return entry.message; } } return undefined; } } export function createMessageBus(config) { return new MessageBus(config); } //# sourceMappingURL=message-bus.js.map