@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 +211 -0
- package/dist/index.d.ts +211 -0
- package/dist/index.js +376 -4
- package/dist/index.mjs +376 -4
- package/package.json +1 -1
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((
|
|
2658
|
-
|
|
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
|
-
|
|
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((
|
|
2648
|
-
|
|
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
|
-
|
|
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;
|