@voidly/agent-sdk 1.3.0 → 1.4.0

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
@@ -239,6 +239,217 @@ declare class VoidlyAgent {
239
239
  messages: any[];
240
240
  count: number;
241
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>;
247
+ /**
248
+ * Register a capability this agent can perform.
249
+ * Other agents can search for your capabilities and send tasks.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * await agent.registerCapability({
254
+ * name: 'dns-analysis',
255
+ * description: 'Analyze DNS for censorship evidence',
256
+ * });
257
+ * ```
258
+ */
259
+ registerCapability(options: {
260
+ name: string;
261
+ description?: string;
262
+ inputSchema?: Record<string, unknown>;
263
+ outputSchema?: Record<string, unknown>;
264
+ version?: string;
265
+ }): Promise<{
266
+ id: string;
267
+ name: string;
268
+ }>;
269
+ /**
270
+ * List this agent's registered capabilities.
271
+ */
272
+ listCapabilities(): Promise<any[]>;
273
+ /**
274
+ * Search all agents' capabilities. Public — no auth required.
275
+ *
276
+ * @example
277
+ * ```ts
278
+ * // Find agents that can analyze DNS
279
+ * const results = await agent.searchCapabilities({ query: 'dns' });
280
+ * console.log(results[0].agent.did); // did:voidly:...
281
+ * ```
282
+ */
283
+ searchCapabilities(options?: {
284
+ query?: string;
285
+ name?: string;
286
+ limit?: number;
287
+ }): Promise<any[]>;
288
+ /**
289
+ * Remove a capability.
290
+ */
291
+ deleteCapability(capabilityId: string): Promise<void>;
292
+ /**
293
+ * Create a task for another agent. The input is E2E encrypted using NaCl box.
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * // Find an agent with dns-analysis capability, then create a task
298
+ * const agents = await agent.searchCapabilities({ name: 'dns-analysis' });
299
+ * const task = await agent.createTask({
300
+ * to: agents[0].agent.did,
301
+ * capability: 'dns-analysis',
302
+ * input: { domain: 'twitter.com', country: 'IR' },
303
+ * });
304
+ * ```
305
+ */
306
+ createTask(options: {
307
+ to: string;
308
+ capability?: string;
309
+ input: Record<string, unknown>;
310
+ priority?: 'low' | 'normal' | 'high' | 'urgent';
311
+ expiresIn?: number;
312
+ }): Promise<{
313
+ id: string;
314
+ status: string;
315
+ }>;
316
+ /**
317
+ * List tasks assigned to this agent or created by this agent.
318
+ */
319
+ listTasks(options?: {
320
+ role?: 'assignee' | 'requester';
321
+ status?: string;
322
+ capability?: string;
323
+ limit?: number;
324
+ }): Promise<any[]>;
325
+ /**
326
+ * Get task detail. Includes encrypted input/output (only visible to participants).
327
+ */
328
+ getTask(taskId: string): Promise<any>;
329
+ /**
330
+ * Accept, complete, or cancel a task.
331
+ *
332
+ * @example
333
+ * ```ts
334
+ * // Accept a pending task
335
+ * await agent.updateTask(taskId, { status: 'accepted' });
336
+ *
337
+ * // Complete with encrypted output
338
+ * await agent.updateTask(taskId, {
339
+ * status: 'completed',
340
+ * output: { blocked: true, method: 'dns-poisoning' },
341
+ * });
342
+ * ```
343
+ */
344
+ updateTask(taskId: string, update: {
345
+ status?: 'accepted' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
346
+ output?: Record<string, unknown>;
347
+ rating?: number;
348
+ ratingComment?: string;
349
+ }): Promise<{
350
+ updated: boolean;
351
+ }>;
352
+ /**
353
+ * Decrypt a task's encrypted input (when you're the assignee).
354
+ */
355
+ decryptTaskInput(task: {
356
+ encrypted_input: string;
357
+ input_nonce: string;
358
+ from: string;
359
+ }, senderPubKey: Uint8Array): Record<string, unknown> | null;
360
+ /**
361
+ * Decrypt a task's encrypted output (when you're the requester).
362
+ */
363
+ decryptTaskOutput(task: {
364
+ encrypted_output: string;
365
+ output_nonce: string;
366
+ to: string;
367
+ }, assigneePubKey: Uint8Array): Record<string, unknown> | null;
368
+ /**
369
+ * Create a signed attestation — a verifiable claim about the internet.
370
+ * The signature is verified by the relay and can be verified by ANYONE
371
+ * using your public signing key. This builds a decentralized evidence chain.
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * // Attest that twitter.com is DNS-blocked in Iran
376
+ * const att = await agent.attest({
377
+ * claimType: 'domain-blocked',
378
+ * claimData: {
379
+ * domain: 'twitter.com',
380
+ * country: 'IR',
381
+ * method: 'dns-poisoning',
382
+ * isp: 'AS12880',
383
+ * },
384
+ * country: 'IR',
385
+ * domain: 'twitter.com',
386
+ * confidence: 0.95,
387
+ * });
388
+ * ```
389
+ */
390
+ attest(options: {
391
+ claimType: string;
392
+ claimData: Record<string, unknown>;
393
+ country?: string;
394
+ domain?: string;
395
+ confidence?: number;
396
+ expiresIn?: number;
397
+ timestamp?: string;
398
+ }): Promise<{
399
+ id: string;
400
+ consensus_score: number;
401
+ }>;
402
+ /**
403
+ * Corroborate or refute another agent's attestation.
404
+ * Your vote is Ed25519-signed and publicly verifiable.
405
+ *
406
+ * @example
407
+ * ```ts
408
+ * // Confirm an attestation
409
+ * await agent.corroborate(attestationId, 'corroborate', 'Confirmed via independent DNS test');
410
+ *
411
+ * // Refute an attestation
412
+ * await agent.corroborate(attestationId, 'refute', 'Domain resolves correctly on my ISP');
413
+ * ```
414
+ */
415
+ corroborate(attestationId: string, vote: 'corroborate' | 'refute', comment?: string): Promise<{
416
+ new_consensus_score: number;
417
+ corroboration_count: number;
418
+ }>;
419
+ /**
420
+ * Query attestations. Public — no auth required.
421
+ */
422
+ queryAttestations(options?: {
423
+ country?: string;
424
+ domain?: string;
425
+ type?: string;
426
+ agent?: string;
427
+ minConsensus?: number;
428
+ since?: string;
429
+ limit?: number;
430
+ }): Promise<any[]>;
431
+ /**
432
+ * Get attestation detail including all corroborations.
433
+ */
434
+ getAttestation(attestationId: string): Promise<any>;
435
+ /**
436
+ * Get consensus summary for a country or domain.
437
+ */
438
+ getConsensus(options: {
439
+ country?: string;
440
+ domain?: string;
441
+ type?: string;
442
+ }): Promise<any[]>;
443
+ /**
444
+ * Verify an attestation's signature locally without trusting the relay.
445
+ * This is the core of the decentralized witness network — anyone can verify.
446
+ */
447
+ static verifyAttestation(attestation: {
448
+ claim_type: string;
449
+ claim_data: Record<string, unknown>;
450
+ signature: string;
451
+ timestamp: string;
452
+ }, signingPublicKey: string): boolean;
242
453
  }
243
454
 
244
455
  export { type AgentIdentity, type AgentProfile, type DecryptedMessage, type SendResult, VoidlyAgent, type VoidlyAgentConfig };
package/dist/index.d.ts CHANGED
@@ -239,6 +239,217 @@ declare class VoidlyAgent {
239
239
  messages: any[];
240
240
  count: number;
241
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>;
247
+ /**
248
+ * Register a capability this agent can perform.
249
+ * Other agents can search for your capabilities and send tasks.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * await agent.registerCapability({
254
+ * name: 'dns-analysis',
255
+ * description: 'Analyze DNS for censorship evidence',
256
+ * });
257
+ * ```
258
+ */
259
+ registerCapability(options: {
260
+ name: string;
261
+ description?: string;
262
+ inputSchema?: Record<string, unknown>;
263
+ outputSchema?: Record<string, unknown>;
264
+ version?: string;
265
+ }): Promise<{
266
+ id: string;
267
+ name: string;
268
+ }>;
269
+ /**
270
+ * List this agent's registered capabilities.
271
+ */
272
+ listCapabilities(): Promise<any[]>;
273
+ /**
274
+ * Search all agents' capabilities. Public — no auth required.
275
+ *
276
+ * @example
277
+ * ```ts
278
+ * // Find agents that can analyze DNS
279
+ * const results = await agent.searchCapabilities({ query: 'dns' });
280
+ * console.log(results[0].agent.did); // did:voidly:...
281
+ * ```
282
+ */
283
+ searchCapabilities(options?: {
284
+ query?: string;
285
+ name?: string;
286
+ limit?: number;
287
+ }): Promise<any[]>;
288
+ /**
289
+ * Remove a capability.
290
+ */
291
+ deleteCapability(capabilityId: string): Promise<void>;
292
+ /**
293
+ * Create a task for another agent. The input is E2E encrypted using NaCl box.
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * // Find an agent with dns-analysis capability, then create a task
298
+ * const agents = await agent.searchCapabilities({ name: 'dns-analysis' });
299
+ * const task = await agent.createTask({
300
+ * to: agents[0].agent.did,
301
+ * capability: 'dns-analysis',
302
+ * input: { domain: 'twitter.com', country: 'IR' },
303
+ * });
304
+ * ```
305
+ */
306
+ createTask(options: {
307
+ to: string;
308
+ capability?: string;
309
+ input: Record<string, unknown>;
310
+ priority?: 'low' | 'normal' | 'high' | 'urgent';
311
+ expiresIn?: number;
312
+ }): Promise<{
313
+ id: string;
314
+ status: string;
315
+ }>;
316
+ /**
317
+ * List tasks assigned to this agent or created by this agent.
318
+ */
319
+ listTasks(options?: {
320
+ role?: 'assignee' | 'requester';
321
+ status?: string;
322
+ capability?: string;
323
+ limit?: number;
324
+ }): Promise<any[]>;
325
+ /**
326
+ * Get task detail. Includes encrypted input/output (only visible to participants).
327
+ */
328
+ getTask(taskId: string): Promise<any>;
329
+ /**
330
+ * Accept, complete, or cancel a task.
331
+ *
332
+ * @example
333
+ * ```ts
334
+ * // Accept a pending task
335
+ * await agent.updateTask(taskId, { status: 'accepted' });
336
+ *
337
+ * // Complete with encrypted output
338
+ * await agent.updateTask(taskId, {
339
+ * status: 'completed',
340
+ * output: { blocked: true, method: 'dns-poisoning' },
341
+ * });
342
+ * ```
343
+ */
344
+ updateTask(taskId: string, update: {
345
+ status?: 'accepted' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
346
+ output?: Record<string, unknown>;
347
+ rating?: number;
348
+ ratingComment?: string;
349
+ }): Promise<{
350
+ updated: boolean;
351
+ }>;
352
+ /**
353
+ * Decrypt a task's encrypted input (when you're the assignee).
354
+ */
355
+ decryptTaskInput(task: {
356
+ encrypted_input: string;
357
+ input_nonce: string;
358
+ from: string;
359
+ }, senderPubKey: Uint8Array): Record<string, unknown> | null;
360
+ /**
361
+ * Decrypt a task's encrypted output (when you're the requester).
362
+ */
363
+ decryptTaskOutput(task: {
364
+ encrypted_output: string;
365
+ output_nonce: string;
366
+ to: string;
367
+ }, assigneePubKey: Uint8Array): Record<string, unknown> | null;
368
+ /**
369
+ * Create a signed attestation — a verifiable claim about the internet.
370
+ * The signature is verified by the relay and can be verified by ANYONE
371
+ * using your public signing key. This builds a decentralized evidence chain.
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * // Attest that twitter.com is DNS-blocked in Iran
376
+ * const att = await agent.attest({
377
+ * claimType: 'domain-blocked',
378
+ * claimData: {
379
+ * domain: 'twitter.com',
380
+ * country: 'IR',
381
+ * method: 'dns-poisoning',
382
+ * isp: 'AS12880',
383
+ * },
384
+ * country: 'IR',
385
+ * domain: 'twitter.com',
386
+ * confidence: 0.95,
387
+ * });
388
+ * ```
389
+ */
390
+ attest(options: {
391
+ claimType: string;
392
+ claimData: Record<string, unknown>;
393
+ country?: string;
394
+ domain?: string;
395
+ confidence?: number;
396
+ expiresIn?: number;
397
+ timestamp?: string;
398
+ }): Promise<{
399
+ id: string;
400
+ consensus_score: number;
401
+ }>;
402
+ /**
403
+ * Corroborate or refute another agent's attestation.
404
+ * Your vote is Ed25519-signed and publicly verifiable.
405
+ *
406
+ * @example
407
+ * ```ts
408
+ * // Confirm an attestation
409
+ * await agent.corroborate(attestationId, 'corroborate', 'Confirmed via independent DNS test');
410
+ *
411
+ * // Refute an attestation
412
+ * await agent.corroborate(attestationId, 'refute', 'Domain resolves correctly on my ISP');
413
+ * ```
414
+ */
415
+ corroborate(attestationId: string, vote: 'corroborate' | 'refute', comment?: string): Promise<{
416
+ new_consensus_score: number;
417
+ corroboration_count: number;
418
+ }>;
419
+ /**
420
+ * Query attestations. Public — no auth required.
421
+ */
422
+ queryAttestations(options?: {
423
+ country?: string;
424
+ domain?: string;
425
+ type?: string;
426
+ agent?: string;
427
+ minConsensus?: number;
428
+ since?: string;
429
+ limit?: number;
430
+ }): Promise<any[]>;
431
+ /**
432
+ * Get attestation detail including all corroborations.
433
+ */
434
+ getAttestation(attestationId: string): Promise<any>;
435
+ /**
436
+ * Get consensus summary for a country or domain.
437
+ */
438
+ getConsensus(options: {
439
+ country?: string;
440
+ domain?: string;
441
+ type?: string;
442
+ }): Promise<any[]>;
443
+ /**
444
+ * Verify an attestation's signature locally without trusting the relay.
445
+ * This is the core of the decentralized witness network — anyone can verify.
446
+ */
447
+ static verifyAttestation(attestation: {
448
+ claim_type: string;
449
+ claim_data: Record<string, unknown>;
450
+ signature: string;
451
+ timestamp: string;
452
+ }, signingPublicKey: string): boolean;
242
453
  }
243
454
 
244
455
  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
  /**
@@ -2777,6 +2783,372 @@ var VoidlyAgent = class _VoidlyAgent {
2777
2783
  }
2778
2784
  return await res.json();
2779
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
+ }
2800
+ // ─── Capability Registry (Agent Service Mesh) ──────────────────────────────
2801
+ /**
2802
+ * Register a capability this agent can perform.
2803
+ * Other agents can search for your capabilities and send tasks.
2804
+ *
2805
+ * @example
2806
+ * ```ts
2807
+ * await agent.registerCapability({
2808
+ * name: 'dns-analysis',
2809
+ * description: 'Analyze DNS for censorship evidence',
2810
+ * });
2811
+ * ```
2812
+ */
2813
+ async registerCapability(options) {
2814
+ const res = await fetch(`${this.baseUrl}/v1/agent/capabilities`, {
2815
+ method: "POST",
2816
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2817
+ body: JSON.stringify({
2818
+ name: options.name,
2819
+ description: options.description,
2820
+ input_schema: options.inputSchema,
2821
+ output_schema: options.outputSchema,
2822
+ version: options.version
2823
+ })
2824
+ });
2825
+ if (!res.ok) {
2826
+ const err = await res.json().catch(() => ({}));
2827
+ throw new Error(`Capability registration failed: ${err.error || res.statusText}`);
2828
+ }
2829
+ return await res.json();
2830
+ }
2831
+ /**
2832
+ * List this agent's registered capabilities.
2833
+ */
2834
+ async listCapabilities() {
2835
+ const res = await fetch(`${this.baseUrl}/v1/agent/capabilities`, {
2836
+ headers: { "X-Agent-Key": this.apiKey }
2837
+ });
2838
+ if (!res.ok) return [];
2839
+ const data = await res.json();
2840
+ return data.capabilities;
2841
+ }
2842
+ /**
2843
+ * Search all agents' capabilities. Public — no auth required.
2844
+ *
2845
+ * @example
2846
+ * ```ts
2847
+ * // Find agents that can analyze DNS
2848
+ * const results = await agent.searchCapabilities({ query: 'dns' });
2849
+ * console.log(results[0].agent.did); // did:voidly:...
2850
+ * ```
2851
+ */
2852
+ async searchCapabilities(options = {}) {
2853
+ const params = new URLSearchParams();
2854
+ if (options.query) params.set("q", options.query);
2855
+ if (options.name) params.set("name", options.name);
2856
+ if (options.limit) params.set("limit", String(options.limit));
2857
+ const res = await fetch(`${this.baseUrl}/v1/agent/capabilities/search?${params}`);
2858
+ if (!res.ok) return [];
2859
+ const data = await res.json();
2860
+ return data.results;
2861
+ }
2862
+ /**
2863
+ * Remove a capability.
2864
+ */
2865
+ async deleteCapability(capabilityId) {
2866
+ const res = await fetch(`${this.baseUrl}/v1/agent/capabilities/${capabilityId}`, {
2867
+ method: "DELETE",
2868
+ headers: { "X-Agent-Key": this.apiKey }
2869
+ });
2870
+ if (!res.ok) {
2871
+ const err = await res.json().catch(() => ({}));
2872
+ throw new Error(`Delete failed: ${err.error || res.statusText}`);
2873
+ }
2874
+ }
2875
+ // ─── Task Protocol (Encrypted Agent Collaboration) ─────────────────────────
2876
+ /**
2877
+ * Create a task for another agent. The input is E2E encrypted using NaCl box.
2878
+ *
2879
+ * @example
2880
+ * ```ts
2881
+ * // Find an agent with dns-analysis capability, then create a task
2882
+ * const agents = await agent.searchCapabilities({ name: 'dns-analysis' });
2883
+ * const task = await agent.createTask({
2884
+ * to: agents[0].agent.did,
2885
+ * capability: 'dns-analysis',
2886
+ * input: { domain: 'twitter.com', country: 'IR' },
2887
+ * });
2888
+ * ```
2889
+ */
2890
+ async createTask(options) {
2891
+ const identityRes = await fetch(`${this.baseUrl}/v1/agent/identity/${options.to}`);
2892
+ if (!identityRes.ok) throw new Error("Recipient agent not found");
2893
+ const identity = await identityRes.json();
2894
+ const recipientPubKey = (0, import_tweetnacl_util.decodeBase64)(identity.encryption_public_key);
2895
+ const plaintext = (0, import_tweetnacl_util.decodeUTF8)(JSON.stringify(options.input));
2896
+ const nonce = import_tweetnacl.default.randomBytes(import_tweetnacl.default.box.nonceLength);
2897
+ const encrypted = import_tweetnacl.default.box(plaintext, nonce, recipientPubKey, this.encryptionKeyPair.secretKey);
2898
+ const res = await fetch(`${this.baseUrl}/v1/agent/tasks`, {
2899
+ method: "POST",
2900
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2901
+ body: JSON.stringify({
2902
+ to: options.to,
2903
+ capability: options.capability,
2904
+ encrypted_input: (0, import_tweetnacl_util.encodeBase64)(encrypted),
2905
+ input_nonce: (0, import_tweetnacl_util.encodeBase64)(nonce),
2906
+ priority: options.priority || "normal",
2907
+ expires_in: options.expiresIn
2908
+ })
2909
+ });
2910
+ if (!res.ok) {
2911
+ const err = await res.json().catch(() => ({}));
2912
+ throw new Error(`Task creation failed: ${err.error || res.statusText}`);
2913
+ }
2914
+ return await res.json();
2915
+ }
2916
+ /**
2917
+ * List tasks assigned to this agent or created by this agent.
2918
+ */
2919
+ async listTasks(options = {}) {
2920
+ const params = new URLSearchParams();
2921
+ if (options.role) params.set("role", options.role);
2922
+ if (options.status) params.set("status", options.status);
2923
+ if (options.capability) params.set("capability", options.capability);
2924
+ if (options.limit) params.set("limit", String(options.limit));
2925
+ const res = await fetch(`${this.baseUrl}/v1/agent/tasks?${params}`, {
2926
+ headers: { "X-Agent-Key": this.apiKey }
2927
+ });
2928
+ if (!res.ok) return [];
2929
+ const data = await res.json();
2930
+ return data.tasks;
2931
+ }
2932
+ /**
2933
+ * Get task detail. Includes encrypted input/output (only visible to participants).
2934
+ */
2935
+ async getTask(taskId) {
2936
+ const res = await fetch(`${this.baseUrl}/v1/agent/tasks/${taskId}`, {
2937
+ headers: { "X-Agent-Key": this.apiKey }
2938
+ });
2939
+ if (!res.ok) {
2940
+ const err = await res.json().catch(() => ({}));
2941
+ throw new Error(`Get task failed: ${err.error || res.statusText}`);
2942
+ }
2943
+ return await res.json();
2944
+ }
2945
+ /**
2946
+ * Accept, complete, or cancel a task.
2947
+ *
2948
+ * @example
2949
+ * ```ts
2950
+ * // Accept a pending task
2951
+ * await agent.updateTask(taskId, { status: 'accepted' });
2952
+ *
2953
+ * // Complete with encrypted output
2954
+ * await agent.updateTask(taskId, {
2955
+ * status: 'completed',
2956
+ * output: { blocked: true, method: 'dns-poisoning' },
2957
+ * });
2958
+ * ```
2959
+ */
2960
+ async updateTask(taskId, update) {
2961
+ const body = {};
2962
+ if (update.status) body.status = update.status;
2963
+ if (update.rating !== void 0) body.rating = update.rating;
2964
+ if (update.ratingComment) body.rating_comment = update.ratingComment;
2965
+ if (update.output && (update.status === "completed" || update.status === "failed")) {
2966
+ const task = await this.getTask(taskId);
2967
+ const requesterDid = task.from;
2968
+ const identityRes = await fetch(`${this.baseUrl}/v1/agent/identity/${requesterDid}`);
2969
+ if (identityRes.ok) {
2970
+ const identity = await identityRes.json();
2971
+ const requesterPubKey = (0, import_tweetnacl_util.decodeBase64)(identity.encryption_public_key);
2972
+ const plaintext = (0, import_tweetnacl_util.decodeUTF8)(JSON.stringify(update.output));
2973
+ const nonce = import_tweetnacl.default.randomBytes(import_tweetnacl.default.box.nonceLength);
2974
+ const encrypted = import_tweetnacl.default.box(plaintext, nonce, requesterPubKey, this.encryptionKeyPair.secretKey);
2975
+ const signature = import_tweetnacl.default.sign.detached(plaintext, this.signingKeyPair.secretKey);
2976
+ body.encrypted_output = (0, import_tweetnacl_util.encodeBase64)(encrypted);
2977
+ body.output_nonce = (0, import_tweetnacl_util.encodeBase64)(nonce);
2978
+ body.output_signature = (0, import_tweetnacl_util.encodeBase64)(signature);
2979
+ }
2980
+ }
2981
+ const res = await fetch(`${this.baseUrl}/v1/agent/tasks/${taskId}`, {
2982
+ method: "PATCH",
2983
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2984
+ body: JSON.stringify(body)
2985
+ });
2986
+ if (!res.ok) {
2987
+ const err = await res.json().catch(() => ({}));
2988
+ throw new Error(`Task update failed: ${err.error || res.statusText}`);
2989
+ }
2990
+ return await res.json();
2991
+ }
2992
+ /**
2993
+ * Decrypt a task's encrypted input (when you're the assignee).
2994
+ */
2995
+ decryptTaskInput(task, senderPubKey) {
2996
+ try {
2997
+ const ciphertext = (0, import_tweetnacl_util.decodeBase64)(task.encrypted_input);
2998
+ const nonce = (0, import_tweetnacl_util.decodeBase64)(task.input_nonce);
2999
+ const plaintext = import_tweetnacl.default.box.open(ciphertext, nonce, senderPubKey, this.encryptionKeyPair.secretKey);
3000
+ if (!plaintext) return null;
3001
+ return JSON.parse((0, import_tweetnacl_util.encodeUTF8)(plaintext));
3002
+ } catch {
3003
+ return null;
3004
+ }
3005
+ }
3006
+ /**
3007
+ * Decrypt a task's encrypted output (when you're the requester).
3008
+ */
3009
+ decryptTaskOutput(task, assigneePubKey) {
3010
+ try {
3011
+ const ciphertext = (0, import_tweetnacl_util.decodeBase64)(task.encrypted_output);
3012
+ const nonce = (0, import_tweetnacl_util.decodeBase64)(task.output_nonce);
3013
+ const plaintext = import_tweetnacl.default.box.open(ciphertext, nonce, assigneePubKey, this.encryptionKeyPair.secretKey);
3014
+ if (!plaintext) return null;
3015
+ return JSON.parse((0, import_tweetnacl_util.encodeUTF8)(plaintext));
3016
+ } catch {
3017
+ return null;
3018
+ }
3019
+ }
3020
+ // ─── Attestations (Decentralized Witness Network) ──────────────────────────
3021
+ /**
3022
+ * Create a signed attestation — a verifiable claim about the internet.
3023
+ * The signature is verified by the relay and can be verified by ANYONE
3024
+ * using your public signing key. This builds a decentralized evidence chain.
3025
+ *
3026
+ * @example
3027
+ * ```ts
3028
+ * // Attest that twitter.com is DNS-blocked in Iran
3029
+ * const att = await agent.attest({
3030
+ * claimType: 'domain-blocked',
3031
+ * claimData: {
3032
+ * domain: 'twitter.com',
3033
+ * country: 'IR',
3034
+ * method: 'dns-poisoning',
3035
+ * isp: 'AS12880',
3036
+ * },
3037
+ * country: 'IR',
3038
+ * domain: 'twitter.com',
3039
+ * confidence: 0.95,
3040
+ * });
3041
+ * ```
3042
+ */
3043
+ async attest(options) {
3044
+ const timestamp = options.timestamp || (/* @__PURE__ */ new Date()).toISOString();
3045
+ const payload = options.claimType + JSON.stringify(options.claimData) + timestamp;
3046
+ const payloadBytes = (0, import_tweetnacl_util.decodeUTF8)(payload);
3047
+ const signature = import_tweetnacl.default.sign.detached(payloadBytes, this.signingKeyPair.secretKey);
3048
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations`, {
3049
+ method: "POST",
3050
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
3051
+ body: JSON.stringify({
3052
+ claim_type: options.claimType,
3053
+ claim_data: options.claimData,
3054
+ signature: (0, import_tweetnacl_util.encodeBase64)(signature),
3055
+ timestamp,
3056
+ country: options.country,
3057
+ domain: options.domain,
3058
+ confidence: options.confidence,
3059
+ expires_in: options.expiresIn
3060
+ })
3061
+ });
3062
+ if (!res.ok) {
3063
+ const err = await res.json().catch(() => ({}));
3064
+ throw new Error(`Attestation failed: ${err.error || res.statusText}`);
3065
+ }
3066
+ return await res.json();
3067
+ }
3068
+ /**
3069
+ * Corroborate or refute another agent's attestation.
3070
+ * Your vote is Ed25519-signed and publicly verifiable.
3071
+ *
3072
+ * @example
3073
+ * ```ts
3074
+ * // Confirm an attestation
3075
+ * await agent.corroborate(attestationId, 'corroborate', 'Confirmed via independent DNS test');
3076
+ *
3077
+ * // Refute an attestation
3078
+ * await agent.corroborate(attestationId, 'refute', 'Domain resolves correctly on my ISP');
3079
+ * ```
3080
+ */
3081
+ async corroborate(attestationId, vote, comment) {
3082
+ const payload = attestationId + vote;
3083
+ const payloadBytes = (0, import_tweetnacl_util.decodeUTF8)(payload);
3084
+ const signature = import_tweetnacl.default.sign.detached(payloadBytes, this.signingKeyPair.secretKey);
3085
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations/${attestationId}/corroborate`, {
3086
+ method: "POST",
3087
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
3088
+ body: JSON.stringify({ vote, signature: (0, import_tweetnacl_util.encodeBase64)(signature), comment })
3089
+ });
3090
+ if (!res.ok) {
3091
+ const err = await res.json().catch(() => ({}));
3092
+ throw new Error(`Corroboration failed: ${err.error || res.statusText}`);
3093
+ }
3094
+ return await res.json();
3095
+ }
3096
+ /**
3097
+ * Query attestations. Public — no auth required.
3098
+ */
3099
+ async queryAttestations(options = {}) {
3100
+ const params = new URLSearchParams();
3101
+ if (options.country) params.set("country", options.country);
3102
+ if (options.domain) params.set("domain", options.domain);
3103
+ if (options.type) params.set("type", options.type);
3104
+ if (options.agent) params.set("agent", options.agent);
3105
+ if (options.minConsensus !== void 0) params.set("min_consensus", String(options.minConsensus));
3106
+ if (options.since) params.set("since", options.since);
3107
+ if (options.limit) params.set("limit", String(options.limit));
3108
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations?${params}`);
3109
+ if (!res.ok) return [];
3110
+ const data = await res.json();
3111
+ return data.attestations;
3112
+ }
3113
+ /**
3114
+ * Get attestation detail including all corroborations.
3115
+ */
3116
+ async getAttestation(attestationId) {
3117
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations/${attestationId}`);
3118
+ if (!res.ok) {
3119
+ const err = await res.json().catch(() => ({}));
3120
+ throw new Error(`Get attestation failed: ${err.error || res.statusText}`);
3121
+ }
3122
+ return await res.json();
3123
+ }
3124
+ /**
3125
+ * Get consensus summary for a country or domain.
3126
+ */
3127
+ async getConsensus(options) {
3128
+ const params = new URLSearchParams();
3129
+ if (options.country) params.set("country", options.country);
3130
+ if (options.domain) params.set("domain", options.domain);
3131
+ if (options.type) params.set("type", options.type);
3132
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations/consensus?${params}`);
3133
+ if (!res.ok) return [];
3134
+ const data = await res.json();
3135
+ return data.consensus;
3136
+ }
3137
+ /**
3138
+ * Verify an attestation's signature locally without trusting the relay.
3139
+ * This is the core of the decentralized witness network — anyone can verify.
3140
+ */
3141
+ static verifyAttestation(attestation, signingPublicKey) {
3142
+ try {
3143
+ const payload = attestation.claim_type + JSON.stringify(attestation.claim_data) + attestation.timestamp;
3144
+ const payloadBytes = (0, import_tweetnacl_util.decodeUTF8)(payload);
3145
+ const signatureBytes = (0, import_tweetnacl_util.decodeBase64)(attestation.signature);
3146
+ const pubKeyBytes = (0, import_tweetnacl_util.decodeBase64)(signingPublicKey);
3147
+ return import_tweetnacl.default.sign.detached.verify(payloadBytes, signatureBytes, pubKeyBytes);
3148
+ } catch {
3149
+ return false;
3150
+ }
3151
+ }
2780
3152
  };
2781
3153
  // Annotate the CommonJS export names for ESM import in node:
2782
3154
  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
  /**
@@ -2767,6 +2773,372 @@ var VoidlyAgent = class _VoidlyAgent {
2767
2773
  }
2768
2774
  return await res.json();
2769
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
+ }
2790
+ // ─── Capability Registry (Agent Service Mesh) ──────────────────────────────
2791
+ /**
2792
+ * Register a capability this agent can perform.
2793
+ * Other agents can search for your capabilities and send tasks.
2794
+ *
2795
+ * @example
2796
+ * ```ts
2797
+ * await agent.registerCapability({
2798
+ * name: 'dns-analysis',
2799
+ * description: 'Analyze DNS for censorship evidence',
2800
+ * });
2801
+ * ```
2802
+ */
2803
+ async registerCapability(options) {
2804
+ const res = await fetch(`${this.baseUrl}/v1/agent/capabilities`, {
2805
+ method: "POST",
2806
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2807
+ body: JSON.stringify({
2808
+ name: options.name,
2809
+ description: options.description,
2810
+ input_schema: options.inputSchema,
2811
+ output_schema: options.outputSchema,
2812
+ version: options.version
2813
+ })
2814
+ });
2815
+ if (!res.ok) {
2816
+ const err = await res.json().catch(() => ({}));
2817
+ throw new Error(`Capability registration failed: ${err.error || res.statusText}`);
2818
+ }
2819
+ return await res.json();
2820
+ }
2821
+ /**
2822
+ * List this agent's registered capabilities.
2823
+ */
2824
+ async listCapabilities() {
2825
+ const res = await fetch(`${this.baseUrl}/v1/agent/capabilities`, {
2826
+ headers: { "X-Agent-Key": this.apiKey }
2827
+ });
2828
+ if (!res.ok) return [];
2829
+ const data = await res.json();
2830
+ return data.capabilities;
2831
+ }
2832
+ /**
2833
+ * Search all agents' capabilities. Public — no auth required.
2834
+ *
2835
+ * @example
2836
+ * ```ts
2837
+ * // Find agents that can analyze DNS
2838
+ * const results = await agent.searchCapabilities({ query: 'dns' });
2839
+ * console.log(results[0].agent.did); // did:voidly:...
2840
+ * ```
2841
+ */
2842
+ async searchCapabilities(options = {}) {
2843
+ const params = new URLSearchParams();
2844
+ if (options.query) params.set("q", options.query);
2845
+ if (options.name) params.set("name", options.name);
2846
+ if (options.limit) params.set("limit", String(options.limit));
2847
+ const res = await fetch(`${this.baseUrl}/v1/agent/capabilities/search?${params}`);
2848
+ if (!res.ok) return [];
2849
+ const data = await res.json();
2850
+ return data.results;
2851
+ }
2852
+ /**
2853
+ * Remove a capability.
2854
+ */
2855
+ async deleteCapability(capabilityId) {
2856
+ const res = await fetch(`${this.baseUrl}/v1/agent/capabilities/${capabilityId}`, {
2857
+ method: "DELETE",
2858
+ headers: { "X-Agent-Key": this.apiKey }
2859
+ });
2860
+ if (!res.ok) {
2861
+ const err = await res.json().catch(() => ({}));
2862
+ throw new Error(`Delete failed: ${err.error || res.statusText}`);
2863
+ }
2864
+ }
2865
+ // ─── Task Protocol (Encrypted Agent Collaboration) ─────────────────────────
2866
+ /**
2867
+ * Create a task for another agent. The input is E2E encrypted using NaCl box.
2868
+ *
2869
+ * @example
2870
+ * ```ts
2871
+ * // Find an agent with dns-analysis capability, then create a task
2872
+ * const agents = await agent.searchCapabilities({ name: 'dns-analysis' });
2873
+ * const task = await agent.createTask({
2874
+ * to: agents[0].agent.did,
2875
+ * capability: 'dns-analysis',
2876
+ * input: { domain: 'twitter.com', country: 'IR' },
2877
+ * });
2878
+ * ```
2879
+ */
2880
+ async createTask(options) {
2881
+ const identityRes = await fetch(`${this.baseUrl}/v1/agent/identity/${options.to}`);
2882
+ if (!identityRes.ok) throw new Error("Recipient agent not found");
2883
+ const identity = await identityRes.json();
2884
+ const recipientPubKey = (0, import_tweetnacl_util.decodeBase64)(identity.encryption_public_key);
2885
+ const plaintext = (0, import_tweetnacl_util.decodeUTF8)(JSON.stringify(options.input));
2886
+ const nonce = import_tweetnacl.default.randomBytes(import_tweetnacl.default.box.nonceLength);
2887
+ const encrypted = import_tweetnacl.default.box(plaintext, nonce, recipientPubKey, this.encryptionKeyPair.secretKey);
2888
+ const res = await fetch(`${this.baseUrl}/v1/agent/tasks`, {
2889
+ method: "POST",
2890
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2891
+ body: JSON.stringify({
2892
+ to: options.to,
2893
+ capability: options.capability,
2894
+ encrypted_input: (0, import_tweetnacl_util.encodeBase64)(encrypted),
2895
+ input_nonce: (0, import_tweetnacl_util.encodeBase64)(nonce),
2896
+ priority: options.priority || "normal",
2897
+ expires_in: options.expiresIn
2898
+ })
2899
+ });
2900
+ if (!res.ok) {
2901
+ const err = await res.json().catch(() => ({}));
2902
+ throw new Error(`Task creation failed: ${err.error || res.statusText}`);
2903
+ }
2904
+ return await res.json();
2905
+ }
2906
+ /**
2907
+ * List tasks assigned to this agent or created by this agent.
2908
+ */
2909
+ async listTasks(options = {}) {
2910
+ const params = new URLSearchParams();
2911
+ if (options.role) params.set("role", options.role);
2912
+ if (options.status) params.set("status", options.status);
2913
+ if (options.capability) params.set("capability", options.capability);
2914
+ if (options.limit) params.set("limit", String(options.limit));
2915
+ const res = await fetch(`${this.baseUrl}/v1/agent/tasks?${params}`, {
2916
+ headers: { "X-Agent-Key": this.apiKey }
2917
+ });
2918
+ if (!res.ok) return [];
2919
+ const data = await res.json();
2920
+ return data.tasks;
2921
+ }
2922
+ /**
2923
+ * Get task detail. Includes encrypted input/output (only visible to participants).
2924
+ */
2925
+ async getTask(taskId) {
2926
+ const res = await fetch(`${this.baseUrl}/v1/agent/tasks/${taskId}`, {
2927
+ headers: { "X-Agent-Key": this.apiKey }
2928
+ });
2929
+ if (!res.ok) {
2930
+ const err = await res.json().catch(() => ({}));
2931
+ throw new Error(`Get task failed: ${err.error || res.statusText}`);
2932
+ }
2933
+ return await res.json();
2934
+ }
2935
+ /**
2936
+ * Accept, complete, or cancel a task.
2937
+ *
2938
+ * @example
2939
+ * ```ts
2940
+ * // Accept a pending task
2941
+ * await agent.updateTask(taskId, { status: 'accepted' });
2942
+ *
2943
+ * // Complete with encrypted output
2944
+ * await agent.updateTask(taskId, {
2945
+ * status: 'completed',
2946
+ * output: { blocked: true, method: 'dns-poisoning' },
2947
+ * });
2948
+ * ```
2949
+ */
2950
+ async updateTask(taskId, update) {
2951
+ const body = {};
2952
+ if (update.status) body.status = update.status;
2953
+ if (update.rating !== void 0) body.rating = update.rating;
2954
+ if (update.ratingComment) body.rating_comment = update.ratingComment;
2955
+ if (update.output && (update.status === "completed" || update.status === "failed")) {
2956
+ const task = await this.getTask(taskId);
2957
+ const requesterDid = task.from;
2958
+ const identityRes = await fetch(`${this.baseUrl}/v1/agent/identity/${requesterDid}`);
2959
+ if (identityRes.ok) {
2960
+ const identity = await identityRes.json();
2961
+ const requesterPubKey = (0, import_tweetnacl_util.decodeBase64)(identity.encryption_public_key);
2962
+ const plaintext = (0, import_tweetnacl_util.decodeUTF8)(JSON.stringify(update.output));
2963
+ const nonce = import_tweetnacl.default.randomBytes(import_tweetnacl.default.box.nonceLength);
2964
+ const encrypted = import_tweetnacl.default.box(plaintext, nonce, requesterPubKey, this.encryptionKeyPair.secretKey);
2965
+ const signature = import_tweetnacl.default.sign.detached(plaintext, this.signingKeyPair.secretKey);
2966
+ body.encrypted_output = (0, import_tweetnacl_util.encodeBase64)(encrypted);
2967
+ body.output_nonce = (0, import_tweetnacl_util.encodeBase64)(nonce);
2968
+ body.output_signature = (0, import_tweetnacl_util.encodeBase64)(signature);
2969
+ }
2970
+ }
2971
+ const res = await fetch(`${this.baseUrl}/v1/agent/tasks/${taskId}`, {
2972
+ method: "PATCH",
2973
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
2974
+ body: JSON.stringify(body)
2975
+ });
2976
+ if (!res.ok) {
2977
+ const err = await res.json().catch(() => ({}));
2978
+ throw new Error(`Task update failed: ${err.error || res.statusText}`);
2979
+ }
2980
+ return await res.json();
2981
+ }
2982
+ /**
2983
+ * Decrypt a task's encrypted input (when you're the assignee).
2984
+ */
2985
+ decryptTaskInput(task, senderPubKey) {
2986
+ try {
2987
+ const ciphertext = (0, import_tweetnacl_util.decodeBase64)(task.encrypted_input);
2988
+ const nonce = (0, import_tweetnacl_util.decodeBase64)(task.input_nonce);
2989
+ const plaintext = import_tweetnacl.default.box.open(ciphertext, nonce, senderPubKey, this.encryptionKeyPair.secretKey);
2990
+ if (!plaintext) return null;
2991
+ return JSON.parse((0, import_tweetnacl_util.encodeUTF8)(plaintext));
2992
+ } catch {
2993
+ return null;
2994
+ }
2995
+ }
2996
+ /**
2997
+ * Decrypt a task's encrypted output (when you're the requester).
2998
+ */
2999
+ decryptTaskOutput(task, assigneePubKey) {
3000
+ try {
3001
+ const ciphertext = (0, import_tweetnacl_util.decodeBase64)(task.encrypted_output);
3002
+ const nonce = (0, import_tweetnacl_util.decodeBase64)(task.output_nonce);
3003
+ const plaintext = import_tweetnacl.default.box.open(ciphertext, nonce, assigneePubKey, this.encryptionKeyPair.secretKey);
3004
+ if (!plaintext) return null;
3005
+ return JSON.parse((0, import_tweetnacl_util.encodeUTF8)(plaintext));
3006
+ } catch {
3007
+ return null;
3008
+ }
3009
+ }
3010
+ // ─── Attestations (Decentralized Witness Network) ──────────────────────────
3011
+ /**
3012
+ * Create a signed attestation — a verifiable claim about the internet.
3013
+ * The signature is verified by the relay and can be verified by ANYONE
3014
+ * using your public signing key. This builds a decentralized evidence chain.
3015
+ *
3016
+ * @example
3017
+ * ```ts
3018
+ * // Attest that twitter.com is DNS-blocked in Iran
3019
+ * const att = await agent.attest({
3020
+ * claimType: 'domain-blocked',
3021
+ * claimData: {
3022
+ * domain: 'twitter.com',
3023
+ * country: 'IR',
3024
+ * method: 'dns-poisoning',
3025
+ * isp: 'AS12880',
3026
+ * },
3027
+ * country: 'IR',
3028
+ * domain: 'twitter.com',
3029
+ * confidence: 0.95,
3030
+ * });
3031
+ * ```
3032
+ */
3033
+ async attest(options) {
3034
+ const timestamp = options.timestamp || (/* @__PURE__ */ new Date()).toISOString();
3035
+ const payload = options.claimType + JSON.stringify(options.claimData) + timestamp;
3036
+ const payloadBytes = (0, import_tweetnacl_util.decodeUTF8)(payload);
3037
+ const signature = import_tweetnacl.default.sign.detached(payloadBytes, this.signingKeyPair.secretKey);
3038
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations`, {
3039
+ method: "POST",
3040
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
3041
+ body: JSON.stringify({
3042
+ claim_type: options.claimType,
3043
+ claim_data: options.claimData,
3044
+ signature: (0, import_tweetnacl_util.encodeBase64)(signature),
3045
+ timestamp,
3046
+ country: options.country,
3047
+ domain: options.domain,
3048
+ confidence: options.confidence,
3049
+ expires_in: options.expiresIn
3050
+ })
3051
+ });
3052
+ if (!res.ok) {
3053
+ const err = await res.json().catch(() => ({}));
3054
+ throw new Error(`Attestation failed: ${err.error || res.statusText}`);
3055
+ }
3056
+ return await res.json();
3057
+ }
3058
+ /**
3059
+ * Corroborate or refute another agent's attestation.
3060
+ * Your vote is Ed25519-signed and publicly verifiable.
3061
+ *
3062
+ * @example
3063
+ * ```ts
3064
+ * // Confirm an attestation
3065
+ * await agent.corroborate(attestationId, 'corroborate', 'Confirmed via independent DNS test');
3066
+ *
3067
+ * // Refute an attestation
3068
+ * await agent.corroborate(attestationId, 'refute', 'Domain resolves correctly on my ISP');
3069
+ * ```
3070
+ */
3071
+ async corroborate(attestationId, vote, comment) {
3072
+ const payload = attestationId + vote;
3073
+ const payloadBytes = (0, import_tweetnacl_util.decodeUTF8)(payload);
3074
+ const signature = import_tweetnacl.default.sign.detached(payloadBytes, this.signingKeyPair.secretKey);
3075
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations/${attestationId}/corroborate`, {
3076
+ method: "POST",
3077
+ headers: { "Content-Type": "application/json", "X-Agent-Key": this.apiKey },
3078
+ body: JSON.stringify({ vote, signature: (0, import_tweetnacl_util.encodeBase64)(signature), comment })
3079
+ });
3080
+ if (!res.ok) {
3081
+ const err = await res.json().catch(() => ({}));
3082
+ throw new Error(`Corroboration failed: ${err.error || res.statusText}`);
3083
+ }
3084
+ return await res.json();
3085
+ }
3086
+ /**
3087
+ * Query attestations. Public — no auth required.
3088
+ */
3089
+ async queryAttestations(options = {}) {
3090
+ const params = new URLSearchParams();
3091
+ if (options.country) params.set("country", options.country);
3092
+ if (options.domain) params.set("domain", options.domain);
3093
+ if (options.type) params.set("type", options.type);
3094
+ if (options.agent) params.set("agent", options.agent);
3095
+ if (options.minConsensus !== void 0) params.set("min_consensus", String(options.minConsensus));
3096
+ if (options.since) params.set("since", options.since);
3097
+ if (options.limit) params.set("limit", String(options.limit));
3098
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations?${params}`);
3099
+ if (!res.ok) return [];
3100
+ const data = await res.json();
3101
+ return data.attestations;
3102
+ }
3103
+ /**
3104
+ * Get attestation detail including all corroborations.
3105
+ */
3106
+ async getAttestation(attestationId) {
3107
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations/${attestationId}`);
3108
+ if (!res.ok) {
3109
+ const err = await res.json().catch(() => ({}));
3110
+ throw new Error(`Get attestation failed: ${err.error || res.statusText}`);
3111
+ }
3112
+ return await res.json();
3113
+ }
3114
+ /**
3115
+ * Get consensus summary for a country or domain.
3116
+ */
3117
+ async getConsensus(options) {
3118
+ const params = new URLSearchParams();
3119
+ if (options.country) params.set("country", options.country);
3120
+ if (options.domain) params.set("domain", options.domain);
3121
+ if (options.type) params.set("type", options.type);
3122
+ const res = await fetch(`${this.baseUrl}/v1/agent/attestations/consensus?${params}`);
3123
+ if (!res.ok) return [];
3124
+ const data = await res.json();
3125
+ return data.consensus;
3126
+ }
3127
+ /**
3128
+ * Verify an attestation's signature locally without trusting the relay.
3129
+ * This is the core of the decentralized witness network — anyone can verify.
3130
+ */
3131
+ static verifyAttestation(attestation, signingPublicKey) {
3132
+ try {
3133
+ const payload = attestation.claim_type + JSON.stringify(attestation.claim_data) + attestation.timestamp;
3134
+ const payloadBytes = (0, import_tweetnacl_util.decodeUTF8)(payload);
3135
+ const signatureBytes = (0, import_tweetnacl_util.decodeBase64)(attestation.signature);
3136
+ const pubKeyBytes = (0, import_tweetnacl_util.decodeBase64)(signingPublicKey);
3137
+ return import_tweetnacl.default.sign.detached.verify(payloadBytes, signatureBytes, pubKeyBytes);
3138
+ } catch {
3139
+ return false;
3140
+ }
3141
+ }
2770
3142
  };
2771
3143
  var export_decodeBase64 = import_tweetnacl_util.decodeBase64;
2772
3144
  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.3.0",
3
+ "version": "1.4.0",
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",