Open source vector database with HNSW indexing, graph relationships, and metadata facets. Features CLI with professional augmentation registry integration for discovering extensions and capabilities.
254 lines
No EOL
7.8 KiB
JavaScript
254 lines
No EOL
7.8 KiB
JavaScript
/**
|
|
* BrainyMCPClient
|
|
*
|
|
* Client for connecting Claude instances to the Brain Jar Broadcast Server
|
|
* Utilizes Brainy for persistent memory and vector search capabilities
|
|
*/
|
|
import WebSocket from 'ws';
|
|
import { BrainyData } from '../brainyData.js';
|
|
import { v4 as uuidv4 } from '../universal/uuid.js';
|
|
export class BrainyMCPClient {
|
|
constructor(options) {
|
|
this.messageHandlers = new Map();
|
|
this.isConnected = false;
|
|
this.options = {
|
|
serverUrl: 'ws://localhost:8765',
|
|
autoReconnect: true,
|
|
useBrainyMemory: true,
|
|
...options
|
|
};
|
|
}
|
|
/**
|
|
* Initialize Brainy for persistent memory
|
|
*/
|
|
async initBrainy() {
|
|
if (this.options.useBrainyMemory && !this.brainy) {
|
|
this.brainy = new BrainyData({
|
|
storage: {
|
|
requestPersistentStorage: true
|
|
}
|
|
});
|
|
await this.brainy.init();
|
|
console.log(`🧠 Brainy memory initialized for ${this.options.name}`);
|
|
}
|
|
}
|
|
/**
|
|
* Connect to the broadcast server
|
|
*/
|
|
async connect() {
|
|
// Initialize Brainy first
|
|
await this.initBrainy();
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
this.socket = new WebSocket(this.options.serverUrl);
|
|
this.socket.on('open', () => {
|
|
console.log(`✅ ${this.options.name} connected to Brain Jar Broadcast`);
|
|
this.isConnected = true;
|
|
// Identify ourselves
|
|
this.send({
|
|
type: 'identify',
|
|
data: {
|
|
name: this.options.name,
|
|
role: this.options.role
|
|
}
|
|
});
|
|
resolve();
|
|
});
|
|
this.socket.on('message', async (data) => {
|
|
try {
|
|
const message = JSON.parse(data.toString());
|
|
await this.handleMessage(message);
|
|
}
|
|
catch (error) {
|
|
console.error('Error parsing message:', error);
|
|
}
|
|
});
|
|
this.socket.on('close', () => {
|
|
console.log(`❌ ${this.options.name} disconnected from Brain Jar`);
|
|
this.isConnected = false;
|
|
if (this.options.autoReconnect) {
|
|
this.scheduleReconnect();
|
|
}
|
|
});
|
|
this.socket.on('error', (error) => {
|
|
console.error(`Connection error for ${this.options.name}:`, error);
|
|
reject(error);
|
|
});
|
|
}
|
|
catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Handle incoming message
|
|
*/
|
|
async handleMessage(message) {
|
|
// Store in Brainy for persistent memory
|
|
if (this.brainy && message.type === 'message') {
|
|
try {
|
|
await this.brainy.add({
|
|
text: `${message.from}: ${JSON.stringify(message.data)}`,
|
|
metadata: {
|
|
messageId: message.id,
|
|
from: message.from,
|
|
to: message.to,
|
|
timestamp: message.timestamp,
|
|
type: message.type,
|
|
event: message.event
|
|
}
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error('Error storing message in Brainy:', error);
|
|
}
|
|
}
|
|
// Handle sync messages (receive history)
|
|
if (message.type === 'sync' && message.data.history) {
|
|
console.log(`📜 ${this.options.name} received ${message.data.history.length} historical messages`);
|
|
// Store history in Brainy
|
|
if (this.brainy) {
|
|
for (const histMsg of message.data.history) {
|
|
await this.brainy.add({
|
|
text: `${histMsg.from}: ${JSON.stringify(histMsg.data)}`,
|
|
metadata: histMsg
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// Call registered handlers
|
|
const handler = this.messageHandlers.get(message.type);
|
|
if (handler) {
|
|
handler(message);
|
|
}
|
|
// Call universal handler
|
|
const universalHandler = this.messageHandlers.get('*');
|
|
if (universalHandler) {
|
|
universalHandler(message);
|
|
}
|
|
}
|
|
/**
|
|
* Send a message
|
|
*/
|
|
send(message) {
|
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
console.error(`${this.options.name} is not connected`);
|
|
return;
|
|
}
|
|
const fullMessage = {
|
|
id: message.id || uuidv4(),
|
|
from: this.options.name,
|
|
type: message.type || 'message',
|
|
data: message.data || {},
|
|
timestamp: Date.now(),
|
|
...message
|
|
};
|
|
this.socket.send(JSON.stringify(fullMessage));
|
|
}
|
|
/**
|
|
* Send a message to specific agent(s)
|
|
*/
|
|
sendTo(recipient, data) {
|
|
this.send({
|
|
to: recipient,
|
|
type: 'message',
|
|
data
|
|
});
|
|
}
|
|
/**
|
|
* Broadcast to all agents
|
|
*/
|
|
broadcast(data) {
|
|
this.send({
|
|
type: 'message',
|
|
data
|
|
});
|
|
}
|
|
/**
|
|
* Register a message handler
|
|
*/
|
|
on(type, handler) {
|
|
this.messageHandlers.set(type, handler);
|
|
}
|
|
/**
|
|
* Remove a message handler
|
|
*/
|
|
off(type) {
|
|
this.messageHandlers.delete(type);
|
|
}
|
|
/**
|
|
* Search historical messages using Brainy's vector search
|
|
*/
|
|
async searchMemory(query, limit = 10) {
|
|
if (!this.brainy) {
|
|
console.warn('Brainy memory not initialized');
|
|
return [];
|
|
}
|
|
const results = await this.brainy.search(query, limit);
|
|
return results.map(r => ({
|
|
...r.metadata,
|
|
relevance: r.score
|
|
}));
|
|
}
|
|
/**
|
|
* Get recent messages from Brainy memory
|
|
*/
|
|
async getRecentMessages(limit = 20) {
|
|
if (!this.brainy) {
|
|
console.warn('Brainy memory not initialized');
|
|
return [];
|
|
}
|
|
// Search for recent activity
|
|
const results = await this.brainy.search('recent messages communication', limit);
|
|
return results
|
|
.map(r => r.metadata)
|
|
.sort((a, b) => b.timestamp - a.timestamp);
|
|
}
|
|
/**
|
|
* Schedule reconnection attempt
|
|
*/
|
|
scheduleReconnect() {
|
|
if (this.reconnectTimeout) {
|
|
clearTimeout(this.reconnectTimeout);
|
|
}
|
|
this.reconnectTimeout = setTimeout(() => {
|
|
console.log(`🔄 ${this.options.name} attempting to reconnect...`);
|
|
this.connect().catch(error => {
|
|
console.error('Reconnection failed:', error);
|
|
this.scheduleReconnect();
|
|
});
|
|
}, 5000);
|
|
}
|
|
/**
|
|
* Disconnect from server
|
|
*/
|
|
disconnect() {
|
|
if (this.reconnectTimeout) {
|
|
clearTimeout(this.reconnectTimeout);
|
|
}
|
|
if (this.socket) {
|
|
this.socket.close(1000, 'Client disconnecting');
|
|
this.socket = undefined;
|
|
}
|
|
this.isConnected = false;
|
|
}
|
|
/**
|
|
* Check if connected
|
|
*/
|
|
getIsConnected() {
|
|
return this.isConnected;
|
|
}
|
|
/**
|
|
* Get agent info
|
|
*/
|
|
getAgentInfo() {
|
|
return {
|
|
name: this.options.name,
|
|
role: this.options.role,
|
|
connected: this.isConnected
|
|
};
|
|
}
|
|
}
|
|
// Export for both environments
|
|
export default BrainyMCPClient;
|
|
//# sourceMappingURL=brainyMCPClient.js.map
|