UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

195 lines (165 loc) 5.8 kB
#!/usr/bin/env node /** * Test 2: Node.js WebSocket Message Bus * * Simulates a message routing system for persistent agents communicating like Slack. * Measures: throughput, latency, memory usage at various connection counts. * * Usage: node node-message-bus.js [--port 8080] */ const WebSocket = require('ws'); const http = require('http'); const PORT = process.argv.includes('--port') ? parseInt(process.argv[process.argv.indexOf('--port') + 1]) : 8080; // Agent registry: agentId -> WebSocket const agents = new Map(); // Statistics const stats = { totalConnections: 0, activeConnections: 0, messagesRouted: 0, messagesFailed: 0, startTime: Date.now(), latencies: [] }; // Create HTTP server for metrics endpoint const server = http.createServer((req, res) => { if (req.url === '/metrics') { const uptime = (Date.now() - stats.startTime) / 1000; const memUsage = process.memoryUsage(); const metrics = { uptime_seconds: uptime, total_connections: stats.totalConnections, active_connections: stats.activeConnections, messages_routed: stats.messagesRouted, messages_failed: stats.messagesFailed, throughput_msg_per_sec: stats.messagesRouted / uptime, memory_mb: { rss: Math.round(memUsage.rss / 1024 / 1024), heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), external: Math.round(memUsage.external / 1024 / 1024) }, latency_p50: percentile(stats.latencies, 50), latency_p95: percentile(stats.latencies, 95), latency_p99: percentile(stats.latencies, 99) }; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(metrics, null, 2)); } else if (req.url === '/health') { res.writeHead(200); res.end('OK'); } else { res.writeHead(404); res.end('Not Found'); } }); // WebSocket server const wss = new WebSocket.Server({ server }); wss.on('connection', (ws, req) => { // Extract agent ID from URL path (e.g., /agent-123) const agentId = req.url.slice(1); if (!agentId || agentId === 'favicon.ico') { ws.close(1008, 'Invalid agent ID'); return; } // Register agent agents.set(agentId, ws); stats.totalConnections++; stats.activeConnections++; console.log(`[CONNECT] ${agentId} | Active: ${stats.activeConnections}`); // Handle incoming messages ws.on('message', (data) => { const receiveTime = Date.now(); try { const message = JSON.parse(data.toString()); // Validate message structure if (!message.to || !message.from || !message.payload) { stats.messagesFailed++; ws.send(JSON.stringify({ error: 'Invalid message format' })); return; } // Route message to target agent const targetAgent = agents.get(message.to); if (targetAgent && targetAgent.readyState === WebSocket.OPEN) { // Add routing metadata const routedMessage = { ...message, routed_at: receiveTime, routed_by: 'message-bus' }; targetAgent.send(JSON.stringify(routedMessage)); stats.messagesRouted++; // Track latency (send time to route time) if (message.sent_at) { const latency = receiveTime - message.sent_at; stats.latencies.push(latency); // Keep only last 10k latencies to prevent memory bloat if (stats.latencies.length > 10000) { stats.latencies.shift(); } } // Send acknowledgment to sender ws.send(JSON.stringify({ ack: true, message_id: message.id, routed_at: receiveTime })); } else { stats.messagesFailed++; ws.send(JSON.stringify({ error: 'Agent not found or disconnected', target: message.to })); } } catch (err) { stats.messagesFailed++; console.error(`[ERROR] Failed to process message: ${err.message}`); ws.send(JSON.stringify({ error: 'Failed to process message' })); } }); // Handle disconnection ws.on('close', () => { agents.delete(agentId); stats.activeConnections--; console.log(`[DISCONNECT] ${agentId} | Active: ${stats.activeConnections}`); }); // Handle errors ws.on('error', (err) => { console.error(`[ERROR] ${agentId}: ${err.message}`); }); // Send welcome message ws.send(JSON.stringify({ type: 'welcome', agent_id: agentId, timestamp: Date.now() })); }); // Periodic stats logging setInterval(() => { const memUsage = process.memoryUsage(); console.log(`[STATS] Active: ${stats.activeConnections} | Routed: ${stats.messagesRouted} | Failed: ${stats.messagesFailed} | Mem: ${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`); }, 5000); // Start server server.listen(PORT, () => { console.log(`Node.js Message Bus running on http://localhost:${PORT}`); console.log(`WebSocket: ws://localhost:${PORT}/agent-{id}`); console.log(`Metrics: http://localhost:${PORT}/metrics`); console.log(`Health: http://localhost:${PORT}/health`); }); // Graceful shutdown process.on('SIGINT', () => { console.log('\n[SHUTDOWN] Closing connections...'); wss.close(() => { console.log('[SHUTDOWN] Server closed'); process.exit(0); }); }); // Utility: Calculate percentile function percentile(arr, p) { if (arr.length === 0) return 0; const sorted = arr.slice().sort((a, b) => a - b); const index = Math.ceil((p / 100) * sorted.length) - 1; return sorted[index] || 0; }