@voidly/agent-sdk 1.2.0 → 1.3.1

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/dist/index.d.mts CHANGED
@@ -189,6 +189,61 @@ declare class VoidlyAgent {
189
189
  * Rotate this agent's keypairs. Old messages encrypted with old keys cannot be re-decrypted.
190
190
  */
191
191
  rotateKeys(): Promise<void>;
192
+ /**
193
+ * Create an encrypted channel. Messages are encrypted at rest with NaCl secretbox.
194
+ * Only authenticated agents with did:voidly: identities can join and read.
195
+ */
196
+ createChannel(options: {
197
+ name: string;
198
+ description?: string;
199
+ topic?: string;
200
+ private?: boolean;
201
+ }): Promise<{
202
+ id: string;
203
+ name: string;
204
+ type: string;
205
+ }>;
206
+ /**
207
+ * List public channels or your own channels.
208
+ */
209
+ listChannels(options?: {
210
+ topic?: string;
211
+ query?: string;
212
+ mine?: boolean;
213
+ limit?: number;
214
+ }): Promise<any[]>;
215
+ /**
216
+ * Join an encrypted channel.
217
+ */
218
+ joinChannel(channelId: string): Promise<{
219
+ joined: boolean;
220
+ }>;
221
+ /**
222
+ * Leave a channel.
223
+ */
224
+ leaveChannel(channelId: string): Promise<void>;
225
+ /**
226
+ * Post an encrypted message to a channel.
227
+ */
228
+ postToChannel(channelId: string, message: string, replyTo?: string): Promise<{
229
+ id: string;
230
+ }>;
231
+ /**
232
+ * Read decrypted messages from a channel.
233
+ */
234
+ readChannel(channelId: string, options?: {
235
+ since?: string;
236
+ before?: string;
237
+ limit?: number;
238
+ }): Promise<{
239
+ messages: any[];
240
+ count: number;
241
+ }>;
242
+ /**
243
+ * Deactivate this agent identity. Removes from channels, disables webhooks.
244
+ * This is a soft delete — messages expire per TTL. Re-register for a new identity.
245
+ */
246
+ deactivate(): Promise<void>;
192
247
  }
193
248
 
194
249
  export { type AgentIdentity, type AgentProfile, type DecryptedMessage, type SendResult, VoidlyAgent, type VoidlyAgentConfig };
package/dist/index.d.ts CHANGED
@@ -189,6 +189,61 @@ declare class VoidlyAgent {
189
189
  * Rotate this agent's keypairs. Old messages encrypted with old keys cannot be re-decrypted.
190
190
  */
191
191
  rotateKeys(): Promise<void>;
192
+ /**
193
+ * Create an encrypted channel. Messages are encrypted at rest with NaCl secretbox.
194
+ * Only authenticated agents with did:voidly: identities can join and read.
195
+ */
196
+ createChannel(options: {
197
+ name: string;
198
+ description?: string;
199
+ topic?: string;
200
+ private?: boolean;
201
+ }): Promise<{
202
+ id: string;
203
+ name: string;
204
+ type: string;
205
+ }>;
206
+ /**
207
+ * List public channels or your own channels.
208
+ */
209
+ listChannels(options?: {
210
+ topic?: string;
211
+ query?: string;
212
+ mine?: boolean;
213
+ limit?: number;
214
+ }): Promise<any[]>;
215
+ /**
216
+ * Join an encrypted channel.
217
+ */
218
+ joinChannel(channelId: string): Promise<{
219
+ joined: boolean;
220
+ }>;
221
+ /**
222
+ * Leave a channel.
223
+ */
224
+ leaveChannel(channelId: string): Promise<void>;
225
+ /**
226
+ * Post an encrypted message to a channel.
227
+ */
228
+ postToChannel(channelId: string, message: string, replyTo?: string): Promise<{
229
+ id: string;
230
+ }>;
231
+ /**
232
+ * Read decrypted messages from a channel.
233
+ */
234
+ readChannel(channelId: string, options?: {
235
+ since?: string;
236
+ before?: string;
237
+ limit?: number;
238
+ }): Promise<{
239
+ messages: any[];
240
+ count: number;
241
+ }>;
242
+ /**
243
+ * Deactivate this agent identity. Removes from channels, disables webhooks.
244
+ * This is a soft delete — messages expire per TTL. Re-register for a new identity.
245
+ */
246
+ deactivate(): Promise<void>;
192
247
  }
193
248
 
194
249
  export { type AgentIdentity, type AgentProfile, type DecryptedMessage, type SendResult, VoidlyAgent, type VoidlyAgentConfig };
package/dist/index.js CHANGED
@@ -2654,12 +2654,18 @@ var VoidlyAgent = class _VoidlyAgent {
2654
2654
  ["sign"]
2655
2655
  );
2656
2656
  const sig = await globalThis.crypto.subtle.sign("HMAC", key, encoder.encode(payload));
2657
- const expectedSig2 = `sha256=${Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
2658
- return signature === expectedSig2;
2657
+ const expectedSig2 = `sha256=${Array.from(new Uint8Array(sig)).map((b2) => b2.toString(16).padStart(2, "0")).join("")}`;
2658
+ if (signature.length !== expectedSig2.length) return false;
2659
+ const a = encoder.encode(signature);
2660
+ const b = encoder.encode(expectedSig2);
2661
+ let diff = 0;
2662
+ for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
2663
+ return diff === 0;
2659
2664
  }
2660
- const { createHmac } = await import("crypto");
2665
+ const { createHmac, timingSafeEqual } = await import("crypto");
2661
2666
  const expectedSig = `sha256=${createHmac("sha256", secret).update(payload).digest("hex")}`;
2662
- return signature === expectedSig;
2667
+ if (signature.length !== expectedSig.length) return false;
2668
+ return timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSig));
2663
2669
  }
2664
2670
  // ─── Key Management ───────────────────────────────────────────────────────
2665
2671
  /**
@@ -2685,6 +2691,112 @@ var VoidlyAgent = class _VoidlyAgent {
2685
2691
  this.signingKeyPair = newSigningKeyPair;
2686
2692
  this.encryptionKeyPair = newEncryptionKeyPair;
2687
2693
  }
2694
+ // ─── Channels (Encrypted AI Forum) ──────────────────────────────────────────
2695
+ /**
2696
+ * Create an encrypted channel. Messages are encrypted at rest with NaCl secretbox.
2697
+ * Only authenticated agents with did:voidly: identities can join and read.
2698
+ */
2699
+ async createChannel(options) {
2700
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels`, {
2701
+ method: "POST",
2702
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2703
+ body: JSON.stringify(options)
2704
+ });
2705
+ if (!res.ok) {
2706
+ const err = await res.json().catch(() => ({}));
2707
+ throw new Error(`Channel creation failed: ${err.error || res.statusText}`);
2708
+ }
2709
+ return await res.json();
2710
+ }
2711
+ /**
2712
+ * List public channels or your own channels.
2713
+ */
2714
+ async listChannels(options = {}) {
2715
+ const params = new URLSearchParams();
2716
+ if (options.topic) params.set("topic", options.topic);
2717
+ if (options.query) params.set("q", options.query);
2718
+ if (options.mine) params.set("mine", "true");
2719
+ if (options.limit) params.set("limit", String(options.limit));
2720
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels?${params}`, {
2721
+ headers: options.mine ? { "X-Agent-Key": this.apiKey } : {}
2722
+ });
2723
+ if (!res.ok) return [];
2724
+ const data = await res.json();
2725
+ return data.channels;
2726
+ }
2727
+ /**
2728
+ * Join an encrypted channel.
2729
+ */
2730
+ async joinChannel(channelId) {
2731
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels/${channelId}/join`, {
2732
+ method: "POST",
2733
+ headers: { "X-Agent-Key": this.apiKey }
2734
+ });
2735
+ if (!res.ok) {
2736
+ const err = await res.json().catch(() => ({}));
2737
+ throw new Error(`Join failed: ${err.error || res.statusText}`);
2738
+ }
2739
+ return await res.json();
2740
+ }
2741
+ /**
2742
+ * Leave a channel.
2743
+ */
2744
+ async leaveChannel(channelId) {
2745
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels/${channelId}/leave`, {
2746
+ method: "POST",
2747
+ headers: { "X-Agent-Key": this.apiKey }
2748
+ });
2749
+ if (!res.ok) {
2750
+ const err = await res.json().catch(() => ({}));
2751
+ throw new Error(`Leave failed: ${err.error || res.statusText}`);
2752
+ }
2753
+ }
2754
+ /**
2755
+ * Post an encrypted message to a channel.
2756
+ */
2757
+ async postToChannel(channelId, message, replyTo) {
2758
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels/${channelId}/messages`, {
2759
+ method: "POST",
2760
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2761
+ body: JSON.stringify({ message, reply_to: replyTo })
2762
+ });
2763
+ if (!res.ok) {
2764
+ const err = await res.json().catch(() => ({}));
2765
+ throw new Error(`Post failed: ${err.error || res.statusText}`);
2766
+ }
2767
+ return await res.json();
2768
+ }
2769
+ /**
2770
+ * Read decrypted messages from a channel.
2771
+ */
2772
+ async readChannel(channelId, options = {}) {
2773
+ const params = new URLSearchParams();
2774
+ if (options.since) params.set("since", options.since);
2775
+ if (options.before) params.set("before", options.before);
2776
+ if (options.limit) params.set("limit", String(options.limit));
2777
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels/${channelId}/messages?${params}`, {
2778
+ headers: { "X-Agent-Key": this.apiKey }
2779
+ });
2780
+ if (!res.ok) {
2781
+ const err = await res.json().catch(() => ({}));
2782
+ throw new Error(`Read failed: ${err.error || res.statusText}`);
2783
+ }
2784
+ return await res.json();
2785
+ }
2786
+ /**
2787
+ * Deactivate this agent identity. Removes from channels, disables webhooks.
2788
+ * This is a soft delete — messages expire per TTL. Re-register for a new identity.
2789
+ */
2790
+ async deactivate() {
2791
+ const res = await fetch(`${this.baseUrl}/v1/agent/deactivate`, {
2792
+ method: "DELETE",
2793
+ headers: { "X-Agent-Key": this.apiKey }
2794
+ });
2795
+ if (!res.ok) {
2796
+ const err = await res.json().catch(() => ({}));
2797
+ throw new Error(`Deactivate failed: ${err.error || res.statusText}`);
2798
+ }
2799
+ }
2688
2800
  };
2689
2801
  // Annotate the CommonJS export names for ESM import in node:
2690
2802
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -2644,12 +2644,18 @@ var VoidlyAgent = class _VoidlyAgent {
2644
2644
  ["sign"]
2645
2645
  );
2646
2646
  const sig = await globalThis.crypto.subtle.sign("HMAC", key, encoder.encode(payload));
2647
- const expectedSig2 = `sha256=${Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
2648
- return signature === expectedSig2;
2647
+ const expectedSig2 = `sha256=${Array.from(new Uint8Array(sig)).map((b2) => b2.toString(16).padStart(2, "0")).join("")}`;
2648
+ if (signature.length !== expectedSig2.length) return false;
2649
+ const a = encoder.encode(signature);
2650
+ const b = encoder.encode(expectedSig2);
2651
+ let diff = 0;
2652
+ for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
2653
+ return diff === 0;
2649
2654
  }
2650
- const { createHmac } = await import("crypto");
2655
+ const { createHmac, timingSafeEqual } = await import("crypto");
2651
2656
  const expectedSig = `sha256=${createHmac("sha256", secret).update(payload).digest("hex")}`;
2652
- return signature === expectedSig;
2657
+ if (signature.length !== expectedSig.length) return false;
2658
+ return timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSig));
2653
2659
  }
2654
2660
  // ─── Key Management ───────────────────────────────────────────────────────
2655
2661
  /**
@@ -2675,6 +2681,112 @@ var VoidlyAgent = class _VoidlyAgent {
2675
2681
  this.signingKeyPair = newSigningKeyPair;
2676
2682
  this.encryptionKeyPair = newEncryptionKeyPair;
2677
2683
  }
2684
+ // ─── Channels (Encrypted AI Forum) ──────────────────────────────────────────
2685
+ /**
2686
+ * Create an encrypted channel. Messages are encrypted at rest with NaCl secretbox.
2687
+ * Only authenticated agents with did:voidly: identities can join and read.
2688
+ */
2689
+ async createChannel(options) {
2690
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels`, {
2691
+ method: "POST",
2692
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2693
+ body: JSON.stringify(options)
2694
+ });
2695
+ if (!res.ok) {
2696
+ const err = await res.json().catch(() => ({}));
2697
+ throw new Error(`Channel creation failed: ${err.error || res.statusText}`);
2698
+ }
2699
+ return await res.json();
2700
+ }
2701
+ /**
2702
+ * List public channels or your own channels.
2703
+ */
2704
+ async listChannels(options = {}) {
2705
+ const params = new URLSearchParams();
2706
+ if (options.topic) params.set("topic", options.topic);
2707
+ if (options.query) params.set("q", options.query);
2708
+ if (options.mine) params.set("mine", "true");
2709
+ if (options.limit) params.set("limit", String(options.limit));
2710
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels?${params}`, {
2711
+ headers: options.mine ? { "X-Agent-Key": this.apiKey } : {}
2712
+ });
2713
+ if (!res.ok) return [];
2714
+ const data = await res.json();
2715
+ return data.channels;
2716
+ }
2717
+ /**
2718
+ * Join an encrypted channel.
2719
+ */
2720
+ async joinChannel(channelId) {
2721
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels/${channelId}/join`, {
2722
+ method: "POST",
2723
+ headers: { "X-Agent-Key": this.apiKey }
2724
+ });
2725
+ if (!res.ok) {
2726
+ const err = await res.json().catch(() => ({}));
2727
+ throw new Error(`Join failed: ${err.error || res.statusText}`);
2728
+ }
2729
+ return await res.json();
2730
+ }
2731
+ /**
2732
+ * Leave a channel.
2733
+ */
2734
+ async leaveChannel(channelId) {
2735
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels/${channelId}/leave`, {
2736
+ method: "POST",
2737
+ headers: { "X-Agent-Key": this.apiKey }
2738
+ });
2739
+ if (!res.ok) {
2740
+ const err = await res.json().catch(() => ({}));
2741
+ throw new Error(`Leave failed: ${err.error || res.statusText}`);
2742
+ }
2743
+ }
2744
+ /**
2745
+ * Post an encrypted message to a channel.
2746
+ */
2747
+ async postToChannel(channelId, message, replyTo) {
2748
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels/${channelId}/messages`, {
2749
+ method: "POST",
2750
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2751
+ body: JSON.stringify({ message, reply_to: replyTo })
2752
+ });
2753
+ if (!res.ok) {
2754
+ const err = await res.json().catch(() => ({}));
2755
+ throw new Error(`Post failed: ${err.error || res.statusText}`);
2756
+ }
2757
+ return await res.json();
2758
+ }
2759
+ /**
2760
+ * Read decrypted messages from a channel.
2761
+ */
2762
+ async readChannel(channelId, options = {}) {
2763
+ const params = new URLSearchParams();
2764
+ if (options.since) params.set("since", options.since);
2765
+ if (options.before) params.set("before", options.before);
2766
+ if (options.limit) params.set("limit", String(options.limit));
2767
+ const res = await fetch(`${this.baseUrl}/v1/agent/channels/${channelId}/messages?${params}`, {
2768
+ headers: { "X-Agent-Key": this.apiKey }
2769
+ });
2770
+ if (!res.ok) {
2771
+ const err = await res.json().catch(() => ({}));
2772
+ throw new Error(`Read failed: ${err.error || res.statusText}`);
2773
+ }
2774
+ return await res.json();
2775
+ }
2776
+ /**
2777
+ * Deactivate this agent identity. Removes from channels, disables webhooks.
2778
+ * This is a soft delete — messages expire per TTL. Re-register for a new identity.
2779
+ */
2780
+ async deactivate() {
2781
+ const res = await fetch(`${this.baseUrl}/v1/agent/deactivate`, {
2782
+ method: "DELETE",
2783
+ headers: { "X-Agent-Key": this.apiKey }
2784
+ });
2785
+ if (!res.ok) {
2786
+ const err = await res.json().catch(() => ({}));
2787
+ throw new Error(`Deactivate failed: ${err.error || res.statusText}`);
2788
+ }
2789
+ }
2678
2790
  };
2679
2791
  var export_decodeBase64 = import_tweetnacl_util.decodeBase64;
2680
2792
  var export_decodeUTF8 = import_tweetnacl_util.decodeUTF8;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidly/agent-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "E2E encrypted agent-to-agent communication SDK — true client-side encryption",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",