tasq/node_modules/agentic-flow/dist/federation/integrations/realtime-federation.js

405 lines
13 KiB
JavaScript

/**
* Real-Time Federation System using Supabase
*
* Leverages Supabase Real-Time for:
* - Live agent coordination
* - Instant memory synchronization
* - Real-time presence tracking
* - Collaborative multi-agent workflows
* - Event-driven agent communication
*/
import { createClient } from '@supabase/supabase-js';
export class RealtimeFederationHub {
client;
config;
channels = new Map();
presenceChannel;
agentId;
tenantId;
heartbeatInterval;
eventHandlers = new Map();
constructor(config, agentId, tenantId = 'default') {
this.config = config;
this.agentId = agentId;
this.tenantId = tenantId;
const key = config.serviceRoleKey || config.anonKey;
this.client = createClient(config.url, key, {
realtime: {
params: {
eventsPerSecond: config.broadcastLatency === 'low' ? 10 : 2,
},
},
});
}
/**
* Initialize real-time federation
*/
async initialize() {
console.log('🌐 Initializing Real-Time Federation Hub...');
console.log(` Agent: ${this.agentId}`);
console.log(` Tenant: ${this.tenantId}`);
// Set up presence tracking
await this.setupPresence();
// Set up memory sync channel
if (this.config.memorySync !== false) {
await this.setupMemorySync();
}
// Set up coordination channel
await this.setupCoordination();
// Start heartbeat
this.startHeartbeat();
console.log('✅ Real-Time Federation Hub Active');
}
/**
* Set up presence tracking for all agents in tenant
*/
async setupPresence() {
const channelName = `presence:${this.tenantId}`;
this.presenceChannel = this.client.channel(channelName, {
config: {
presence: {
key: this.agentId,
},
},
});
// Track agent join
this.presenceChannel.on('presence', { event: 'join' }, ({ key, newPresences }) => {
console.log(`🟢 Agent joined: ${key}`);
this.emit('agent:join', { agent_id: key, presences: newPresences });
});
// Track agent leave
this.presenceChannel.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
console.log(`🔴 Agent left: ${key}`);
this.emit('agent:leave', { agent_id: key, presences: leftPresences });
});
// Track agent sync (periodic updates)
this.presenceChannel.on('presence', { event: 'sync' }, () => {
const state = this.presenceChannel.presenceState();
this.emit('agents:sync', { agents: this.getActiveAgents(state) });
});
// Subscribe and track presence
await this.presenceChannel.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await this.presenceChannel.track({
agent_id: this.agentId,
tenant_id: this.tenantId,
status: 'online',
started_at: new Date().toISOString(),
last_heartbeat: new Date().toISOString(),
});
}
});
}
/**
* Set up real-time memory synchronization
*/
async setupMemorySync() {
const channelName = `memories:${this.tenantId}`;
const channel = this.client.channel(channelName);
// Listen for new memories from database
channel
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'agent_memories',
filter: `tenant_id=eq.${this.tenantId}`,
}, (payload) => {
const memory = {
type: 'memory_added',
tenant_id: payload.new.tenant_id,
agent_id: payload.new.agent_id,
session_id: payload.new.session_id,
content: payload.new.content,
embedding: payload.new.embedding,
metadata: payload.new.metadata,
timestamp: payload.new.created_at,
};
this.emit('memory:added', memory);
})
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'agent_memories',
filter: `tenant_id=eq.${this.tenantId}`,
}, (payload) => {
const memory = {
type: 'memory_updated',
tenant_id: payload.new.tenant_id,
agent_id: payload.new.agent_id,
session_id: payload.new.session_id,
content: payload.new.content,
embedding: payload.new.embedding,
metadata: payload.new.metadata,
timestamp: new Date().toISOString(),
};
this.emit('memory:updated', memory);
});
await channel.subscribe();
this.channels.set('memory-sync', channel);
console.log('💾 Real-time memory sync enabled');
}
/**
* Set up coordination channel for agent-to-agent communication
*/
async setupCoordination() {
const channelName = `coordination:${this.tenantId}`;
const channel = this.client.channel(channelName);
// Listen for broadcast messages
channel.on('broadcast', { event: 'coordination' }, ({ payload }) => {
const message = payload;
// Only process if message is for us or broadcast
if (!message.to_agent || message.to_agent === this.agentId) {
this.emit('message:received', message);
// Emit specific event types
this.emit(`message:${message.type}`, message);
}
});
await channel.subscribe();
this.channels.set('coordination', channel);
console.log('📡 Agent coordination channel active');
}
/**
* Broadcast message to all agents in tenant
*/
async broadcast(type, payload) {
const channel = this.channels.get('coordination');
if (!channel) {
throw new Error('Coordination channel not initialized');
}
const message = {
from_agent: this.agentId,
type,
payload,
timestamp: new Date().toISOString(),
};
await channel.send({
type: 'broadcast',
event: 'coordination',
payload: message,
});
}
/**
* Send direct message to specific agent
*/
async sendMessage(toAgent, type, payload) {
const channel = this.channels.get('coordination');
if (!channel) {
throw new Error('Coordination channel not initialized');
}
const message = {
from_agent: this.agentId,
to_agent: toAgent,
type,
payload,
timestamp: new Date().toISOString(),
};
await channel.send({
type: 'broadcast',
event: 'coordination',
payload: message,
});
}
/**
* Assign task to another agent
*/
async assignTask(task) {
await this.sendMessage(task.assigned_to, 'task_assignment', task);
console.log(`📋 Task assigned: ${task.task_id}${task.assigned_to}`);
}
/**
* Report task completion
*/
async reportTaskComplete(taskId, result) {
await this.broadcast('task_complete', {
task_id: taskId,
result,
completed_by: this.agentId,
});
console.log(`✅ Task completed: ${taskId}`);
}
/**
* Request help from other agents
*/
async requestHelp(problem, context) {
await this.broadcast('request_help', {
problem,
context,
from: this.agentId,
});
console.log(`🆘 Help requested: ${problem}`);
}
/**
* Share knowledge with other agents
*/
async shareKnowledge(knowledge, metadata) {
await this.broadcast('share_knowledge', {
knowledge,
metadata,
from: this.agentId,
});
console.log(`💡 Knowledge shared: ${knowledge.substring(0, 50)}...`);
}
/**
* Update agent status
*/
async updateStatus(status, task) {
if (!this.presenceChannel)
return;
await this.presenceChannel.track({
agent_id: this.agentId,
tenant_id: this.tenantId,
status,
task,
last_heartbeat: new Date().toISOString(),
});
await this.broadcast('status_update', {
agent_id: this.agentId,
status,
task,
});
}
/**
* Get list of active agents
*/
getActiveAgents(presenceState) {
if (!this.presenceChannel)
return [];
const state = presenceState || this.presenceChannel.presenceState();
const agents = [];
for (const [agentId, presences] of Object.entries(state)) {
const presence = presences[0];
agents.push({
agent_id: agentId,
tenant_id: presence.tenant_id,
status: presence.status,
task: presence.task,
started_at: presence.started_at,
last_heartbeat: presence.last_heartbeat,
metadata: presence.metadata,
});
}
return agents;
}
/**
* Start heartbeat to maintain presence
*/
startHeartbeat() {
const interval = this.config.presenceHeartbeat || 30000;
this.heartbeatInterval = setInterval(async () => {
if (this.presenceChannel) {
await this.presenceChannel.track({
agent_id: this.agentId,
tenant_id: this.tenantId,
last_heartbeat: new Date().toISOString(),
});
}
}, interval);
}
/**
* Stop heartbeat
*/
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = undefined;
}
}
/**
* Subscribe to events
*/
on(event, handler) {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, new Set());
}
this.eventHandlers.get(event).add(handler);
}
/**
* Unsubscribe from events
*/
off(event, handler) {
const handlers = this.eventHandlers.get(event);
if (handlers) {
handlers.delete(handler);
}
}
/**
* Emit event to handlers
*/
emit(event, data) {
const handlers = this.eventHandlers.get(event);
if (handlers) {
handlers.forEach((handler) => {
try {
handler(data);
}
catch (error) {
console.error(`Error in event handler for ${event}:`, error);
}
});
}
}
/**
* Get real-time statistics
*/
async getStats() {
const activeAgents = this.getActiveAgents();
return {
tenant_id: this.tenantId,
agent_id: this.agentId,
active_agents: activeAgents.length,
agents: activeAgents,
channels: Array.from(this.channels.keys()),
heartbeat_interval: this.config.presenceHeartbeat || 30000,
memory_sync: this.config.memorySync !== false,
timestamp: new Date().toISOString(),
};
}
/**
* Shutdown and cleanup
*/
async shutdown() {
console.log('🛑 Shutting down Real-Time Federation Hub...');
// Stop heartbeat
this.stopHeartbeat();
// Update status to offline
if (this.presenceChannel) {
await this.presenceChannel.track({
agent_id: this.agentId,
tenant_id: this.tenantId,
status: 'offline',
});
await this.presenceChannel.untrack();
}
// Unsubscribe from all channels
for (const [name, channel] of this.channels) {
await channel.unsubscribe();
console.log(` Unsubscribed from ${name}`);
}
if (this.presenceChannel) {
await this.presenceChannel.unsubscribe();
}
this.channels.clear();
this.eventHandlers.clear();
console.log('✅ Real-Time Federation Hub shutdown complete');
}
}
/**
* Create real-time federation hub from environment
*/
export function createRealtimeHub(agentId, tenantId = 'default') {
const url = process.env.SUPABASE_URL;
const anonKey = process.env.SUPABASE_ANON_KEY;
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!url || !anonKey) {
throw new Error('Missing Supabase credentials. Set SUPABASE_URL and SUPABASE_ANON_KEY');
}
return new RealtimeFederationHub({
url,
anonKey,
serviceRoleKey,
presenceHeartbeat: parseInt(process.env.FEDERATION_HEARTBEAT_INTERVAL || '30000'),
memorySync: process.env.FEDERATION_MEMORY_SYNC !== 'false',
broadcastLatency: process.env.FEDERATION_BROADCAST_LATENCY || 'low',
}, agentId, tenantId);
}
//# sourceMappingURL=realtime-federation.js.map