agentic-flow 2.0.12-fix.4 → 2.0.12-fix.6

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.
@@ -1,9 +1,37 @@
1
+ /** TLS configuration for wss:// peers (ADR-107). */
2
+ export interface TlsConfig {
3
+ /** Path to PEM cert file (server side — bind certs for the listener). */
4
+ certPath?: string;
5
+ /** Path to PEM key file (server side). */
6
+ keyPath?: string;
7
+ /**
8
+ * Pinned `sha256/<base64>` fingerprints of acceptable peer certs
9
+ * (client side — outbound connections).
10
+ *
11
+ * When set, ONLY these exact certs are accepted. CA validation is
12
+ * skipped — the fingerprint IS the trust anchor. Fail-closed: if the
13
+ * peer's cert rotates and the fingerprint doesn't match, the
14
+ * connection is refused (operator must update config + restart).
15
+ *
16
+ * This prevents:
17
+ * - Compromised public CAs issuing rogue certs for our domain
18
+ * - TLS-MITM attacks where the attacker holds a valid cert chain
19
+ */
20
+ pinnedFingerprints?: string[];
21
+ /**
22
+ * Optional CA bundle path for non-pinned mode (e.g. private CA).
23
+ * Used only when `pinnedFingerprints` is empty/unset.
24
+ */
25
+ caPath?: string;
26
+ }
1
27
  /** Caller-facing config — minimal common surface across both backends. */
2
28
  export interface QuicTransportConfig {
3
29
  serverName?: string;
4
30
  maxIdleTimeoutMs?: number;
5
31
  maxConcurrentStreams?: number;
6
32
  enable0Rtt?: boolean;
33
+ /** TLS materials for wss:// listeners + clients (ADR-107). */
34
+ tls?: TlsConfig;
7
35
  }
8
36
  export interface AgentMessage {
9
37
  id: string;
@@ -82,6 +110,12 @@ declare class WebSocketFallbackTransport implements AgentTransport {
82
110
  * - serverNoContextTakeover: true — bound per-conn memory growth
83
111
  */
84
112
  listen(port: number, host?: string): Promise<void>;
113
+ /**
114
+ * Wire the server's `connection` and per-socket `message` handlers.
115
+ * Extracted so the wss:// path (where the WebSocketServer is attached
116
+ * to a pre-created https.Server) can share the same logic.
117
+ */
118
+ private attachServerHandlers;
85
119
  private getOrCreateConnection;
86
120
  send(address: string, message: AgentMessage): Promise<void>;
87
121
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"quic-loader.d.ts","sourceRoot":"","sources":["../../src/transport/quic-loader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"quic-loader.d.ts","sourceRoot":"","sources":["../../src/transport/quic-loader.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,9 @@
21
21
  // the native QUIC build lands later.
22
22
  import { logger } from '../utils/logger.js';
23
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';
24
27
  /**
25
28
  * WebSocket fallback transport.
26
29
  *
@@ -58,6 +61,7 @@ class WebSocketFallbackTransport {
58
61
  maxConcurrentStreams: config.maxConcurrentStreams ?? 100,
59
62
  // Not applicable for WebSocket — record but ignore
60
63
  enable0Rtt: config.enable0Rtt ?? false,
64
+ tls: config.tls ?? {},
61
65
  };
62
66
  return new WebSocketFallbackTransport(fullConfig);
63
67
  }
@@ -77,35 +81,58 @@ class WebSocketFallbackTransport {
77
81
  if (this.servers.has(port))
78
82
  return;
79
83
  return new Promise((resolve, reject) => {
80
- const wss = new WebSocketServer({
81
- port,
82
- host,
84
+ const tls = this.config.tls;
85
+ const wssOpts = {
83
86
  perMessageDeflate: {
84
87
  threshold: 256,
85
88
  zlibDeflateOptions: { level: 3 },
86
89
  serverNoContextTakeover: true,
87
90
  clientNoContextTakeover: true,
88
91
  },
89
- });
92
+ };
93
+ // ADR-107: if cert+key paths are configured, bind via https.Server
94
+ // (wss://). Otherwise bind plain ws:// directly.
95
+ if (tls?.certPath && tls?.keyPath) {
96
+ const cert = readFileSync(tls.certPath);
97
+ const key = readFileSync(tls.keyPath);
98
+ const httpsServer = createHttpsServer({ cert, key });
99
+ httpsServer.listen(port, host, () => {
100
+ const wss = new WebSocketServer({ ...wssOpts, server: httpsServer });
101
+ this.attachServerHandlers(wss);
102
+ this.servers.set(port, wss);
103
+ resolve();
104
+ });
105
+ httpsServer.on('error', reject);
106
+ return;
107
+ }
108
+ const wss = new WebSocketServer({ ...wssOpts, port, host });
90
109
  wss.on('listening', () => {
91
110
  this.servers.set(port, wss);
92
111
  resolve();
93
112
  });
94
113
  wss.on('error', reject);
95
- wss.on('connection', (ws, req) => {
96
- const remoteAddr = `${req.socket.remoteAddress}:${req.socket.remotePort}`;
97
- ws.on('message', (raw) => {
98
- try {
99
- const message = JSON.parse(raw.toString());
100
- const queue = this.messageQueue.get(remoteAddr) ?? [];
101
- queue.push(message);
102
- this.messageQueue.set(remoteAddr, queue);
103
- this.dispatchInbound(remoteAddr, message);
104
- }
105
- catch (err) {
106
- logger.warn('Dropped malformed inbound WS message', { remoteAddr, err });
107
- }
108
- });
114
+ this.attachServerHandlers(wss);
115
+ });
116
+ }
117
+ /**
118
+ * Wire the server's `connection` and per-socket `message` handlers.
119
+ * Extracted so the wss:// path (where the WebSocketServer is attached
120
+ * to a pre-created https.Server) can share the same logic.
121
+ */
122
+ attachServerHandlers(wss) {
123
+ wss.on('connection', (ws, req) => {
124
+ const remoteAddr = `${req.socket.remoteAddress}:${req.socket.remotePort}`;
125
+ ws.on('message', (raw) => {
126
+ try {
127
+ const message = JSON.parse(raw.toString());
128
+ const queue = this.messageQueue.get(remoteAddr) ?? [];
129
+ queue.push(message);
130
+ this.messageQueue.set(remoteAddr, queue);
131
+ this.dispatchInbound(remoteAddr, message);
132
+ }
133
+ catch (err) {
134
+ logger.warn('Dropped malformed inbound WS message', { remoteAddr, err });
135
+ }
109
136
  });
110
137
  });
111
138
  }
@@ -119,16 +146,53 @@ class WebSocketFallbackTransport {
119
146
  const url = address.startsWith('ws://') || address.startsWith('wss://')
120
147
  ? address
121
148
  : `ws://${address}`;
122
- // Match server-side compression so handshake negotiates deflate.
123
- // Same parameters as in listen() above.
124
- const ws = new WebSocket(url, {
149
+ // ADR-107: cert pinning for wss:// peers. Build the WS options
150
+ // with both compression (perf) and TLS hooks (security).
151
+ const tls = this.config.tls;
152
+ const isWss = url.startsWith('wss://');
153
+ const wsOpts = {
125
154
  perMessageDeflate: {
126
155
  threshold: 256,
127
156
  zlibDeflateOptions: { level: 3 },
128
157
  serverNoContextTakeover: true,
129
158
  clientNoContextTakeover: true,
130
159
  },
131
- });
160
+ };
161
+ if (isWss && tls?.pinnedFingerprints && tls.pinnedFingerprints.length > 0) {
162
+ // Fail-closed pinning: ONLY accept the listed cert fingerprints.
163
+ // CA path validation is irrelevant — the fingerprint IS the trust
164
+ // anchor. If the cert rotates, the operator must update config.
165
+ const pinned = new Set(tls.pinnedFingerprints);
166
+ // Cast: ws's `checkServerIdentity` is typed as returning boolean
167
+ // but the underlying `tls.checkServerIdentity` (which it
168
+ // forwards to) accepts `Error | undefined`. The runtime
169
+ // semantics are: throw OR return Error → reject; otherwise accept.
170
+ // The boolean type signature is overly restrictive.
171
+ wsOpts.checkServerIdentity = (_host, cert) => {
172
+ // cert.raw is the DER-encoded cert bytes; sha256/<base64> matches
173
+ // common pinning notation (and what `openssl x509 -fingerprint
174
+ // -sha256` outputs after base64-encoding).
175
+ const fp = `sha256/${createHash('sha256').update(cert.raw).digest('base64')}`;
176
+ if (!pinned.has(fp)) {
177
+ return new Error(`Federation TLS: peer cert fingerprint ${fp} not in pinned set ` +
178
+ `(${pinned.size} fingerprint(s) configured)`);
179
+ }
180
+ return undefined; // accept
181
+ };
182
+ // When pinning is on, we explicitly DON'T want CA validation
183
+ // (the fingerprint check above is the real auth). But we also
184
+ // DON'T want to silently accept ANY cert — the checkServerIdentity
185
+ // above is still called.
186
+ wsOpts.rejectUnauthorized = false;
187
+ }
188
+ else if (isWss && tls?.caPath) {
189
+ // Non-pinned mode: validate against the configured CA bundle.
190
+ wsOpts.ca = readFileSync(tls.caPath);
191
+ wsOpts.rejectUnauthorized = true;
192
+ }
193
+ // (no tls config + ws:// → plain unencrypted; ADR-104 documents
194
+ // tailnet-as-TLS as the recommended path)
195
+ const ws = new WebSocket(url, wsOpts);
132
196
  ws.on('open', () => {
133
197
  this.connections.set(address, ws);
134
198
  this.connectionsCreated++;
@@ -1 +1 @@
1
- {"version":3,"file":"quic-loader.js","sourceRoot":"","sources":["../../src/transport/quic-loader.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,8DAA8D;AAC9D,8CAA8C;AAC9C,EAAE;AACF,2EAA2E;AAC3E,6CAA6C;AAC7C,yEAAyE;AACzE,8DAA8D;AAC9D,0EAA0E;AAC1E,qEAAqE;AACrE,yDAAyD;AACzD,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,gCAAgC;AAChC,EAAE;AACF,oEAAoE;AACpE,yEAAyE;AACzE,uEAAuE;AACvE,qCAAqC;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,SAAS,EAAE,EAAE,eAAe,EAAgB,MAAM,IAAI,CAAC;AAoD9D;;;;;;;;;;;;;GAaG;AACH,MAAM,0BAA0B;IAaD;IAZrB,WAAW,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC3C,YAAY,GAAG,IAAI,GAAG,EAA0B,CAAC;IACjD,kBAAkB,GAAG,CAAC,CAAC;IACvB,iBAAiB,GAAG,CAAC,CAAC;IACtB,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IACrD;;;;OAIG;IACK,eAAe,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE3D,YAA6B,MAAqC;QAArC,WAAM,GAAN,MAAM,CAA+B;IAAG,CAAC;IAEtE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAA8B,EAAE;QAClD,MAAM,UAAU,GAAkC;YAChD,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,WAAW;YAC5C,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,IAAI,KAAK;YAClD,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,GAAG;YACxD,mDAAmD;YACnD,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,KAAK;SACvC,CAAC;QACF,OAAO,IAAI,0BAA0B,CAAC,UAAU,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,MAAM,CAAC,IAAY,EAAE,IAAI,GAAG,SAAS;QACzC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QACnC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC;gBAC9B,IAAI;gBACJ,IAAI;gBACJ,iBAAiB,EAAE;oBACjB,SAAS,EAAE,GAAG;oBACd,kBAAkB,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;oBAChC,uBAAuB,EAAE,IAAI;oBAC7B,uBAAuB,EAAE,IAAI;iBAC9B;aACF,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;gBACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC5B,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACxB,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE;gBAC/B,MAAM,UAAU,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC1E,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,EAAE;oBAChC,IAAI,CAAC;wBACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAiB,CAAC;wBAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;wBACtD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;wBACpB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;wBACzC,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;oBAC5C,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,CAAC,IAAI,CAAC,sCAAsC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;oBAC3E,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,qBAAqB,CAAC,OAAe;QACjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC/C,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACvD,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAClB,OAAO;YACT,CAAC;YAED,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC;gBACrE,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,QAAQ,OAAO,EAAE,CAAC;YACtB,iEAAiE;YACjE,wCAAwC;YACxC,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE;gBAC5B,iBAAiB,EAAE;oBACjB,SAAS,EAAE,GAAG;oBACd,kBAAkB,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;oBAChC,uBAAuB,EAAE,IAAI;oBAC7B,uBAAuB,EAAE,IAAI;iBAC9B;aACF,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBAClC,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC1B,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;gBAC9B,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,GAAG,YAAY,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC/E,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBAClB,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACzB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,EAAE;gBAChC,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAiB,CAAC;oBAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;oBACnD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACpB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;oBACtC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACzC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,MAAM,CAAC,IAAI,CAAC,qCAAqC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAe,EAAE,OAAqB;QAC/C,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACrD,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;OAMG;IACH,SAAS,CAAC,OAA8B;QACtC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;;;OAKG;IACK,eAAe,CAAC,OAAe,EAAE,OAAqB;QAC5D,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAC5C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACrC,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC9B,IAAI,CAAC,IAAI,OAAQ,CAAmB,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;oBACzD,CAAmB,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;wBACjC,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;oBAC5D,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAe;QAC3B,YAAY;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACnD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC,KAAK,EAAG,CAAC;QAE5C,oEAAoE;QACpE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;gBAChC,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC/C,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACjB,aAAa,CAAC,QAAQ,CAAC,CAAC;oBACxB,OAAO,CAAC,CAAC,CAAC,KAAK,EAAG,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAe,EAAE,OAAqB;QAClD,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,OAAe,EAAE,QAAwB;QACvD,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI;YAC7B,IAAI,EAAE,CAAC;YACP,OAAO,EAAE,IAAI,CAAC,kBAAkB;YAChC,MAAM,EAAE,IAAI,CAAC,iBAAiB;SAC/B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK;QACT,0BAA0B;QAC1B,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,EAAE,CAAC,SAAS,EAAE,CAAC;QACjB,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAE1B,+DAA+D;QAC/D,2DAA2D;QAC3D,oDAAoD;QACpD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACxC,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjC,IAAI,CAAC;oBACH,MAAM,CAAC,SAAS,EAAE,CAAC;gBACrB,CAAC;gBAAC,MAAM,CAAC;oBACP,yBAAyB;gBAC3B,CAAC;YACH,CAAC;YACD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,KAAK,UAAU,mBAAmB;IAChC,IAAI,CAAC;QACH,iEAAiE;QACjE,mEAAmE;QACnE,oEAAoE;QACpE,2DAA2D;QAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,GAAG,CAAC;QAC5D,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAA8B,EAAE;IAEhC,IAAI,MAAM,mBAAmB,EAAE,EAAE,CAAC;QAChC,2CAA2C;QAC3C,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CACT,+DAA+D;gBAC7D,kEAAkE,CACrE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,0BAA0B,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACnD,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,OAAO,mBAAmB,EAAE,CAAC;AAC/B,CAAC;AAQD,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,MAAM,IAAI,GAAG,MAAM,mBAAmB,EAAE,CAAC;IACzC,OAAO;QACL,aAAa,EAAE,IAAI;QACnB,0BAA0B,EAAE,IAAI;QAChC,eAAe,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;KAC7C,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC","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';\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}\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\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/** Common interface both real-QUIC and fallback transports satisfy. */\nexport interface AgentTransport {\n send(address: string, message: AgentMessage): Promise<void>;\n receive(address: string): 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 message\n * received by this transport (both server-side accepted connections\n * and client-side receive callbacks). Multiple handlers may be\n * registered. 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): 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 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 registered via onMessage. Fired for every received\n * message after it lands in the per-address queue (queue stays for the\n * receive() poll API; handlers add push-style delivery on top).\n */\n private inboundHandlers = new Set<InboundMessageHandler>();\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 };\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 wss = new WebSocketServer({\n port,\n host,\n perMessageDeflate: {\n threshold: 256,\n zlibDeflateOptions: { level: 3 },\n serverNoContextTakeover: true,\n clientNoContextTakeover: true,\n },\n });\n wss.on('listening', () => {\n this.servers.set(port, wss);\n resolve();\n });\n wss.on('error', reject);\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 const queue = this.messageQueue.get(remoteAddr) ?? [];\n queue.push(message);\n this.messageQueue.set(remoteAddr, 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\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 // Match server-side compression so handshake negotiates deflate.\n // Same parameters as in listen() above.\n const ws = new WebSocket(url, {\n perMessageDeflate: {\n threshold: 256,\n zlibDeflateOptions: { level: 3 },\n serverNoContextTakeover: true,\n clientNoContextTakeover: true,\n },\n });\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 queue = this.messageQueue.get(address) ?? [];\n queue.push(message);\n this.messageQueue.set(address, 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. Returns nothing; handlers are\n * de-duplicated by reference (registering the same function twice\n * has no effect). To unregister, the caller would need to keep the\n * reference and call `set.delete(handler)` — exposed via a separate\n * helper if needed in the future.\n */\n onMessage(handler: InboundMessageHandler): void {\n this.inboundHandlers.add(handler);\n }\n\n /**\n * Fire all registered handlers for a received message. Errors thrown\n * synchronously OR rejected asynchronously by a handler are logged\n * but do not stop delivery to other handlers — push-style delivery is\n * fire-and-forget per-handler.\n */\n private dispatchInbound(address: string, message: AgentMessage): void {\n if (this.inboundHandlers.size === 0) return;\n for (const h of this.inboundHandlers) {\n try {\n const r = h(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): Promise<AgentMessage> {\n // Fast path\n const queue = this.messageQueue.get(address) ?? [];\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(address) ?? [];\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"]}
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\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/** Common interface both real-QUIC and fallback transports satisfy. */\nexport interface AgentTransport {\n send(address: string, message: AgentMessage): Promise<void>;\n receive(address: string): 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 message\n * received by this transport (both server-side accepted connections\n * and client-side receive callbacks). Multiple handlers may be\n * registered. 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): 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 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 registered via onMessage. Fired for every received\n * message after it lands in the per-address queue (queue stays for the\n * receive() poll API; handlers add push-style delivery on top).\n */\n private inboundHandlers = new Set<InboundMessageHandler>();\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 const queue = this.messageQueue.get(remoteAddr) ?? [];\n queue.push(message);\n this.messageQueue.set(remoteAddr, 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 queue = this.messageQueue.get(address) ?? [];\n queue.push(message);\n this.messageQueue.set(address, 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. Returns nothing; handlers are\n * de-duplicated by reference (registering the same function twice\n * has no effect). To unregister, the caller would need to keep the\n * reference and call `set.delete(handler)` — exposed via a separate\n * helper if needed in the future.\n */\n onMessage(handler: InboundMessageHandler): void {\n this.inboundHandlers.add(handler);\n }\n\n /**\n * Fire all registered handlers for a received message. Errors thrown\n * synchronously OR rejected asynchronously by a handler are logged\n * but do not stop delivery to other handlers — push-style delivery is\n * fire-and-forget per-handler.\n */\n private dispatchInbound(address: string, message: AgentMessage): void {\n if (this.inboundHandlers.size === 0) return;\n for (const h of this.inboundHandlers) {\n try {\n const r = h(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): Promise<AgentMessage> {\n // Fast path\n const queue = this.messageQueue.get(address) ?? [];\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(address) ?? [];\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-flow",
3
- "version": "2.0.12-fix.4",
3
+ "version": "2.0.12-fix.6",
4
4
  "description": "Production-ready AI agent orchestration platform with 66 specialized agents, 213 MCP tools, ReasoningBank learning memory, and autonomous multi-agent swarms. Built by @ruvnet with Claude Agent SDK, neural networks, memory persistence, GitHub integration, and distributed consensus protocols.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",