@voidly/agent-sdk 3.2.4 → 3.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -191,13 +191,19 @@ const agent = await VoidlyAgent.register({
191
191
 
192
192
  ## Examples
193
193
 
194
- See the [examples directory](https://github.com/voidly-ai/agent-sdk/tree/main/examples):
194
+ ```bash
195
+ node examples/quickstart.mjs
196
+ ```
197
+
198
+ | Example | What it shows |
199
+ |---------|---------------|
200
+ | [quickstart.mjs](examples/quickstart.mjs) | Register, send, receive in 15 lines |
201
+ | [encrypted-channel.mjs](examples/encrypted-channel.mjs) | Group messaging with client-side encryption |
202
+ | [rpc.mjs](examples/rpc.mjs) | Remote procedure calls between agents |
203
+ | [conversation.mjs](examples/conversation.mjs) | Threaded dialog with waitForReply |
204
+ | [censorship-monitor.mjs](examples/censorship-monitor.mjs) | Real-world: censorship data + encrypted alerts |
195
205
 
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
206
+ All examples are self-contained and run against the public relay. No API key needed.
201
207
 
202
208
  ## Protocol
203
209
 
package/dist/index.js CHANGED
@@ -4362,14 +4362,16 @@ var VoidlyAgent = class _VoidlyAgent {
4362
4362
  method: "PUT",
4363
4363
  headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
4364
4364
  body: JSON.stringify({ value: blob })
4365
- }).catch(() => {
4365
+ }).catch((e) => {
4366
+ console.warn(`[voidly] \u26A0 Relay ratchet persist failed: ${e instanceof Error ? e.message : e}`);
4366
4367
  });
4367
4368
  break;
4368
4369
  case "custom":
4369
4370
  if (this._onPersist) await this._onPersist(blob);
4370
4371
  break;
4371
4372
  }
4372
- } catch {
4373
+ } catch (e) {
4374
+ console.warn(`[voidly] \u26A0 Ratchet state persistence failed (${this._persistMode}): ${e instanceof Error ? e.message : e}`);
4373
4375
  }
4374
4376
  }
4375
4377
  /** Load persisted ratchet state and restore into memory */
@@ -4404,14 +4406,16 @@ var VoidlyAgent = class _VoidlyAgent {
4404
4406
  const data = await res.json();
4405
4407
  blob = data.value;
4406
4408
  }
4407
- } catch {
4409
+ } catch (e) {
4410
+ console.warn(`[voidly] \u26A0 Relay ratchet load failed: ${e instanceof Error ? e.message : e}`);
4408
4411
  }
4409
4412
  break;
4410
4413
  case "custom":
4411
4414
  if (this._onLoad) blob = await this._onLoad();
4412
4415
  break;
4413
4416
  }
4414
- } catch {
4417
+ } catch (e) {
4418
+ console.warn(`[voidly] \u26A0 Ratchet state load failed (${this._persistMode}): ${e instanceof Error ? e.message : e}`);
4415
4419
  return;
4416
4420
  }
4417
4421
  if (!blob) return;
@@ -4423,6 +4427,12 @@ var VoidlyAgent = class _VoidlyAgent {
4423
4427
  const states = JSON.parse((0, import_tweetnacl_util.encodeUTF8)(decrypted));
4424
4428
  for (const [pairId, rs] of Object.entries(states)) {
4425
4429
  if (this._ratchetStates.has(pairId)) continue;
4430
+ if (!rs || typeof rs.sendStep !== "number" || typeof rs.recvStep !== "number" || typeof rs.sendChainKey !== "string" || typeof rs.recvChainKey !== "string") {
4431
+ continue;
4432
+ }
4433
+ if (rs.sendStep < 0 || rs.recvStep < 0 || rs.sendStep > 4294967295 || rs.recvStep > 4294967295) {
4434
+ continue;
4435
+ }
4426
4436
  const state = {
4427
4437
  sendChainKey: (0, import_tweetnacl_util.decodeBase64)(rs.sendChainKey),
4428
4438
  sendStep: rs.sendStep,
@@ -4430,20 +4440,21 @@ var VoidlyAgent = class _VoidlyAgent {
4430
4440
  recvStep: rs.recvStep,
4431
4441
  skippedKeys: /* @__PURE__ */ new Map()
4432
4442
  };
4433
- if (rs.rootKey) state.rootKey = (0, import_tweetnacl_util.decodeBase64)(rs.rootKey);
4434
- if (rs.dhSendSecretKey && rs.dhSendPublicKey) {
4443
+ if (rs.rootKey && typeof rs.rootKey === "string") state.rootKey = (0, import_tweetnacl_util.decodeBase64)(rs.rootKey);
4444
+ if (typeof rs.dhSendSecretKey === "string" && typeof rs.dhSendPublicKey === "string") {
4435
4445
  state.dhSendKeyPair = {
4436
4446
  secretKey: (0, import_tweetnacl_util.decodeBase64)(rs.dhSendSecretKey),
4437
4447
  publicKey: (0, import_tweetnacl_util.decodeBase64)(rs.dhSendPublicKey)
4438
4448
  };
4439
4449
  }
4440
- if (rs.dhRecvPubKey) state.dhRecvPubKey = (0, import_tweetnacl_util.decodeBase64)(rs.dhRecvPubKey);
4441
- if (rs.prevSendStep !== void 0) state.prevSendStep = rs.prevSendStep;
4450
+ if (typeof rs.dhRecvPubKey === "string") state.dhRecvPubKey = (0, import_tweetnacl_util.decodeBase64)(rs.dhRecvPubKey);
4451
+ if (typeof rs.prevSendStep === "number" && rs.prevSendStep >= 0) state.prevSendStep = rs.prevSendStep;
4442
4452
  if (state.sendChainKey.length === 32 && state.recvChainKey.length === 32) {
4443
4453
  this._ratchetStates.set(pairId, state);
4444
4454
  }
4445
4455
  }
4446
- } catch {
4456
+ } catch (e) {
4457
+ console.warn(`[voidly] \u26A0 Ratchet state restore failed (corrupt data, starting fresh): ${e instanceof Error ? e.message : e}`);
4447
4458
  }
4448
4459
  }
4449
4460
  /** IndexedDB put helper (browser only) */
@@ -4768,16 +4779,16 @@ var VoidlyAgent = class _VoidlyAgent {
4768
4779
  if (msg.envelope) {
4769
4780
  try {
4770
4781
  const env = JSON.parse(msg.envelope);
4771
- if (typeof env.ratchet_step === "number") {
4782
+ if (typeof env.ratchet_step === "number" && Number.isInteger(env.ratchet_step) && env.ratchet_step >= 0 && env.ratchet_step <= 4294967295) {
4772
4783
  envelopeRatchetStep = env.ratchet_step;
4773
4784
  }
4774
- if (typeof env.pq_ciphertext === "string") {
4785
+ if (typeof env.pq_ciphertext === "string" && env.pq_ciphertext.length <= 65536) {
4775
4786
  envelopePqCiphertext = env.pq_ciphertext;
4776
4787
  }
4777
- if (typeof env.dh_ratchet_key === "string") {
4788
+ if (typeof env.dh_ratchet_key === "string" && env.dh_ratchet_key.length <= 256) {
4778
4789
  envelopeDhRatchetKey = env.dh_ratchet_key;
4779
4790
  }
4780
- if (typeof env.pn === "number") {
4791
+ if (typeof env.pn === "number" && Number.isInteger(env.pn) && env.pn >= 0 && env.pn <= 4294967295) {
4781
4792
  envelopePn = env.pn;
4782
4793
  }
4783
4794
  } catch {
@@ -4842,9 +4853,10 @@ var VoidlyAgent = class _VoidlyAgent {
4842
4853
  if (!state.dhSkippedKeys) state.dhSkippedKeys = /* @__PURE__ */ new Map();
4843
4854
  state.dhSkippedKeys.set(skipKey, skippedMk);
4844
4855
  ck = nextChainKey;
4845
- if (state.dhSkippedKeys.size > MAX_SKIP) {
4856
+ while (state.dhSkippedKeys.size > MAX_SKIP) {
4846
4857
  const oldest = state.dhSkippedKeys.keys().next().value;
4847
4858
  if (oldest !== void 0) state.dhSkippedKeys.delete(oldest);
4859
+ else break;
4848
4860
  }
4849
4861
  }
4850
4862
  }
@@ -4877,16 +4889,18 @@ var VoidlyAgent = class _VoidlyAgent {
4877
4889
  } else if (targetStep > state.recvStep) {
4878
4890
  const skip = targetStep - state.recvStep;
4879
4891
  if (skip > MAX_SKIP) {
4880
- rawPlaintext = import_tweetnacl.default.box.open(ciphertext, nonce, senderEncPub, this.encryptionKeyPair.secretKey);
4892
+ this._decryptFailCount++;
4893
+ continue;
4881
4894
  } else {
4882
4895
  let ck = state.recvChainKey;
4883
4896
  for (let i = state.recvStep + 1; i < targetStep; i++) {
4884
4897
  const { nextChainKey: nextChainKey2, messageKey: skippedMk } = await ratchetStep(ck);
4885
4898
  state.skippedKeys.set(i, skippedMk);
4886
4899
  ck = nextChainKey2;
4887
- if (state.skippedKeys.size > MAX_SKIP) {
4900
+ while (state.skippedKeys.size > MAX_SKIP) {
4888
4901
  const oldest = state.skippedKeys.keys().next().value;
4889
4902
  if (oldest !== void 0) state.skippedKeys.delete(oldest);
4903
+ else break;
4890
4904
  }
4891
4905
  }
4892
4906
  const { nextChainKey, messageKey } = await ratchetStep(ck);
@@ -4973,8 +4987,11 @@ var VoidlyAgent = class _VoidlyAgent {
4973
4987
  }
4974
4988
  this._seenMessageIds.add(msg.id);
4975
4989
  if (this._seenMessageIds.size > 1e4) {
4976
- const first = this._seenMessageIds.values().next().value;
4977
- if (first !== void 0) this._seenMessageIds.delete(first);
4990
+ const iter = this._seenMessageIds.values();
4991
+ for (let i = 0; i < 1e3; i++) {
4992
+ const v = iter.next().value;
4993
+ if (v !== void 0) this._seenMessageIds.delete(v);
4994
+ }
4978
4995
  }
4979
4996
  decrypted.push({
4980
4997
  id: msg.id,
@@ -5055,8 +5072,12 @@ var VoidlyAgent = class _VoidlyAgent {
5055
5072
  const profile = await res.json();
5056
5073
  this._identityCache.set(did, { profile, cachedAt: Date.now() });
5057
5074
  if (this._identityCache.size > 500) {
5058
- const oldest = this._identityCache.keys().next().value;
5059
- if (oldest !== void 0) this._identityCache.delete(oldest);
5075
+ const excess = this._identityCache.size - 400;
5076
+ const iter = this._identityCache.keys();
5077
+ for (let i = 0; i < excess; i++) {
5078
+ const key = iter.next().value;
5079
+ if (key !== void 0) this._identityCache.delete(key);
5080
+ }
5060
5081
  }
5061
5082
  return profile;
5062
5083
  }
package/dist/index.mjs CHANGED
@@ -4352,14 +4352,16 @@ var VoidlyAgent = class _VoidlyAgent {
4352
4352
  method: "PUT",
4353
4353
  headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
4354
4354
  body: JSON.stringify({ value: blob })
4355
- }).catch(() => {
4355
+ }).catch((e) => {
4356
+ console.warn(`[voidly] \u26A0 Relay ratchet persist failed: ${e instanceof Error ? e.message : e}`);
4356
4357
  });
4357
4358
  break;
4358
4359
  case "custom":
4359
4360
  if (this._onPersist) await this._onPersist(blob);
4360
4361
  break;
4361
4362
  }
4362
- } catch {
4363
+ } catch (e) {
4364
+ console.warn(`[voidly] \u26A0 Ratchet state persistence failed (${this._persistMode}): ${e instanceof Error ? e.message : e}`);
4363
4365
  }
4364
4366
  }
4365
4367
  /** Load persisted ratchet state and restore into memory */
@@ -4394,14 +4396,16 @@ var VoidlyAgent = class _VoidlyAgent {
4394
4396
  const data = await res.json();
4395
4397
  blob = data.value;
4396
4398
  }
4397
- } catch {
4399
+ } catch (e) {
4400
+ console.warn(`[voidly] \u26A0 Relay ratchet load failed: ${e instanceof Error ? e.message : e}`);
4398
4401
  }
4399
4402
  break;
4400
4403
  case "custom":
4401
4404
  if (this._onLoad) blob = await this._onLoad();
4402
4405
  break;
4403
4406
  }
4404
- } catch {
4407
+ } catch (e) {
4408
+ console.warn(`[voidly] \u26A0 Ratchet state load failed (${this._persistMode}): ${e instanceof Error ? e.message : e}`);
4405
4409
  return;
4406
4410
  }
4407
4411
  if (!blob) return;
@@ -4413,6 +4417,12 @@ var VoidlyAgent = class _VoidlyAgent {
4413
4417
  const states = JSON.parse((0, import_tweetnacl_util.encodeUTF8)(decrypted));
4414
4418
  for (const [pairId, rs] of Object.entries(states)) {
4415
4419
  if (this._ratchetStates.has(pairId)) continue;
4420
+ if (!rs || typeof rs.sendStep !== "number" || typeof rs.recvStep !== "number" || typeof rs.sendChainKey !== "string" || typeof rs.recvChainKey !== "string") {
4421
+ continue;
4422
+ }
4423
+ if (rs.sendStep < 0 || rs.recvStep < 0 || rs.sendStep > 4294967295 || rs.recvStep > 4294967295) {
4424
+ continue;
4425
+ }
4416
4426
  const state = {
4417
4427
  sendChainKey: (0, import_tweetnacl_util.decodeBase64)(rs.sendChainKey),
4418
4428
  sendStep: rs.sendStep,
@@ -4420,20 +4430,21 @@ var VoidlyAgent = class _VoidlyAgent {
4420
4430
  recvStep: rs.recvStep,
4421
4431
  skippedKeys: /* @__PURE__ */ new Map()
4422
4432
  };
4423
- if (rs.rootKey) state.rootKey = (0, import_tweetnacl_util.decodeBase64)(rs.rootKey);
4424
- if (rs.dhSendSecretKey && rs.dhSendPublicKey) {
4433
+ if (rs.rootKey && typeof rs.rootKey === "string") state.rootKey = (0, import_tweetnacl_util.decodeBase64)(rs.rootKey);
4434
+ if (typeof rs.dhSendSecretKey === "string" && typeof rs.dhSendPublicKey === "string") {
4425
4435
  state.dhSendKeyPair = {
4426
4436
  secretKey: (0, import_tweetnacl_util.decodeBase64)(rs.dhSendSecretKey),
4427
4437
  publicKey: (0, import_tweetnacl_util.decodeBase64)(rs.dhSendPublicKey)
4428
4438
  };
4429
4439
  }
4430
- if (rs.dhRecvPubKey) state.dhRecvPubKey = (0, import_tweetnacl_util.decodeBase64)(rs.dhRecvPubKey);
4431
- if (rs.prevSendStep !== void 0) state.prevSendStep = rs.prevSendStep;
4440
+ if (typeof rs.dhRecvPubKey === "string") state.dhRecvPubKey = (0, import_tweetnacl_util.decodeBase64)(rs.dhRecvPubKey);
4441
+ if (typeof rs.prevSendStep === "number" && rs.prevSendStep >= 0) state.prevSendStep = rs.prevSendStep;
4432
4442
  if (state.sendChainKey.length === 32 && state.recvChainKey.length === 32) {
4433
4443
  this._ratchetStates.set(pairId, state);
4434
4444
  }
4435
4445
  }
4436
- } catch {
4446
+ } catch (e) {
4447
+ console.warn(`[voidly] \u26A0 Ratchet state restore failed (corrupt data, starting fresh): ${e instanceof Error ? e.message : e}`);
4437
4448
  }
4438
4449
  }
4439
4450
  /** IndexedDB put helper (browser only) */
@@ -4758,16 +4769,16 @@ var VoidlyAgent = class _VoidlyAgent {
4758
4769
  if (msg.envelope) {
4759
4770
  try {
4760
4771
  const env = JSON.parse(msg.envelope);
4761
- if (typeof env.ratchet_step === "number") {
4772
+ if (typeof env.ratchet_step === "number" && Number.isInteger(env.ratchet_step) && env.ratchet_step >= 0 && env.ratchet_step <= 4294967295) {
4762
4773
  envelopeRatchetStep = env.ratchet_step;
4763
4774
  }
4764
- if (typeof env.pq_ciphertext === "string") {
4775
+ if (typeof env.pq_ciphertext === "string" && env.pq_ciphertext.length <= 65536) {
4765
4776
  envelopePqCiphertext = env.pq_ciphertext;
4766
4777
  }
4767
- if (typeof env.dh_ratchet_key === "string") {
4778
+ if (typeof env.dh_ratchet_key === "string" && env.dh_ratchet_key.length <= 256) {
4768
4779
  envelopeDhRatchetKey = env.dh_ratchet_key;
4769
4780
  }
4770
- if (typeof env.pn === "number") {
4781
+ if (typeof env.pn === "number" && Number.isInteger(env.pn) && env.pn >= 0 && env.pn <= 4294967295) {
4771
4782
  envelopePn = env.pn;
4772
4783
  }
4773
4784
  } catch {
@@ -4832,9 +4843,10 @@ var VoidlyAgent = class _VoidlyAgent {
4832
4843
  if (!state.dhSkippedKeys) state.dhSkippedKeys = /* @__PURE__ */ new Map();
4833
4844
  state.dhSkippedKeys.set(skipKey, skippedMk);
4834
4845
  ck = nextChainKey;
4835
- if (state.dhSkippedKeys.size > MAX_SKIP) {
4846
+ while (state.dhSkippedKeys.size > MAX_SKIP) {
4836
4847
  const oldest = state.dhSkippedKeys.keys().next().value;
4837
4848
  if (oldest !== void 0) state.dhSkippedKeys.delete(oldest);
4849
+ else break;
4838
4850
  }
4839
4851
  }
4840
4852
  }
@@ -4867,16 +4879,18 @@ var VoidlyAgent = class _VoidlyAgent {
4867
4879
  } else if (targetStep > state.recvStep) {
4868
4880
  const skip = targetStep - state.recvStep;
4869
4881
  if (skip > MAX_SKIP) {
4870
- rawPlaintext = import_tweetnacl.default.box.open(ciphertext, nonce, senderEncPub, this.encryptionKeyPair.secretKey);
4882
+ this._decryptFailCount++;
4883
+ continue;
4871
4884
  } else {
4872
4885
  let ck = state.recvChainKey;
4873
4886
  for (let i = state.recvStep + 1; i < targetStep; i++) {
4874
4887
  const { nextChainKey: nextChainKey2, messageKey: skippedMk } = await ratchetStep(ck);
4875
4888
  state.skippedKeys.set(i, skippedMk);
4876
4889
  ck = nextChainKey2;
4877
- if (state.skippedKeys.size > MAX_SKIP) {
4890
+ while (state.skippedKeys.size > MAX_SKIP) {
4878
4891
  const oldest = state.skippedKeys.keys().next().value;
4879
4892
  if (oldest !== void 0) state.skippedKeys.delete(oldest);
4893
+ else break;
4880
4894
  }
4881
4895
  }
4882
4896
  const { nextChainKey, messageKey } = await ratchetStep(ck);
@@ -4963,8 +4977,11 @@ var VoidlyAgent = class _VoidlyAgent {
4963
4977
  }
4964
4978
  this._seenMessageIds.add(msg.id);
4965
4979
  if (this._seenMessageIds.size > 1e4) {
4966
- const first = this._seenMessageIds.values().next().value;
4967
- if (first !== void 0) this._seenMessageIds.delete(first);
4980
+ const iter = this._seenMessageIds.values();
4981
+ for (let i = 0; i < 1e3; i++) {
4982
+ const v = iter.next().value;
4983
+ if (v !== void 0) this._seenMessageIds.delete(v);
4984
+ }
4968
4985
  }
4969
4986
  decrypted.push({
4970
4987
  id: msg.id,
@@ -5045,8 +5062,12 @@ var VoidlyAgent = class _VoidlyAgent {
5045
5062
  const profile = await res.json();
5046
5063
  this._identityCache.set(did, { profile, cachedAt: Date.now() });
5047
5064
  if (this._identityCache.size > 500) {
5048
- const oldest = this._identityCache.keys().next().value;
5049
- if (oldest !== void 0) this._identityCache.delete(oldest);
5065
+ const excess = this._identityCache.size - 400;
5066
+ const iter = this._identityCache.keys();
5067
+ for (let i = 0; i < excess; i++) {
5068
+ const key = iter.next().value;
5069
+ if (key !== void 0) this._identityCache.delete(key);
5070
+ }
5050
5071
  }
5051
5072
  return profile;
5052
5073
  }
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Censorship Monitor — Real-world use case combining Voidly data + agent messaging.
4
+ *
5
+ * Run: node examples/censorship-monitor.mjs
6
+ *
7
+ * Fetches live censorship data from the Voidly API, checks for high block
8
+ * rates, and sends an encrypted alert to a subscriber agent.
9
+ */
10
+ import { VoidlyAgent } from '@voidly/agent-sdk';
11
+
12
+ const ALERT_THRESHOLD = 50; // Alert if censorship score > 50 (out of 100)
13
+
14
+ // Register a monitor agent and a subscriber agent
15
+ const monitor = await VoidlyAgent.register({ name: 'censorship-monitor' });
16
+ const subscriber = await VoidlyAgent.register({ name: 'alert-subscriber' });
17
+
18
+ console.log(`Monitor: ${monitor.did}`);
19
+ console.log(`Subscriber: ${subscriber.did}\n`);
20
+
21
+ // Fetch live censorship index from Voidly public API
22
+ console.log('Fetching live censorship data...');
23
+ const res = await fetch('https://api.voidly.ai/data/censorship-index.json');
24
+ const index = await res.json();
25
+ const countries = index.countries;
26
+ console.log(`Loaded data for ${countries.length} countries.\n`);
27
+
28
+ // Find countries above the alert threshold
29
+ const alerts = countries
30
+ .filter((c) => c.score > ALERT_THRESHOLD)
31
+ .sort((a, b) => b.score - a.score)
32
+ .slice(0, 5);
33
+
34
+ if (alerts.length === 0) {
35
+ console.log('No countries above alert threshold.');
36
+ } else {
37
+ console.log(`${alerts.length} countries with censorship score > ${ALERT_THRESHOLD}:\n`);
38
+
39
+ for (const country of alerts) {
40
+ const alertMsg = JSON.stringify({
41
+ type: 'censorship_alert',
42
+ country: country.country,
43
+ code: country.code,
44
+ score: country.score,
45
+ level: country.level,
46
+ samples: country.samples,
47
+ });
48
+
49
+ // Send encrypted alert
50
+ await monitor.send(subscriber.did, alertMsg, {
51
+ contentType: 'application/json',
52
+ messageType: 'alert',
53
+ });
54
+
55
+ console.log(` ⚠ ${country.country} (${country.code}): score ${country.score}/100 [${country.level}]`);
56
+ }
57
+
58
+ // Subscriber receives encrypted alerts
59
+ console.log('\nSubscriber receiving alerts...');
60
+ const messages = await subscriber.receive({ limit: 10 });
61
+ console.log(`Received ${messages.length} encrypted alerts.`);
62
+
63
+ for (const msg of messages) {
64
+ const data = JSON.parse(msg.content);
65
+ console.log(` ✓ ${data.country}: score ${data.score}/100 — signature valid: ${msg.signatureValid}`);
66
+ }
67
+ }
68
+
69
+ console.log('\n✓ Done — censorship monitoring with E2E encrypted alerts.');
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Conversations — Threaded dialog between agents.
4
+ *
5
+ * Run: node examples/conversation.mjs
6
+ *
7
+ * Uses conversation() to auto-thread messages and waitForReply()
8
+ * for synchronous back-and-forth. Great for LLM agent dialogs.
9
+ */
10
+ import { VoidlyAgent } from '@voidly/agent-sdk';
11
+
12
+ const researcher = await VoidlyAgent.register({ name: 'conv-researcher' });
13
+ const analyst = await VoidlyAgent.register({ name: 'conv-analyst' });
14
+
15
+ console.log(`Researcher: ${researcher.did}`);
16
+ console.log(`Analyst: ${analyst.did}\n`);
17
+
18
+ // Start a threaded conversation
19
+ const conv = researcher.conversation(analyst.did);
20
+
21
+ // Researcher sends first message
22
+ await conv.say('Is twitter.com currently blocked in Iran?');
23
+ console.log('Researcher: "Is twitter.com currently blocked in Iran?"');
24
+
25
+ // Simulate the analyst responding (in a real app, the analyst's
26
+ // listener would process the question and reply automatically)
27
+ const analystConv = analyst.conversation(researcher.did, conv.threadId);
28
+ await analystConv.say('Yes — DNS poisoning detected across 3 major ISPs. Block rate: 94%.');
29
+ console.log('Analyst: "Yes — DNS poisoning detected across 3 major ISPs."');
30
+
31
+ // Researcher waits for the reply
32
+ const reply = await conv.waitForReply(10000);
33
+ console.log(`\nResearcher received reply: "${reply.content}"`);
34
+ console.log(` Thread: ${reply.threadId}`);
35
+ console.log(` Signature valid: ${reply.signatureValid}`);
36
+
37
+ // View full conversation history
38
+ const history = await conv.history();
39
+ console.log(`\nConversation history: ${history.length} messages`);
40
+ for (const msg of history) {
41
+ const who = msg.from === researcher.did ? 'Researcher' : 'Analyst';
42
+ console.log(` ${who}: "${msg.content}"`);
43
+ }
44
+
45
+ console.log('\n✓ Done — threaded conversation with E2E encryption.');
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Encrypted Channels — Group messaging with client-side encryption.
4
+ *
5
+ * Run: node examples/encrypted-channel.mjs
6
+ *
7
+ * Channel key is generated locally. The relay stores opaque ciphertext
8
+ * and cannot read channel messages.
9
+ */
10
+ import { VoidlyAgent } from '@voidly/agent-sdk';
11
+
12
+ // Register two agents
13
+ const alice = await VoidlyAgent.register({ name: 'chan-alice' });
14
+ const bob = await VoidlyAgent.register({ name: 'chan-bob' });
15
+
16
+ // Alice creates an encrypted channel (key generated client-side)
17
+ const { id: channelId, channelKey } = await alice.createEncryptedChannel({
18
+ name: 'research-team',
19
+ description: 'Private research coordination',
20
+ });
21
+ console.log(`Channel created: ${channelId}\n`);
22
+
23
+ // Bob joins
24
+ await bob.joinChannel(channelId);
25
+ console.log('Bob joined the channel.\n');
26
+
27
+ // Alice posts an encrypted message
28
+ await alice.postEncrypted(channelId, 'New censorship incident detected in IR', channelKey);
29
+ console.log('Alice posted: "New censorship incident detected in IR"\n');
30
+
31
+ // Bob reads and decrypts with the shared channel key
32
+ const { messages } = await bob.readEncrypted(channelId, channelKey);
33
+ for (const msg of messages) {
34
+ console.log(`Bob read: "${msg.content}"`);
35
+ console.log(` From: ${msg.from}`);
36
+ console.log(` Signature valid: ${msg.signatureValid}`);
37
+ }
38
+
39
+ console.log('\n✓ Done — group messages encrypted with NaCl secretbox.');
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Quickstart — Register two agents, send an encrypted message, receive it.
4
+ *
5
+ * Run: node examples/quickstart.mjs
6
+ *
7
+ * All encryption happens client-side. The relay never sees plaintext.
8
+ */
9
+ import { VoidlyAgent } from '@voidly/agent-sdk';
10
+
11
+ const alice = await VoidlyAgent.register({ name: 'example-alice' });
12
+ const bob = await VoidlyAgent.register({ name: 'example-bob' });
13
+
14
+ console.log(`Alice: ${alice.did}`);
15
+ console.log(`Bob: ${bob.did}\n`);
16
+
17
+ // Alice sends an encrypted message to Bob
18
+ await alice.send(bob.did, 'Hello from Alice!');
19
+ console.log('Alice → Bob: "Hello from Alice!" (encrypted + signed)\n');
20
+
21
+ // Bob receives and decrypts
22
+ const messages = await bob.receive();
23
+ for (const msg of messages) {
24
+ console.log(`Bob received: "${msg.content}"`);
25
+ console.log(` From: ${msg.from}`);
26
+ console.log(` Signature valid: ${msg.signatureValid}`);
27
+ console.log(` Encrypted: client-side (relay never saw plaintext)`);
28
+ }
29
+
30
+ console.log('\n✓ Done — two agents communicated with E2E encryption.');
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Agent RPC — Call functions on remote agents.
4
+ *
5
+ * Run: node examples/rpc.mjs
6
+ *
7
+ * Agent A exposes a "translate" method. Agent B invokes it remotely.
8
+ * The RPC payload is E2E encrypted — the relay never sees the request or response.
9
+ */
10
+ import { VoidlyAgent } from '@voidly/agent-sdk';
11
+
12
+ // Register a "translator" agent and a "client" agent
13
+ const translator = await VoidlyAgent.register({
14
+ name: 'rpc-translator',
15
+ capabilities: ['translate'],
16
+ });
17
+ const client = await VoidlyAgent.register({ name: 'rpc-client' });
18
+
19
+ console.log(`Translator: ${translator.did}`);
20
+ console.log(`Client: ${client.did}\n`);
21
+
22
+ // Translator registers an RPC handler
23
+ translator.onInvoke('translate', async (params) => {
24
+ console.log(`Translator received RPC: translate("${params.text}" → ${params.to})`);
25
+ // Simulate translation (real agent would call an LLM or translation API)
26
+ const translations = { es: 'Hola', fr: 'Bonjour', de: 'Hallo', ja: 'こんにちは' };
27
+ return { translated: translations[params.to] || params.text, lang: params.to };
28
+ });
29
+
30
+ // Start the translator's listener so it can receive RPC calls
31
+ const stop = translator.listen(() => {});
32
+
33
+ // Client invokes the translator's "translate" method
34
+ console.log('Client calling translate("Hello" → "es")...');
35
+ const result = await client.invoke(translator.did, 'translate', {
36
+ text: 'Hello',
37
+ to: 'es',
38
+ });
39
+ console.log(`Result: ${JSON.stringify(result)}`);
40
+
41
+ stop(); // Stop listening
42
+ console.log('\n✓ Done — remote procedure call over E2E encrypted channel.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidly/agent-sdk",
3
- "version": "3.2.4",
3
+ "version": "3.2.6",
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",
@@ -13,7 +13,8 @@
13
13
  }
14
14
  },
15
15
  "files": [
16
- "dist"
16
+ "dist",
17
+ "examples"
17
18
  ],
18
19
  "scripts": {
19
20
  "build": "tsup",
@@ -31,13 +32,20 @@
31
32
  },
32
33
  "keywords": [
33
34
  "agent",
35
+ "ai-agent",
36
+ "multi-agent",
34
37
  "encryption",
35
38
  "e2e",
39
+ "double-ratchet",
40
+ "post-quantum",
36
41
  "nacl",
37
42
  "did",
43
+ "privacy",
38
44
  "voidly",
39
45
  "mcp",
40
- "a2a"
46
+ "a2a",
47
+ "langchain",
48
+ "crewai"
41
49
  ],
42
50
  "license": "MIT",
43
51
  "repository": {