agent-comms 8.15.9 → 8.15.10

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.
Files changed (184) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/bridges/user/web/server.d.ts.map +1 -1
  4. package/dist/bridges/user/web/server.js +44 -24
  5. package/dist/bridges/user/web/server.js.map +1 -1
  6. package/dist/bridges/user/web/test/web-assets.unit.test.d.ts +5 -0
  7. package/dist/bridges/user/web/test/web-assets.unit.test.d.ts.map +1 -0
  8. package/dist/bridges/user/web/test/web-assets.unit.test.js +90 -0
  9. package/dist/bridges/user/web/test/web-assets.unit.test.js.map +1 -0
  10. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts +2 -0
  11. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts.map +1 -0
  12. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js +75 -0
  13. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js.map +1 -0
  14. package/dist/bridges/user/web/web-assets.d.ts +28 -0
  15. package/dist/bridges/user/web/web-assets.d.ts.map +1 -0
  16. package/dist/bridges/user/web/web-assets.js +67 -0
  17. package/dist/bridges/user/web/web-assets.js.map +1 -0
  18. package/package.json +8 -2
  19. package/src/account-cli.ts +195 -0
  20. package/src/account-join-cli.ts +191 -0
  21. package/src/bridges/cc-peer/approval-commands.ts +82 -0
  22. package/src/bridges/cc-peer/bridge.ts +138 -0
  23. package/src/bridges/cc-peer/default-front.ts +58 -0
  24. package/src/bridges/cc-peer/front-controller.ts +160 -0
  25. package/src/bridges/cc-peer/front-relay.ts +191 -0
  26. package/src/bridges/cc-peer/front-runtime.ts +167 -0
  27. package/src/bridges/cc-peer/front.ts +59 -0
  28. package/src/bridges/cc-peer/reply-aliases.ts +90 -0
  29. package/src/bridges/cc-peer/run.ts +100 -0
  30. package/src/bridges/cc-peer/shared-peer.ts +28 -0
  31. package/src/bridges/claude-code/channel.ts +318 -0
  32. package/src/bridges/codex/tool.ts +132 -0
  33. package/src/bridges/mcp/server.ts +137 -0
  34. package/src/bridges/opencode/plugin.ts +130 -0
  35. package/src/bridges/pi/extension.ts +330 -0
  36. package/src/bridges/registry.ts +17 -0
  37. package/src/bridges/user/cli.ts +87 -0
  38. package/src/bridges/user/controller.ts +345 -0
  39. package/src/bridges/user/tui.ts +300 -0
  40. package/src/bridges/user/web/actions.ts +173 -0
  41. package/src/bridges/user/web/contract.ts +222 -0
  42. package/src/bridges/user/web/dist/bundle.js +49 -0
  43. package/src/bridges/user/web/dist/icons/icon-192x192.svg +9 -0
  44. package/src/bridges/user/web/dist/icons/icon-512x512.svg +9 -0
  45. package/src/bridges/user/web/dist/icons/icon-96x96.svg +9 -0
  46. package/src/bridges/user/web/dist/index.html +21 -0
  47. package/src/bridges/user/web/dist/manifest.json +26 -0
  48. package/src/bridges/user/web/dist/mesh-worker.js +64 -0
  49. package/src/bridges/user/web/dist/sw.js +1 -0
  50. package/src/bridges/user/web/event-publisher.ts +114 -0
  51. package/src/bridges/user/web/port-discovery.ts +49 -0
  52. package/src/bridges/user/web/reads.ts +35 -0
  53. package/src/bridges/user/web/router.ts +158 -0
  54. package/src/bridges/user/web/server.ts +782 -0
  55. package/src/bridges/user/web/vite.config.ts +71 -0
  56. package/src/bridges/user/web/web-access.ts +404 -0
  57. package/src/bridges/user/web/web-assets.ts +90 -0
  58. package/src/bridges/user/web/web-beacon.ts +69 -0
  59. package/src/bridges/user/web/web-console-static.ts +90 -0
  60. package/src/cli-main.ts +635 -0
  61. package/src/cli.ts +13 -0
  62. package/src/core/account-bundle.ts +264 -0
  63. package/src/core/account-join.ts +289 -0
  64. package/src/core/account-ledger-crypto.ts +154 -0
  65. package/src/core/account-ledger-store.ts +66 -0
  66. package/src/core/account-ledger.ts +385 -0
  67. package/src/core/agent-details.ts +216 -0
  68. package/src/core/agent-list-format.ts +97 -0
  69. package/src/core/agent-registry.ts +282 -0
  70. package/src/core/agent-table.ts +58 -0
  71. package/src/core/atomic-file.ts +46 -0
  72. package/src/core/bind-retry.ts +38 -0
  73. package/src/core/bridge-mesh.ts +176 -0
  74. package/src/core/bridge.ts +718 -0
  75. package/src/core/capability-ask.ts +162 -0
  76. package/src/core/capability-bubble-up.ts +237 -0
  77. package/src/core/comms-store.ts +130 -0
  78. package/src/core/connection-approval.ts +134 -0
  79. package/src/core/connection-code-tool.ts +92 -0
  80. package/src/core/connection-code.ts +296 -0
  81. package/src/core/coordinator-role.ts +224 -0
  82. package/src/core/data-directory.ts +32 -0
  83. package/src/core/data-frame-routing.ts +309 -0
  84. package/src/core/default-command.ts +12 -0
  85. package/src/core/delegation-policy.ts +41 -0
  86. package/src/core/delivery-engine.ts +649 -0
  87. package/src/core/device-membership-verification.ts +125 -0
  88. package/src/core/device-membership.ts +130 -0
  89. package/src/core/directory-admission.ts +155 -0
  90. package/src/core/discovery-mdns.ts +247 -0
  91. package/src/core/discovery-tailscale.ts +224 -0
  92. package/src/core/discovery.ts +303 -0
  93. package/src/core/display-name.ts +73 -0
  94. package/src/core/dm-grant-actions.ts +72 -0
  95. package/src/core/dm-send-delegation.ts +73 -0
  96. package/src/core/dm-token-verification.ts +76 -0
  97. package/src/core/election-sessions.ts +135 -0
  98. package/src/core/error-reporting.ts +85 -0
  99. package/src/core/first-contact.ts +242 -0
  100. package/src/core/gateway-trust-actions.ts +184 -0
  101. package/src/core/gateway-trust.ts +305 -0
  102. package/src/core/gossip-directory.ts +276 -0
  103. package/src/core/gossip-extensions.ts +123 -0
  104. package/src/core/group-proofs.ts +205 -0
  105. package/src/core/handshake.ts +354 -0
  106. package/src/core/hub-forwarding.ts +24 -0
  107. package/src/core/hub-link.ts +176 -0
  108. package/src/core/hub-session.ts +374 -0
  109. package/src/core/identity-store.ts +715 -0
  110. package/src/core/identity.ts +489 -0
  111. package/src/core/index.ts +65 -0
  112. package/src/core/issuer-grouping.ts +165 -0
  113. package/src/core/issuer-identity-file.ts +187 -0
  114. package/src/core/lan-web-actions.ts +34 -0
  115. package/src/core/lan-web-uis.ts +55 -0
  116. package/src/core/ledger-lock.ts +81 -0
  117. package/src/core/listener-registry.ts +133 -0
  118. package/src/core/loopback-address.ts +22 -0
  119. package/src/core/machine-identity.ts +77 -0
  120. package/src/core/machine-list-groups.ts +53 -0
  121. package/src/core/mcp-registry-retry.ts +69 -0
  122. package/src/core/mcp-registry-version.ts +18 -0
  123. package/src/core/membership-proof.ts +139 -0
  124. package/src/core/membership-proofs.ts +149 -0
  125. package/src/core/mesh-graph-trace-tool.ts +81 -0
  126. package/src/core/mesh-graph.ts +89 -0
  127. package/src/core/mesh-network-actions.ts +183 -0
  128. package/src/core/mesh-store-options.ts +23 -0
  129. package/src/core/mesh-store-shared.ts +134 -0
  130. package/src/core/mesh-store.ts +1114 -0
  131. package/src/core/name-claim.ts +145 -0
  132. package/src/core/naming-actions.ts +90 -0
  133. package/src/core/naming.ts +138 -0
  134. package/src/core/nanoid.ts +20 -0
  135. package/src/core/network-interfaces.ts +24 -0
  136. package/src/core/package-version.ts +34 -0
  137. package/src/core/peer-lifecycle.ts +377 -0
  138. package/src/core/peer-link-error.ts +41 -0
  139. package/src/core/peer-versions.ts +98 -0
  140. package/src/core/pending-connection.ts +53 -0
  141. package/src/core/petnames.ts +121 -0
  142. package/src/core/pgp-keyserver.ts +32 -0
  143. package/src/core/pid-lock-file.ts +122 -0
  144. package/src/core/push-manager.ts +76 -0
  145. package/src/core/random-id.ts +9 -0
  146. package/src/core/receiver-set.ts +42 -0
  147. package/src/core/relay-role.ts +182 -0
  148. package/src/core/relay-selection.ts +120 -0
  149. package/src/core/relay-server.ts +198 -0
  150. package/src/core/relay-uplink.ts +78 -0
  151. package/src/core/request-timeouts.ts +37 -0
  152. package/src/core/room-discovery.ts +48 -0
  153. package/src/core/room-lifecycle.ts +838 -0
  154. package/src/core/room-lookup.ts +41 -0
  155. package/src/core/room-messaging.ts +247 -0
  156. package/src/core/room-notice-log.ts +61 -0
  157. package/src/core/room-path.ts +84 -0
  158. package/src/core/room-protocol.ts +893 -0
  159. package/src/core/room-router.ts +180 -0
  160. package/src/core/room-token-verification.ts +85 -0
  161. package/src/core/room-wire-extensions.ts +60 -0
  162. package/src/core/self-name-claim.ts +67 -0
  163. package/src/core/send-outcome.ts +69 -0
  164. package/src/core/shutdown-signals.ts +57 -0
  165. package/src/core/stale-agent-checker.ts +108 -0
  166. package/src/core/store.ts +704 -0
  167. package/src/core/token-text.ts +40 -0
  168. package/src/core/tool.ts +853 -0
  169. package/src/core/transport-events.ts +73 -0
  170. package/src/core/transport-queries.ts +54 -0
  171. package/src/core/transport.ts +367 -0
  172. package/src/core/types.ts +550 -0
  173. package/src/core/user-identity.ts +290 -0
  174. package/src/core/vapid.ts +81 -0
  175. package/src/core/version-check.ts +147 -0
  176. package/src/core/version-report-actions.ts +79 -0
  177. package/src/core/web-beacon.ts +31 -0
  178. package/src/core/web-push.ts +322 -0
  179. package/src/core/wire-mesh-core-version.ts +25 -0
  180. package/src/core/wire-mesh-identity.ts +36 -0
  181. package/src/core/wire-mesh-transport-options.ts +82 -0
  182. package/src/core/wire-mesh-transport.ts +1007 -0
  183. package/src/core/wire-protocol.ts +153 -0
  184. package/src/core/ws-dial.ts +191 -0
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Web Push encryption and VAPID JWT signing (RFC 8291 + RFC 8292).
3
+ *
4
+ * Implements the Web Push protocol using only Node.js built-in `crypto`
5
+ * and `https` — no external dependencies.
6
+ *
7
+ * Encryption flow (RFC 8291):
8
+ * 1. Derive a shared ECDH secret from the subscription's p256dh key
9
+ * 2. HKDF-derive the encryption key and nonce from the shared secret + auth
10
+ * 3. Encrypt the payload with AES-128-GCM
11
+ * 4. Prepend the sender's public key (65 bytes) to the ciphertext
12
+ *
13
+ * VAPID signing (RFC 8292):
14
+ * 1. Build a JWT with sub, exp, and aud claims
15
+ * 2. Sign with ES256 (ECDSA + P-256 + SHA-256)
16
+ * 3. Append the public key and token to the Crypto-Key header
17
+ */
18
+
19
+ import * as crypto from "node:crypto";
20
+ import * as https from "node:https";
21
+ import * as http from "node:http";
22
+ import { URL } from "node:url";
23
+
24
+ import type { VapidKeys } from "./vapid.js";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Types
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export interface PushSubscription {
31
+ endpoint: string;
32
+ keys: { p256dh: string; auth: string };
33
+ }
34
+
35
+ export interface PushPayload {
36
+ title: string;
37
+ body: string;
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Constants
42
+ // ---------------------------------------------------------------------------
43
+ const CONTENT_ENCODING = "aes128gcm";
44
+ const KEY_LENGTH = 16;
45
+ const NONCE_LENGTH = 12;
46
+ const SALT_LENGTH = 16;
47
+ const MAX_PAYLOAD_SIZE = 4078;
48
+ const EC_UNCOMPRESSED_POINT_LENGTH = 65;
49
+ const RECORD_SIZE = 4096;
50
+ const RECORD_SIZE_FIELD_LENGTH = 4;
51
+ const VAPID_JWT_EXPIRY_HOURS = 12;
52
+ const MINUTES_PER_HOUR = 60;
53
+ const SECONDS_PER_MINUTE = 60;
54
+ const MS_PER_SECOND = 1000;
55
+ const HTTPS_DEFAULT_PORT = 443;
56
+ const HTTP_DEFAULT_PORT = 80;
57
+ const HTTP_STATUS_OK_MIN = 200;
58
+ const HTTP_STATUS_OK_MAX = 299;
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Public API
62
+ // ---------------------------------------------------------------------------
63
+
64
+ /**
65
+ * Send a Web Push notification.
66
+ *
67
+ * Encrypts the payload per RFC 8291, signs with VAPID per RFC 8292,
68
+ * and POSTs to the push service endpoint.
69
+ */
70
+ export async function sendWebPush(
71
+ subscription: PushSubscription,
72
+ payload: Readonly<PushPayload>,
73
+ vapidKeys: Readonly<VapidKeys>,
74
+ subject: string,
75
+ ): Promise<void> {
76
+ const plaintext = JSON.stringify(payload);
77
+ const plaintextBuf = Buffer.from(plaintext, "utf-8");
78
+
79
+ if (plaintextBuf.length > MAX_PAYLOAD_SIZE) {
80
+ throw new Error(
81
+ `Payload too large: ${String(plaintextBuf.length)} bytes (max ${String(MAX_PAYLOAD_SIZE)})`,
82
+ );
83
+ }
84
+
85
+ const encrypted = encrypt(plaintextBuf, subscription.keys);
86
+ const headers = buildHeaders(
87
+ encrypted,
88
+ subscription.endpoint,
89
+ vapidKeys,
90
+ subject,
91
+ );
92
+
93
+ await post(subscription.endpoint, headers, encrypted);
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Encryption (RFC 8291)
98
+ // ---------------------------------------------------------------------------
99
+
100
+ function encrypt(plaintext: Buffer, keys: PushSubscription["keys"]): Buffer {
101
+ // Decode the subscription's p256dh and auth keys
102
+ const recipientPubKey = base64urlDecode(keys.p256dh);
103
+ const authKey = base64urlDecode(keys.auth);
104
+
105
+ // Generate an ephemeral ECDH key pair for this message
106
+ const ecdh = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
107
+ const senderPrivateKey = crypto.createPrivateKey({
108
+ key: ecdh.privateKey.export({ type: "pkcs8", format: "der" }),
109
+ format: "der",
110
+ type: "pkcs8",
111
+ });
112
+ const senderPublicDer = ecdh.publicKey.export({
113
+ type: "spki",
114
+ format: "der",
115
+ });
116
+ const senderPublicKey = senderPublicDer.subarray(
117
+ senderPublicDer.length - EC_UNCOMPRESSED_POINT_LENGTH,
118
+ );
119
+
120
+ // Derive the shared secret via ECDH
121
+ const sharedSecret = crypto.diffieHellman({
122
+ privateKey: senderPrivateKey,
123
+ publicKey: crypto.createPublicKey({
124
+ key: concat(
125
+ Buffer.from(
126
+ "3059301306072a8648ce3d020106082a8648ce3d030107034200",
127
+ "hex",
128
+ ),
129
+ recipientPubKey,
130
+ ),
131
+ format: "der",
132
+ type: "spki",
133
+ }),
134
+ });
135
+
136
+ // Derive the pseudo-random key (PRK) using HKDF with auth
137
+ const prk = hkdfExtract(authKey, sharedSecret);
138
+
139
+ // Derive the encryption key and nonce
140
+ const keyInfo = concat(
141
+ Buffer.from("Content-Encoding: aes128gcm\0", "utf-8"),
142
+ senderPublicKey,
143
+ );
144
+ const nonceInfo = Buffer.from("Content-Encoding: nonce\0", "utf-8");
145
+
146
+ const encryptionKey = hkdfExpand(prk, keyInfo, KEY_LENGTH);
147
+ const nonce = hkdfExpand(prk, nonceInfo, NONCE_LENGTH);
148
+
149
+ // Generate a random salt
150
+ const salt = crypto.randomBytes(SALT_LENGTH);
151
+
152
+ // Pad the plaintext: plaintext + 2-byte padding delimiter (0x02 0x00 for no padding)
153
+ // RFC 8291 §4: record is plaintext || 0x02 || padding zeros
154
+ const padded = concat(plaintext, Buffer.from([0x02]));
155
+
156
+ // Encrypt with AES-128-GCM
157
+ const cipher = crypto.createCipheriv("aes-128-gcm", encryptionKey, nonce);
158
+ const ciphertext = cipher.update(padded);
159
+ const authTag = cipher.getAuthTag();
160
+
161
+ // RFC 8291 §4: salt (16) || rs (4, big-endian, 4096) || sender pubkey (65) || ciphertext || tag
162
+ const rs = Buffer.alloc(RECORD_SIZE_FIELD_LENGTH);
163
+ rs.writeUInt32BE(RECORD_SIZE);
164
+
165
+ return concat(salt, rs, senderPublicKey, ciphertext, authTag);
166
+ }
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // HKDF helpers
170
+ // ---------------------------------------------------------------------------
171
+
172
+ function hkdfExtract(salt: Buffer, ikm: Buffer): Buffer {
173
+ const hmac = crypto.createHmac("sha256", salt);
174
+ hmac.update(ikm);
175
+ return hmac.digest();
176
+ }
177
+
178
+ function hkdfExpand(prk: Buffer, info: Buffer, length: number): Buffer {
179
+ const hmac = crypto.createHmac("sha256", prk);
180
+ hmac.update(concat(info, Buffer.from([0x01])));
181
+ const t = hmac.digest();
182
+ return t.subarray(0, length);
183
+ }
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // VAPID JWT signing (RFC 8292)
187
+ // ---------------------------------------------------------------------------
188
+
189
+ function buildVapidJwt(
190
+ audience: string,
191
+ vapidKeys: Readonly<VapidKeys>,
192
+ subject: string,
193
+ ): string {
194
+ const header = base64url(Buffer.from('{"alg":"ES256","typ":"JWT"}', "utf-8"));
195
+
196
+ const exp =
197
+ Math.floor(Date.now() / MS_PER_SECOND) +
198
+ VAPID_JWT_EXPIRY_HOURS * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;
199
+ const claims = JSON.stringify({ aud: audience, exp, sub: subject });
200
+ const payload = base64url(Buffer.from(claims, "utf-8"));
201
+
202
+ const signInput = `${header}.${payload}`;
203
+
204
+ // Reconstruct the private key from the raw 32-byte scalar
205
+ // P-256 PKCS8 DER wrapper: 39 bytes header + 32 bytes scalar (with 0x00 prefix)
206
+ const privScalar = base64urlDecode(vapidKeys.privateKey);
207
+ // Build the EC private key in SEC1 DER format
208
+ const sec1Der = concat(
209
+ Buffer.from("30770201010420", "hex"),
210
+ privScalar,
211
+ Buffer.from("a00a06082a8648ce3d030107a14403420004", "hex"),
212
+ base64urlDecode(vapidKeys.publicKey),
213
+ );
214
+ const privateKey = crypto.createPrivateKey({
215
+ key: sec1Der,
216
+ format: "der",
217
+ type: "sec1",
218
+ });
219
+
220
+ const signature = crypto.sign("sha256", Buffer.from(signInput), privateKey);
221
+ // ECDSA signature is a DER-encoded SEQUENCE of two INTEGERs.
222
+ // Web Push requires base64url encoding of the raw DER.
223
+ const sigB64 = base64url(signature);
224
+
225
+ return `${signInput}.${sigB64}`;
226
+ }
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // HTTP headers
230
+ // ---------------------------------------------------------------------------
231
+
232
+ function buildHeaders(
233
+ encrypted: Buffer,
234
+ endpoint: string,
235
+ vapidKeys: Readonly<VapidKeys>,
236
+ subject: string,
237
+ ): Record<string, string> {
238
+ const audience = new URL(endpoint).origin;
239
+ const jwt = buildVapidJwt(audience, vapidKeys, subject);
240
+
241
+ return {
242
+ "Content-Type": "application/octet-stream",
243
+ "Content-Encoding": CONTENT_ENCODING,
244
+ "Content-Length": String(encrypted.length),
245
+ Authorization: `vapid t=${jwt}, k=${vapidKeys.publicKey}`,
246
+ TTL: "86400",
247
+ Urgency: "high",
248
+ };
249
+ }
250
+
251
+ // ---------------------------------------------------------------------------
252
+ // HTTP POST
253
+ // ---------------------------------------------------------------------------
254
+
255
+ async function post(
256
+ endpoint: string,
257
+ headers: Readonly<Record<string, string>>,
258
+ body: Buffer,
259
+ ): Promise<void> {
260
+ return new Promise((resolve, reject) => {
261
+ const url = new URL(endpoint);
262
+ const isHttps = url.protocol === "https:";
263
+ const mod = isHttps ? https : http;
264
+ const port =
265
+ url.port !== ""
266
+ ? url.port
267
+ : String(isHttps ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT);
268
+
269
+ const req = mod.request(
270
+ {
271
+ hostname: url.hostname,
272
+ port,
273
+ path: url.pathname + url.search,
274
+ method: "POST",
275
+ headers,
276
+ },
277
+ (res) => {
278
+ // Drain the response body
279
+ const chunks: Buffer[] = [];
280
+ res.on("data", (chunk: Buffer) => {
281
+ chunks.push(chunk);
282
+ });
283
+ res.on("end", () => {
284
+ if (
285
+ res.statusCode !== undefined &&
286
+ (res.statusCode < HTTP_STATUS_OK_MIN ||
287
+ res.statusCode > HTTP_STATUS_OK_MAX)
288
+ ) {
289
+ const responseBody = Buffer.concat(chunks).toString("utf-8");
290
+ reject(
291
+ new Error(
292
+ `Push service returned ${String(res.statusCode)}: ${responseBody}`,
293
+ ),
294
+ );
295
+ } else {
296
+ resolve();
297
+ }
298
+ });
299
+ },
300
+ );
301
+
302
+ req.on("error", reject);
303
+ req.write(body);
304
+ req.end();
305
+ });
306
+ }
307
+
308
+ // ---------------------------------------------------------------------------
309
+ // Utilities
310
+ // ---------------------------------------------------------------------------
311
+
312
+ function base64urlDecode(input: string): Buffer {
313
+ return Buffer.from(input, "base64url");
314
+ }
315
+
316
+ function base64url(input: Buffer): string {
317
+ return input.toString("base64url");
318
+ }
319
+
320
+ function concat(...buffers: readonly Buffer[]): Buffer {
321
+ return Buffer.concat(buffers);
322
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * This process's own installed wire-mesh-core dependency version, read directly from its package.json rather than duplicated as a hand-maintained constant -- the local fallback agent-comms#198 calls for once wire-mesh#179 (wire-mesh-core self-advertising CORE_VERSION_GOSSIP_KEY on every self-advert) makes the value moot for a REMOTE peer. own-version.ts (wire-mesh-core's own internal module producing that gossiped value) is not exported as a package subpath, so this side cannot import its OWN_VERSION constant directly -- but it is exactly this side's own installed wire-mesh-core/package.json version, which is exported ("./package.json") and is what own-version.ts itself reads to produce OWN_VERSION in the first place.
3
+ */
4
+
5
+ import { readFileSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import { isVersionedPackageJson } from "./package-version.js";
8
+
9
+ let cachedVersion: string | undefined;
10
+
11
+ /** This process's own installed wire-mesh-core version. Resolved via import.meta.resolve (Node's own module resolver) rather than a static JSON import, so it always reads whatever wire-mesh-core is actually installed at runtime -- immune to any bundler that might otherwise inline a JSON import at agent-comms' own build time. Cached after the first successful read, matching getOwnPackageVersion's own convention. */
12
+ export function getWireMeshCoreVersion(): string {
13
+ if (cachedVersion !== undefined) return cachedVersion;
14
+ const packageJsonUrl = import.meta.resolve("wire-mesh-core/package.json");
15
+ const parsed: unknown = JSON.parse(
16
+ readFileSync(fileURLToPath(packageJsonUrl), "utf-8"),
17
+ );
18
+ if (!isVersionedPackageJson(parsed)) {
19
+ throw new Error(
20
+ `wire-mesh-core's package.json at ${packageJsonUrl} has no "version" string`,
21
+ );
22
+ }
23
+ cachedVersion = parsed.version;
24
+ return cachedVersion;
25
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Adapts agent-comms' own PeerIdentity (a PEM-encoded ECDSA P-256 keypair plus a self-signed X.509 certificate, identity.ts's own concern) into wire-mesh-core's IdentityPort -- the shape MeshSession and createTlsTransport both expect. Both sides wrap the exact same keypair; only the envelope differs. The certificate itself plays no part here -- IdentityPort's own device-id is derived straight from the raw public key, identically to how identity.ts's own deriveDeviceId already works, so the two stay consistent by construction rather than by convention.
3
+ */
4
+
5
+ import { webcrypto } from "node:crypto";
6
+ import { createNodeIdentity } from "wire-mesh-core/adapters/node-identity";
7
+ import type { IdentityPort } from "wire-mesh-core/ports/identity";
8
+ import type { PeerIdentity } from "./identity.js";
9
+ import { rawPublicKeyFromPrivateKey } from "./identity.js";
10
+
11
+ /** COSE algorithm identifier for ES256 (P-256 + SHA-256) -- wire-mesh's own identity-key.alg convention. */
12
+ const ES256 = -7;
13
+
14
+ /** Strips a PEM envelope down to its raw DER bytes, copied into a fresh, non-shared, whole-buffer Uint8Array -- Web Crypto's BufferSource parameters reject a view over a SharedArrayBuffer or a sub-range view, neither of which Buffer.from's return type is guaranteed not to be. */
15
+ function pemToDer(pem: string, label: string): Uint8Array<ArrayBuffer> {
16
+ const b64 = pem
17
+ .replace(new RegExp(`-----BEGIN ${label}-----`), "")
18
+ .replace(new RegExp(`-----END ${label}-----`), "")
19
+ .replace(/\s/g, "");
20
+ return Uint8Array.from(Buffer.from(b64, "base64"));
21
+ }
22
+
23
+ export async function toIdentityPort(
24
+ identity: Readonly<PeerIdentity>,
25
+ ): Promise<IdentityPort> {
26
+ const privateKeyDer = pemToDer(identity.privateKey, "PRIVATE KEY");
27
+ const privateKey = await webcrypto.subtle.importKey(
28
+ "pkcs8",
29
+ privateKeyDer,
30
+ { name: "ECDSA", namedCurve: "P-256" },
31
+ true,
32
+ ["sign"],
33
+ );
34
+ const publicKeyBytes = rawPublicKeyFromPrivateKey(identity.privateKey);
35
+ return createNodeIdentity(privateKey, publicKeyBytes, ES256);
36
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * WireMeshTransport's own constructor options, split out purely to keep wire-mesh-transport.ts under the repo's max-lines cap, the same reason connection-approval.ts, room-router.ts, hub-session.ts, peer-lifecycle.ts, gossip-directory.ts, and listener-registry.ts were each split from that file.
3
+ */
4
+
5
+ import type { KeyValueStorage } from "wire-mesh-core/ports/storage";
6
+ import type { AccountLedgerReplica } from "./account-ledger.js";
7
+ import type { GatewayTrustReader } from "./gateway-trust.js";
8
+ import type { RoomVerbHandler } from "./room-router.js";
9
+ import type { AgentStatus } from "./types.js";
10
+ import type { AgentSelfAdvert, HostedRoomAdvert } from "./gossip-extensions.js";
11
+ import type { ListenerPolicy } from "./transport.js";
12
+
13
+ /** roomVerbHandlers, getCurrentPresence, getHostedRooms, dataStorage, getSelfAgentAdvert, and gatewayTrust each match the field-level doc comment on the WireMeshTransport field they back; accountReplication, verifyMembership and roomJoinApprovalTimeoutMs are described by their own doc comments below. pendingConnectionTimeoutMs and presenceReadvertiseIntervalMs default to DEFAULT_PENDING_CONNECTION_TIMEOUT_MS and PRESENCE_READVERTISE_INTERVAL_MS. Every field is optional and they are bundled into this one options type, so a caller needing only one of them never passes `undefined` for the rest. */
14
+ export interface WireMeshTransportOptions {
15
+ roomVerbHandlers?: Partial<Record<string, RoomVerbHandler>>;
16
+ pendingConnectionTimeoutMs?: number | undefined;
17
+ /** How long a room.join this transport sends may await a human decision at the receiving end. Defaults to ROOM_JOIN_APPROVAL_TIMEOUT_MS; a test shortens it. */
18
+ roomJoinApprovalTimeoutMs?: number | undefined;
19
+ getCurrentPresence?: () => AgentStatus | undefined;
20
+ presenceReadvertiseIntervalMs?: number | undefined;
21
+ getHostedRooms?: () => readonly HostedRoomAdvert[];
22
+ dataStorage?: KeyValueStorage;
23
+ getSelfAgentAdvert?: () => AgentSelfAdvert | undefined;
24
+ /** Replicates the account's grant ledger (agent-comms#344) with peers holding the same account: the ledger, read when a data frame for one of its writer logs arrives and on each re-advertise tick, and the check of a peer's gossiped membership proof against this machine's own principal that decides which peers those are. Absent for a caller with no account ledger. */
25
+ accountReplication?: Readonly<{
26
+ getLedger: () => AccountLedgerReplica | undefined;
27
+ isAccountMember: (
28
+ claim: Readonly<{ proof: string; deviceHex: string }>,
29
+ ) => Promise<boolean>;
30
+ }>;
31
+ gatewayTrust?: Readonly<GatewayTrustReader>;
32
+ /** The interface the relay this transport serves listens on (agent-comms#342). Defaults to every interface (ALL_INTERFACES_HOST), so the relay reaches the local network; a test serves loopback only. */
33
+ relayListenHost?: string | undefined;
34
+ /** Checks a gossiped membership proof against a principal or machine this side trusts (MembershipProofs.verify). Without it, a directory entry from a device not trusted by id is refused even when it carries a proof. */
35
+ verifyMembership?: (
36
+ claim: Readonly<{ proof: string; deviceHex: string; issuerHex: string }>,
37
+ ) => Promise<{ ok: true; expires: number } | { ok: false; reason: string }>;
38
+ }
39
+
40
+ /** WireMeshTransport.connectToRemote's own bundled parameters: see MeshTransport's own connectToRemote doc comment (transport.ts) for what each one means. */
41
+ export interface ConnectToRemoteOptions {
42
+ host: string;
43
+ port: number;
44
+ peerId: string;
45
+ dataPort: number;
46
+ name: string;
47
+ fingerprint: string;
48
+ }
49
+
50
+ /** How an accepted connection is treated, set by the listener that accepted it. fireOnPeerConnected is true only for the data server, whose connections are a known peer's own data dial; requiresApproval quarantines a listener a stranger can dial cold; machineLocal marks a loopback-only listener, whose sessions take part in the coordinator election (election-sessions.ts). */
51
+ export interface AcceptOptions {
52
+ policy: ListenerPolicy | undefined;
53
+ fireOnPeerConnected: boolean;
54
+ requiresApproval: boolean;
55
+ machineLocal: boolean;
56
+ }
57
+
58
+ /** The data server: bound on loopback, dialled only by peers that already know its port. */
59
+ export const DATA_SERVER_ACCEPT: Readonly<AcceptOptions> = {
60
+ policy: undefined,
61
+ fireOnPeerConnected: true,
62
+ requiresApproval: false,
63
+ machineLocal: true,
64
+ };
65
+
66
+ /** The default well-known port listener: bound on loopback, but dialled cold, so quarantined until an introduce or an approved connect_request. */
67
+ export const DEFAULT_LISTENER_ACCEPT: Readonly<AcceptOptions> = {
68
+ policy: "full",
69
+ fireOnPeerConnected: false,
70
+ requiresApproval: true,
71
+ machineLocal: true,
72
+ };
73
+
74
+ /** A listener an operator added with addListener: on whatever interface they chose, so it may cross machines and never carries coordinator claims. */
75
+ export function operatorListenerAccept(policy: ListenerPolicy): AcceptOptions {
76
+ return {
77
+ policy,
78
+ fireOnPeerConnected: false,
79
+ requiresApproval: true,
80
+ machineLocal: false,
81
+ };
82
+ }