Open source vector database with HNSW indexing, graph relationships, and metadata facets. Features CLI with professional augmentation registry integration for discovering extensions and capabilities.
303 lines
No EOL
10 KiB
JavaScript
303 lines
No EOL
10 KiB
JavaScript
/**
|
|
* BrainyMCPBroadcast
|
|
*
|
|
* Enhanced MCP service with real-time WebSocket broadcasting capabilities
|
|
* for multi-agent coordination (Jarvis ↔ Picasso communication)
|
|
*
|
|
* Features:
|
|
* - WebSocket server for real-time push notifications
|
|
* - Subscription management for multiple Claude instances
|
|
* - Message broadcasting to all connected agents
|
|
* - Works both locally and with cloud deployment
|
|
*/
|
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
import { createServer } from 'http';
|
|
import { BrainyMCPService } from './brainyMCPService.js';
|
|
import { v4 as uuidv4 } from '../universal/uuid.js';
|
|
export class BrainyMCPBroadcast extends BrainyMCPService {
|
|
constructor(brainyData, options = {}) {
|
|
super(brainyData, options);
|
|
this.agents = new Map();
|
|
this.messageHistory = [];
|
|
this.maxHistorySize = 100;
|
|
}
|
|
/**
|
|
* Start the WebSocket broadcast server
|
|
* @param port Port to listen on (default: 8765)
|
|
* @param isCloud Whether this is a cloud deployment
|
|
*/
|
|
async startBroadcastServer(port = 8765, isCloud = false) {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
// Create HTTP server
|
|
this.httpServer = createServer((req, res) => {
|
|
// Health check endpoint
|
|
if (req.url === '/health') {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({
|
|
status: 'healthy',
|
|
agents: Array.from(this.agents.values()).map(a => ({
|
|
id: a.id,
|
|
name: a.name,
|
|
role: a.role,
|
|
connected: true
|
|
})),
|
|
uptime: process.uptime()
|
|
}));
|
|
}
|
|
else {
|
|
res.writeHead(404);
|
|
res.end('Not found');
|
|
}
|
|
});
|
|
// Create WebSocket server
|
|
this.wsServer = new WebSocketServer({
|
|
server: this.httpServer,
|
|
perMessageDeflate: false // Better performance
|
|
});
|
|
this.wsServer.on('connection', (socket, request) => {
|
|
this.handleNewConnection(socket, request);
|
|
});
|
|
// Start listening
|
|
this.httpServer.listen(port, () => {
|
|
console.log(`🧠 Brain Jar Broadcast Server running on ${isCloud ? 'cloud' : 'local'} port ${port}`);
|
|
console.log(`📡 WebSocket: ws://localhost:${port}`);
|
|
console.log(`🔍 Health: http://localhost:${port}/health`);
|
|
resolve();
|
|
});
|
|
// Heartbeat to keep connections alive
|
|
setInterval(() => {
|
|
this.agents.forEach((agent) => {
|
|
if (Date.now() - agent.lastSeen > 30000) {
|
|
// Remove inactive agents
|
|
this.removeAgent(agent.id);
|
|
}
|
|
else {
|
|
// Send heartbeat
|
|
this.sendToAgent(agent.id, {
|
|
id: uuidv4(),
|
|
from: 'server',
|
|
type: 'heartbeat',
|
|
data: { timestamp: Date.now() },
|
|
timestamp: Date.now()
|
|
});
|
|
}
|
|
});
|
|
}, 15000);
|
|
}
|
|
catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Handle new WebSocket connection
|
|
*/
|
|
handleNewConnection(socket, request) {
|
|
const agentId = uuidv4();
|
|
// Send welcome message
|
|
socket.send(JSON.stringify({
|
|
id: uuidv4(),
|
|
from: 'server',
|
|
type: 'notification',
|
|
event: 'welcome',
|
|
data: {
|
|
agentId,
|
|
message: 'Connected to Brain Jar Broadcast Server',
|
|
agents: Array.from(this.agents.values()).map(a => ({
|
|
id: a.id,
|
|
name: a.name,
|
|
role: a.role
|
|
}))
|
|
},
|
|
timestamp: Date.now()
|
|
}));
|
|
// Handle messages from this agent
|
|
socket.on('message', (data) => {
|
|
try {
|
|
const message = JSON.parse(data.toString());
|
|
this.handleAgentMessage(agentId, message);
|
|
}
|
|
catch (error) {
|
|
console.error('Invalid message from agent:', error);
|
|
}
|
|
});
|
|
// Handle disconnection
|
|
socket.on('close', () => {
|
|
this.removeAgent(agentId);
|
|
});
|
|
// Handle errors
|
|
socket.on('error', (error) => {
|
|
console.error(`Agent ${agentId} error:`, error);
|
|
});
|
|
// Store temporary connection until identified
|
|
this.agents.set(agentId, {
|
|
id: agentId,
|
|
name: 'Unknown',
|
|
role: 'Unknown',
|
|
socket,
|
|
lastSeen: Date.now()
|
|
});
|
|
}
|
|
/**
|
|
* Handle message from an agent
|
|
*/
|
|
handleAgentMessage(agentId, message) {
|
|
const agent = this.agents.get(agentId);
|
|
if (!agent)
|
|
return;
|
|
// Update last seen
|
|
agent.lastSeen = Date.now();
|
|
// Handle identification
|
|
if (message.type === 'identify') {
|
|
agent.name = message.name || agent.name;
|
|
agent.role = message.role || agent.role;
|
|
// Notify all agents about new member
|
|
this.broadcast({
|
|
id: uuidv4(),
|
|
from: 'server',
|
|
type: 'notification',
|
|
event: 'agent_joined',
|
|
data: {
|
|
agent: {
|
|
id: agent.id,
|
|
name: agent.name,
|
|
role: agent.role
|
|
}
|
|
},
|
|
timestamp: Date.now()
|
|
}, agentId); // Exclude the joining agent
|
|
// Send recent history to new agent
|
|
if (this.messageHistory.length > 0) {
|
|
this.sendToAgent(agentId, {
|
|
id: uuidv4(),
|
|
from: 'server',
|
|
type: 'sync',
|
|
data: {
|
|
history: this.messageHistory.slice(-20) // Last 20 messages
|
|
},
|
|
timestamp: Date.now()
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
// Create broadcast message
|
|
const broadcastMsg = {
|
|
id: message.id || uuidv4(),
|
|
from: agent.name,
|
|
to: message.to,
|
|
type: message.type || 'message',
|
|
event: message.event,
|
|
data: message.data,
|
|
timestamp: Date.now()
|
|
};
|
|
// Store in history
|
|
this.addToHistory(broadcastMsg);
|
|
// Broadcast based on recipient
|
|
if (message.to) {
|
|
// Send to specific agent(s)
|
|
const recipients = Array.isArray(message.to) ? message.to : [message.to];
|
|
recipients.forEach((recipientName) => {
|
|
const recipient = Array.from(this.agents.values()).find(a => a.name === recipientName);
|
|
if (recipient) {
|
|
this.sendToAgent(recipient.id, broadcastMsg);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
// Broadcast to all agents except sender
|
|
this.broadcast(broadcastMsg, agentId);
|
|
}
|
|
}
|
|
/**
|
|
* Broadcast message to all connected agents
|
|
*/
|
|
broadcast(message, excludeId) {
|
|
const messageStr = JSON.stringify(message);
|
|
this.agents.forEach((agent) => {
|
|
if (agent.id !== excludeId && agent.socket.readyState === WebSocket.OPEN) {
|
|
agent.socket.send(messageStr);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Send message to specific agent
|
|
*/
|
|
sendToAgent(agentId, message) {
|
|
const agent = this.agents.get(agentId);
|
|
if (agent && agent.socket.readyState === WebSocket.OPEN) {
|
|
agent.socket.send(JSON.stringify(message));
|
|
}
|
|
}
|
|
/**
|
|
* Remove agent from connected list
|
|
*/
|
|
removeAgent(agentId) {
|
|
const agent = this.agents.get(agentId);
|
|
if (agent) {
|
|
// Notify others about disconnection
|
|
this.broadcast({
|
|
id: uuidv4(),
|
|
from: 'server',
|
|
type: 'notification',
|
|
event: 'agent_left',
|
|
data: {
|
|
agent: {
|
|
id: agent.id,
|
|
name: agent.name,
|
|
role: agent.role
|
|
}
|
|
},
|
|
timestamp: Date.now()
|
|
});
|
|
this.agents.delete(agentId);
|
|
}
|
|
}
|
|
/**
|
|
* Add message to history
|
|
*/
|
|
addToHistory(message) {
|
|
this.messageHistory.push(message);
|
|
// Trim history if too large
|
|
if (this.messageHistory.length > this.maxHistorySize) {
|
|
this.messageHistory = this.messageHistory.slice(-this.maxHistorySize);
|
|
}
|
|
}
|
|
/**
|
|
* Stop the broadcast server
|
|
*/
|
|
async stopBroadcastServer() {
|
|
// Close all agent connections
|
|
this.agents.forEach(agent => {
|
|
agent.socket.close(1000, 'Server shutting down');
|
|
});
|
|
this.agents.clear();
|
|
// Close WebSocket server
|
|
if (this.wsServer) {
|
|
this.wsServer.close();
|
|
}
|
|
// Close HTTP server
|
|
if (this.httpServer) {
|
|
this.httpServer.close();
|
|
}
|
|
}
|
|
/**
|
|
* Get connected agents
|
|
*/
|
|
getConnectedAgents() {
|
|
return Array.from(this.agents.values()).map(a => ({
|
|
id: a.id,
|
|
name: a.name,
|
|
role: a.role
|
|
}));
|
|
}
|
|
/**
|
|
* Get message history
|
|
*/
|
|
getMessageHistory() {
|
|
return [...this.messageHistory];
|
|
}
|
|
}
|
|
// Export for both environments
|
|
export default BrainyMCPBroadcast;
|
|
//# sourceMappingURL=brainyMCPBroadcast.js.map
|