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

224 lines (189 loc) • 7.04 kB
import { FederationEnvelope, type FederationEnvelopeProps, type FederationMessageType, CONSENSUS_REQUIRED_TYPES, } from '../entities/federation-envelope.js'; import { FederationSession } from '../entities/federation-session.js'; export type RoutingMode = 'direct' | 'broadcast' | 'consensus'; export interface RoutingResult { readonly success: boolean; readonly mode: RoutingMode; readonly envelopeId: string; readonly targetNodeIds: readonly string[]; readonly error?: string; readonly latencyMs?: number; } export interface ConsensusProposal { readonly proposalId: string; readonly proposerNodeId: string; readonly messageType: FederationMessageType; readonly payload: unknown; readonly quorumRequired: number; readonly votes: Map<string, boolean>; readonly createdAt: Date; readonly expiresAt: Date; } export interface RoutingServiceDeps { generateEnvelopeId: () => string; generateNonce: () => string; signEnvelope: (payload: string, sessionToken: string) => string; verifyEnvelope: (payload: string, signature: string, sessionToken: string) => boolean; scanPii: (text: string, trustLevel: number) => { transformedText: string; scanResult: FederationEnvelopeProps['piiScanResult'] }; sendToNode: (nodeId: string, envelope: FederationEnvelope) => Promise<void>; getActiveSessions: () => FederationSession[]; getLocalNodeId: () => string; } export class RoutingService { private readonly deps: RoutingServiceDeps; private readonly pendingProposals: Map<string, ConsensusProposal>; constructor(deps: RoutingServiceDeps) { this.deps = deps; this.pendingProposals = new Map(); } selectMode(messageType: FederationMessageType): RoutingMode { if (CONSENSUS_REQUIRED_TYPES.has(messageType)) { return 'consensus'; } if (messageType === 'status-broadcast') { return 'broadcast'; } return 'direct'; } async send<T>( session: FederationSession, messageType: FederationMessageType, payload: T, ): Promise<RoutingResult> { const startTime = Date.now(); if (!session.active) { return { success: false, mode: 'direct', envelopeId: '', targetNodeIds: [], error: 'Session is not active' }; } if (session.isExpired()) { return { success: false, mode: 'direct', envelopeId: '', targetNodeIds: [], error: 'Session has expired' }; } const payloadStr = JSON.stringify(payload); const { transformedText, scanResult } = this.deps.scanPii(payloadStr, session.trustLevel); if (scanResult.piiFound && scanResult.actionsApplied.includes('block')) { return { success: false, mode: 'direct', envelopeId: '', targetNodeIds: [session.remoteNodeId], error: 'Message blocked by PII policy' }; } const envelopeId = this.deps.generateEnvelopeId(); const nonce = this.deps.generateNonce(); const timestamp = new Date(); const unsignedPayload = JSON.stringify({ envelopeId, sourceNodeId: session.localNodeId, targetNodeId: session.remoteNodeId, sessionId: session.sessionId, messageType, payload: JSON.parse(transformedText), timestamp: timestamp.toISOString(), nonce, }); const hmacSignature = this.deps.signEnvelope(unsignedPayload, session.sessionToken); const envelope = new FederationEnvelope<T>({ envelopeId, sourceNodeId: session.localNodeId, targetNodeId: session.remoteNodeId, sessionId: session.sessionId, messageType, payload: JSON.parse(transformedText) as T, timestamp, nonce, hmacSignature, piiScanResult: scanResult, }); try { await this.deps.sendToNode(session.remoteNodeId, envelope); session.recordMessageSent(); if (scanResult.piiFound) { session.recordPiiRedaction(); } return { success: true, mode: 'direct', envelopeId, targetNodeIds: [session.remoteNodeId], latencyMs: Date.now() - startTime, }; } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err); return { success: false, mode: 'direct', envelopeId, targetNodeIds: [session.remoteNodeId], error: errorMsg }; } } async broadcast<T>( messageType: FederationMessageType, payload: T, ): Promise<RoutingResult[]> { const sessions = this.deps.getActiveSessions().filter(s => s.active && !s.isExpired()); const results: RoutingResult[] = []; for (const session of sessions) { const result = await this.send(session, messageType, payload); results.push(result); } return results; } async propose<T>( messageType: FederationMessageType, payload: T, quorumFraction: number = 2 / 3, ): Promise<ConsensusProposal> { const sessions = this.deps.getActiveSessions().filter(s => s.active && !s.isExpired()); const quorumRequired = Math.ceil(sessions.length * quorumFraction); const proposal: ConsensusProposal = { proposalId: this.deps.generateEnvelopeId(), proposerNodeId: this.deps.getLocalNodeId(), messageType, payload, quorumRequired, votes: new Map(), createdAt: new Date(), expiresAt: new Date(Date.now() + 30_000), }; this.pendingProposals.set(proposal.proposalId, proposal); await this.broadcast(messageType, { type: 'consensus-proposal', proposalId: proposal.proposalId, messageType, payload, quorumRequired, }); return proposal; } recordVote(proposalId: string, nodeId: string, vote: boolean): boolean { const proposal = this.pendingProposals.get(proposalId); if (!proposal) return false; if (new Date() > proposal.expiresAt) { this.pendingProposals.delete(proposalId); return false; } proposal.votes.set(nodeId, vote); return true; } isConsensusReached(proposalId: string): { reached: boolean; approved: boolean } { const proposal = this.pendingProposals.get(proposalId); if (!proposal) return { reached: false, approved: false }; const approvals = Array.from(proposal.votes.values()).filter(v => v).length; if (approvals >= proposal.quorumRequired) { return { reached: true, approved: true }; } const rejections = Array.from(proposal.votes.values()).filter(v => !v).length; const totalVoters = this.deps.getActiveSessions().length; if (rejections > totalVoters - proposal.quorumRequired) { return { reached: true, approved: false }; } return { reached: false, approved: false }; } verifyInboundEnvelope(envelope: FederationEnvelope, sessionToken: string): boolean { const signablePayload = envelope.toSignablePayload(); return this.deps.verifyEnvelope(signablePayload, envelope.hmacSignature, sessionToken); } cleanExpiredProposals(): void { const now = new Date(); for (const [id, proposal] of this.pendingProposals) { if (now > proposal.expiresAt) { this.pendingProposals.delete(id); } } } }