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

144 lines 6.13 kB
/** * ADR-095 G2 — FederationTransport: a ConsensusTransport backed by the * federation plugin's ADR-104 WS wire (agentic-flow/transport/loader). * * @claude-flow/swarm has zero runtime dependencies and shouldn't take a * hard dep on @claude-flow/plugin-agent-federation (the wiring would also * be circular-ish). So this adapter takes its WS primitives by injection: * pass an object that satisfies `AgenticFlowTransportLike` (which the * federation plugin's `loadQuicTransport` result does) plus a small amount * of consensus-level metadata (this node's id, how to address peers, who * the peers are). * * The agentic-flow transport is fire-and-forget message delivery; this * adapter layers request-response on top via per-message correlation ids * (`corr`) + a pending-replies map + per-call timeout. Stream multiplexing * (ADR-104 stream-mux) is used for the `streamId` so consensus traffic * doesn't interleave with application traffic on the same connection. * * Optional Ed25519 signing: when a keypair is supplied, outbound messages * are signed and inbound are verified against the sender's published * public key (resolved via `resolvePeerPublicKey`) before reaching the * handler. Fail-closed — an unverifiable inbound message is dropped. */ import { randomBytes } from 'node:crypto'; import { signMessage, verifyMessage } from './transport.js'; export class FederationTransport { nodeId; wire; addressOf; peerIdsFn; streamId; defaultTimeoutMs; keyPair; resolvePeerPublicKey; handler = null; closed = false; seqCounter = 0; pending = new Map(); lastSeenSeq = new Map(); constructor(wire, opts) { this.wire = wire; this.nodeId = opts.nodeId; this.addressOf = opts.addressOf; this.peerIdsFn = opts.peerIds; this.streamId = opts.streamId ?? 'ruflo-consensus'; this.defaultTimeoutMs = opts.defaultTimeoutMs ?? 5_000; this.keyPair = opts.keyPair; this.resolvePeerPublicKey = opts.resolvePeerPublicKey; this.wire.onMessage(async (raw) => { if (this.closed) return; const env = raw.payload; if (!env || !env.msg) return; const msg = env.msg; // Signature verification (if this node expects signed messages). if (this.keyPair && this.resolvePeerPublicKey) { const pub = this.resolvePeerPublicKey(msg.from); if (!pub || !verifyMessage(msg, pub)) return; // fail-closed: drop if (typeof msg.seq === 'number') { const last = this.lastSeenSeq.get(msg.from) ?? 0; if (msg.seq <= last) return; // replayed / out of order: drop this.lastSeenSeq.set(msg.from, msg.seq); } } // Reply to a pending `send`? if (env.isReply && env.corr) { const p = this.pending.get(env.corr); if (p) { clearTimeout(p.timer); this.pending.delete(env.corr); p.resolve(msg); } return; } // Inbound request → run the handler; if it returns a reply, send it back. if (this.handler) { const reply = await this.handler(msg); if (reply && env.corr) { const addr = this.addressOf(msg.from); if (addr) { const replyMsg = this.stamp({ ...reply, to: msg.from }); await this.wire.send(addr, { type: 'consensus', payload: { corr: env.corr, isReply: true, msg: replyMsg }, streamId: this.streamId }).catch(() => { }); } } } }); } onMessage(handler) { this.handler = handler; } peers() { return this.peerIdsFn().filter(id => id !== this.nodeId); } stamp(msg) { const base = { ...msg, from: this.nodeId, seq: this.keyPair ? ++this.seqCounter : msg.seq, }; return this.keyPair ? { ...base, signature: signMessage(base, this.keyPair.privateKeyPem) } : base; } async send(to, msg, timeoutMs) { if (this.closed) throw new Error('FederationTransport: closed'); const addr = this.addressOf(to); if (!addr) throw new Error(`FederationTransport: no address for peer ${to}`); const corr = randomBytes(8).toString('hex'); const stamped = this.stamp({ ...msg, to }); const t = timeoutMs ?? this.defaultTimeoutMs; return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.pending.delete(corr); reject(new Error(`FederationTransport: send to ${to} timed out (${t}ms)`)); }, t); this.pending.set(corr, { resolve, reject, timer }); this.wire.send(addr, { type: 'consensus', payload: { corr, msg: stamped }, streamId: this.streamId }) .catch((e) => { clearTimeout(timer); this.pending.delete(corr); reject(e instanceof Error ? e : new Error(String(e))); }); }); } async broadcast(msg) { if (this.closed) throw new Error('FederationTransport: closed'); const stamped = this.stamp(msg); await Promise.allSettled(this.peers().map(async (to) => { const addr = this.addressOf(to); if (!addr) return; await this.wire.send(addr, { type: 'consensus', payload: { msg: stamped }, streamId: this.streamId }).catch(() => { }); })); } async close() { this.closed = true; this.handler = null; for (const [, p] of this.pending) { clearTimeout(p.timer); p.reject(new Error('FederationTransport: closed')); } this.pending.clear(); if (this.wire.close) await this.wire.close(); } } //# sourceMappingURL=federation-transport.js.map