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
227 lines (212 loc) • 8.6 kB
text/typescript
/**
* Midstream-aware federation transport loader (ADR-120, Step 2).
*
* Extends the agentic-flow `loadQuicTransport` loader pattern with a
* preferred branch that probes `midstreamer` first. When `midstreamer`
* ships real QUIC (currently the WASM build is a counter-tracking
* stub per ADR-119), this loader picks it up automatically without
* any change to consumer plugins.
*
* Resolution order:
*
* 1. If `MIDSTREAMER_QUIC_NATIVE=1` AND the `midstreamer` module
* exposes a real `QuicMultistream`-derived `AgentTransport`,
* use it. (Today: probes fail closed and we fall through.)
*
* 2. Otherwise: defer to agentic-flow's existing loader, which
* itself respects `AGENTIC_FLOW_QUIC_NATIVE=1` (ADR-108) and
* falls back to WebSocket (ADR-104) otherwise.
*
* The loader is opt-in: callers must explicitly invoke
* `loadFederationTransport()` instead of `loadQuicTransport()`.
* Behavior is identical to `loadQuicTransport()` until upstream
* `midstreamer` ships its real QUIC build AND the operator sets the
* env flag — so this change is safe to land before any of that
* happens.
*
* Re-exports the `AgentTransport` / `AgentMessage` / `QuicTransportConfig`
* surface from agentic-flow so consumers only import from one place.
*/
// Type-only imports are erased at compile time — they do not force the
// installation of `agentic-flow`. The value import (`loadQuicTransport`)
// is now lazy via `loadAgenticFlowQuicTransport()` below so the
// federation plugin can install + boot in environments that block the
// koa transitive chain that agentic-flow pulls in (e.g. hardened npm
// registries that block `cookies@0.9.1` per issue #1949).
import type {
AgentTransport,
AgentMessage,
QuicTransportConfig,
} from 'agentic-flow/transport/loader';
export type { AgentTransport, AgentMessage, QuicTransportConfig };
/**
* Lazy loader for agentic-flow's `loadQuicTransport`. Returns `null`
* when `agentic-flow` is not installed — callers must then fall back
* to the midstream-first path or surface a clear error.
*
* Per ADR-120 + issue #1949, `agentic-flow` is now an **optional**
* peer dependency. Operators who only want the midstream-native
* transport (via `MIDSTREAMER_QUIC_NATIVE=1`) can omit it to avoid
* the deep `koa-router` → `cookies@0.9.1` transitive chain that
* hardened npm registries reject.
*/
async function loadAgenticFlowQuicTransport(
config?: QuicTransportConfig,
): Promise<AgentTransport | null> {
// Direct dynamic `import()` (not the `new Function` trick) so
// test frameworks like vitest can intercept via `vi.mock`. The
// try/catch makes the module-not-found case a clean `null` so the
// caller falls back gracefully — agentic-flow is now an optional
// peer dependency.
let mod: {
loadQuicTransport?: (c?: QuicTransportConfig) => Promise<AgentTransport>;
default?: {
loadQuicTransport?: (c?: QuicTransportConfig) => Promise<AgentTransport>;
};
};
try {
mod = (await import('agentic-flow/transport/loader')) as typeof mod;
} catch {
return null;
}
const fn =
typeof mod.loadQuicTransport === 'function'
? mod.loadQuicTransport
: mod.default?.loadQuicTransport;
if (typeof fn !== 'function') {
return null;
}
try {
return await fn(config);
} catch {
return null;
}
}
/** Result envelope describing which backend the loader picked. */
export interface LoadedFederationTransport {
/** The live transport. Send/receive against this. */
transport: AgentTransport;
/** Which loader branch resolved. Useful for logs/metrics. */
source: 'midstreamer-native' | 'agentic-flow-loader';
/** Free-form note when a probe failed (helps explain a fallback). */
fallbackReason?: string;
}
/**
* Probe the `midstreamer` npm package for a real QUIC transport.
* Returns `null` when the env flag is off, when the package isn't
* installed, when the import surface doesn't match expectations, or
* when the loaded module is detectably the WASM stub (per ADR-119
* the current shipped build is a counter-tracking stub — `isNative()`
* or `isStub()` probes are checked when available).
*
* The function never throws — any failure becomes `null` so the
* outer `loadFederationTransport` can transparently fall back.
*/
async function probeMidstreamerTransport(
config?: QuicTransportConfig,
): Promise<{ transport: AgentTransport; reason?: string } | null> {
if (process.env.MIDSTREAMER_QUIC_NATIVE !== '1') {
return null;
}
let mod: unknown;
try {
// Lazy + indirect so bundlers don't try to resolve at compile time.
// Prefer the `midstreamer/quic` sub-path (added in midstreamer@0.3.1
// per upstream ruvnet/midstream#81) which exposes
// `loadQuicTransport` directly without WASM init. Fall back to the
// root `midstreamer` package for older versions that put the QUIC
// surface on the default export.
const dynamicImport: (s: string) => Promise<unknown> = new Function(
's',
'return import(s)',
) as (s: string) => Promise<unknown>;
try {
mod = await dynamicImport('midstreamer/quic');
} catch {
mod = await dynamicImport('midstreamer');
}
} catch {
return null;
}
const candidate = mod as {
loadQuicTransport?: (c?: QuicTransportConfig) => Promise<AgentTransport>;
isNative?: () => boolean;
isStub?: () => boolean;
default?: {
loadQuicTransport?: (c?: QuicTransportConfig) => Promise<AgentTransport>;
isNative?: () => boolean;
isStub?: () => boolean;
};
};
// CommonJS sub-path exposes its API via `module.exports = {...};
// module.exports.default = module.exports;` — so we also accept the
// `.default` form. ESM imports flatten the named exports directly.
const surface = (typeof candidate.loadQuicTransport === 'function'
? candidate
: candidate.default) as {
loadQuicTransport?: (c?: QuicTransportConfig) => Promise<AgentTransport>;
isNative?: () => boolean;
isStub?: () => boolean;
} | undefined;
if (!surface) {
return null;
}
// Refuse to use the WASM stub. ADR-119 documented the previous
// QuicMultistream as a counter-tracking stub with no real UDP, TLS,
// or protocol — using it would silently downgrade the federation
// path. midstreamer@0.3.1+ ships a real QUIC transport via
// `midstreamer/quic` (ADR-120, Step 1 — upstream PR ruvnet/midstream#81)
// and reports `isNative() === true`. Older versions either expose
// `isStub()` returning true or omit both probes.
if (typeof surface.isStub === 'function' && surface.isStub()) {
return { transport: null as unknown as AgentTransport, reason: 'midstreamer module reports isStub() === true; refusing to bind a stub QUIC backend (ADR-119)' };
}
if (typeof surface.loadQuicTransport !== 'function') {
return null;
}
if (typeof surface.isNative === 'function' && !surface.isNative()) {
return null;
}
try {
const transport = await surface.loadQuicTransport(config);
return { transport };
} catch (err) {
return {
transport: null as unknown as AgentTransport,
reason: `midstreamer.loadQuicTransport failed: ${err instanceof Error ? err.message : String(err)}`,
};
}
}
/**
* Top-level loader for federation transport. Identical signature to
* agentic-flow's `loadQuicTransport`, but with the midstreamer-first
* preference. Use this from `plugins/ruflo-federation` in place of
* the bare `loadQuicTransport`.
*
* Failure mode: if midstreamer is requested but rejects (stub, init
* error, missing package), this function falls through to the
* agentic-flow loader silently — the federation peer always gets a
* transport (WebSocket fallback in the worst case, per ADR-104).
*/
export async function loadFederationTransport(
config?: QuicTransportConfig,
): Promise<LoadedFederationTransport> {
const probe = await probeMidstreamerTransport(config);
if (probe && probe.transport) {
return { transport: probe.transport, source: 'midstreamer-native' };
}
const transport = await loadAgenticFlowQuicTransport(config);
if (!transport) {
throw new Error(
'No federation transport available. Install `agentic-flow` ' +
'(default) or `midstreamer` and set `MIDSTREAMER_QUIC_NATIVE=1` ' +
'(per ADR-120). Both are now optional peer dependencies — at ' +
'least one must be present at runtime.',
);
}
return {
transport,
source: 'agentic-flow-loader',
fallbackReason: probe?.reason,
};
}