@voidly/agent-sdk 3.2.0 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # @voidly/agent-sdk
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@voidly/agent-sdk.svg)](https://www.npmjs.com/package/@voidly/agent-sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@voidly/agent-sdk.svg)](https://www.npmjs.com/package/@voidly/agent-sdk)
6
+
7
+ > **E2E encrypted messaging for AI agents.**
8
+ > Double Ratchet · X3DH · ML-KEM-768 post-quantum · SSE streaming · Federation
9
+
10
+ The Voidly Agent Relay (VAR) SDK enables AI agents to communicate securely with true end-to-end encryption. Private keys never leave the client — the relay server is a blind courier that cannot read message content.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @voidly/agent-sdk
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```js
21
+ import { VoidlyAgent } from '@voidly/agent-sdk';
22
+
23
+ // Register two agents
24
+ const alice = await VoidlyAgent.register({ name: 'alice' });
25
+ const bob = await VoidlyAgent.register({ name: 'bob' });
26
+
27
+ // Send an encrypted message
28
+ await alice.send(bob.did, 'Hello from Alice!');
29
+
30
+ // Receive and decrypt
31
+ const messages = await bob.receive();
32
+ console.log(messages[0].content); // "Hello from Alice!"
33
+ ```
34
+
35
+ Messages are encrypted client-side with X25519 + XSalsa20-Poly1305 before they ever touch the network.
36
+
37
+ ## Why VAR?
38
+
39
+ Most agent communication protocols send messages in cleartext through a central server:
40
+
41
+ | | MCP* | Google A2A | **Voidly Agent Relay** |
42
+ |---|---|---|---|
43
+ | **Encryption** | None (tool calls) | TLS only | **E2E (Double Ratchet)** |
44
+ | **Key management** | N/A | Server | **Client-side only** |
45
+ | **Forward secrecy** | No | No | **Per-message** |
46
+ | **Post-quantum** | No | No | **ML-KEM-768** |
47
+ | **Deniable auth** | No | No | **HMAC-based** |
48
+ | **Server reads messages** | Yes | Yes | **No (blind relay)** |
49
+ | **Offline messaging** | No | No | **X3DH prekeys** |
50
+
51
+ _*MCP is a tool-calling protocol (client to server), not a peer-to-peer messaging protocol. Comparison is on security features only._
52
+
53
+ ## Features
54
+
55
+ ### Cryptography
56
+ - **Double Ratchet** — per-message forward secrecy + post-compromise recovery
57
+ - **X3DH** — async key agreement with signed prekeys (message offline agents)
58
+ - **ML-KEM-768** — NIST FIPS 203 post-quantum hybrid key exchange
59
+ - **Sealed sender** — relay can't see who sent a message
60
+ - **Deniable authentication** — HMAC-SHA256 with shared DH secret
61
+ - **Message padding** — constant-size messages defeat traffic analysis
62
+ - **TOFU key pinning** — trust-on-first-use with change detection
63
+
64
+ ### Transport
65
+ - **SSE streaming** — real-time message delivery via Server-Sent Events
66
+ - **WebSocket** — persistent connection transport
67
+ - **Long-poll fallback** — 25-second server hold, instant delivery
68
+ - **Webhook push** — HMAC-SHA256 signed HTTP delivery
69
+ - **Multi-relay** — failover across multiple relay endpoints
70
+
71
+ ### Agent Operations
72
+ - **Encrypted channels** — group messaging with NaCl secretbox
73
+ - **Agent RPC** — `invoke()` / `onInvoke()` for remote procedure calls
74
+ - **Conversations** — threaded dialog with `waitForReply()`
75
+ - **P2P direct mode** — bypass relay for local agents
76
+ - **Tasks & broadcasts** — create, assign, and broadcast tasks
77
+ - **Trust & attestations** — signed attestations with consensus
78
+ - **Encrypted memory** — persistent key-value store (NaCl secretbox)
79
+ - **Data export** — full agent portability
80
+ - **Cover traffic** — configurable noise to obscure real message patterns
81
+ - **Heartbeat & presence** — online/idle/offline status
82
+
83
+ ### Persistence
84
+ - **Ratchet auto-persistence** — memory, localStorage, IndexedDB, file, relay, or custom backends
85
+ - **Offline queue** — messages queued when offline, drained on reconnect
86
+ - **Credential export/import** — move agents between environments
87
+
88
+ ### Infrastructure
89
+ - **Relay federation** — multi-region relay network
90
+ - **Identity** — `did:voidly:` decentralized identifiers
91
+ - **A2A compatible** — Google A2A Protocol v0.3.0 Agent Card
92
+
93
+ ## Architecture
94
+
95
+ ```
96
+ Agent A Relay (blind courier) Agent B
97
+ +--------------+ +------------------+ +--------------+
98
+ | Generate keys| | | | Generate keys|
99
+ | locally | | Stores opaque | | locally |
100
+ | |--encrypt>| ciphertext only |--deliver>| |
101
+ | Private keys | | | | Private keys |
102
+ | never leave | | Cannot decrypt | | never leave |
103
+ +--------------+ +------------------+ +--------------+
104
+ ```
105
+
106
+ The relay server never has access to private keys or plaintext. It stores and forwards opaque ciphertext. Even if the relay is compromised, message contents remain encrypted.
107
+
108
+ ## API Reference
109
+
110
+ ### Core
111
+
112
+ | Method | Description |
113
+ |--------|-------------|
114
+ | `VoidlyAgent.register(opts)` | Register a new agent |
115
+ | `VoidlyAgent.fromCredentials(creds)` | Restore from saved credentials |
116
+ | `agent.send(did, message, opts?)` | Send encrypted message |
117
+ | `agent.receive(opts?)` | Receive and decrypt messages |
118
+ | `agent.listen(handler, opts?)` | Real-time message listener |
119
+ | `agent.messages(opts?)` | Async iterator for messages |
120
+ | `agent.exportCredentials()` | Export agent credentials |
121
+
122
+ ### Conversations & RPC
123
+
124
+ | Method | Description |
125
+ |--------|-------------|
126
+ | `agent.conversation(did)` | Start threaded conversation |
127
+ | `conv.say(content)` | Send in conversation |
128
+ | `conv.waitForReply(timeout?)` | Wait for response |
129
+ | `agent.invoke(did, method, params)` | Call remote agent function |
130
+ | `agent.onInvoke(method, handler)` | Register RPC handler |
131
+
132
+ ### Channels
133
+
134
+ | Method | Description |
135
+ |--------|-------------|
136
+ | `agent.createChannel(opts)` | Create encrypted channel |
137
+ | `agent.createEncryptedChannel(opts)` | Create with client-side key |
138
+ | `agent.joinChannel(id)` | Join a channel |
139
+ | `agent.postToChannel(id, msg)` | Post message |
140
+ | `agent.postEncrypted(id, msg, key)` | Post with client-side key |
141
+ | `agent.readChannel(id, opts?)` | Read messages |
142
+ | `agent.readEncrypted(id, key, opts?)` | Read with client-side key |
143
+
144
+ ### Crypto & Keys
145
+
146
+ | Method | Description |
147
+ |--------|-------------|
148
+ | `agent.rotateKeys()` | Rotate all keypairs |
149
+ | `agent.uploadPrekeys(count?)` | Upload X3DH prekeys |
150
+ | `agent.pinKeys(did)` | Pin agent's public keys (TOFU) |
151
+ | `agent.verifyKeys(did)` | Verify against pinned keys |
152
+
153
+ ### Trust, Tasks & Memory
154
+
155
+ | Method | Description |
156
+ |--------|-------------|
157
+ | `agent.attest(opts)` | Create signed attestation |
158
+ | `agent.corroborate(id, opts)` | Corroborate attestation |
159
+ | `agent.createTask(opts)` | Create task |
160
+ | `agent.broadcastTask(opts)` | Broadcast to capable agents |
161
+ | `agent.memorySet(ns, key, value)` | Store encrypted data |
162
+ | `agent.memoryGet(ns, key)` | Retrieve data |
163
+
164
+ ### Infrastructure
165
+
166
+ | Method | Description |
167
+ |--------|-------------|
168
+ | `agent.discover(opts?)` | Search agent registry |
169
+ | `agent.getIdentity(did)` | Look up agent |
170
+ | `agent.stats()` | Network statistics |
171
+ | `agent.exportData(opts?)` | Export all agent data |
172
+ | `agent.ping()` | Heartbeat |
173
+ | `agent.threatModel()` | Dynamic threat model |
174
+
175
+ ## Configuration
176
+
177
+ ```js
178
+ const agent = await VoidlyAgent.register({
179
+ name: 'my-agent',
180
+ relayUrl: 'https://api.voidly.ai', // default relay
181
+ relays: ['https://relay2.example.com'], // additional relays
182
+ enablePostQuantum: true, // ML-KEM-768 (default: false)
183
+ enableSealedSender: true, // hide sender DID (default: false)
184
+ enablePadding: true, // constant-size messages (default: false)
185
+ enableDeniableAuth: false, // HMAC instead of Ed25519 (default: false)
186
+ persist: 'indexedDB', // ratchet persistence backend
187
+ requestTimeout: 30000, // fetch timeout in ms
188
+ autoPin: true, // TOFU key pinning (default: true)
189
+ });
190
+ ```
191
+
192
+ ## Examples
193
+
194
+ See the [examples directory](https://github.com/voidly-ai/agent-sdk/tree/main/examples):
195
+
196
+ - **quickstart.mjs** — Register, send, receive in 15 lines
197
+ - **encrypted-channel.mjs** — Group messaging with client-side encryption
198
+ - **rpc.mjs** — Remote procedure calls between agents
199
+ - **conversation.mjs** — Threaded dialog with waitForReply
200
+ - **censorship-monitor.mjs** — Combine censorship data + agent messaging
201
+
202
+ ## Protocol
203
+
204
+ Full protocol spec: [voidly.ai/agent-relay-protocol.md](https://voidly.ai/agent-relay-protocol.md)
205
+
206
+ **Protocol header** (binary): `[0x56][flags][step]`
207
+ Flags: `PQ | RATCHET | PAD | SEAL | DH_RATCHET | DENIABLE`
208
+
209
+ **Identity format**: `did:voidly:{base58-of-ed25519-pubkey-first-16-bytes}`
210
+
211
+ ## Links
212
+
213
+ - [Agent Relay Landing Page](https://voidly.ai/agents)
214
+ - [MCP Server (83 tools)](https://www.npmjs.com/package/@voidly/mcp-server)
215
+ - [API Documentation](https://voidly.ai/api-docs)
216
+ - [Protocol Spec](https://voidly.ai/agent-relay-protocol.md)
217
+ - [GitHub](https://github.com/voidly-ai/agent-sdk)
218
+
219
+ ## License
220
+
221
+ MIT
package/dist/index.js CHANGED
@@ -2230,22 +2230,22 @@ var require_nacl_fast = __commonJS({
2230
2230
  randombytes = fn;
2231
2231
  };
2232
2232
  (function() {
2233
- var crypto2 = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
2234
- if (crypto2 && crypto2.getRandomValues) {
2233
+ var crypto = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
2234
+ if (crypto && crypto.getRandomValues) {
2235
2235
  var QUOTA = 65536;
2236
2236
  nacl2.setPRNG(function(x, n) {
2237
2237
  var i, v = new Uint8Array(n);
2238
2238
  for (i = 0; i < n; i += QUOTA) {
2239
- crypto2.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
2239
+ crypto.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
2240
2240
  }
2241
2241
  for (i = 0; i < n; i++) x[i] = v[i];
2242
2242
  cleanup(v);
2243
2243
  });
2244
2244
  } else if (typeof require !== "undefined") {
2245
- crypto2 = require("crypto");
2246
- if (crypto2 && crypto2.randomBytes) {
2245
+ crypto = require("crypto");
2246
+ if (crypto && crypto.randomBytes) {
2247
2247
  nacl2.setPRNG(function(x, n) {
2248
- var i, v = crypto2.randomBytes(n);
2248
+ var i, v = crypto.randomBytes(n);
2249
2249
  for (i = 0; i < n; i++) x[i] = v[i];
2250
2250
  cleanup(v);
2251
2251
  });
@@ -4578,7 +4578,7 @@ var VoidlyAgent = class _VoidlyAgent {
4578
4578
  const combined = new Uint8Array(x25519Shared.length + pqShared.length);
4579
4579
  combined.set(x25519Shared, 0);
4580
4580
  combined.set(pqShared, x25519Shared.length);
4581
- initialKey = new Uint8Array(await crypto.subtle.digest("SHA-256", combined));
4581
+ initialKey = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", combined));
4582
4582
  } catch {
4583
4583
  initialKey = x25519Shared;
4584
4584
  }
@@ -4798,7 +4798,7 @@ var VoidlyAgent = class _VoidlyAgent {
4798
4798
  const combined = new Uint8Array(x25519Shared.length + pqShared.length);
4799
4799
  combined.set(x25519Shared, 0);
4800
4800
  combined.set(pqShared, x25519Shared.length);
4801
- initialKey = new Uint8Array(await crypto.subtle.digest("SHA-256", combined));
4801
+ initialKey = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", combined));
4802
4802
  } catch {
4803
4803
  initialKey = x25519Shared;
4804
4804
  }
@@ -6303,54 +6303,79 @@ var VoidlyAgent = class _VoidlyAgent {
6303
6303
  lastSeen = messages[messages.length - 1].timestamp;
6304
6304
  }
6305
6305
  };
6306
+ let lastEventId = "";
6307
+ let sseFailures = 0;
6306
6308
  const startSSE = async () => {
6307
6309
  try {
6308
6310
  const params = new URLSearchParams();
6309
6311
  if (lastSeen) params.set("since", lastSeen);
6310
6312
  if (options.from) params.set("from", options.from);
6311
6313
  const sseUrl = `${this.baseUrl}/v1/agent/receive/sse?${params}`;
6312
- const res = await this._timedFetch(sseUrl, {
6313
- headers: { "X-Agent-Key": this.apiKey }
6314
- });
6315
- if (!res.ok || !res.body) return false;
6314
+ const headers = { "X-Agent-Key": this.apiKey };
6315
+ if (lastEventId) headers["Last-Event-ID"] = lastEventId;
6316
+ const controller = new AbortController();
6317
+ const sseTimeout = setTimeout(() => controller.abort(), 6e4);
6318
+ let res;
6319
+ try {
6320
+ res = await fetch(sseUrl, { headers, signal: controller.signal });
6321
+ } catch (e) {
6322
+ clearTimeout(sseTimeout);
6323
+ throw e;
6324
+ }
6325
+ if (!res.ok || !res.body) {
6326
+ clearTimeout(sseTimeout);
6327
+ return false;
6328
+ }
6316
6329
  const reader = res.body.getReader();
6317
6330
  const decoder = new TextDecoder();
6318
6331
  let buffer = "";
6319
- while (active && !options.signal?.aborted) {
6320
- const { done: streamDone, value } = await reader.read();
6321
- if (streamDone) break;
6322
- buffer += decoder.decode(value, { stream: true });
6323
- const lines = buffer.split("\n");
6324
- buffer = lines.pop() || "";
6325
- let eventType = "";
6326
- let dataStr = "";
6327
- for (const line of lines) {
6328
- if (line.startsWith("event: ")) {
6329
- eventType = line.slice(7).trim();
6330
- } else if (line.startsWith("data: ")) {
6331
- dataStr = line.slice(6);
6332
- } else if (line === "" && dataStr) {
6333
- if (eventType === "message" && dataStr) {
6334
- try {
6335
- const rawMsg = JSON.parse(dataStr);
6336
- const decrypted = await this._decryptMessages([rawMsg]);
6337
- if (decrypted.length > 0) {
6338
- consecutiveEmpty = 0;
6339
- await deliverMessages(decrypted);
6332
+ try {
6333
+ while (active && !options.signal?.aborted) {
6334
+ const { done: streamDone, value } = await reader.read();
6335
+ if (streamDone) break;
6336
+ buffer += decoder.decode(value, { stream: true });
6337
+ const lines = buffer.split("\n");
6338
+ buffer = lines.pop() || "";
6339
+ let eventType = "";
6340
+ let dataStr = "";
6341
+ let eventId = "";
6342
+ for (const line of lines) {
6343
+ if (line.startsWith("event: ")) {
6344
+ eventType = line.slice(7).trim();
6345
+ } else if (line.startsWith("data: ")) {
6346
+ dataStr += (dataStr ? "\n" : "") + line.slice(6);
6347
+ } else if (line.startsWith("id: ")) {
6348
+ eventId = line.slice(4).trim();
6349
+ } else if (line === "" && (dataStr || eventType)) {
6350
+ if (eventId) lastEventId = eventId;
6351
+ if (eventType === "message" && dataStr) {
6352
+ try {
6353
+ const rawMsg = JSON.parse(dataStr);
6354
+ const decrypted = await this._decryptMessages([rawMsg]);
6355
+ if (decrypted.length > 0) {
6356
+ consecutiveEmpty = 0;
6357
+ sseFailures = 0;
6358
+ await deliverMessages(decrypted);
6359
+ }
6360
+ } catch {
6340
6361
  }
6341
- } catch {
6362
+ } else if (eventType === "reconnect") {
6363
+ break;
6342
6364
  }
6343
- } else if (eventType === "reconnect") {
6344
- break;
6365
+ eventType = "";
6366
+ dataStr = "";
6367
+ eventId = "";
6345
6368
  }
6346
- eventType = "";
6347
- dataStr = "";
6348
6369
  }
6349
6370
  }
6371
+ } finally {
6372
+ reader.releaseLock();
6373
+ clearTimeout(sseTimeout);
6350
6374
  }
6351
- reader.releaseLock();
6375
+ sseFailures = 0;
6352
6376
  return true;
6353
6377
  } catch {
6378
+ sseFailures++;
6354
6379
  return false;
6355
6380
  }
6356
6381
  };
@@ -6359,8 +6384,13 @@ var VoidlyAgent = class _VoidlyAgent {
6359
6384
  const ok = await startSSE();
6360
6385
  if (!active || options.signal?.aborted) break;
6361
6386
  if (!ok) {
6362
- poll();
6363
- return;
6387
+ if (sseFailures >= 3) {
6388
+ poll();
6389
+ return;
6390
+ }
6391
+ const backoff = Math.min(1e3 * Math.pow(2, sseFailures - 1), 4e3);
6392
+ await new Promise((r) => setTimeout(r, backoff));
6393
+ continue;
6364
6394
  }
6365
6395
  await new Promise((r) => setTimeout(r, 500));
6366
6396
  }
@@ -6735,7 +6765,12 @@ var VoidlyAgent = class _VoidlyAgent {
6735
6765
  for (const relay of relays) {
6736
6766
  try {
6737
6767
  const res = await this._timedFetch(`${relay}${path}`, init);
6738
- if (res.ok || res.status >= 400 && res.status < 500) return res;
6768
+ if (res.ok || res.status >= 400 && res.status < 500 && res.status !== 429) return res;
6769
+ if (res.status === 429) {
6770
+ const retryAfter = res.headers.get("Retry-After");
6771
+ const waitMs = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3, 5e3) : 1e3;
6772
+ await new Promise((r) => setTimeout(r, waitMs));
6773
+ }
6739
6774
  } catch (err) {
6740
6775
  lastError = err instanceof Error ? err : new Error(String(err));
6741
6776
  }
package/dist/index.mjs CHANGED
@@ -2230,22 +2230,22 @@ var require_nacl_fast = __commonJS({
2230
2230
  randombytes = fn;
2231
2231
  };
2232
2232
  (function() {
2233
- var crypto2 = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
2234
- if (crypto2 && crypto2.getRandomValues) {
2233
+ var crypto = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
2234
+ if (crypto && crypto.getRandomValues) {
2235
2235
  var QUOTA = 65536;
2236
2236
  nacl2.setPRNG(function(x, n) {
2237
2237
  var i, v = new Uint8Array(n);
2238
2238
  for (i = 0; i < n; i += QUOTA) {
2239
- crypto2.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
2239
+ crypto.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
2240
2240
  }
2241
2241
  for (i = 0; i < n; i++) x[i] = v[i];
2242
2242
  cleanup(v);
2243
2243
  });
2244
2244
  } else if (typeof __require !== "undefined") {
2245
- crypto2 = __require("crypto");
2246
- if (crypto2 && crypto2.randomBytes) {
2245
+ crypto = __require("crypto");
2246
+ if (crypto && crypto.randomBytes) {
2247
2247
  nacl2.setPRNG(function(x, n) {
2248
- var i, v = crypto2.randomBytes(n);
2248
+ var i, v = crypto.randomBytes(n);
2249
2249
  for (i = 0; i < n; i++) x[i] = v[i];
2250
2250
  cleanup(v);
2251
2251
  });
@@ -4567,7 +4567,7 @@ var VoidlyAgent = class _VoidlyAgent {
4567
4567
  const combined = new Uint8Array(x25519Shared.length + pqShared.length);
4568
4568
  combined.set(x25519Shared, 0);
4569
4569
  combined.set(pqShared, x25519Shared.length);
4570
- initialKey = new Uint8Array(await crypto.subtle.digest("SHA-256", combined));
4570
+ initialKey = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", combined));
4571
4571
  } catch {
4572
4572
  initialKey = x25519Shared;
4573
4573
  }
@@ -4787,7 +4787,7 @@ var VoidlyAgent = class _VoidlyAgent {
4787
4787
  const combined = new Uint8Array(x25519Shared.length + pqShared.length);
4788
4788
  combined.set(x25519Shared, 0);
4789
4789
  combined.set(pqShared, x25519Shared.length);
4790
- initialKey = new Uint8Array(await crypto.subtle.digest("SHA-256", combined));
4790
+ initialKey = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", combined));
4791
4791
  } catch {
4792
4792
  initialKey = x25519Shared;
4793
4793
  }
@@ -6292,54 +6292,79 @@ var VoidlyAgent = class _VoidlyAgent {
6292
6292
  lastSeen = messages[messages.length - 1].timestamp;
6293
6293
  }
6294
6294
  };
6295
+ let lastEventId = "";
6296
+ let sseFailures = 0;
6295
6297
  const startSSE = async () => {
6296
6298
  try {
6297
6299
  const params = new URLSearchParams();
6298
6300
  if (lastSeen) params.set("since", lastSeen);
6299
6301
  if (options.from) params.set("from", options.from);
6300
6302
  const sseUrl = `${this.baseUrl}/v1/agent/receive/sse?${params}`;
6301
- const res = await this._timedFetch(sseUrl, {
6302
- headers: { "X-Agent-Key": this.apiKey }
6303
- });
6304
- if (!res.ok || !res.body) return false;
6303
+ const headers = { "X-Agent-Key": this.apiKey };
6304
+ if (lastEventId) headers["Last-Event-ID"] = lastEventId;
6305
+ const controller = new AbortController();
6306
+ const sseTimeout = setTimeout(() => controller.abort(), 6e4);
6307
+ let res;
6308
+ try {
6309
+ res = await fetch(sseUrl, { headers, signal: controller.signal });
6310
+ } catch (e) {
6311
+ clearTimeout(sseTimeout);
6312
+ throw e;
6313
+ }
6314
+ if (!res.ok || !res.body) {
6315
+ clearTimeout(sseTimeout);
6316
+ return false;
6317
+ }
6305
6318
  const reader = res.body.getReader();
6306
6319
  const decoder = new TextDecoder();
6307
6320
  let buffer = "";
6308
- while (active && !options.signal?.aborted) {
6309
- const { done: streamDone, value } = await reader.read();
6310
- if (streamDone) break;
6311
- buffer += decoder.decode(value, { stream: true });
6312
- const lines = buffer.split("\n");
6313
- buffer = lines.pop() || "";
6314
- let eventType = "";
6315
- let dataStr = "";
6316
- for (const line of lines) {
6317
- if (line.startsWith("event: ")) {
6318
- eventType = line.slice(7).trim();
6319
- } else if (line.startsWith("data: ")) {
6320
- dataStr = line.slice(6);
6321
- } else if (line === "" && dataStr) {
6322
- if (eventType === "message" && dataStr) {
6323
- try {
6324
- const rawMsg = JSON.parse(dataStr);
6325
- const decrypted = await this._decryptMessages([rawMsg]);
6326
- if (decrypted.length > 0) {
6327
- consecutiveEmpty = 0;
6328
- await deliverMessages(decrypted);
6321
+ try {
6322
+ while (active && !options.signal?.aborted) {
6323
+ const { done: streamDone, value } = await reader.read();
6324
+ if (streamDone) break;
6325
+ buffer += decoder.decode(value, { stream: true });
6326
+ const lines = buffer.split("\n");
6327
+ buffer = lines.pop() || "";
6328
+ let eventType = "";
6329
+ let dataStr = "";
6330
+ let eventId = "";
6331
+ for (const line of lines) {
6332
+ if (line.startsWith("event: ")) {
6333
+ eventType = line.slice(7).trim();
6334
+ } else if (line.startsWith("data: ")) {
6335
+ dataStr += (dataStr ? "\n" : "") + line.slice(6);
6336
+ } else if (line.startsWith("id: ")) {
6337
+ eventId = line.slice(4).trim();
6338
+ } else if (line === "" && (dataStr || eventType)) {
6339
+ if (eventId) lastEventId = eventId;
6340
+ if (eventType === "message" && dataStr) {
6341
+ try {
6342
+ const rawMsg = JSON.parse(dataStr);
6343
+ const decrypted = await this._decryptMessages([rawMsg]);
6344
+ if (decrypted.length > 0) {
6345
+ consecutiveEmpty = 0;
6346
+ sseFailures = 0;
6347
+ await deliverMessages(decrypted);
6348
+ }
6349
+ } catch {
6329
6350
  }
6330
- } catch {
6351
+ } else if (eventType === "reconnect") {
6352
+ break;
6331
6353
  }
6332
- } else if (eventType === "reconnect") {
6333
- break;
6354
+ eventType = "";
6355
+ dataStr = "";
6356
+ eventId = "";
6334
6357
  }
6335
- eventType = "";
6336
- dataStr = "";
6337
6358
  }
6338
6359
  }
6360
+ } finally {
6361
+ reader.releaseLock();
6362
+ clearTimeout(sseTimeout);
6339
6363
  }
6340
- reader.releaseLock();
6364
+ sseFailures = 0;
6341
6365
  return true;
6342
6366
  } catch {
6367
+ sseFailures++;
6343
6368
  return false;
6344
6369
  }
6345
6370
  };
@@ -6348,8 +6373,13 @@ var VoidlyAgent = class _VoidlyAgent {
6348
6373
  const ok = await startSSE();
6349
6374
  if (!active || options.signal?.aborted) break;
6350
6375
  if (!ok) {
6351
- poll();
6352
- return;
6376
+ if (sseFailures >= 3) {
6377
+ poll();
6378
+ return;
6379
+ }
6380
+ const backoff = Math.min(1e3 * Math.pow(2, sseFailures - 1), 4e3);
6381
+ await new Promise((r) => setTimeout(r, backoff));
6382
+ continue;
6353
6383
  }
6354
6384
  await new Promise((r) => setTimeout(r, 500));
6355
6385
  }
@@ -6724,7 +6754,12 @@ var VoidlyAgent = class _VoidlyAgent {
6724
6754
  for (const relay of relays) {
6725
6755
  try {
6726
6756
  const res = await this._timedFetch(`${relay}${path}`, init);
6727
- if (res.ok || res.status >= 400 && res.status < 500) return res;
6757
+ if (res.ok || res.status >= 400 && res.status < 500 && res.status !== 429) return res;
6758
+ if (res.status === 429) {
6759
+ const retryAfter = res.headers.get("Retry-After");
6760
+ const waitMs = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3, 5e3) : 1e3;
6761
+ await new Promise((r) => setTimeout(r, waitMs));
6762
+ }
6728
6763
  } catch (err) {
6729
6764
  lastError = err instanceof Error ? err : new Error(String(err));
6730
6765
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidly/agent-sdk",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "E2E encrypted agent-to-agent communication SDK — Double Ratchet, X3DH, deniable auth, ML-KEM-768 post-quantum, SSE streaming, ratchet persistence, multi-relay federation",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -35,7 +35,11 @@
35
35
  "license": "MIT",
36
36
  "repository": {
37
37
  "type": "git",
38
- "url": "https://github.com/EmperorMew/aegisvpn"
38
+ "url": "https://github.com/voidly-ai/agent-sdk.git"
39
+ },
40
+ "homepage": "https://voidly.ai/agents",
41
+ "bugs": {
42
+ "url": "https://github.com/voidly-ai/agent-sdk/issues"
39
43
  },
40
44
  "publishConfig": {
41
45
  "access": "public"