agentic-flow 2.0.12-fix.8 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/billing/cli.js +0 -0
- package/dist/cli-proxy.js +0 -0
- package/dist/reasoningbank/utils/embeddings.d.ts +6 -0
- package/dist/reasoningbank/utils/embeddings.d.ts.map +1 -1
- package/dist/reasoningbank/utils/embeddings.js +43 -9
- package/dist/reasoningbank/utils/embeddings.js.map +1 -1
- package/dist/router/providers/onnx-local-optimized.d.ts +10 -1
- package/dist/router/providers/onnx-local-optimized.d.ts.map +1 -1
- package/dist/router/providers/onnx-local-optimized.js +10 -9
- package/dist/router/providers/onnx-local-optimized.js.map +1 -1
- package/dist/router/providers/onnx-local.d.ts +9 -0
- package/dist/router/providers/onnx-local.d.ts.map +1 -1
- package/dist/router/providers/onnx-local.js +22 -7
- package/dist/router/providers/onnx-local.js.map +1 -1
- package/dist/transport/index.d.ts +0 -1
- package/dist/transport/index.d.ts.map +1 -1
- package/dist/transport/index.js +0 -1
- package/dist/transport/index.js.map +1 -1
- package/package.json +4 -5
- package/wasm/reasoningbank/reasoningbank_wasm_bg.js +28 -28
- package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm +0 -0
- package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm.d.ts +2 -2
- package/dist/transport/quic-loader.d.ts +0 -217
- package/dist/transport/quic-loader.d.ts.map +0 -1
- package/dist/transport/quic-loader.js +0 -412
- package/dist/transport/quic-loader.js.map +0 -1
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
// QUIC Transport Loader with WebSocket fallback
|
|
2
|
-
//
|
|
3
|
-
// Backported from outer repo loader pattern. Exposes a single
|
|
4
|
-
// loadQuicTransport(config) entry point that:
|
|
5
|
-
//
|
|
6
|
-
// 1. Tries to load the WASM-backed QuicClient/QuicServer (real QUIC if a
|
|
7
|
-
// native build is wired in the future).
|
|
8
|
-
// 2. Falls back to WebSocketFallbackTransport when QUIC is unavailable
|
|
9
|
-
// OR when the WASM stub is detected (current state — see
|
|
10
|
-
// crates/agentic-flow-quic/src/wasm.rs comment: WASM build is a stub
|
|
11
|
-
// because browsers can't do raw UDP/QUIC; production QUIC needs
|
|
12
|
-
// native Node.js builds which haven't shipped yet).
|
|
13
|
-
//
|
|
14
|
-
// The fallback uses standard WebSocket (ws://) so it works on all Node
|
|
15
|
-
// versions without complex native dependencies. Same async send/receive
|
|
16
|
-
// API surface as QuicTransport.
|
|
17
|
-
//
|
|
18
|
-
// Federation use case (ruvnet/ruflo ADR-104): two peers on the same
|
|
19
|
-
// tailnet can call loadQuicTransport({ serverName: 'peer.tailnet' }) and
|
|
20
|
-
// exchange signed envelopes today, with zero code change required when
|
|
21
|
-
// the native QUIC build lands later.
|
|
22
|
-
import { logger } from '../utils/logger.js';
|
|
23
|
-
import WebSocket, { WebSocketServer } from 'ws';
|
|
24
|
-
import { createHash } from 'node:crypto';
|
|
25
|
-
import { createServer as createHttpsServer } from 'node:https';
|
|
26
|
-
import { readFileSync } from 'node:fs';
|
|
27
|
-
/** Default streamId when caller omits it. Backward-compat sentinel. */
|
|
28
|
-
export const DEFAULT_STREAM_ID = 'default';
|
|
29
|
-
/**
|
|
30
|
-
* WebSocket fallback transport.
|
|
31
|
-
*
|
|
32
|
-
* Spec compliance: implements the AgentTransport interface using
|
|
33
|
-
* `ws://` (or `wss://` if address starts with `wss://`). Each call to
|
|
34
|
-
* `send` lazily opens (or reuses) a connection to `address`. The
|
|
35
|
-
* `receive(address)` call drains the next queued message for that
|
|
36
|
-
* address; if none is queued it polls every 100ms until one arrives.
|
|
37
|
-
*
|
|
38
|
-
* Limits vs real QUIC: no 0-RTT resumption, no multiplexed streams
|
|
39
|
-
* (one TCP connection per peer), TLS handled by the WS layer (use
|
|
40
|
-
* `wss://` for encryption). Performance is "good enough" for federation
|
|
41
|
-
* messages at human/agent rates (≤ 100 RPS per peer).
|
|
42
|
-
*/
|
|
43
|
-
class WebSocketFallbackTransport {
|
|
44
|
-
config;
|
|
45
|
-
connections = new Map();
|
|
46
|
-
/**
|
|
47
|
-
* Per-(address, streamId) message queue. Composite key shape
|
|
48
|
-
* `${address}#${streamId}` — see {@link queueKey}. Each stream gets
|
|
49
|
-
* its own FIFO so receive(addr, streamA) is independent of
|
|
50
|
-
* receive(addr, streamB) — eliminates head-of-line blocking on a
|
|
51
|
-
* single peer connection.
|
|
52
|
-
*/
|
|
53
|
-
messageQueue = new Map();
|
|
54
|
-
connectionsCreated = 0;
|
|
55
|
-
connectionsClosed = 0;
|
|
56
|
-
servers = new Map();
|
|
57
|
-
/**
|
|
58
|
-
* Inbound handlers. Each entry is { handler, streamId? }. When
|
|
59
|
-
* streamId is undefined the handler receives ALL messages
|
|
60
|
-
* regardless of stream; otherwise only messages with the matching
|
|
61
|
-
* streamId. Lets callers register both per-stream + catch-all.
|
|
62
|
-
*/
|
|
63
|
-
inboundHandlers = new Set();
|
|
64
|
-
/** Compose the per-(address, streamId) queue key. */
|
|
65
|
-
queueKey(address, streamId) {
|
|
66
|
-
return `${address}#${streamId}`;
|
|
67
|
-
}
|
|
68
|
-
/** Resolve the streamId for a message — defaults to DEFAULT_STREAM_ID. */
|
|
69
|
-
streamOf(message) {
|
|
70
|
-
return message.streamId ?? DEFAULT_STREAM_ID;
|
|
71
|
-
}
|
|
72
|
-
constructor(config) {
|
|
73
|
-
this.config = config;
|
|
74
|
-
}
|
|
75
|
-
static async create(config = {}) {
|
|
76
|
-
const fullConfig = {
|
|
77
|
-
serverName: config.serverName ?? 'localhost',
|
|
78
|
-
maxIdleTimeoutMs: config.maxIdleTimeoutMs ?? 30000,
|
|
79
|
-
maxConcurrentStreams: config.maxConcurrentStreams ?? 100,
|
|
80
|
-
// Not applicable for WebSocket — record but ignore
|
|
81
|
-
enable0Rtt: config.enable0Rtt ?? false,
|
|
82
|
-
tls: config.tls ?? {},
|
|
83
|
-
};
|
|
84
|
-
return new WebSocketFallbackTransport(fullConfig);
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Bind a server-side listener so this transport instance can RECEIVE
|
|
88
|
-
* messages from a remote peer (in addition to sending). Federation
|
|
89
|
-
* peers run BOTH a listener and a client — calling listen(9100) plus
|
|
90
|
-
* send('peer:9100', ...) gives bidirectional connectivity.
|
|
91
|
-
*
|
|
92
|
-
* Enables `permessage-deflate` compression with thresholds chosen
|
|
93
|
-
* for federation envelopes (typically JSON, 100B-10KB):
|
|
94
|
-
* - threshold: 256B — don't waste CPU compressing tiny pings
|
|
95
|
-
* - level: 3 — balanced compression vs CPU (zlib's BEST_SPEED→6 range)
|
|
96
|
-
* - serverNoContextTakeover: true — bound per-conn memory growth
|
|
97
|
-
*/
|
|
98
|
-
async listen(port, host = '0.0.0.0') {
|
|
99
|
-
if (this.servers.has(port))
|
|
100
|
-
return;
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
const tls = this.config.tls;
|
|
103
|
-
const wssOpts = {
|
|
104
|
-
perMessageDeflate: {
|
|
105
|
-
threshold: 256,
|
|
106
|
-
zlibDeflateOptions: { level: 3 },
|
|
107
|
-
serverNoContextTakeover: true,
|
|
108
|
-
clientNoContextTakeover: true,
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
// ADR-107: if cert+key paths are configured, bind via https.Server
|
|
112
|
-
// (wss://). Otherwise bind plain ws:// directly.
|
|
113
|
-
if (tls?.certPath && tls?.keyPath) {
|
|
114
|
-
const cert = readFileSync(tls.certPath);
|
|
115
|
-
const key = readFileSync(tls.keyPath);
|
|
116
|
-
const httpsServer = createHttpsServer({ cert, key });
|
|
117
|
-
httpsServer.listen(port, host, () => {
|
|
118
|
-
const wss = new WebSocketServer({ ...wssOpts, server: httpsServer });
|
|
119
|
-
this.attachServerHandlers(wss);
|
|
120
|
-
this.servers.set(port, wss);
|
|
121
|
-
resolve();
|
|
122
|
-
});
|
|
123
|
-
httpsServer.on('error', reject);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const wss = new WebSocketServer({ ...wssOpts, port, host });
|
|
127
|
-
wss.on('listening', () => {
|
|
128
|
-
this.servers.set(port, wss);
|
|
129
|
-
resolve();
|
|
130
|
-
});
|
|
131
|
-
wss.on('error', reject);
|
|
132
|
-
this.attachServerHandlers(wss);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Wire the server's `connection` and per-socket `message` handlers.
|
|
137
|
-
* Extracted so the wss:// path (where the WebSocketServer is attached
|
|
138
|
-
* to a pre-created https.Server) can share the same logic.
|
|
139
|
-
*/
|
|
140
|
-
attachServerHandlers(wss) {
|
|
141
|
-
wss.on('connection', (ws, req) => {
|
|
142
|
-
const remoteAddr = `${req.socket.remoteAddress}:${req.socket.remotePort}`;
|
|
143
|
-
ws.on('message', (raw) => {
|
|
144
|
-
try {
|
|
145
|
-
const message = JSON.parse(raw.toString());
|
|
146
|
-
// Per-stream queue (default stream when message.streamId omitted)
|
|
147
|
-
const key = this.queueKey(remoteAddr, this.streamOf(message));
|
|
148
|
-
const queue = this.messageQueue.get(key) ?? [];
|
|
149
|
-
queue.push(message);
|
|
150
|
-
this.messageQueue.set(key, queue);
|
|
151
|
-
this.dispatchInbound(remoteAddr, message);
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
logger.warn('Dropped malformed inbound WS message', { remoteAddr, err });
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
async getOrCreateConnection(address) {
|
|
160
|
-
return new Promise((resolve, reject) => {
|
|
161
|
-
const existing = this.connections.get(address);
|
|
162
|
-
if (existing && existing.readyState === WebSocket.OPEN) {
|
|
163
|
-
resolve(existing);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const url = address.startsWith('ws://') || address.startsWith('wss://')
|
|
167
|
-
? address
|
|
168
|
-
: `ws://${address}`;
|
|
169
|
-
// ADR-107: cert pinning for wss:// peers. Build the WS options
|
|
170
|
-
// with both compression (perf) and TLS hooks (security).
|
|
171
|
-
const tls = this.config.tls;
|
|
172
|
-
const isWss = url.startsWith('wss://');
|
|
173
|
-
const wsOpts = {
|
|
174
|
-
perMessageDeflate: {
|
|
175
|
-
threshold: 256,
|
|
176
|
-
zlibDeflateOptions: { level: 3 },
|
|
177
|
-
serverNoContextTakeover: true,
|
|
178
|
-
clientNoContextTakeover: true,
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
if (isWss && tls?.pinnedFingerprints && tls.pinnedFingerprints.length > 0) {
|
|
182
|
-
// Fail-closed pinning: ONLY accept the listed cert fingerprints.
|
|
183
|
-
// CA path validation is irrelevant — the fingerprint IS the trust
|
|
184
|
-
// anchor. If the cert rotates, the operator must update config.
|
|
185
|
-
const pinned = new Set(tls.pinnedFingerprints);
|
|
186
|
-
// Cast: ws's `checkServerIdentity` is typed as returning boolean
|
|
187
|
-
// but the underlying `tls.checkServerIdentity` (which it
|
|
188
|
-
// forwards to) accepts `Error | undefined`. The runtime
|
|
189
|
-
// semantics are: throw OR return Error → reject; otherwise accept.
|
|
190
|
-
// The boolean type signature is overly restrictive.
|
|
191
|
-
wsOpts.checkServerIdentity = (_host, cert) => {
|
|
192
|
-
// cert.raw is the DER-encoded cert bytes; sha256/<base64> matches
|
|
193
|
-
// common pinning notation (and what `openssl x509 -fingerprint
|
|
194
|
-
// -sha256` outputs after base64-encoding).
|
|
195
|
-
const fp = `sha256/${createHash('sha256').update(cert.raw).digest('base64')}`;
|
|
196
|
-
if (!pinned.has(fp)) {
|
|
197
|
-
return new Error(`Federation TLS: peer cert fingerprint ${fp} not in pinned set ` +
|
|
198
|
-
`(${pinned.size} fingerprint(s) configured)`);
|
|
199
|
-
}
|
|
200
|
-
return undefined; // accept
|
|
201
|
-
};
|
|
202
|
-
// When pinning is on, we explicitly DON'T want CA validation
|
|
203
|
-
// (the fingerprint check above is the real auth). But we also
|
|
204
|
-
// DON'T want to silently accept ANY cert — the checkServerIdentity
|
|
205
|
-
// above is still called.
|
|
206
|
-
wsOpts.rejectUnauthorized = false;
|
|
207
|
-
}
|
|
208
|
-
else if (isWss && tls?.caPath) {
|
|
209
|
-
// Non-pinned mode: validate against the configured CA bundle.
|
|
210
|
-
wsOpts.ca = readFileSync(tls.caPath);
|
|
211
|
-
wsOpts.rejectUnauthorized = true;
|
|
212
|
-
}
|
|
213
|
-
// (no tls config + ws:// → plain unencrypted; ADR-104 documents
|
|
214
|
-
// tailnet-as-TLS as the recommended path)
|
|
215
|
-
const ws = new WebSocket(url, wsOpts);
|
|
216
|
-
ws.on('open', () => {
|
|
217
|
-
this.connections.set(address, ws);
|
|
218
|
-
this.connectionsCreated++;
|
|
219
|
-
resolve(ws);
|
|
220
|
-
});
|
|
221
|
-
ws.on('error', (error) => {
|
|
222
|
-
reject(new Error(`WebSocket connection to ${url} failed: ${error.message}`));
|
|
223
|
-
});
|
|
224
|
-
ws.on('close', () => {
|
|
225
|
-
this.connectionsClosed++;
|
|
226
|
-
this.connections.delete(address);
|
|
227
|
-
});
|
|
228
|
-
ws.on('message', (raw) => {
|
|
229
|
-
try {
|
|
230
|
-
const message = JSON.parse(raw.toString());
|
|
231
|
-
const key = this.queueKey(address, this.streamOf(message));
|
|
232
|
-
const queue = this.messageQueue.get(key) ?? [];
|
|
233
|
-
queue.push(message);
|
|
234
|
-
this.messageQueue.set(key, queue);
|
|
235
|
-
this.dispatchInbound(address, message);
|
|
236
|
-
}
|
|
237
|
-
catch (err) {
|
|
238
|
-
logger.warn('Dropped malformed WebSocket message', { address, err });
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
async send(address, message) {
|
|
244
|
-
const ws = await this.getOrCreateConnection(address);
|
|
245
|
-
ws.send(JSON.stringify(message));
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Register an inbound handler. Optional `options.streamId` scopes
|
|
249
|
-
* the handler to a specific stream (only fires for messages with
|
|
250
|
-
* matching streamId). Omit to subscribe to ALL streams.
|
|
251
|
-
*
|
|
252
|
-
* Patterns:
|
|
253
|
-
* onMessage(h) — receives all
|
|
254
|
-
* onMessage(h, { streamId: 'rpc' }) — receives only rpc
|
|
255
|
-
* onMessage(h, { streamId: 'event' }) — receives only event
|
|
256
|
-
* (both registered) — both fire on
|
|
257
|
-
* their respective streams
|
|
258
|
-
*/
|
|
259
|
-
onMessage(handler, options = {}) {
|
|
260
|
-
this.inboundHandlers.add({ handler, streamId: options.streamId });
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Fire all matching handlers for a received message. Stream-scoped
|
|
264
|
-
* handlers only fire when the message's streamId matches; all-stream
|
|
265
|
-
* handlers always fire. Errors thrown sync OR async-rejected by one
|
|
266
|
-
* handler don't stop delivery to others.
|
|
267
|
-
*/
|
|
268
|
-
dispatchInbound(address, message) {
|
|
269
|
-
if (this.inboundHandlers.size === 0)
|
|
270
|
-
return;
|
|
271
|
-
const msgStream = this.streamOf(message);
|
|
272
|
-
for (const entry of this.inboundHandlers) {
|
|
273
|
-
// Stream filter: scoped handler only fires on matching streamId
|
|
274
|
-
if (entry.streamId !== undefined && entry.streamId !== msgStream)
|
|
275
|
-
continue;
|
|
276
|
-
try {
|
|
277
|
-
const r = entry.handler(address, message);
|
|
278
|
-
if (r && typeof r.catch === 'function') {
|
|
279
|
-
r.catch((err) => {
|
|
280
|
-
logger.warn('Inbound handler rejected', { address, err });
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
catch (err) {
|
|
285
|
-
logger.warn('Inbound handler threw', { address, err });
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
async receive(address, streamId = DEFAULT_STREAM_ID) {
|
|
290
|
-
const key = this.queueKey(address, streamId);
|
|
291
|
-
// Fast path
|
|
292
|
-
const queue = this.messageQueue.get(key) ?? [];
|
|
293
|
-
if (queue.length > 0)
|
|
294
|
-
return queue.shift();
|
|
295
|
-
// Poll (caller must time out externally if they don't want to wait)
|
|
296
|
-
return new Promise((resolve) => {
|
|
297
|
-
const interval = setInterval(() => {
|
|
298
|
-
const q = this.messageQueue.get(key) ?? [];
|
|
299
|
-
if (q.length > 0) {
|
|
300
|
-
clearInterval(interval);
|
|
301
|
-
resolve(q.shift());
|
|
302
|
-
}
|
|
303
|
-
}, 100);
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
async request(address, message) {
|
|
307
|
-
await this.send(address, message);
|
|
308
|
-
return this.receive(address);
|
|
309
|
-
}
|
|
310
|
-
async sendBatch(address, messages) {
|
|
311
|
-
await Promise.all(messages.map((m) => this.send(address, m)));
|
|
312
|
-
}
|
|
313
|
-
async getStats() {
|
|
314
|
-
return {
|
|
315
|
-
active: this.connections.size,
|
|
316
|
-
idle: 0,
|
|
317
|
-
created: this.connectionsCreated,
|
|
318
|
-
closed: this.connectionsClosed,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
async close() {
|
|
322
|
-
// Outbound clients first.
|
|
323
|
-
for (const ws of this.connections.values()) {
|
|
324
|
-
ws.terminate();
|
|
325
|
-
}
|
|
326
|
-
this.connections.clear();
|
|
327
|
-
this.messageQueue.clear();
|
|
328
|
-
// Inbound: WebSocketServer.close() blocks until every accepted
|
|
329
|
-
// socket disconnects. Forcibly terminate them so the close
|
|
330
|
-
// callback fires within the test/CI timeout window.
|
|
331
|
-
for (const wss of this.servers.values()) {
|
|
332
|
-
for (const client of wss.clients) {
|
|
333
|
-
try {
|
|
334
|
-
client.terminate();
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
/* socket already gone */
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
await new Promise((resolve) => wss.close(() => resolve()));
|
|
341
|
-
}
|
|
342
|
-
this.servers.clear();
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Detect whether the WASM-backed QUIC transport is "real" (i.e. it
|
|
347
|
-
* actually moves bytes on the wire) vs the current stub. The stub
|
|
348
|
-
* returns 0ms for connect+send and never increments the server's
|
|
349
|
-
* received-bytes counter. We probe by observing a documented marker
|
|
350
|
-
* on the WASM module: when it's truly wired the loader function
|
|
351
|
-
* `defaultConfig` returns an object whose round-trip through
|
|
352
|
-
* `WasmQuicClient.new` actually opens a UDP socket — failing fast on
|
|
353
|
-
* an OS that blocks UDP outbound (e.g. some sandboxed CI envs).
|
|
354
|
-
*
|
|
355
|
-
* Until the native build lands this returns false; the loader picks
|
|
356
|
-
* WebSocket. When the native binding is wired this returns true and
|
|
357
|
-
* the loader picks real QUIC. Callers get the same API either way.
|
|
358
|
-
*/
|
|
359
|
-
async function isRealQuicAvailable() {
|
|
360
|
-
try {
|
|
361
|
-
// The WASM file is published in `wasm/quic/` of this package. We
|
|
362
|
-
// do NOT use it for federation today (per the wasm.rs note: it's a
|
|
363
|
-
// stub since browsers can't do UDP). When a native binding is added
|
|
364
|
-
// this probe should switch to detect that binding instead.
|
|
365
|
-
const native = process.env.AGENTIC_FLOW_QUIC_NATIVE === '1';
|
|
366
|
-
return native;
|
|
367
|
-
}
|
|
368
|
-
catch {
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Public API — load a working transport, preferring real QUIC when
|
|
374
|
-
* available, falling back to WebSocket otherwise. The returned object
|
|
375
|
-
* satisfies the AgentTransport interface in both cases.
|
|
376
|
-
*
|
|
377
|
-
* Example:
|
|
378
|
-
* const t = await loadQuicTransport({ serverName: 'ruvultra:9100' });
|
|
379
|
-
* await t.send('ruvultra:9100', { id: '1', type: 'task', payload: {...} });
|
|
380
|
-
*
|
|
381
|
-
* Federation v1 ships on the WebSocket fallback (this is the actual
|
|
382
|
-
* working transport today). When the native QUIC binding lands, set
|
|
383
|
-
* the AGENTIC_FLOW_QUIC_NATIVE=1 environment variable and the same
|
|
384
|
-
* code path picks up the upgrade with no API changes.
|
|
385
|
-
*/
|
|
386
|
-
export async function loadQuicTransport(config = {}) {
|
|
387
|
-
if (await isRealQuicAvailable()) {
|
|
388
|
-
// Future: wire to the native binding here.
|
|
389
|
-
logger.info('QUIC transport: native binding selected');
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
393
|
-
logger.warn('QUIC native binding not available; using WebSocket fallback. ' +
|
|
394
|
-
'Set AGENTIC_FLOW_QUIC_NATIVE=1 once a native build is installed.');
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return WebSocketFallbackTransport.create(config);
|
|
398
|
-
}
|
|
399
|
-
/** Quick capability probe for the doctor / health surface. */
|
|
400
|
-
export async function isQuicAvailable() {
|
|
401
|
-
return isRealQuicAvailable();
|
|
402
|
-
}
|
|
403
|
-
export async function getTransportCapabilities() {
|
|
404
|
-
const quic = await isRealQuicAvailable();
|
|
405
|
-
return {
|
|
406
|
-
quicAvailable: quic,
|
|
407
|
-
webSocketFallbackAvailable: true,
|
|
408
|
-
selectedBackend: quic ? 'quic' : 'websocket',
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
export { WebSocketFallbackTransport };
|
|
412
|
-
//# sourceMappingURL=quic-loader.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"quic-loader.js","sourceRoot":"","sources":["../../src/transport/quic-loader.ts"],"names":[],"mappings":"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","sourcesContent":["// QUIC Transport Loader with WebSocket fallback\n//\n// Backported from outer repo loader pattern. Exposes a single\n// loadQuicTransport(config) entry point that:\n//\n// 1. Tries to load the WASM-backed QuicClient/QuicServer (real QUIC if a\n// native build is wired in the future).\n// 2. Falls back to WebSocketFallbackTransport when QUIC is unavailable\n// OR when the WASM stub is detected (current state — see\n// crates/agentic-flow-quic/src/wasm.rs comment: WASM build is a stub\n// because browsers can't do raw UDP/QUIC; production QUIC needs\n// native Node.js builds which haven't shipped yet).\n//\n// The fallback uses standard WebSocket (ws://) so it works on all Node\n// versions without complex native dependencies. Same async send/receive\n// API surface as QuicTransport.\n//\n// Federation use case (ruvnet/ruflo ADR-104): two peers on the same\n// tailnet can call loadQuicTransport({ serverName: 'peer.tailnet' }) and\n// exchange signed envelopes today, with zero code change required when\n// the native QUIC build lands later.\n\nimport { logger } from '../utils/logger.js';\nimport WebSocket, { WebSocketServer, type RawData } from 'ws';\nimport { createHash } from 'node:crypto';\nimport { createServer as createHttpsServer } from 'node:https';\nimport { readFileSync } from 'node:fs';\nimport type { TLSSocket } from 'node:tls';\n\n/** TLS configuration for wss:// peers (ADR-107). */\nexport interface TlsConfig {\n /** Path to PEM cert file (server side — bind certs for the listener). */\n certPath?: string;\n /** Path to PEM key file (server side). */\n keyPath?: string;\n /**\n * Pinned `sha256/<base64>` fingerprints of acceptable peer certs\n * (client side — outbound connections).\n *\n * When set, ONLY these exact certs are accepted. CA validation is\n * skipped — the fingerprint IS the trust anchor. Fail-closed: if the\n * peer's cert rotates and the fingerprint doesn't match, the\n * connection is refused (operator must update config + restart).\n *\n * This prevents:\n * - Compromised public CAs issuing rogue certs for our domain\n * - TLS-MITM attacks where the attacker holds a valid cert chain\n */\n pinnedFingerprints?: string[];\n /**\n * Optional CA bundle path for non-pinned mode (e.g. private CA).\n * Used only when `pinnedFingerprints` is empty/unset.\n */\n caPath?: string;\n}\n\n/** Caller-facing config — minimal common surface across both backends. */\nexport interface QuicTransportConfig {\n serverName?: string;\n maxIdleTimeoutMs?: number;\n maxConcurrentStreams?: number;\n enable0Rtt?: boolean;\n /** TLS materials for wss:// listeners + clients (ADR-107). */\n tls?: TlsConfig;\n}\n\nexport interface AgentMessage {\n id: string;\n type: 'task' | 'result' | 'status' | 'coordination' | 'heartbeat' | string;\n payload: unknown;\n metadata?: Record<string, unknown>;\n /**\n * Stream multiplexing identifier. Messages with different streamIds\n * to the same peer are independent — receive queues and onMessage\n * handlers can scope per-stream, eliminating head-of-line blocking\n * for sequential `await` patterns on a single peer connection.\n *\n * Defaults to `'default'` if omitted (backward compat). Common\n * patterns:\n * - One stream per logical request type (`'rpc'`, `'event'`,\n * `'control'`)\n * - One stream per task (`taskId` doubled as streamId)\n * - One stream per priority class (`'high'`, `'normal'`, `'low'`)\n *\n * Maps cleanly to native QUIC streams when AGENTIC_FLOW_QUIC_NATIVE=1\n * (each app-layer streamId becomes a QUIC stream id at that point).\n */\n streamId?: string | number;\n}\n\n/** Default streamId when caller omits it. Backward-compat sentinel. */\nexport const DEFAULT_STREAM_ID = 'default';\n\nexport interface PoolStatistics {\n active: number;\n idle: number;\n created: number;\n closed: number;\n}\n\n/** Inbound message handler — called for every received message. */\nexport type InboundMessageHandler = (\n address: string,\n message: AgentMessage,\n) => void | Promise<void>;\n\n/**\n * Per-stream subscription options. Pass to `onMessage` to scope a\n * handler to a specific streamId (only fires for messages with that\n * exact streamId). Omit to receive all streams.\n */\nexport interface OnMessageOptions {\n readonly streamId?: string | number;\n}\n\n/** Common interface both real-QUIC and fallback transports satisfy. */\nexport interface AgentTransport {\n send(address: string, message: AgentMessage): Promise<void>;\n /**\n * Receive the next message from a peer. Optional `streamId` scopes\n * to that stream's queue (independent of other streams to the same\n * peer). Omit to use the default stream — backward-compat behavior.\n */\n receive(address: string, streamId?: string | number): Promise<AgentMessage>;\n request(address: string, message: AgentMessage): Promise<AgentMessage>;\n sendBatch(address: string, messages: AgentMessage[]): Promise<void>;\n getStats(): Promise<PoolStatistics>;\n close(): Promise<void>;\n /**\n * Subscribe to inbound messages. The handler fires for every received\n * message that matches `options.streamId` (if provided) or for every\n * message regardless of streamId (if options omitted).\n *\n * Multiple handlers may be registered (per-stream OR all-streams or\n * a mix). Errors thrown by a handler are logged but do not stop\n * delivery to other handlers.\n *\n * Optional method — implementations that don't support push-style\n * delivery may omit it. Callers should use `transport.onMessage?.(h)`\n * to gracefully degrade.\n */\n onMessage?(handler: InboundMessageHandler, options?: OnMessageOptions): void;\n}\n\n/**\n * WebSocket fallback transport.\n *\n * Spec compliance: implements the AgentTransport interface using\n * `ws://` (or `wss://` if address starts with `wss://`). Each call to\n * `send` lazily opens (or reuses) a connection to `address`. The\n * `receive(address)` call drains the next queued message for that\n * address; if none is queued it polls every 100ms until one arrives.\n *\n * Limits vs real QUIC: no 0-RTT resumption, no multiplexed streams\n * (one TCP connection per peer), TLS handled by the WS layer (use\n * `wss://` for encryption). Performance is \"good enough\" for federation\n * messages at human/agent rates (≤ 100 RPS per peer).\n */\nclass WebSocketFallbackTransport implements AgentTransport {\n private connections = new Map<string, WebSocket>();\n /**\n * Per-(address, streamId) message queue. Composite key shape\n * `${address}#${streamId}` — see {@link queueKey}. Each stream gets\n * its own FIFO so receive(addr, streamA) is independent of\n * receive(addr, streamB) — eliminates head-of-line blocking on a\n * single peer connection.\n */\n private messageQueue = new Map<string, AgentMessage[]>();\n private connectionsCreated = 0;\n private connectionsClosed = 0;\n private servers = new Map<number, WebSocketServer>();\n /**\n * Inbound handlers. Each entry is { handler, streamId? }. When\n * streamId is undefined the handler receives ALL messages\n * regardless of stream; otherwise only messages with the matching\n * streamId. Lets callers register both per-stream + catch-all.\n */\n private inboundHandlers = new Set<{\n handler: InboundMessageHandler;\n streamId?: string | number;\n }>();\n\n /** Compose the per-(address, streamId) queue key. */\n private queueKey(address: string, streamId: string | number): string {\n return `${address}#${streamId}`;\n }\n\n /** Resolve the streamId for a message — defaults to DEFAULT_STREAM_ID. */\n private streamOf(message: AgentMessage): string | number {\n return message.streamId ?? DEFAULT_STREAM_ID;\n }\n\n constructor(private readonly config: Required<QuicTransportConfig>) {}\n\n static async create(config: QuicTransportConfig = {}): Promise<WebSocketFallbackTransport> {\n const fullConfig: Required<QuicTransportConfig> = {\n serverName: config.serverName ?? 'localhost',\n maxIdleTimeoutMs: config.maxIdleTimeoutMs ?? 30000,\n maxConcurrentStreams: config.maxConcurrentStreams ?? 100,\n // Not applicable for WebSocket — record but ignore\n enable0Rtt: config.enable0Rtt ?? false,\n tls: config.tls ?? {},\n };\n return new WebSocketFallbackTransport(fullConfig);\n }\n\n /**\n * Bind a server-side listener so this transport instance can RECEIVE\n * messages from a remote peer (in addition to sending). Federation\n * peers run BOTH a listener and a client — calling listen(9100) plus\n * send('peer:9100', ...) gives bidirectional connectivity.\n *\n * Enables `permessage-deflate` compression with thresholds chosen\n * for federation envelopes (typically JSON, 100B-10KB):\n * - threshold: 256B — don't waste CPU compressing tiny pings\n * - level: 3 — balanced compression vs CPU (zlib's BEST_SPEED→6 range)\n * - serverNoContextTakeover: true — bound per-conn memory growth\n */\n async listen(port: number, host = '0.0.0.0'): Promise<void> {\n if (this.servers.has(port)) return;\n return new Promise((resolve, reject) => {\n const tls = this.config.tls;\n const wssOpts: ConstructorParameters<typeof WebSocketServer>[0] = {\n perMessageDeflate: {\n threshold: 256,\n zlibDeflateOptions: { level: 3 },\n serverNoContextTakeover: true,\n clientNoContextTakeover: true,\n },\n };\n // ADR-107: if cert+key paths are configured, bind via https.Server\n // (wss://). Otherwise bind plain ws:// directly.\n if (tls?.certPath && tls?.keyPath) {\n const cert = readFileSync(tls.certPath);\n const key = readFileSync(tls.keyPath);\n const httpsServer = createHttpsServer({ cert, key });\n httpsServer.listen(port, host, () => {\n const wss = new WebSocketServer({ ...wssOpts, server: httpsServer });\n this.attachServerHandlers(wss);\n this.servers.set(port, wss);\n resolve();\n });\n httpsServer.on('error', reject);\n return;\n }\n const wss = new WebSocketServer({ ...wssOpts, port, host });\n wss.on('listening', () => {\n this.servers.set(port, wss);\n resolve();\n });\n wss.on('error', reject);\n this.attachServerHandlers(wss);\n });\n }\n\n /**\n * Wire the server's `connection` and per-socket `message` handlers.\n * Extracted so the wss:// path (where the WebSocketServer is attached\n * to a pre-created https.Server) can share the same logic.\n */\n private attachServerHandlers(wss: WebSocketServer): void {\n wss.on('connection', (ws, req) => {\n const remoteAddr = `${req.socket.remoteAddress}:${req.socket.remotePort}`;\n ws.on('message', (raw: RawData) => {\n try {\n const message = JSON.parse(raw.toString()) as AgentMessage;\n // Per-stream queue (default stream when message.streamId omitted)\n const key = this.queueKey(remoteAddr, this.streamOf(message));\n const queue = this.messageQueue.get(key) ?? [];\n queue.push(message);\n this.messageQueue.set(key, queue);\n this.dispatchInbound(remoteAddr, message);\n } catch (err) {\n logger.warn('Dropped malformed inbound WS message', { remoteAddr, err });\n }\n });\n });\n }\n\n private async getOrCreateConnection(address: string): Promise<WebSocket> {\n return new Promise((resolve, reject) => {\n const existing = this.connections.get(address);\n if (existing && existing.readyState === WebSocket.OPEN) {\n resolve(existing);\n return;\n }\n\n const url = address.startsWith('ws://') || address.startsWith('wss://')\n ? address\n : `ws://${address}`;\n\n // ADR-107: cert pinning for wss:// peers. Build the WS options\n // with both compression (perf) and TLS hooks (security).\n const tls = this.config.tls;\n const isWss = url.startsWith('wss://');\n const wsOpts: WebSocket.ClientOptions = {\n perMessageDeflate: {\n threshold: 256,\n zlibDeflateOptions: { level: 3 },\n serverNoContextTakeover: true,\n clientNoContextTakeover: true,\n },\n };\n\n if (isWss && tls?.pinnedFingerprints && tls.pinnedFingerprints.length > 0) {\n // Fail-closed pinning: ONLY accept the listed cert fingerprints.\n // CA path validation is irrelevant — the fingerprint IS the trust\n // anchor. If the cert rotates, the operator must update config.\n const pinned = new Set(tls.pinnedFingerprints);\n // Cast: ws's `checkServerIdentity` is typed as returning boolean\n // but the underlying `tls.checkServerIdentity` (which it\n // forwards to) accepts `Error | undefined`. The runtime\n // semantics are: throw OR return Error → reject; otherwise accept.\n // The boolean type signature is overly restrictive.\n (wsOpts as unknown as { checkServerIdentity: unknown }).checkServerIdentity = (\n _host: string,\n cert: { raw: Buffer },\n ): Error | undefined => {\n // cert.raw is the DER-encoded cert bytes; sha256/<base64> matches\n // common pinning notation (and what `openssl x509 -fingerprint\n // -sha256` outputs after base64-encoding).\n const fp = `sha256/${createHash('sha256').update(cert.raw).digest('base64')}`;\n if (!pinned.has(fp)) {\n return new Error(\n `Federation TLS: peer cert fingerprint ${fp} not in pinned set ` +\n `(${pinned.size} fingerprint(s) configured)`,\n );\n }\n return undefined; // accept\n };\n // When pinning is on, we explicitly DON'T want CA validation\n // (the fingerprint check above is the real auth). But we also\n // DON'T want to silently accept ANY cert — the checkServerIdentity\n // above is still called.\n wsOpts.rejectUnauthorized = false;\n } else if (isWss && tls?.caPath) {\n // Non-pinned mode: validate against the configured CA bundle.\n wsOpts.ca = readFileSync(tls.caPath);\n wsOpts.rejectUnauthorized = true;\n }\n // (no tls config + ws:// → plain unencrypted; ADR-104 documents\n // tailnet-as-TLS as the recommended path)\n\n const ws = new WebSocket(url, wsOpts);\n\n ws.on('open', () => {\n this.connections.set(address, ws);\n this.connectionsCreated++;\n resolve(ws);\n });\n\n ws.on('error', (error: Error) => {\n reject(new Error(`WebSocket connection to ${url} failed: ${error.message}`));\n });\n\n ws.on('close', () => {\n this.connectionsClosed++;\n this.connections.delete(address);\n });\n\n ws.on('message', (raw: RawData) => {\n try {\n const message = JSON.parse(raw.toString()) as AgentMessage;\n const key = this.queueKey(address, this.streamOf(message));\n const queue = this.messageQueue.get(key) ?? [];\n queue.push(message);\n this.messageQueue.set(key, queue);\n this.dispatchInbound(address, message);\n } catch (err) {\n logger.warn('Dropped malformed WebSocket message', { address, err });\n }\n });\n });\n }\n\n async send(address: string, message: AgentMessage): Promise<void> {\n const ws = await this.getOrCreateConnection(address);\n ws.send(JSON.stringify(message));\n }\n\n /**\n * Register an inbound handler. Optional `options.streamId` scopes\n * the handler to a specific stream (only fires for messages with\n * matching streamId). Omit to subscribe to ALL streams.\n *\n * Patterns:\n * onMessage(h) — receives all\n * onMessage(h, { streamId: 'rpc' }) — receives only rpc\n * onMessage(h, { streamId: 'event' }) — receives only event\n * (both registered) — both fire on\n * their respective streams\n */\n onMessage(handler: InboundMessageHandler, options: OnMessageOptions = {}): void {\n this.inboundHandlers.add({ handler, streamId: options.streamId });\n }\n\n /**\n * Fire all matching handlers for a received message. Stream-scoped\n * handlers only fire when the message's streamId matches; all-stream\n * handlers always fire. Errors thrown sync OR async-rejected by one\n * handler don't stop delivery to others.\n */\n private dispatchInbound(address: string, message: AgentMessage): void {\n if (this.inboundHandlers.size === 0) return;\n const msgStream = this.streamOf(message);\n for (const entry of this.inboundHandlers) {\n // Stream filter: scoped handler only fires on matching streamId\n if (entry.streamId !== undefined && entry.streamId !== msgStream) continue;\n try {\n const r = entry.handler(address, message);\n if (r && typeof (r as Promise<void>).catch === 'function') {\n (r as Promise<void>).catch((err) => {\n logger.warn('Inbound handler rejected', { address, err });\n });\n }\n } catch (err) {\n logger.warn('Inbound handler threw', { address, err });\n }\n }\n }\n\n async receive(address: string, streamId: string | number = DEFAULT_STREAM_ID): Promise<AgentMessage> {\n const key = this.queueKey(address, streamId);\n // Fast path\n const queue = this.messageQueue.get(key) ?? [];\n if (queue.length > 0) return queue.shift()!;\n\n // Poll (caller must time out externally if they don't want to wait)\n return new Promise((resolve) => {\n const interval = setInterval(() => {\n const q = this.messageQueue.get(key) ?? [];\n if (q.length > 0) {\n clearInterval(interval);\n resolve(q.shift()!);\n }\n }, 100);\n });\n }\n\n async request(address: string, message: AgentMessage): Promise<AgentMessage> {\n await this.send(address, message);\n return this.receive(address);\n }\n\n async sendBatch(address: string, messages: AgentMessage[]): Promise<void> {\n await Promise.all(messages.map((m) => this.send(address, m)));\n }\n\n async getStats(): Promise<PoolStatistics> {\n return {\n active: this.connections.size,\n idle: 0,\n created: this.connectionsCreated,\n closed: this.connectionsClosed,\n };\n }\n\n async close(): Promise<void> {\n // Outbound clients first.\n for (const ws of this.connections.values()) {\n ws.terminate();\n }\n this.connections.clear();\n this.messageQueue.clear();\n\n // Inbound: WebSocketServer.close() blocks until every accepted\n // socket disconnects. Forcibly terminate them so the close\n // callback fires within the test/CI timeout window.\n for (const wss of this.servers.values()) {\n for (const client of wss.clients) {\n try {\n client.terminate();\n } catch {\n /* socket already gone */\n }\n }\n await new Promise<void>((resolve) => wss.close(() => resolve()));\n }\n this.servers.clear();\n }\n}\n\n/**\n * Detect whether the WASM-backed QUIC transport is \"real\" (i.e. it\n * actually moves bytes on the wire) vs the current stub. The stub\n * returns 0ms for connect+send and never increments the server's\n * received-bytes counter. We probe by observing a documented marker\n * on the WASM module: when it's truly wired the loader function\n * `defaultConfig` returns an object whose round-trip through\n * `WasmQuicClient.new` actually opens a UDP socket — failing fast on\n * an OS that blocks UDP outbound (e.g. some sandboxed CI envs).\n *\n * Until the native build lands this returns false; the loader picks\n * WebSocket. When the native binding is wired this returns true and\n * the loader picks real QUIC. Callers get the same API either way.\n */\nasync function isRealQuicAvailable(): Promise<boolean> {\n try {\n // The WASM file is published in `wasm/quic/` of this package. We\n // do NOT use it for federation today (per the wasm.rs note: it's a\n // stub since browsers can't do UDP). When a native binding is added\n // this probe should switch to detect that binding instead.\n const native = process.env.AGENTIC_FLOW_QUIC_NATIVE === '1';\n return native;\n } catch {\n return false;\n }\n}\n\n/**\n * Public API — load a working transport, preferring real QUIC when\n * available, falling back to WebSocket otherwise. The returned object\n * satisfies the AgentTransport interface in both cases.\n *\n * Example:\n * const t = await loadQuicTransport({ serverName: 'ruvultra:9100' });\n * await t.send('ruvultra:9100', { id: '1', type: 'task', payload: {...} });\n *\n * Federation v1 ships on the WebSocket fallback (this is the actual\n * working transport today). When the native QUIC binding lands, set\n * the AGENTIC_FLOW_QUIC_NATIVE=1 environment variable and the same\n * code path picks up the upgrade with no API changes.\n */\nexport async function loadQuicTransport(\n config: QuicTransportConfig = {},\n): Promise<AgentTransport> {\n if (await isRealQuicAvailable()) {\n // Future: wire to the native binding here.\n logger.info('QUIC transport: native binding selected');\n } else {\n if (process.env.NODE_ENV !== 'test') {\n logger.warn(\n 'QUIC native binding not available; using WebSocket fallback. ' +\n 'Set AGENTIC_FLOW_QUIC_NATIVE=1 once a native build is installed.',\n );\n }\n }\n return WebSocketFallbackTransport.create(config);\n}\n\n/** Quick capability probe for the doctor / health surface. */\nexport async function isQuicAvailable(): Promise<boolean> {\n return isRealQuicAvailable();\n}\n\nexport interface TransportCapabilities {\n quicAvailable: boolean;\n webSocketFallbackAvailable: true;\n selectedBackend: 'quic' | 'websocket';\n}\n\nexport async function getTransportCapabilities(): Promise<TransportCapabilities> {\n const quic = await isRealQuicAvailable();\n return {\n quicAvailable: quic,\n webSocketFallbackAvailable: true,\n selectedBackend: quic ? 'quic' : 'websocket',\n };\n}\n\nexport { WebSocketFallbackTransport };\n"]}
|