@rookdaemon/agora 0.2.8 → 0.2.9

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 (143) hide show
  1. package/dist/chunk-JUOGKXFN.js +1645 -0
  2. package/dist/chunk-JUOGKXFN.js.map +1 -0
  3. package/dist/cli.d.ts +0 -2
  4. package/dist/cli.js +1163 -1137
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.d.ts +1613 -30
  7. package/dist/index.js +1135 -29
  8. package/dist/index.js.map +1 -1
  9. package/package.json +4 -2
  10. package/dist/cli.d.ts.map +0 -1
  11. package/dist/config.d.ts +0 -59
  12. package/dist/config.d.ts.map +0 -1
  13. package/dist/config.js +0 -115
  14. package/dist/config.js.map +0 -1
  15. package/dist/discovery/bootstrap.d.ts +0 -32
  16. package/dist/discovery/bootstrap.d.ts.map +0 -1
  17. package/dist/discovery/bootstrap.js +0 -36
  18. package/dist/discovery/bootstrap.js.map +0 -1
  19. package/dist/discovery/peer-discovery.d.ts +0 -59
  20. package/dist/discovery/peer-discovery.d.ts.map +0 -1
  21. package/dist/discovery/peer-discovery.js +0 -108
  22. package/dist/discovery/peer-discovery.js.map +0 -1
  23. package/dist/identity/keypair.d.ts +0 -42
  24. package/dist/identity/keypair.d.ts.map +0 -1
  25. package/dist/identity/keypair.js +0 -83
  26. package/dist/identity/keypair.js.map +0 -1
  27. package/dist/index.d.ts.map +0 -1
  28. package/dist/message/envelope.d.ts +0 -59
  29. package/dist/message/envelope.d.ts.map +0 -1
  30. package/dist/message/envelope.js +0 -83
  31. package/dist/message/envelope.js.map +0 -1
  32. package/dist/message/types/paper-discovery.d.ts +0 -28
  33. package/dist/message/types/paper-discovery.d.ts.map +0 -1
  34. package/dist/message/types/paper-discovery.js +0 -2
  35. package/dist/message/types/paper-discovery.js.map +0 -1
  36. package/dist/message/types/peer-discovery.d.ts +0 -78
  37. package/dist/message/types/peer-discovery.d.ts.map +0 -1
  38. package/dist/message/types/peer-discovery.js +0 -90
  39. package/dist/message/types/peer-discovery.js.map +0 -1
  40. package/dist/peer/client.d.ts +0 -50
  41. package/dist/peer/client.d.ts.map +0 -1
  42. package/dist/peer/client.js +0 -138
  43. package/dist/peer/client.js.map +0 -1
  44. package/dist/peer/manager.d.ts +0 -65
  45. package/dist/peer/manager.d.ts.map +0 -1
  46. package/dist/peer/manager.js +0 -153
  47. package/dist/peer/manager.js.map +0 -1
  48. package/dist/peer/server.d.ts +0 -65
  49. package/dist/peer/server.d.ts.map +0 -1
  50. package/dist/peer/server.js +0 -154
  51. package/dist/peer/server.js.map +0 -1
  52. package/dist/registry/capability.d.ts +0 -44
  53. package/dist/registry/capability.d.ts.map +0 -1
  54. package/dist/registry/capability.js +0 -94
  55. package/dist/registry/capability.js.map +0 -1
  56. package/dist/registry/discovery-service.d.ts +0 -64
  57. package/dist/registry/discovery-service.d.ts.map +0 -1
  58. package/dist/registry/discovery-service.js +0 -129
  59. package/dist/registry/discovery-service.js.map +0 -1
  60. package/dist/registry/messages.d.ts +0 -105
  61. package/dist/registry/messages.d.ts.map +0 -1
  62. package/dist/registry/messages.js +0 -2
  63. package/dist/registry/messages.js.map +0 -1
  64. package/dist/registry/peer-store.d.ts +0 -57
  65. package/dist/registry/peer-store.d.ts.map +0 -1
  66. package/dist/registry/peer-store.js +0 -92
  67. package/dist/registry/peer-store.js.map +0 -1
  68. package/dist/registry/peer.d.ts +0 -20
  69. package/dist/registry/peer.d.ts.map +0 -1
  70. package/dist/registry/peer.js +0 -2
  71. package/dist/registry/peer.js.map +0 -1
  72. package/dist/relay/client.d.ts +0 -112
  73. package/dist/relay/client.d.ts.map +0 -1
  74. package/dist/relay/client.js +0 -281
  75. package/dist/relay/client.js.map +0 -1
  76. package/dist/relay/jwt-auth.d.ts +0 -40
  77. package/dist/relay/jwt-auth.d.ts.map +0 -1
  78. package/dist/relay/jwt-auth.js +0 -109
  79. package/dist/relay/jwt-auth.js.map +0 -1
  80. package/dist/relay/message-buffer.d.ts +0 -41
  81. package/dist/relay/message-buffer.d.ts.map +0 -1
  82. package/dist/relay/message-buffer.js +0 -53
  83. package/dist/relay/message-buffer.js.map +0 -1
  84. package/dist/relay/rest-api.d.ts +0 -68
  85. package/dist/relay/rest-api.d.ts.map +0 -1
  86. package/dist/relay/rest-api.js +0 -225
  87. package/dist/relay/rest-api.js.map +0 -1
  88. package/dist/relay/run-relay.d.ts +0 -33
  89. package/dist/relay/run-relay.d.ts.map +0 -1
  90. package/dist/relay/run-relay.js +0 -57
  91. package/dist/relay/run-relay.js.map +0 -1
  92. package/dist/relay/server.d.ts +0 -91
  93. package/dist/relay/server.d.ts.map +0 -1
  94. package/dist/relay/server.js +0 -385
  95. package/dist/relay/server.js.map +0 -1
  96. package/dist/relay/store.d.ts +0 -19
  97. package/dist/relay/store.d.ts.map +0 -1
  98. package/dist/relay/store.js +0 -55
  99. package/dist/relay/store.js.map +0 -1
  100. package/dist/relay/types.d.ts +0 -35
  101. package/dist/relay/types.d.ts.map +0 -1
  102. package/dist/relay/types.js +0 -2
  103. package/dist/relay/types.js.map +0 -1
  104. package/dist/reputation/commit-reveal.d.ts +0 -45
  105. package/dist/reputation/commit-reveal.d.ts.map +0 -1
  106. package/dist/reputation/commit-reveal.js +0 -125
  107. package/dist/reputation/commit-reveal.js.map +0 -1
  108. package/dist/reputation/scoring.d.ts +0 -31
  109. package/dist/reputation/scoring.d.ts.map +0 -1
  110. package/dist/reputation/scoring.js +0 -105
  111. package/dist/reputation/scoring.js.map +0 -1
  112. package/dist/reputation/store.d.ts +0 -83
  113. package/dist/reputation/store.d.ts.map +0 -1
  114. package/dist/reputation/store.js +0 -202
  115. package/dist/reputation/store.js.map +0 -1
  116. package/dist/reputation/types.d.ts +0 -150
  117. package/dist/reputation/types.d.ts.map +0 -1
  118. package/dist/reputation/types.js +0 -113
  119. package/dist/reputation/types.js.map +0 -1
  120. package/dist/reputation/verification.d.ts +0 -28
  121. package/dist/reputation/verification.d.ts.map +0 -1
  122. package/dist/reputation/verification.js +0 -91
  123. package/dist/reputation/verification.js.map +0 -1
  124. package/dist/service.d.ts +0 -90
  125. package/dist/service.d.ts.map +0 -1
  126. package/dist/service.js +0 -176
  127. package/dist/service.js.map +0 -1
  128. package/dist/transport/http.d.ts +0 -41
  129. package/dist/transport/http.d.ts.map +0 -1
  130. package/dist/transport/http.js +0 -103
  131. package/dist/transport/http.js.map +0 -1
  132. package/dist/transport/peer-config.d.ts +0 -38
  133. package/dist/transport/peer-config.d.ts.map +0 -1
  134. package/dist/transport/peer-config.js +0 -41
  135. package/dist/transport/peer-config.js.map +0 -1
  136. package/dist/transport/relay.d.ts +0 -30
  137. package/dist/transport/relay.d.ts.map +0 -1
  138. package/dist/transport/relay.js +0 -85
  139. package/dist/transport/relay.js.map +0 -1
  140. package/dist/utils.d.ts +0 -40
  141. package/dist/utils.d.ts.map +0 -1
  142. package/dist/utils.js +0 -59
  143. package/dist/utils.js.map +0 -1
@@ -0,0 +1,1645 @@
1
+ // src/identity/keypair.ts
2
+ import { sign, verify, generateKeyPairSync } from "crypto";
3
+ function generateKeyPair() {
4
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
5
+ return {
6
+ publicKey: publicKey.export({ type: "spki", format: "der" }).toString("hex"),
7
+ privateKey: privateKey.export({ type: "pkcs8", format: "der" }).toString("hex")
8
+ };
9
+ }
10
+ function signMessage(message, privateKeyHex) {
11
+ const messageBuffer = typeof message === "string" ? Buffer.from(message) : message;
12
+ const privateKey = Buffer.from(privateKeyHex, "hex");
13
+ const signature = sign(null, messageBuffer, {
14
+ key: privateKey,
15
+ format: "der",
16
+ type: "pkcs8"
17
+ });
18
+ return signature.toString("hex");
19
+ }
20
+ function verifySignature(message, signatureHex, publicKeyHex) {
21
+ const messageBuffer = typeof message === "string" ? Buffer.from(message) : message;
22
+ const signature = Buffer.from(signatureHex, "hex");
23
+ const publicKey = Buffer.from(publicKeyHex, "hex");
24
+ try {
25
+ return verify(null, messageBuffer, {
26
+ key: publicKey,
27
+ format: "der",
28
+ type: "spki"
29
+ }, signature);
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+ function exportKeyPair(keyPair) {
35
+ return {
36
+ publicKey: keyPair.publicKey,
37
+ privateKey: keyPair.privateKey
38
+ };
39
+ }
40
+ function importKeyPair(publicKeyHex, privateKeyHex) {
41
+ const hexPattern = /^[0-9a-f]+$/i;
42
+ if (!hexPattern.test(publicKeyHex)) {
43
+ throw new Error("Invalid public key: must be a hex string");
44
+ }
45
+ if (!hexPattern.test(privateKeyHex)) {
46
+ throw new Error("Invalid private key: must be a hex string");
47
+ }
48
+ return {
49
+ publicKey: publicKeyHex,
50
+ privateKey: privateKeyHex
51
+ };
52
+ }
53
+
54
+ // src/transport/peer-config.ts
55
+ import { readFileSync, writeFileSync, existsSync } from "fs";
56
+ function loadPeerConfig(path2) {
57
+ const content = readFileSync(path2, "utf-8");
58
+ return JSON.parse(content);
59
+ }
60
+ function savePeerConfig(path2, config) {
61
+ const content = JSON.stringify(config, null, 2);
62
+ writeFileSync(path2, content, "utf-8");
63
+ }
64
+ function initPeerConfig(path2) {
65
+ if (existsSync(path2)) {
66
+ return loadPeerConfig(path2);
67
+ }
68
+ const identity = generateKeyPair();
69
+ const config = {
70
+ identity,
71
+ peers: {}
72
+ };
73
+ savePeerConfig(path2, config);
74
+ return config;
75
+ }
76
+
77
+ // src/message/envelope.ts
78
+ import { createHash } from "crypto";
79
+ function stableStringify(value) {
80
+ if (value === null || value === void 0) return JSON.stringify(value);
81
+ if (typeof value !== "object") return JSON.stringify(value);
82
+ if (Array.isArray(value)) {
83
+ return "[" + value.map(stableStringify).join(",") + "]";
84
+ }
85
+ const keys = Object.keys(value).sort();
86
+ const pairs = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(value[k]));
87
+ return "{" + pairs.join(",") + "}";
88
+ }
89
+ function canonicalize(type, sender, timestamp, payload, inReplyTo) {
90
+ const obj = { payload, sender, timestamp, type };
91
+ if (inReplyTo !== void 0) {
92
+ obj.inReplyTo = inReplyTo;
93
+ }
94
+ return stableStringify(obj);
95
+ }
96
+ function computeId(canonical) {
97
+ return createHash("sha256").update(canonical).digest("hex");
98
+ }
99
+ function createEnvelope(type, sender, privateKey, payload, timestamp = Date.now(), inReplyTo) {
100
+ const canonical = canonicalize(type, sender, timestamp, payload, inReplyTo);
101
+ const id = computeId(canonical);
102
+ const signature = signMessage(canonical, privateKey);
103
+ return {
104
+ id,
105
+ type,
106
+ sender,
107
+ timestamp,
108
+ ...inReplyTo !== void 0 ? { inReplyTo } : {},
109
+ payload,
110
+ signature
111
+ };
112
+ }
113
+ function verifyEnvelope(envelope) {
114
+ const { id, type, sender, timestamp, payload, signature, inReplyTo } = envelope;
115
+ const canonical = canonicalize(type, sender, timestamp, payload, inReplyTo);
116
+ const expectedId = computeId(canonical);
117
+ if (id !== expectedId) {
118
+ return { valid: false, reason: "id_mismatch" };
119
+ }
120
+ const sigValid = verifySignature(canonical, signature, sender);
121
+ if (!sigValid) {
122
+ return { valid: false, reason: "signature_invalid" };
123
+ }
124
+ return { valid: true };
125
+ }
126
+
127
+ // src/transport/http.ts
128
+ async function sendToPeer(config, peerPublicKey, type, payload, inReplyTo) {
129
+ const peer = config.peers.get(peerPublicKey);
130
+ if (!peer) {
131
+ return { ok: false, status: 0, error: "Unknown peer" };
132
+ }
133
+ if (!peer.url) {
134
+ return { ok: false, status: 0, error: "No webhook URL configured" };
135
+ }
136
+ const envelope = createEnvelope(
137
+ type,
138
+ config.identity.publicKey,
139
+ config.identity.privateKey,
140
+ payload,
141
+ Date.now(),
142
+ inReplyTo
143
+ );
144
+ const envelopeJson = JSON.stringify(envelope);
145
+ const envelopeBase64 = Buffer.from(envelopeJson).toString("base64url");
146
+ const webhookPayload = {
147
+ message: `[AGORA_ENVELOPE]${envelopeBase64}`,
148
+ name: "Agora",
149
+ sessionKey: `agora:${envelope.sender.substring(0, 16)}`,
150
+ deliver: false
151
+ };
152
+ try {
153
+ const response = await fetch(`${peer.url}/agent`, {
154
+ method: "POST",
155
+ headers: {
156
+ "Authorization": `Bearer ${peer.token}`,
157
+ "Content-Type": "application/json"
158
+ },
159
+ body: JSON.stringify(webhookPayload)
160
+ });
161
+ return {
162
+ ok: response.ok,
163
+ status: response.status,
164
+ error: response.ok ? void 0 : await response.text()
165
+ };
166
+ } catch (err) {
167
+ return {
168
+ ok: false,
169
+ status: 0,
170
+ error: err instanceof Error ? err.message : String(err)
171
+ };
172
+ }
173
+ }
174
+ function decodeInboundEnvelope(message, knownPeers) {
175
+ const prefix = "[AGORA_ENVELOPE]";
176
+ if (!message.startsWith(prefix)) {
177
+ return { ok: false, reason: "not_agora_message" };
178
+ }
179
+ const base64Payload = message.substring(prefix.length);
180
+ if (!base64Payload) {
181
+ return { ok: false, reason: "invalid_base64" };
182
+ }
183
+ let envelopeJson;
184
+ try {
185
+ const decoded = Buffer.from(base64Payload, "base64url");
186
+ if (decoded.length === 0) {
187
+ return { ok: false, reason: "invalid_base64" };
188
+ }
189
+ envelopeJson = decoded.toString("utf-8");
190
+ } catch {
191
+ return { ok: false, reason: "invalid_base64" };
192
+ }
193
+ let envelope;
194
+ try {
195
+ envelope = JSON.parse(envelopeJson);
196
+ } catch {
197
+ return { ok: false, reason: "invalid_json" };
198
+ }
199
+ const verification = verifyEnvelope(envelope);
200
+ if (!verification.valid) {
201
+ return { ok: false, reason: verification.reason || "verification_failed" };
202
+ }
203
+ const senderKnown = knownPeers.has(envelope.sender);
204
+ if (!senderKnown) {
205
+ return { ok: false, reason: "unknown_sender" };
206
+ }
207
+ return { ok: true, envelope };
208
+ }
209
+
210
+ // src/transport/relay.ts
211
+ import WebSocket from "ws";
212
+ async function sendViaRelay(config, peerPublicKey, type, payload, inReplyTo) {
213
+ if (config.relayClient && config.relayClient.connected()) {
214
+ const envelope = createEnvelope(
215
+ type,
216
+ config.identity.publicKey,
217
+ config.identity.privateKey,
218
+ payload,
219
+ Date.now(),
220
+ inReplyTo
221
+ );
222
+ return config.relayClient.send(peerPublicKey, envelope);
223
+ }
224
+ return new Promise((resolve) => {
225
+ const ws = new WebSocket(config.relayUrl);
226
+ let registered = false;
227
+ let messageSent = false;
228
+ let resolved = false;
229
+ const resolveOnce = (result) => {
230
+ if (!resolved) {
231
+ resolved = true;
232
+ clearTimeout(timeout);
233
+ resolve(result);
234
+ }
235
+ };
236
+ const timeout = setTimeout(() => {
237
+ if (!messageSent) {
238
+ ws.close();
239
+ resolveOnce({ ok: false, error: "Relay connection timeout" });
240
+ }
241
+ }, 1e4);
242
+ ws.on("open", () => {
243
+ const registerMsg = {
244
+ type: "register",
245
+ publicKey: config.identity.publicKey
246
+ };
247
+ ws.send(JSON.stringify(registerMsg));
248
+ });
249
+ ws.on("message", (data) => {
250
+ try {
251
+ const msg = JSON.parse(data.toString());
252
+ if (msg.type === "registered" && !registered) {
253
+ registered = true;
254
+ const envelope = createEnvelope(
255
+ type,
256
+ config.identity.publicKey,
257
+ config.identity.privateKey,
258
+ payload,
259
+ Date.now(),
260
+ inReplyTo
261
+ );
262
+ const relayMsg = {
263
+ type: "message",
264
+ to: peerPublicKey,
265
+ envelope
266
+ };
267
+ ws.send(JSON.stringify(relayMsg));
268
+ messageSent = true;
269
+ setTimeout(() => {
270
+ ws.close();
271
+ resolveOnce({ ok: true });
272
+ }, 100);
273
+ } else if (msg.type === "error") {
274
+ ws.close();
275
+ resolveOnce({ ok: false, error: msg.message || "Relay server error" });
276
+ }
277
+ } catch (err) {
278
+ ws.close();
279
+ resolveOnce({ ok: false, error: err instanceof Error ? err.message : String(err) });
280
+ }
281
+ });
282
+ ws.on("error", (err) => {
283
+ ws.close();
284
+ resolveOnce({ ok: false, error: err.message });
285
+ });
286
+ ws.on("close", () => {
287
+ if (!messageSent) {
288
+ resolveOnce({ ok: false, error: "Connection closed before message sent" });
289
+ }
290
+ });
291
+ });
292
+ }
293
+
294
+ // src/relay/store.ts
295
+ import * as fs from "fs";
296
+ import * as path from "path";
297
+ var MessageStore = class {
298
+ storageDir;
299
+ constructor(storageDir) {
300
+ this.storageDir = storageDir;
301
+ fs.mkdirSync(storageDir, { recursive: true });
302
+ }
303
+ recipientDir(publicKey) {
304
+ const safe = publicKey.replace(/[^a-zA-Z0-9_-]/g, "_");
305
+ return path.join(this.storageDir, safe);
306
+ }
307
+ save(recipientKey, message) {
308
+ const dir = this.recipientDir(recipientKey);
309
+ fs.mkdirSync(dir, { recursive: true });
310
+ const filename = `${Date.now()}-${crypto.randomUUID()}.json`;
311
+ fs.writeFileSync(path.join(dir, filename), JSON.stringify(message));
312
+ }
313
+ load(recipientKey) {
314
+ const dir = this.recipientDir(recipientKey);
315
+ if (!fs.existsSync(dir)) return [];
316
+ const files = fs.readdirSync(dir).sort();
317
+ const messages = [];
318
+ for (const file of files) {
319
+ if (!file.endsWith(".json")) continue;
320
+ try {
321
+ const data = fs.readFileSync(path.join(dir, file), "utf8");
322
+ messages.push(JSON.parse(data));
323
+ } catch {
324
+ }
325
+ }
326
+ return messages;
327
+ }
328
+ clear(recipientKey) {
329
+ const dir = this.recipientDir(recipientKey);
330
+ if (!fs.existsSync(dir)) return;
331
+ const files = fs.readdirSync(dir);
332
+ for (const file of files) {
333
+ if (file.endsWith(".json")) {
334
+ fs.unlinkSync(path.join(dir, file));
335
+ }
336
+ }
337
+ }
338
+ };
339
+
340
+ // src/relay/server.ts
341
+ import { EventEmitter } from "events";
342
+ import { WebSocketServer, WebSocket as WebSocket2 } from "ws";
343
+ var RelayServer = class extends EventEmitter {
344
+ wss = null;
345
+ agents = /* @__PURE__ */ new Map();
346
+ identity;
347
+ storagePeers = [];
348
+ store = null;
349
+ constructor(options) {
350
+ super();
351
+ if (options) {
352
+ if ("identity" in options && options.identity) {
353
+ this.identity = options.identity;
354
+ } else if ("publicKey" in options && "privateKey" in options) {
355
+ this.identity = { publicKey: options.publicKey, privateKey: options.privateKey };
356
+ }
357
+ const opts = options;
358
+ if (opts.storagePeers?.length && opts.storageDir) {
359
+ this.storagePeers = opts.storagePeers;
360
+ this.store = new MessageStore(opts.storageDir);
361
+ }
362
+ }
363
+ }
364
+ /**
365
+ * Start the relay server
366
+ * @param port - Port to listen on
367
+ * @param host - Optional host (default: all interfaces)
368
+ */
369
+ start(port, host) {
370
+ return new Promise((resolve, reject) => {
371
+ try {
372
+ this.wss = new WebSocketServer({ port, host: host ?? "0.0.0.0" });
373
+ let resolved = false;
374
+ this.wss.on("error", (error) => {
375
+ this.emit("error", error);
376
+ if (!resolved) {
377
+ resolved = true;
378
+ reject(error);
379
+ }
380
+ });
381
+ this.wss.on("listening", () => {
382
+ if (!resolved) {
383
+ resolved = true;
384
+ resolve();
385
+ }
386
+ });
387
+ this.wss.on("connection", (socket) => {
388
+ this.handleConnection(socket);
389
+ });
390
+ } catch (error) {
391
+ reject(error);
392
+ }
393
+ });
394
+ }
395
+ /**
396
+ * Stop the relay server
397
+ */
398
+ async stop() {
399
+ return new Promise((resolve, reject) => {
400
+ if (!this.wss) {
401
+ resolve();
402
+ return;
403
+ }
404
+ for (const agent of this.agents.values()) {
405
+ agent.socket.close();
406
+ }
407
+ this.agents.clear();
408
+ this.wss.close((err) => {
409
+ if (err) {
410
+ reject(err);
411
+ } else {
412
+ this.wss = null;
413
+ resolve();
414
+ }
415
+ });
416
+ });
417
+ }
418
+ /**
419
+ * Get all connected agents
420
+ */
421
+ getAgents() {
422
+ return new Map(this.agents);
423
+ }
424
+ /**
425
+ * Handle incoming connection
426
+ */
427
+ handleConnection(socket) {
428
+ let agentPublicKey = null;
429
+ socket.on("message", (data) => {
430
+ try {
431
+ const msg = JSON.parse(data.toString());
432
+ if (msg.type === "register" && !agentPublicKey) {
433
+ if (!msg.publicKey || typeof msg.publicKey !== "string") {
434
+ this.sendError(socket, "Invalid registration: missing or invalid publicKey");
435
+ socket.close();
436
+ return;
437
+ }
438
+ const publicKey = msg.publicKey;
439
+ const name = msg.name;
440
+ agentPublicKey = publicKey;
441
+ const existing = this.agents.get(publicKey);
442
+ if (existing) {
443
+ existing.socket.close();
444
+ }
445
+ const agent = {
446
+ publicKey,
447
+ name,
448
+ socket,
449
+ lastSeen: Date.now()
450
+ };
451
+ this.agents.set(publicKey, agent);
452
+ this.emit("agent-registered", publicKey);
453
+ let peers = Array.from(this.agents.values()).filter((a) => a.publicKey !== publicKey).map((a) => ({ publicKey: a.publicKey, name: a.name }));
454
+ for (const storagePeer of this.storagePeers) {
455
+ if (storagePeer !== publicKey && !this.agents.has(storagePeer)) {
456
+ peers.push({ publicKey: storagePeer, name: void 0 });
457
+ }
458
+ }
459
+ socket.send(JSON.stringify({
460
+ type: "registered",
461
+ publicKey,
462
+ peers
463
+ }));
464
+ this.broadcastPeerEvent("peer_online", publicKey, name);
465
+ if (this.store && this.storagePeers.includes(publicKey)) {
466
+ const queued = this.store.load(publicKey);
467
+ for (const stored of queued) {
468
+ socket.send(JSON.stringify({
469
+ type: "message",
470
+ from: stored.from,
471
+ name: stored.name,
472
+ envelope: stored.envelope
473
+ }));
474
+ }
475
+ this.store.clear(publicKey);
476
+ }
477
+ return;
478
+ }
479
+ if (!agentPublicKey) {
480
+ this.sendError(socket, "Not registered: send registration message first");
481
+ socket.close();
482
+ return;
483
+ }
484
+ if (msg.type === "message") {
485
+ if (!msg.to || typeof msg.to !== "string") {
486
+ this.sendError(socket, 'Invalid message: missing or invalid "to" field');
487
+ return;
488
+ }
489
+ if (!msg.envelope || typeof msg.envelope !== "object") {
490
+ this.sendError(socket, 'Invalid message: missing or invalid "envelope" field');
491
+ return;
492
+ }
493
+ const envelope = msg.envelope;
494
+ const verification = verifyEnvelope(envelope);
495
+ if (!verification.valid) {
496
+ this.sendError(socket, `Invalid envelope: ${verification.reason || "verification failed"}`);
497
+ return;
498
+ }
499
+ if (envelope.sender !== agentPublicKey) {
500
+ this.sendError(socket, "Envelope sender does not match registered public key");
501
+ return;
502
+ }
503
+ const senderAgent = this.agents.get(agentPublicKey);
504
+ if (senderAgent) {
505
+ senderAgent.lastSeen = Date.now();
506
+ }
507
+ if (envelope.type === "peer_list_request" && this.identity && msg.to === this.identity.publicKey) {
508
+ this.handlePeerListRequest(envelope, socket, agentPublicKey);
509
+ return;
510
+ }
511
+ const recipient = this.agents.get(msg.to);
512
+ if (!recipient || recipient.socket.readyState !== WebSocket2.OPEN) {
513
+ if (this.store && this.storagePeers.includes(msg.to)) {
514
+ const senderAgent2 = this.agents.get(agentPublicKey);
515
+ this.store.save(msg.to, {
516
+ from: agentPublicKey,
517
+ name: senderAgent2?.name,
518
+ envelope
519
+ });
520
+ this.emit("message-relayed", agentPublicKey, msg.to, envelope);
521
+ } else {
522
+ this.sendError(socket, "Recipient not connected");
523
+ }
524
+ return;
525
+ }
526
+ try {
527
+ const senderAgent2 = this.agents.get(agentPublicKey);
528
+ const relayMessage = {
529
+ type: "message",
530
+ from: agentPublicKey,
531
+ name: senderAgent2?.name,
532
+ envelope
533
+ };
534
+ recipient.socket.send(JSON.stringify(relayMessage));
535
+ this.emit("message-relayed", agentPublicKey, msg.to, envelope);
536
+ } catch (err) {
537
+ this.sendError(socket, "Failed to relay message");
538
+ this.emit("error", err);
539
+ }
540
+ return;
541
+ }
542
+ if (msg.type === "broadcast") {
543
+ if (!msg.envelope || typeof msg.envelope !== "object") {
544
+ this.sendError(socket, 'Invalid broadcast: missing or invalid "envelope" field');
545
+ return;
546
+ }
547
+ const envelope = msg.envelope;
548
+ const verification = verifyEnvelope(envelope);
549
+ if (!verification.valid) {
550
+ this.sendError(socket, `Invalid envelope: ${verification.reason || "verification failed"}`);
551
+ return;
552
+ }
553
+ if (envelope.sender !== agentPublicKey) {
554
+ this.sendError(socket, "Envelope sender does not match registered public key");
555
+ return;
556
+ }
557
+ const senderAgentBroadcast = this.agents.get(agentPublicKey);
558
+ if (senderAgentBroadcast) {
559
+ senderAgentBroadcast.lastSeen = Date.now();
560
+ }
561
+ const senderAgent = this.agents.get(agentPublicKey);
562
+ const relayMessage = {
563
+ type: "message",
564
+ from: agentPublicKey,
565
+ name: senderAgent?.name,
566
+ envelope
567
+ };
568
+ const messageStr = JSON.stringify(relayMessage);
569
+ for (const agent of this.agents.values()) {
570
+ if (agent.publicKey !== agentPublicKey && agent.socket.readyState === WebSocket2.OPEN) {
571
+ try {
572
+ agent.socket.send(messageStr);
573
+ } catch (err) {
574
+ this.emit("error", err);
575
+ }
576
+ }
577
+ }
578
+ return;
579
+ }
580
+ if (msg.type === "ping") {
581
+ socket.send(JSON.stringify({ type: "pong" }));
582
+ return;
583
+ }
584
+ this.sendError(socket, `Unknown message type: ${msg.type}`);
585
+ } catch (err) {
586
+ this.emit("error", new Error(`Message parsing failed: ${err instanceof Error ? err.message : String(err)}`));
587
+ this.sendError(socket, "Invalid message format");
588
+ }
589
+ });
590
+ socket.on("close", () => {
591
+ if (agentPublicKey) {
592
+ const agent = this.agents.get(agentPublicKey);
593
+ const agentName = agent?.name;
594
+ this.agents.delete(agentPublicKey);
595
+ this.emit("agent-disconnected", agentPublicKey);
596
+ if (!this.storagePeers.includes(agentPublicKey)) {
597
+ this.broadcastPeerEvent("peer_offline", agentPublicKey, agentName);
598
+ }
599
+ }
600
+ });
601
+ socket.on("error", (error) => {
602
+ this.emit("error", error);
603
+ });
604
+ }
605
+ /**
606
+ * Send an error message to a client
607
+ */
608
+ sendError(socket, message) {
609
+ try {
610
+ if (socket.readyState === WebSocket2.OPEN) {
611
+ socket.send(JSON.stringify({ type: "error", message }));
612
+ }
613
+ } catch (err) {
614
+ this.emit("error", new Error(`Failed to send error message: ${err instanceof Error ? err.message : String(err)}`));
615
+ }
616
+ }
617
+ /**
618
+ * Broadcast a peer event to all connected agents
619
+ */
620
+ broadcastPeerEvent(eventType, publicKey, name) {
621
+ const message = {
622
+ type: eventType,
623
+ publicKey,
624
+ name
625
+ };
626
+ const messageStr = JSON.stringify(message);
627
+ for (const agent of this.agents.values()) {
628
+ if (agent.publicKey !== publicKey && agent.socket.readyState === WebSocket2.OPEN) {
629
+ try {
630
+ agent.socket.send(messageStr);
631
+ } catch (err) {
632
+ this.emit("error", new Error(`Failed to send ${eventType} event: ${err instanceof Error ? err.message : String(err)}`));
633
+ }
634
+ }
635
+ }
636
+ }
637
+ /**
638
+ * Handle peer list request from an agent
639
+ */
640
+ handlePeerListRequest(envelope, socket, requesterPublicKey) {
641
+ if (!this.identity) {
642
+ this.sendError(socket, "Relay does not support peer discovery (no identity configured)");
643
+ return;
644
+ }
645
+ const { filters } = envelope.payload;
646
+ const now = Date.now();
647
+ let peers = Array.from(this.agents.values());
648
+ peers = peers.filter((p) => p.publicKey !== requesterPublicKey);
649
+ if (filters?.activeWithin) {
650
+ peers = peers.filter((p) => now - p.lastSeen < filters.activeWithin);
651
+ }
652
+ if (filters?.limit && filters.limit > 0) {
653
+ peers = peers.slice(0, filters.limit);
654
+ }
655
+ const response = {
656
+ peers: peers.map((p) => ({
657
+ publicKey: p.publicKey,
658
+ metadata: p.name || p.metadata ? {
659
+ name: p.name,
660
+ version: p.metadata?.version,
661
+ capabilities: p.metadata?.capabilities
662
+ } : void 0,
663
+ lastSeen: p.lastSeen
664
+ })),
665
+ totalPeers: this.agents.size - 1,
666
+ // Exclude requester from count
667
+ relayPublicKey: this.identity.publicKey
668
+ };
669
+ const responseEnvelope = createEnvelope(
670
+ "peer_list_response",
671
+ this.identity.publicKey,
672
+ this.identity.privateKey,
673
+ response,
674
+ Date.now(),
675
+ envelope.id
676
+ // Reply to the request
677
+ );
678
+ const relayMessage = {
679
+ type: "message",
680
+ from: this.identity.publicKey,
681
+ name: "relay",
682
+ envelope: responseEnvelope
683
+ };
684
+ try {
685
+ socket.send(JSON.stringify(relayMessage));
686
+ } catch (err) {
687
+ this.emit("error", new Error(`Failed to send peer list response: ${err instanceof Error ? err.message : String(err)}`));
688
+ }
689
+ }
690
+ };
691
+
692
+ // src/relay/client.ts
693
+ import { EventEmitter as EventEmitter2 } from "events";
694
+ import WebSocket3 from "ws";
695
+ var RelayClient = class extends EventEmitter2 {
696
+ ws = null;
697
+ config;
698
+ reconnectAttempts = 0;
699
+ reconnectTimeout = null;
700
+ pingInterval = null;
701
+ isConnected = false;
702
+ isRegistered = false;
703
+ shouldReconnect = true;
704
+ onlinePeers = /* @__PURE__ */ new Map();
705
+ constructor(config) {
706
+ super();
707
+ this.config = {
708
+ pingInterval: 3e4,
709
+ maxReconnectDelay: 6e4,
710
+ ...config
711
+ };
712
+ }
713
+ /**
714
+ * Connect to the relay server
715
+ */
716
+ async connect() {
717
+ if (this.ws && (this.ws.readyState === WebSocket3.CONNECTING || this.ws.readyState === WebSocket3.OPEN)) {
718
+ return;
719
+ }
720
+ this.shouldReconnect = true;
721
+ return this.doConnect();
722
+ }
723
+ /**
724
+ * Disconnect from the relay server
725
+ */
726
+ disconnect() {
727
+ this.shouldReconnect = false;
728
+ this.cleanup();
729
+ if (this.ws) {
730
+ this.ws.close();
731
+ this.ws = null;
732
+ }
733
+ }
734
+ /**
735
+ * Check if currently connected and registered
736
+ */
737
+ connected() {
738
+ return this.isConnected && this.isRegistered;
739
+ }
740
+ /**
741
+ * Send a message to a specific peer
742
+ */
743
+ async send(to, envelope) {
744
+ if (!this.connected()) {
745
+ return { ok: false, error: "Not connected to relay" };
746
+ }
747
+ const message = {
748
+ type: "message",
749
+ to,
750
+ envelope
751
+ };
752
+ try {
753
+ this.ws.send(JSON.stringify(message));
754
+ return { ok: true };
755
+ } catch (err) {
756
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
757
+ }
758
+ }
759
+ /**
760
+ * Broadcast a message to all connected peers
761
+ */
762
+ async broadcast(envelope) {
763
+ if (!this.connected()) {
764
+ return { ok: false, error: "Not connected to relay" };
765
+ }
766
+ const message = {
767
+ type: "broadcast",
768
+ envelope
769
+ };
770
+ try {
771
+ this.ws.send(JSON.stringify(message));
772
+ return { ok: true };
773
+ } catch (err) {
774
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
775
+ }
776
+ }
777
+ /**
778
+ * Get list of currently online peers
779
+ */
780
+ getOnlinePeers() {
781
+ return Array.from(this.onlinePeers.values());
782
+ }
783
+ /**
784
+ * Check if a specific peer is online
785
+ */
786
+ isPeerOnline(publicKey) {
787
+ return this.onlinePeers.has(publicKey);
788
+ }
789
+ /**
790
+ * Internal: Perform connection
791
+ */
792
+ async doConnect() {
793
+ return new Promise((resolve, reject) => {
794
+ try {
795
+ this.ws = new WebSocket3(this.config.relayUrl);
796
+ let resolved = false;
797
+ const resolveOnce = (callback) => {
798
+ if (!resolved) {
799
+ resolved = true;
800
+ callback();
801
+ }
802
+ };
803
+ this.ws.on("open", () => {
804
+ this.isConnected = true;
805
+ this.reconnectAttempts = 0;
806
+ this.startPingInterval();
807
+ const registerMsg = {
808
+ type: "register",
809
+ publicKey: this.config.publicKey,
810
+ name: this.config.name
811
+ };
812
+ this.ws.send(JSON.stringify(registerMsg));
813
+ });
814
+ this.ws.on("message", (data) => {
815
+ try {
816
+ const msg = JSON.parse(data.toString());
817
+ this.handleMessage(msg);
818
+ if (msg.type === "registered" && !resolved) {
819
+ resolveOnce(() => resolve());
820
+ }
821
+ } catch (err) {
822
+ this.emit("error", new Error(`Failed to parse message: ${err instanceof Error ? err.message : String(err)}`));
823
+ }
824
+ });
825
+ this.ws.on("close", () => {
826
+ this.isConnected = false;
827
+ this.isRegistered = false;
828
+ this.cleanup();
829
+ this.emit("disconnected");
830
+ if (this.shouldReconnect) {
831
+ this.scheduleReconnect();
832
+ }
833
+ if (!resolved) {
834
+ resolveOnce(() => reject(new Error("Connection closed before registration")));
835
+ }
836
+ });
837
+ this.ws.on("error", (err) => {
838
+ this.emit("error", err);
839
+ if (!resolved) {
840
+ resolveOnce(() => reject(err));
841
+ }
842
+ });
843
+ } catch (err) {
844
+ reject(err);
845
+ }
846
+ });
847
+ }
848
+ /**
849
+ * Handle incoming message from relay
850
+ */
851
+ handleMessage(msg) {
852
+ switch (msg.type) {
853
+ case "registered":
854
+ this.isRegistered = true;
855
+ if (msg.peers) {
856
+ for (const peer of msg.peers) {
857
+ this.onlinePeers.set(peer.publicKey, peer);
858
+ }
859
+ }
860
+ this.emit("connected");
861
+ break;
862
+ case "message":
863
+ if (msg.envelope && msg.from) {
864
+ const verification = verifyEnvelope(msg.envelope);
865
+ if (!verification.valid) {
866
+ this.emit("error", new Error(`Invalid envelope signature: ${verification.reason}`));
867
+ return;
868
+ }
869
+ if (msg.envelope.sender !== msg.from) {
870
+ this.emit("error", new Error("Envelope sender does not match relay from field"));
871
+ return;
872
+ }
873
+ this.emit("message", msg.envelope, msg.from, msg.name);
874
+ }
875
+ break;
876
+ case "peer_online":
877
+ if (msg.publicKey) {
878
+ const peer = {
879
+ publicKey: msg.publicKey,
880
+ name: msg.name
881
+ };
882
+ this.onlinePeers.set(msg.publicKey, peer);
883
+ this.emit("peer_online", peer);
884
+ }
885
+ break;
886
+ case "peer_offline":
887
+ if (msg.publicKey) {
888
+ const peer = this.onlinePeers.get(msg.publicKey);
889
+ if (peer) {
890
+ this.onlinePeers.delete(msg.publicKey);
891
+ this.emit("peer_offline", peer);
892
+ }
893
+ }
894
+ break;
895
+ case "error":
896
+ this.emit("error", new Error(`Relay error: ${msg.message || "Unknown error"}`));
897
+ break;
898
+ case "pong":
899
+ break;
900
+ default:
901
+ break;
902
+ }
903
+ }
904
+ /**
905
+ * Schedule reconnection with exponential backoff
906
+ */
907
+ scheduleReconnect() {
908
+ if (this.reconnectTimeout) {
909
+ return;
910
+ }
911
+ const delay = Math.min(
912
+ 1e3 * Math.pow(2, this.reconnectAttempts),
913
+ this.config.maxReconnectDelay
914
+ );
915
+ this.reconnectAttempts++;
916
+ this.reconnectTimeout = setTimeout(() => {
917
+ this.reconnectTimeout = null;
918
+ if (this.shouldReconnect) {
919
+ this.doConnect().catch((err) => {
920
+ this.emit("error", err);
921
+ });
922
+ }
923
+ }, delay);
924
+ }
925
+ /**
926
+ * Start periodic ping messages
927
+ */
928
+ startPingInterval() {
929
+ this.stopPingInterval();
930
+ this.pingInterval = setInterval(() => {
931
+ if (this.ws && this.ws.readyState === WebSocket3.OPEN) {
932
+ const ping = { type: "ping" };
933
+ this.ws.send(JSON.stringify(ping));
934
+ }
935
+ }, this.config.pingInterval);
936
+ }
937
+ /**
938
+ * Stop ping interval
939
+ */
940
+ stopPingInterval() {
941
+ if (this.pingInterval) {
942
+ clearInterval(this.pingInterval);
943
+ this.pingInterval = null;
944
+ }
945
+ }
946
+ /**
947
+ * Cleanup resources
948
+ */
949
+ cleanup() {
950
+ this.stopPingInterval();
951
+ if (this.reconnectTimeout) {
952
+ clearTimeout(this.reconnectTimeout);
953
+ this.reconnectTimeout = null;
954
+ }
955
+ this.onlinePeers.clear();
956
+ }
957
+ };
958
+
959
+ // src/discovery/peer-discovery.ts
960
+ import { EventEmitter as EventEmitter3 } from "events";
961
+ var PeerDiscoveryService = class extends EventEmitter3 {
962
+ config;
963
+ constructor(config) {
964
+ super();
965
+ this.config = config;
966
+ this.config.relayClient.on("message", (envelope, from) => {
967
+ if (envelope.type === "peer_list_response") {
968
+ this.handlePeerList(envelope);
969
+ } else if (envelope.type === "peer_referral") {
970
+ this.handleReferral(envelope, from);
971
+ }
972
+ });
973
+ }
974
+ /**
975
+ * Request peer list from relay
976
+ */
977
+ async discoverViaRelay(filters) {
978
+ if (!this.config.relayPublicKey) {
979
+ throw new Error("Relay public key not configured");
980
+ }
981
+ if (!this.config.relayClient.connected()) {
982
+ throw new Error("Not connected to relay");
983
+ }
984
+ const payload = filters ? { filters } : {};
985
+ const envelope = createEnvelope(
986
+ "peer_list_request",
987
+ this.config.publicKey,
988
+ this.config.privateKey,
989
+ payload
990
+ );
991
+ const result = await this.config.relayClient.send(this.config.relayPublicKey, envelope);
992
+ if (!result.ok) {
993
+ throw new Error(`Failed to send peer list request: ${result.error}`);
994
+ }
995
+ return new Promise((resolve, reject) => {
996
+ const timeout = setTimeout(() => {
997
+ cleanup();
998
+ reject(new Error("Peer list request timed out"));
999
+ }, 1e4);
1000
+ const messageHandler = (responseEnvelope, from) => {
1001
+ if (responseEnvelope.type === "peer_list_response" && responseEnvelope.inReplyTo === envelope.id && from === this.config.relayPublicKey) {
1002
+ cleanup();
1003
+ resolve(responseEnvelope.payload);
1004
+ }
1005
+ };
1006
+ const cleanup = () => {
1007
+ clearTimeout(timeout);
1008
+ this.config.relayClient.off("message", messageHandler);
1009
+ };
1010
+ this.config.relayClient.on("message", messageHandler);
1011
+ });
1012
+ }
1013
+ /**
1014
+ * Send peer referral to another agent
1015
+ */
1016
+ async referPeer(recipientPublicKey, referredPublicKey, metadata) {
1017
+ if (!this.config.relayClient.connected()) {
1018
+ return { ok: false, error: "Not connected to relay" };
1019
+ }
1020
+ const payload = {
1021
+ publicKey: referredPublicKey,
1022
+ endpoint: metadata?.endpoint,
1023
+ metadata: metadata?.name ? { name: metadata.name } : void 0,
1024
+ comment: metadata?.comment,
1025
+ trustScore: metadata?.trustScore
1026
+ };
1027
+ const envelope = createEnvelope(
1028
+ "peer_referral",
1029
+ this.config.publicKey,
1030
+ this.config.privateKey,
1031
+ payload
1032
+ );
1033
+ return this.config.relayClient.send(recipientPublicKey, envelope);
1034
+ }
1035
+ /**
1036
+ * Handle incoming peer referral
1037
+ */
1038
+ handleReferral(envelope, from) {
1039
+ const verification = verifyEnvelope(envelope);
1040
+ if (!verification.valid) {
1041
+ this.emit("error", new Error(`Invalid peer referral: ${verification.reason}`));
1042
+ return;
1043
+ }
1044
+ this.emit("peer-referral", envelope.payload, from);
1045
+ }
1046
+ /**
1047
+ * Handle incoming peer list from relay
1048
+ */
1049
+ handlePeerList(envelope) {
1050
+ const verification = verifyEnvelope(envelope);
1051
+ if (!verification.valid) {
1052
+ this.emit("error", new Error(`Invalid peer list response: ${verification.reason}`));
1053
+ return;
1054
+ }
1055
+ if (envelope.sender !== this.config.relayPublicKey) {
1056
+ this.emit("error", new Error("Peer list response not from configured relay"));
1057
+ return;
1058
+ }
1059
+ this.emit("peers-discovered", envelope.payload.peers);
1060
+ }
1061
+ };
1062
+
1063
+ // src/discovery/bootstrap.ts
1064
+ var DEFAULT_BOOTSTRAP_RELAYS = [
1065
+ {
1066
+ url: "wss://agora-relay.lbsa71.net",
1067
+ name: "Primary Bootstrap Relay"
1068
+ // Note: Public key would need to be set when the relay is actually deployed
1069
+ // For now, this is a placeholder that would be configured when the relay is running
1070
+ }
1071
+ ];
1072
+ function getDefaultBootstrapRelay() {
1073
+ return {
1074
+ relayUrl: DEFAULT_BOOTSTRAP_RELAYS[0].url,
1075
+ timeout: 1e4
1076
+ };
1077
+ }
1078
+ function parseBootstrapRelay(url, publicKey) {
1079
+ return {
1080
+ relayUrl: url,
1081
+ relayPublicKey: publicKey,
1082
+ timeout: 1e4
1083
+ };
1084
+ }
1085
+
1086
+ // src/utils.ts
1087
+ function shortKey(publicKey) {
1088
+ return "..." + publicKey.slice(-8);
1089
+ }
1090
+ function resolveBroadcastName(config, cliName) {
1091
+ if (cliName) {
1092
+ return cliName;
1093
+ }
1094
+ if (config.relay) {
1095
+ if (typeof config.relay === "object" && config.relay.name) {
1096
+ return config.relay.name;
1097
+ }
1098
+ }
1099
+ if (config.identity.name) {
1100
+ return config.identity.name;
1101
+ }
1102
+ return void 0;
1103
+ }
1104
+ function formatDisplayName(name, publicKey) {
1105
+ const shortId = shortKey(publicKey);
1106
+ if (!name || name.trim() === "" || name.startsWith("...")) {
1107
+ return shortId;
1108
+ }
1109
+ return `${name} (${shortId})`;
1110
+ }
1111
+
1112
+ // src/reputation/types.ts
1113
+ function validateVerificationRecord(record) {
1114
+ const errors = [];
1115
+ if (typeof record !== "object" || record === null) {
1116
+ return { valid: false, errors: ["Record must be an object"] };
1117
+ }
1118
+ const r = record;
1119
+ if (typeof r.id !== "string" || r.id.length === 0) {
1120
+ errors.push("id must be a non-empty string");
1121
+ }
1122
+ if (typeof r.verifier !== "string" || r.verifier.length === 0) {
1123
+ errors.push("verifier must be a non-empty string");
1124
+ }
1125
+ if (typeof r.target !== "string" || r.target.length === 0) {
1126
+ errors.push("target must be a non-empty string");
1127
+ }
1128
+ if (typeof r.domain !== "string" || r.domain.length === 0) {
1129
+ errors.push("domain must be a non-empty string");
1130
+ }
1131
+ if (!["correct", "incorrect", "disputed"].includes(r.verdict)) {
1132
+ errors.push("verdict must be one of: correct, incorrect, disputed");
1133
+ }
1134
+ if (typeof r.confidence !== "number" || r.confidence < 0 || r.confidence > 1) {
1135
+ errors.push("confidence must be a number between 0 and 1");
1136
+ }
1137
+ if (r.evidence !== void 0 && typeof r.evidence !== "string") {
1138
+ errors.push("evidence must be a string if provided");
1139
+ }
1140
+ if (typeof r.timestamp !== "number" || r.timestamp <= 0) {
1141
+ errors.push("timestamp must be a positive number");
1142
+ }
1143
+ if (typeof r.signature !== "string" || r.signature.length === 0) {
1144
+ errors.push("signature must be a non-empty string");
1145
+ }
1146
+ return { valid: errors.length === 0, errors };
1147
+ }
1148
+ function validateCommitRecord(record) {
1149
+ const errors = [];
1150
+ if (typeof record !== "object" || record === null) {
1151
+ return { valid: false, errors: ["Record must be an object"] };
1152
+ }
1153
+ const r = record;
1154
+ if (typeof r.id !== "string" || r.id.length === 0) {
1155
+ errors.push("id must be a non-empty string");
1156
+ }
1157
+ if (typeof r.agent !== "string" || r.agent.length === 0) {
1158
+ errors.push("agent must be a non-empty string");
1159
+ }
1160
+ if (typeof r.domain !== "string" || r.domain.length === 0) {
1161
+ errors.push("domain must be a non-empty string");
1162
+ }
1163
+ if (typeof r.commitment !== "string" || r.commitment.length !== 64) {
1164
+ errors.push("commitment must be a 64-character hex string (SHA-256 hash)");
1165
+ }
1166
+ if (typeof r.timestamp !== "number" || r.timestamp <= 0) {
1167
+ errors.push("timestamp must be a positive number");
1168
+ }
1169
+ if (typeof r.expiry !== "number" || r.expiry <= 0) {
1170
+ errors.push("expiry must be a positive number");
1171
+ }
1172
+ if (typeof r.expiry === "number" && typeof r.timestamp === "number" && r.expiry <= r.timestamp) {
1173
+ errors.push("expiry must be after timestamp");
1174
+ }
1175
+ if (typeof r.signature !== "string" || r.signature.length === 0) {
1176
+ errors.push("signature must be a non-empty string");
1177
+ }
1178
+ return { valid: errors.length === 0, errors };
1179
+ }
1180
+ function validateRevealRecord(record) {
1181
+ const errors = [];
1182
+ if (typeof record !== "object" || record === null) {
1183
+ return { valid: false, errors: ["Record must be an object"] };
1184
+ }
1185
+ const r = record;
1186
+ if (typeof r.id !== "string" || r.id.length === 0) {
1187
+ errors.push("id must be a non-empty string");
1188
+ }
1189
+ if (typeof r.agent !== "string" || r.agent.length === 0) {
1190
+ errors.push("agent must be a non-empty string");
1191
+ }
1192
+ if (typeof r.commitmentId !== "string" || r.commitmentId.length === 0) {
1193
+ errors.push("commitmentId must be a non-empty string");
1194
+ }
1195
+ if (typeof r.prediction !== "string" || r.prediction.length === 0) {
1196
+ errors.push("prediction must be a non-empty string");
1197
+ }
1198
+ if (typeof r.outcome !== "string" || r.outcome.length === 0) {
1199
+ errors.push("outcome must be a non-empty string");
1200
+ }
1201
+ if (r.evidence !== void 0 && typeof r.evidence !== "string") {
1202
+ errors.push("evidence must be a string if provided");
1203
+ }
1204
+ if (typeof r.timestamp !== "number" || r.timestamp <= 0) {
1205
+ errors.push("timestamp must be a positive number");
1206
+ }
1207
+ if (typeof r.signature !== "string" || r.signature.length === 0) {
1208
+ errors.push("signature must be a non-empty string");
1209
+ }
1210
+ return { valid: errors.length === 0, errors };
1211
+ }
1212
+
1213
+ // src/reputation/store.ts
1214
+ import { promises as fs2 } from "fs";
1215
+ import { dirname } from "path";
1216
+ var ReputationStore = class {
1217
+ filePath;
1218
+ verifications = /* @__PURE__ */ new Map();
1219
+ commits = /* @__PURE__ */ new Map();
1220
+ reveals = /* @__PURE__ */ new Map();
1221
+ loaded = false;
1222
+ constructor(filePath) {
1223
+ this.filePath = filePath;
1224
+ }
1225
+ /**
1226
+ * Load records from JSONL file
1227
+ */
1228
+ async load() {
1229
+ try {
1230
+ const content = await fs2.readFile(this.filePath, "utf-8");
1231
+ const lines = content.trim().split("\n").filter((line) => line.length > 0);
1232
+ for (const line of lines) {
1233
+ try {
1234
+ const record = JSON.parse(line);
1235
+ switch (record.type) {
1236
+ case "verification": {
1237
+ const { type: _type, ...verification } = record;
1238
+ const validation = validateVerificationRecord(verification);
1239
+ if (validation.valid) {
1240
+ this.verifications.set(verification.id, verification);
1241
+ }
1242
+ break;
1243
+ }
1244
+ case "commit": {
1245
+ const { type: _type, ...commit } = record;
1246
+ const validation = validateCommitRecord(commit);
1247
+ if (validation.valid) {
1248
+ this.commits.set(commit.id, commit);
1249
+ }
1250
+ break;
1251
+ }
1252
+ case "reveal": {
1253
+ const { type: _type, ...reveal } = record;
1254
+ const validation = validateRevealRecord(reveal);
1255
+ if (validation.valid) {
1256
+ this.reveals.set(reveal.id, reveal);
1257
+ }
1258
+ break;
1259
+ }
1260
+ }
1261
+ } catch {
1262
+ continue;
1263
+ }
1264
+ }
1265
+ this.loaded = true;
1266
+ } catch (error) {
1267
+ if (error.code === "ENOENT") {
1268
+ this.loaded = true;
1269
+ return;
1270
+ }
1271
+ throw error;
1272
+ }
1273
+ }
1274
+ /**
1275
+ * Ensure the store is loaded
1276
+ */
1277
+ async ensureLoaded() {
1278
+ if (!this.loaded) {
1279
+ await this.load();
1280
+ }
1281
+ }
1282
+ /**
1283
+ * Append a record to the JSONL file
1284
+ */
1285
+ async appendToFile(record) {
1286
+ await fs2.mkdir(dirname(this.filePath), { recursive: true });
1287
+ const line = JSON.stringify(record) + "\n";
1288
+ await fs2.appendFile(this.filePath, line, "utf-8");
1289
+ }
1290
+ /**
1291
+ * Add a verification record
1292
+ */
1293
+ async addVerification(verification) {
1294
+ await this.ensureLoaded();
1295
+ const validation = validateVerificationRecord(verification);
1296
+ if (!validation.valid) {
1297
+ throw new Error(`Invalid verification: ${validation.errors.join(", ")}`);
1298
+ }
1299
+ this.verifications.set(verification.id, verification);
1300
+ await this.appendToFile({ type: "verification", ...verification });
1301
+ }
1302
+ /**
1303
+ * Add a commit record
1304
+ */
1305
+ async addCommit(commit) {
1306
+ await this.ensureLoaded();
1307
+ const validation = validateCommitRecord(commit);
1308
+ if (!validation.valid) {
1309
+ throw new Error(`Invalid commit: ${validation.errors.join(", ")}`);
1310
+ }
1311
+ this.commits.set(commit.id, commit);
1312
+ await this.appendToFile({ type: "commit", ...commit });
1313
+ }
1314
+ /**
1315
+ * Add a reveal record
1316
+ */
1317
+ async addReveal(reveal) {
1318
+ await this.ensureLoaded();
1319
+ const validation = validateRevealRecord(reveal);
1320
+ if (!validation.valid) {
1321
+ throw new Error(`Invalid reveal: ${validation.errors.join(", ")}`);
1322
+ }
1323
+ this.reveals.set(reveal.id, reveal);
1324
+ await this.appendToFile({ type: "reveal", ...reveal });
1325
+ }
1326
+ /**
1327
+ * Get all verifications
1328
+ */
1329
+ async getVerifications() {
1330
+ await this.ensureLoaded();
1331
+ return Array.from(this.verifications.values());
1332
+ }
1333
+ /**
1334
+ * Get verifications for a specific target
1335
+ */
1336
+ async getVerificationsByTarget(target) {
1337
+ await this.ensureLoaded();
1338
+ return Array.from(this.verifications.values()).filter((v) => v.target === target);
1339
+ }
1340
+ /**
1341
+ * Get verifications by domain
1342
+ */
1343
+ async getVerificationsByDomain(domain) {
1344
+ await this.ensureLoaded();
1345
+ return Array.from(this.verifications.values()).filter((v) => v.domain === domain);
1346
+ }
1347
+ /**
1348
+ * Get verifications for an agent (where they are the target of verification)
1349
+ * This requires looking up the target message to find the agent
1350
+ * For now, we'll return all verifications and let the caller filter
1351
+ */
1352
+ async getVerificationsByDomainForAgent(domain) {
1353
+ return this.getVerificationsByDomain(domain);
1354
+ }
1355
+ /**
1356
+ * Get all commits
1357
+ */
1358
+ async getCommits() {
1359
+ await this.ensureLoaded();
1360
+ return Array.from(this.commits.values());
1361
+ }
1362
+ /**
1363
+ * Get commit by ID
1364
+ */
1365
+ async getCommit(id) {
1366
+ await this.ensureLoaded();
1367
+ return this.commits.get(id) || null;
1368
+ }
1369
+ /**
1370
+ * Get commits by agent
1371
+ */
1372
+ async getCommitsByAgent(agent) {
1373
+ await this.ensureLoaded();
1374
+ return Array.from(this.commits.values()).filter((c) => c.agent === agent);
1375
+ }
1376
+ /**
1377
+ * Get all reveals
1378
+ */
1379
+ async getReveals() {
1380
+ await this.ensureLoaded();
1381
+ return Array.from(this.reveals.values());
1382
+ }
1383
+ /**
1384
+ * Get reveal by commitment ID
1385
+ */
1386
+ async getRevealByCommitment(commitmentId) {
1387
+ await this.ensureLoaded();
1388
+ return Array.from(this.reveals.values()).find((r) => r.commitmentId === commitmentId) || null;
1389
+ }
1390
+ /**
1391
+ * Get reveals by agent
1392
+ */
1393
+ async getRevealsByAgent(agent) {
1394
+ await this.ensureLoaded();
1395
+ return Array.from(this.reveals.values()).filter((r) => r.agent === agent);
1396
+ }
1397
+ };
1398
+
1399
+ // src/reputation/verification.ts
1400
+ function createVerification(verifier, privateKey, target, domain, verdict, confidence, timestamp, evidence) {
1401
+ if (confidence < 0 || confidence > 1) {
1402
+ throw new Error("confidence must be between 0 and 1");
1403
+ }
1404
+ const payload = {
1405
+ verifier,
1406
+ target,
1407
+ domain,
1408
+ verdict,
1409
+ confidence,
1410
+ timestamp
1411
+ };
1412
+ if (evidence !== void 0) {
1413
+ payload.evidence = evidence;
1414
+ }
1415
+ const envelope = createEnvelope("verification", verifier, privateKey, payload, timestamp);
1416
+ const record = {
1417
+ id: envelope.id,
1418
+ verifier,
1419
+ target,
1420
+ domain,
1421
+ verdict,
1422
+ confidence,
1423
+ timestamp,
1424
+ signature: envelope.signature
1425
+ };
1426
+ if (evidence !== void 0) {
1427
+ record.evidence = evidence;
1428
+ }
1429
+ return record;
1430
+ }
1431
+ function verifyVerificationSignature(record) {
1432
+ const structureValidation = validateVerificationRecord(record);
1433
+ if (!structureValidation.valid) {
1434
+ return {
1435
+ valid: false,
1436
+ reason: `Invalid structure: ${structureValidation.errors.join(", ")}`
1437
+ };
1438
+ }
1439
+ const payload = {
1440
+ verifier: record.verifier,
1441
+ target: record.target,
1442
+ domain: record.domain,
1443
+ verdict: record.verdict,
1444
+ confidence: record.confidence,
1445
+ timestamp: record.timestamp
1446
+ };
1447
+ if (record.evidence !== void 0) {
1448
+ payload.evidence = record.evidence;
1449
+ }
1450
+ const envelope = {
1451
+ id: record.id,
1452
+ type: "verification",
1453
+ sender: record.verifier,
1454
+ timestamp: record.timestamp,
1455
+ payload,
1456
+ signature: record.signature
1457
+ };
1458
+ return verifyEnvelope(envelope);
1459
+ }
1460
+
1461
+ // src/reputation/commit-reveal.ts
1462
+ import { createHash as createHash2 } from "crypto";
1463
+ function hashPrediction(prediction) {
1464
+ return createHash2("sha256").update(prediction).digest("hex");
1465
+ }
1466
+ function createCommit(agent, privateKey, domain, prediction, timestamp, expiryMs) {
1467
+ const commitment = hashPrediction(prediction);
1468
+ const expiry = timestamp + expiryMs;
1469
+ const payload = {
1470
+ agent,
1471
+ domain,
1472
+ commitment,
1473
+ timestamp,
1474
+ expiry
1475
+ };
1476
+ const envelope = createEnvelope("commit", agent, privateKey, payload, timestamp);
1477
+ return {
1478
+ id: envelope.id,
1479
+ agent,
1480
+ domain,
1481
+ commitment,
1482
+ timestamp,
1483
+ expiry,
1484
+ signature: envelope.signature
1485
+ };
1486
+ }
1487
+ function createReveal(agent, privateKey, commitmentId, prediction, outcome, timestamp, evidence) {
1488
+ const payload = {
1489
+ agent,
1490
+ commitmentId,
1491
+ prediction,
1492
+ outcome,
1493
+ timestamp
1494
+ };
1495
+ if (evidence !== void 0) {
1496
+ payload.evidence = evidence;
1497
+ }
1498
+ const envelope = createEnvelope("reveal", agent, privateKey, payload, timestamp);
1499
+ const record = {
1500
+ id: envelope.id,
1501
+ agent,
1502
+ commitmentId,
1503
+ prediction,
1504
+ outcome,
1505
+ timestamp,
1506
+ signature: envelope.signature
1507
+ };
1508
+ if (evidence !== void 0) {
1509
+ record.evidence = evidence;
1510
+ }
1511
+ return record;
1512
+ }
1513
+ function verifyReveal(commit, reveal) {
1514
+ const commitValidation = validateCommitRecord(commit);
1515
+ if (!commitValidation.valid) {
1516
+ return { valid: false, reason: `Invalid commit: ${commitValidation.errors.join(", ")}` };
1517
+ }
1518
+ const revealValidation = validateRevealRecord(reveal);
1519
+ if (!revealValidation.valid) {
1520
+ return { valid: false, reason: `Invalid reveal: ${revealValidation.errors.join(", ")}` };
1521
+ }
1522
+ if (reveal.commitmentId !== commit.id) {
1523
+ return { valid: false, reason: "Reveal does not reference this commit" };
1524
+ }
1525
+ if (reveal.agent !== commit.agent) {
1526
+ return { valid: false, reason: "Reveal agent does not match commit agent" };
1527
+ }
1528
+ if (reveal.timestamp < commit.expiry) {
1529
+ return { valid: false, reason: "Reveal timestamp is before commit expiry" };
1530
+ }
1531
+ const predictedHash = hashPrediction(reveal.prediction);
1532
+ if (predictedHash !== commit.commitment) {
1533
+ return { valid: false, reason: "Prediction hash does not match commitment" };
1534
+ }
1535
+ return { valid: true };
1536
+ }
1537
+
1538
+ // src/reputation/scoring.ts
1539
+ function decay(deltaTimeMs, lambda = Math.log(2) / 70) {
1540
+ const deltaDays = deltaTimeMs / (1e3 * 60 * 60 * 24);
1541
+ return Math.exp(-lambda * deltaDays);
1542
+ }
1543
+ function verdictWeight(verdict) {
1544
+ switch (verdict) {
1545
+ case "correct":
1546
+ return 1;
1547
+ case "incorrect":
1548
+ return -1;
1549
+ case "disputed":
1550
+ return 0;
1551
+ }
1552
+ }
1553
+ function computeTrustScore(agent, domain, verifications, currentTime) {
1554
+ const relevantVerifications = verifications.filter(
1555
+ (v) => v.target === agent && v.domain === domain
1556
+ );
1557
+ if (relevantVerifications.length === 0) {
1558
+ return {
1559
+ agent,
1560
+ domain,
1561
+ score: 0,
1562
+ verificationCount: 0,
1563
+ lastVerified: 0,
1564
+ topVerifiers: []
1565
+ };
1566
+ }
1567
+ let totalWeight = 0;
1568
+ const verifierWeights = /* @__PURE__ */ new Map();
1569
+ for (const verification of relevantVerifications) {
1570
+ const deltaTime = currentTime - verification.timestamp;
1571
+ const decayFactor = decay(deltaTime);
1572
+ const verdict = verdictWeight(verification.verdict);
1573
+ const weight = verdict * verification.confidence * decayFactor;
1574
+ totalWeight += weight;
1575
+ const currentVerifierWeight = verifierWeights.get(verification.verifier) || 0;
1576
+ verifierWeights.set(verification.verifier, currentVerifierWeight + Math.abs(weight));
1577
+ }
1578
+ const rawScore = totalWeight / Math.max(relevantVerifications.length, 1);
1579
+ const normalizedScore = Math.max(0, Math.min(1, (rawScore + 1) / 2));
1580
+ const lastVerified = Math.max(...relevantVerifications.map((v) => v.timestamp));
1581
+ const topVerifiers = Array.from(verifierWeights.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([verifier]) => verifier);
1582
+ return {
1583
+ agent,
1584
+ domain,
1585
+ score: normalizedScore,
1586
+ verificationCount: relevantVerifications.length,
1587
+ lastVerified,
1588
+ topVerifiers
1589
+ };
1590
+ }
1591
+ function computeTrustScores(agent, verifications, currentTime) {
1592
+ const domains = new Set(
1593
+ verifications.filter((v) => v.target === agent).map((v) => v.domain)
1594
+ );
1595
+ const scores = /* @__PURE__ */ new Map();
1596
+ for (const domain of domains) {
1597
+ const score = computeTrustScore(agent, domain, verifications, currentTime);
1598
+ scores.set(domain, score);
1599
+ }
1600
+ return scores;
1601
+ }
1602
+ var computeAllTrustScores = computeTrustScores;
1603
+
1604
+ export {
1605
+ generateKeyPair,
1606
+ signMessage,
1607
+ verifySignature,
1608
+ exportKeyPair,
1609
+ importKeyPair,
1610
+ loadPeerConfig,
1611
+ savePeerConfig,
1612
+ initPeerConfig,
1613
+ canonicalize,
1614
+ computeId,
1615
+ createEnvelope,
1616
+ verifyEnvelope,
1617
+ sendToPeer,
1618
+ decodeInboundEnvelope,
1619
+ sendViaRelay,
1620
+ MessageStore,
1621
+ RelayServer,
1622
+ RelayClient,
1623
+ PeerDiscoveryService,
1624
+ DEFAULT_BOOTSTRAP_RELAYS,
1625
+ getDefaultBootstrapRelay,
1626
+ parseBootstrapRelay,
1627
+ shortKey,
1628
+ resolveBroadcastName,
1629
+ formatDisplayName,
1630
+ validateVerificationRecord,
1631
+ validateCommitRecord,
1632
+ validateRevealRecord,
1633
+ ReputationStore,
1634
+ createVerification,
1635
+ verifyVerificationSignature,
1636
+ hashPrediction,
1637
+ createCommit,
1638
+ createReveal,
1639
+ verifyReveal,
1640
+ decay,
1641
+ computeTrustScore,
1642
+ computeTrustScores,
1643
+ computeAllTrustScores
1644
+ };
1645
+ //# sourceMappingURL=chunk-JUOGKXFN.js.map