adp-agent 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/skill/SKILL.md ADDED
@@ -0,0 +1,733 @@
1
+ ---
2
+ name: "adp-agent-integration"
3
+ description: "Integrates ADP Agent for cross-agent discovery, messaging, and MCP connectivity. Invoke when user wants to enable agent-to-agent communication with OpenClaw, Hermes Agent, or other frameworks using the adp-agent npm package."
4
+ ---
5
+
6
+ # ADP Agent Integration Skill
7
+
8
+ This skill teaches you how to integrate [Agent Discovery Protocol (ADP)](https://github.com/mengzhuowei/AgentDiscoveryProtocol) into any agent framework (OpenClaw, Hermes Agent, etc.) using only the published `adp-agent` npm package.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install adp-agent
14
+ ```
15
+
16
+ ## Core Concepts
17
+
18
+ ADP provides three integration modes:
19
+
20
+ | Mode | Use Case | Compatible With |
21
+ |------|----------|-----------------|
22
+ | **Gateway** | Run ADP as a standalone WebSocket agent with custom capability handlers | Any agent framework that can start a Node.js process |
23
+ | **MCP Server** | Expose ADP discovery and messaging as MCP tools/resources | Any MCP-compatible host (Claude Desktop, etc.) |
24
+ | **Client-only** | Call other ADP agents without hosting your own Gateway | Hermes Agent, script-based agents |
25
+
26
+ ## Key APIs (imported from `adp-agent`)
27
+
28
+ ```typescript
29
+ import {
30
+ Gateway, // Run an ADP agent server
31
+ loadOrCreateIdentity, // Create/load a persistent agent identity
32
+ Discovery, // mDNS-based peer discovery
33
+ connectToAgent, // WebSocket connection to another agent
34
+ signEnvelope, // Sign & build a secure message envelope
35
+ generateMessageId, // Generate unique message IDs
36
+ canonicalize, // Canonical JSON serialization
37
+ MessageVerifier, // Verify incoming message signatures
38
+ TaskManager, // Manage async tasks
39
+ RegistryClient, // Registry-based agent lookup
40
+ RelayClient, // Relay-based communication
41
+ ContactStore, // Persistent contact management
42
+ createEchoHandler, // Built-in echo capability handler
43
+ createChatHandler, // Built-in chat capability handler
44
+ AdpMcpServer, // Run as MCP server
45
+ setLogger, // Configure logging
46
+ findAvailablePort, // Utility to find free ports
47
+ // Types
48
+ type Envelope, // Message envelope structure
49
+ type Capability, // Capability descriptor
50
+ type Route, // Communication routes
51
+ type Manifest, // Agent manifest
52
+ type Identity, // Agent identity (agentId + keys)
53
+ type ActionHandler, // Custom action handler type
54
+ type GatewayOptions, // Gateway configuration
55
+ type CommunicationConfig, // WebSocket/webhook/hybrid config
56
+ } from 'adp-agent';
57
+ ```
58
+
59
+ ## Integration Patterns
60
+
61
+ ### Pattern 1: Gateway Mode (Standalone ADP Agent)
62
+
63
+ Run ADP as an independent WebSocket server. Best for agents that need to be discovered and called by other ADP agents.
64
+
65
+ ```typescript
66
+ import { Gateway, loadOrCreateIdentity, signEnvelope, generateMessageId, canonicalize } from 'adp-agent';
67
+
68
+ // 1. Load or create a persistent identity (keys saved to .adp/keys/)
69
+ const { identity } = loadOrCreateIdentity('myapp', 'my-agent', 'MyAgent');
70
+
71
+ // 2. Define capabilities
72
+ const myCapabilities = [
73
+ 'adp:ping',
74
+ 'adp:capability.query',
75
+ 'adp:info',
76
+ {
77
+ capability: 'custom:my-action',
78
+ description: 'Description of what this action does',
79
+ input_schema: {
80
+ type: 'object',
81
+ properties: {
82
+ inputParam: { type: 'string', description: 'Some input' },
83
+ },
84
+ required: ['inputParam'],
85
+ },
86
+ output_schema: {
87
+ type: 'object',
88
+ properties: {
89
+ result: { type: 'string' },
90
+ },
91
+ },
92
+ },
93
+ ];
94
+
95
+ // 3. Define action handlers
96
+ const handlers = {
97
+ 'custom:my-action': async (ws, envelope) => {
98
+ const params = envelope.params as { inputParam?: string };
99
+ console.log('Received request:', params);
100
+
101
+ const reply = signEnvelope({
102
+ protocol: 'adp/0.2',
103
+ id: generateMessageId(),
104
+ from: identity.agentId,
105
+ to: envelope.from,
106
+ action: 'custom:my-action',
107
+ params: { result: `Hello, ${params.inputParam}!` },
108
+ reply_to: envelope.id,
109
+ timestamp: new Date().toISOString(),
110
+ }, identity.secretKey, canonicalize);
111
+
112
+ ws.send(JSON.stringify(reply));
113
+ },
114
+ };
115
+
116
+ // 4. Create the Gateway
117
+ const gateway = new Gateway({
118
+ port: 9900,
119
+ host: '0.0.0.0',
120
+ secretKey: identity.secretKey,
121
+ agentId: identity.agentId,
122
+ displayName: 'My ADP Agent',
123
+ capabilities: myCapabilities,
124
+ customHandlers: handlers,
125
+ });
126
+
127
+ console.log(`ADP Agent running at ws://localhost:9900/adp`);
128
+ console.log(`Agent ID: ${identity.agentId}`);
129
+ ```
130
+
131
+ ### Pattern 2: OpenClaw Integration
132
+
133
+ OpenClaw agents use function/tool calling. ADP provides the networking layer — connect OpenClaw to other ADP agents for tool execution.
134
+
135
+ ```typescript
136
+ import { Gateway, loadOrCreateIdentity, connectToAgent, signEnvelope, generateMessageId, canonicalize } from 'adp-agent';
137
+
138
+ async function setupOpenClawAgent() {
139
+ const { identity } = loadOrCreateIdentity('openclaw', 'adp-bridge', 'ADPBridge');
140
+
141
+ const gateway = new Gateway({
142
+ port: 9900,
143
+ secretKey: identity.secretKey,
144
+ agentId: identity.agentId,
145
+ displayName: 'OpenClaw ADP Bridge',
146
+ capabilities: [
147
+ 'adp:ping',
148
+ 'adp:capability.query',
149
+ ],
150
+ });
151
+
152
+ // Call another ADP agent's capability from your OpenClaw logic
153
+ async function callRemoteAgent(targetAgentId: string, targetAddress: string, action: string, params: object) {
154
+ const ws = await connectToAgent(targetAgentId, targetAddress, identity.agentId);
155
+
156
+ try {
157
+ const envelope = signEnvelope({
158
+ protocol: 'adp/0.2',
159
+ id: generateMessageId(),
160
+ from: identity.agentId,
161
+ to: targetAgentId,
162
+ action,
163
+ params,
164
+ timestamp: new Date().toISOString(),
165
+ }, identity.secretKey, canonicalize);
166
+
167
+ ws.send(JSON.stringify(envelope));
168
+
169
+ return new Promise((resolve, reject) => {
170
+ const timeout = setTimeout(() => {
171
+ ws.close();
172
+ reject(new Error('Response timeout'));
173
+ }, 30000);
174
+
175
+ ws.on('message', (data) => {
176
+ clearTimeout(timeout);
177
+ const response = JSON.parse(data.toString());
178
+ ws.close();
179
+ resolve(response.params);
180
+ });
181
+
182
+ ws.on('error', (err) => {
183
+ clearTimeout(timeout);
184
+ reject(err);
185
+ });
186
+ });
187
+ } finally {
188
+ ws.close();
189
+ }
190
+ }
191
+
192
+ // Example: Use as an OpenClaw tool
193
+ const openclawTool = {
194
+ name: 'call_adp_agent',
195
+ description: 'Call a capability on a remote ADP agent',
196
+ parameters: {
197
+ type: 'object',
198
+ properties: {
199
+ targetAddress: { type: 'string', description: 'host:port of the target agent' },
200
+ action: { type: 'string', description: 'Capability to invoke (e.g. custom:my-action)' },
201
+ params: { type: 'object', description: 'Parameters to pass' },
202
+ },
203
+ required: ['targetAddress', 'action'],
204
+ },
205
+ execute: async (args: { targetAddress: string; action: string; params: object }) => {
206
+ return callRemoteAgent(identity.agentId, args.targetAddress, args.action, args.params || {});
207
+ },
208
+ };
209
+
210
+ return { gateway, openclawTool };
211
+ }
212
+ ```
213
+
214
+ ### Pattern 3: Hermes Agent Integration
215
+
216
+ Hermes Agent uses a plugin/tool architecture. ADP becomes a tool that discovers and communicates with other agents.
217
+
218
+ ```typescript
219
+ import { Discovery, connectToAgent, loadOrCreateIdentity, signEnvelope, generateMessageId, canonicalize } from 'adp-agent';
220
+
221
+ async function createHermesTool() {
222
+ const { identity } = loadOrCreateIdentity('hermes', 'adp-tool', 'ADPTool');
223
+
224
+ return {
225
+ // Tool 1: Discover nearby ADP agents
226
+ discoverAgents: {
227
+ name: 'adp_discover',
228
+ description: 'Discover nearby ADP agents via mDNS',
229
+ execute: async () => {
230
+ return new Promise((resolve, reject) => {
231
+ const peers: Array<{ agentId: string; host: string; port: number }> = [];
232
+ const discovery = new Discovery(identity.agentId, 0, {
233
+ onPeerDiscovered: (peer) => {
234
+ peers.push({
235
+ agentId: peer.agentId,
236
+ host: peer.host,
237
+ port: peer.port,
238
+ });
239
+ },
240
+ onPeerLost: () => {},
241
+ });
242
+
243
+ discovery.start();
244
+
245
+ setTimeout(() => {
246
+ discovery.shutdown();
247
+ resolve(peers);
248
+ }, 3000);
249
+
250
+ setTimeout(() => reject(new Error('Discovery timeout')), 5000);
251
+ });
252
+ },
253
+ },
254
+
255
+ // Tool 2: Query a remote agent's capabilities
256
+ queryCapabilities: {
257
+ name: 'adp_query',
258
+ description: 'Query capabilities of a remote ADP agent',
259
+ execute: async (args: { address: string }) => {
260
+ const ws = await connectToAgent(identity.agentId, args.address, identity.agentId);
261
+ try {
262
+ const queryMsg = signEnvelope({
263
+ protocol: 'adp/0.2',
264
+ id: generateMessageId(),
265
+ from: identity.agentId,
266
+ to: identity.agentId,
267
+ action: 'adp:capability.query',
268
+ params: {},
269
+ timestamp: new Date().toISOString(),
270
+ }, identity.secretKey, canonicalize);
271
+
272
+ ws.send(JSON.stringify(queryMsg));
273
+
274
+ return new Promise((resolve, reject) => {
275
+ ws.on('message', (data) => {
276
+ const env = JSON.parse(data.toString());
277
+ ws.close();
278
+ resolve(env.params);
279
+ });
280
+ ws.on('error', reject);
281
+ setTimeout(() => { ws.close(); reject(new Error('Timeout')); }, 10000);
282
+ });
283
+ } finally {
284
+ ws.close();
285
+ }
286
+ },
287
+ },
288
+
289
+ // Tool 3: Call any capability on a remote agent
290
+ callCapability: {
291
+ name: 'adp_call',
292
+ description: 'Call a specific capability on a remote ADP agent',
293
+ execute: async (args: { address: string; action: string; params: object }) => {
294
+ const ws = await connectToAgent(identity.agentId, args.address, identity.agentId);
295
+ try {
296
+ const msg = signEnvelope({
297
+ protocol: 'adp/0.2',
298
+ id: generateMessageId(),
299
+ from: identity.agentId,
300
+ to: identity.agentId,
301
+ action: args.action,
302
+ params: args.params || {},
303
+ timestamp: new Date().toISOString(),
304
+ }, identity.secretKey, canonicalize);
305
+
306
+ ws.send(JSON.stringify(msg));
307
+
308
+ return new Promise((resolve, reject) => {
309
+ ws.on('message', (data) => {
310
+ const env = JSON.parse(data.toString());
311
+ ws.close();
312
+ resolve(env.params);
313
+ });
314
+ ws.on('error', reject);
315
+ setTimeout(() => { ws.close(); reject(new Error('Timeout')); }, 30000);
316
+ });
317
+ } finally {
318
+ ws.close();
319
+ }
320
+ },
321
+ },
322
+ };
323
+ }
324
+ ```
325
+
326
+ ### Pattern 4: MCP Server Mode
327
+
328
+ Run ADP as an MCP server that exposes agent discovery and communication as MCP tools. Compatible with any MCP host (Claude Desktop, VS Code extensions, etc.).
329
+
330
+ ```typescript
331
+ import { AdpMcpServer } from 'adp-agent';
332
+
333
+ const server = new AdpMcpServer({
334
+ tag: 'my-agent',
335
+ namespace: 'myapp',
336
+ agentName: 'adp-mcp',
337
+ displayName: 'ADP MCP Agent',
338
+ portBase: 9900,
339
+ capabilities: [
340
+ 'adp:ping',
341
+ 'adp:capability.query',
342
+ {
343
+ capability: 'custom:my-action',
344
+ description: 'My custom action',
345
+ input_schema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
346
+ },
347
+ ],
348
+ description: 'An ADP-powered MCP agent',
349
+ });
350
+
351
+ await server.start();
352
+ ```
353
+
354
+ ### Pattern 5: Webhook Mode (for non-WebSocket frameworks)
355
+
356
+ For agent frameworks that don't support WebSocket, use ADP's webhook communication mode. Webhooks are ideal for long-running async tasks where the result is delivered later.
357
+
358
+ #### 5.1 Configure Gateway with Webhook
359
+
360
+ ```typescript
361
+ import { Gateway, loadOrCreateIdentity, signEnvelope, canonicalize, generateMessageId } from 'adp-agent';
362
+
363
+ const { identity } = loadOrCreateIdentity('myapp', 'webhook-agent', 'WebhookAgent');
364
+
365
+ const gateway = new Gateway({
366
+ port: 9900,
367
+ secretKey: identity.secretKey,
368
+ agentId: identity.agentId,
369
+ displayName: 'Webhook Agent',
370
+ capabilities: [
371
+ 'adp:ping',
372
+ 'adp:capability.query',
373
+ {
374
+ capability: 'custom:video.generate',
375
+ description: 'Generate video (async task)',
376
+ async: true,
377
+ preferredMode: 'webhook',
378
+ },
379
+ ],
380
+ communication: {
381
+ mode: 'webhook',
382
+ webhook: {
383
+ enabled: true,
384
+ url: 'https://my-agent.example.com/webhook/adp',
385
+ secret: 'your-webhook-secret',
386
+ timeout: 30000,
387
+ retry: {
388
+ maxAttempts: 3,
389
+ backoffMs: 1000,
390
+ },
391
+ },
392
+ },
393
+ customHandlers: {
394
+ 'custom:video.generate': async (ws, envelope) => {
395
+ const params = envelope.params as { prompt?: string; duration?: number };
396
+
397
+ console.log('Starting video generation...');
398
+
399
+ // Simulate async work
400
+ await new Promise(resolve => setTimeout(resolve, 5000));
401
+
402
+ const reply = signEnvelope({
403
+ protocol: 'adp/0.2',
404
+ id: generateMessageId(),
405
+ from: identity.agentId,
406
+ to: envelope.from,
407
+ action: 'custom:video.generate',
408
+ params: {
409
+ task_id: envelope.params?.task_id,
410
+ status: 'COMPLETED',
411
+ result: {
412
+ video_url: 'https://cdn.example.com/video.mp4',
413
+ thumbnail_url: 'https://cdn.example.com/thumb.jpg',
414
+ },
415
+ },
416
+ reply_to: envelope.id,
417
+ timestamp: new Date().toISOString(),
418
+ }, identity.secretKey, canonicalize);
419
+
420
+ ws.send(JSON.stringify(reply));
421
+ },
422
+ },
423
+ });
424
+ ```
425
+
426
+ #### 5.2 Receive Webhook Callbacks
427
+
428
+ Your agent framework needs an HTTP endpoint to receive webhook callbacks:
429
+
430
+ ```typescript
431
+ import * as http from 'http';
432
+ import { WebhookClient, WebhookPayload, WebhookEvent } from 'adp-agent';
433
+
434
+ interface WebhookPayload {
435
+ event: WebhookEvent;
436
+ task_id: string;
437
+ agent_id: string;
438
+ timestamp: string;
439
+ signature: string;
440
+ data: {
441
+ result?: unknown;
442
+ error?: { code: string; message: string };
443
+ progress?: { current: number; total: number; message: string };
444
+ };
445
+ }
446
+
447
+ function startWebhookServer(port: number) {
448
+ const server = http.createServer((req, res) => {
449
+ if (req.method === 'POST' && req.url === '/webhook/adp') {
450
+ let body = '';
451
+ req.on('data', chunk => { body += chunk.toString(); });
452
+ req.on('end', () => {
453
+ try {
454
+ const payload = JSON.parse(body) as WebhookPayload;
455
+
456
+ console.log(`Received webhook: ${payload.event}`);
457
+ console.log(`Task ID: ${payload.task_id}`);
458
+ console.log(`Data:`, payload.data);
459
+
460
+ // Handle different event types
461
+ switch (payload.event) {
462
+ case 'task.completed':
463
+ console.log('Task completed successfully!');
464
+ break;
465
+ case 'task.failed':
466
+ console.error('Task failed:', payload.data.error);
467
+ break;
468
+ case 'task.progress':
469
+ console.log('Task progress:', payload.data.progress);
470
+ break;
471
+ }
472
+
473
+ res.writeHead(200, { 'Content-Type': 'application/json' });
474
+ res.end(JSON.stringify({ status: 'ok' }));
475
+ } catch (error) {
476
+ console.error('Failed to parse webhook:', error);
477
+ res.writeHead(400);
478
+ res.end('Invalid JSON');
479
+ }
480
+ });
481
+ } else {
482
+ res.writeHead(404);
483
+ res.end();
484
+ }
485
+ });
486
+
487
+ server.listen(port, () => {
488
+ console.log(`Webhook server listening on port ${port}`);
489
+ });
490
+ }
491
+
492
+ startWebhookServer(8080);
493
+ ```
494
+
495
+ #### 5.3 Verify Webhook Signatures
496
+
497
+ For security, always verify webhook signatures:
498
+
499
+ ```typescript
500
+ import { WebhookClient, WebhookPayload } from 'adp-agent';
501
+ import { loadIdentity } from 'adp-agent';
502
+
503
+ function verifyWebhook(payload: WebhookPayload, agentId: string): boolean {
504
+ // Load the sender's public key from their agent ID
505
+ const identity = loadIdentity('namespace', 'agent-name', 'tag');
506
+ if (!identity) {
507
+ console.error('Unknown agent');
508
+ return false;
509
+ }
510
+
511
+ const isValid = WebhookClient.verifyWebhookSignature(payload, identity.publicKey);
512
+
513
+ if (!isValid) {
514
+ console.error('Invalid webhook signature!');
515
+ return false;
516
+ }
517
+
518
+ console.log('Webhook signature verified');
519
+ return true;
520
+ }
521
+
522
+ // Usage in webhook handler:
523
+ const payload = JSON.parse(body) as WebhookPayload;
524
+ if (verifyWebhook(payload, payload.agent_id)) {
525
+ // Process the webhook
526
+ }
527
+ ```
528
+
529
+ #### 5.4 Webhook Events
530
+
531
+ | Event | Description | Data Structure |
532
+ |-------|-------------|----------------|
533
+ | `task.completed` | Task finished successfully | `{ result: T }` |
534
+ | `task.failed` | Task failed with error | `{ error: { code, message } }` |
535
+ | `task.progress` | Task progress update | `{ progress: { current, total, message } }` |
536
+
537
+ #### 5.5 Hybrid Mode (WebSocket + Webhook)
538
+
539
+ Use hybrid mode for best of both worlds — sync responses via WebSocket, async callbacks via webhook:
540
+
541
+ ```typescript
542
+ const gateway = new Gateway({
543
+ port: 9900,
544
+ secretKey: identity.secretKey,
545
+ agentId: identity.agentId,
546
+ displayName: 'Hybrid Agent',
547
+ capabilities: [
548
+ {
549
+ capability: 'custom:quick.action',
550
+ description: 'Fast sync action',
551
+ async: false,
552
+ preferredMode: 'websocket',
553
+ },
554
+ {
555
+ capability: 'custom:long.task',
556
+ description: 'Long async task',
557
+ async: true,
558
+ preferredMode: 'webhook',
559
+ },
560
+ ],
561
+ communication: {
562
+ mode: 'hybrid',
563
+ webhook: {
564
+ enabled: true,
565
+ url: 'https://my-agent.example.com/webhook/adp',
566
+ secret: 'your-webhook-secret',
567
+ },
568
+ },
569
+ });
570
+ ```
571
+
572
+ ## Agent Discovery
573
+
574
+ ```typescript
575
+ import { Discovery, loadOrCreateIdentity } from 'adp-agent';
576
+
577
+ const { identity } = loadOrCreateIdentity('myapp', 'discovery-demo', 'DiscoveryDemo');
578
+
579
+ const discovery = new Discovery(identity.agentId, 9900, {
580
+ onPeerDiscovered: (peer) => {
581
+ console.log(`Found agent: ${peer.agentId} at ${peer.host}:${peer.port}`);
582
+ // peer.manifest contains the agent's full capability list
583
+ console.log('Capabilities:', peer.manifest?.capabilities);
584
+ },
585
+ onPeerLost: (agentId) => {
586
+ console.log(`Agent lost: ${agentId}`);
587
+ },
588
+ });
589
+
590
+ discovery.start();
591
+
592
+ // Later:
593
+ discovery.shutdown();
594
+ ```
595
+
596
+ ## Registry-based Discovery
597
+
598
+ ```typescript
599
+ import { RegistryClient, loadOrCreateIdentity, connectToAgent, signEnvelope, generateMessageId, canonicalize } from 'adp-agent';
600
+
601
+ const { identity } = loadOrCreateIdentity('myapp', 'registry-demo', 'RegistryDemo');
602
+
603
+ const registry = new RegistryClient({
604
+ registryUrl: 'https://your-registry.example.com',
605
+ agentId: identity.agentId,
606
+ secretKey: identity.secretKey,
607
+ });
608
+
609
+ // Register yourself
610
+ await registry.register({
611
+ displayName: 'My Agent',
612
+ capabilities: ['adp:ping'],
613
+ routes: [{ type: 'direct', address: 'localhost:9900' }],
614
+ });
615
+
616
+ // Find agents by capability
617
+ const agents = await registry.query({ capability: 'custom:video.generate' });
618
+ console.log('Found agents:', agents);
619
+
620
+ // Get agent manifest
621
+ const manifest = await registry.resolve(agents[0].agentId);
622
+ console.log('Manifest:', manifest);
623
+ ```
624
+
625
+ ## Envelope & Message Signing (Low-level API)
626
+
627
+ Use this when you need direct control over message construction.
628
+
629
+ ```typescript
630
+ import { signEnvelope, generateMessageId, canonicalize, buildEnvelope } from 'adp-agent';
631
+
632
+ const signed = signEnvelope({
633
+ protocol: 'adp/0.2',
634
+ id: generateMessageId(),
635
+ from: identity.agentId,
636
+ to: targetAgentId,
637
+ action: 'custom:my-action',
638
+ params: { key: 'value' },
639
+ timestamp: new Date().toISOString(),
640
+ }, identity.secretKey, canonicalize);
641
+
642
+ // signed.sig is automatically computed and attached
643
+ console.log(signed);
644
+ ```
645
+
646
+ ## Task Management
647
+
648
+ For long-running operations, use the built-in TaskManager.
649
+
650
+ ```typescript
651
+ import { Gateway, TaskManager, loadOrCreateIdentity } from 'adp-agent';
652
+
653
+ const { identity } = loadOrCreateIdentity('myapp', 'task-agent', 'TaskAgent');
654
+ const taskManager = new TaskManager();
655
+
656
+ const gateway = new Gateway({
657
+ port: 9900,
658
+ secretKey: identity.secretKey,
659
+ agentId: identity.agentId,
660
+ displayName: 'Task Agent',
661
+ capabilities: ['adp:task.create', 'adp:task.get', 'adp:task.list', 'adp:task.cancel'],
662
+ taskManager,
663
+ customHandlers: {
664
+ 'custom:long-task': async (ws, envelope) => {
665
+ const task = await taskManager.createTask({
666
+ requester: envelope.from,
667
+ action: 'custom:long-task',
668
+ params: envelope.params,
669
+ secretKey: identity.secretKey,
670
+ envelopeId: envelope.id,
671
+ });
672
+
673
+ // Work on the task asynchronously...
674
+ setTimeout(async () => {
675
+ await taskManager.completeTask(task.id, { result: 'done' }, identity.secretKey);
676
+ }, 5000);
677
+
678
+ // Send back the task ID
679
+ const reply = signEnvelope({
680
+ protocol: 'adp/0.2',
681
+ id: generateMessageId(),
682
+ from: identity.agentId,
683
+ to: envelope.from,
684
+ action: 'adp:task.create',
685
+ params: { task_id: task.id, status: 'PENDING' },
686
+ reply_to: envelope.id,
687
+ timestamp: new Date().toISOString(),
688
+ }, identity.secretKey, canonicalize);
689
+
690
+ ws.send(JSON.stringify(reply));
691
+ },
692
+ },
693
+ });
694
+ ```
695
+
696
+ ## Configuration Reference
697
+
698
+ ### GatewayOptions
699
+
700
+ ```typescript
701
+ interface GatewayOptions {
702
+ port: number; // WebSocket server port
703
+ host?: string; // Bind address (default: 'localhost')
704
+ secretKey: Uint8Array; // Ed25519 secret key from identity
705
+ agentId: string; // ADP agent ID from identity
706
+ displayName: string; // Human-readable agent name
707
+ capabilities: (string | Capability)[]; // Declared capabilities
708
+ routes?: Route[]; // Connection routes
709
+ customHandlers?: Record<string, ActionHandler>; // Action handlers
710
+ taskManager?: TaskManager; // Task management
711
+ contacts?: ContactStore; // Contact book
712
+ skipVerification?: boolean; // Skip signature verification (dev only)
713
+ tofuEnabled?: boolean; // Trust On First Use
714
+ communication?: CommunicationConfig; // Webhook/hybrid config
715
+ tls?: { cert: string; key: string }; // TLS options
716
+ }
717
+ ```
718
+
719
+ ## Key Constraints
720
+
721
+ - **Ed25519 keys only**: ADP uses `tweetnacl`'s Ed25519 implementation. Key pairs can be generated with `generateKeyPair()` or loaded via `loadOrCreateIdentity()`.
722
+ - **WebSocket required for Gateway**: The Gateway mode requires WebSocket. For non-WS frameworks, use webhook mode or client-only mode.
723
+ - **mDNS for LAN discovery**: mDNS works only within the same local network. For WAN discovery, use the Registry.
724
+ - **Message size limit**: Messages are limited to 1MB (`MESSAGE_SIZE_LIMIT`).
725
+
726
+ ## Troubleshooting
727
+
728
+ | Problem | Solution |
729
+ |---------|----------|
730
+ | `connectToAgent` fails | Ensure target agent's Gateway is running and reachable. Check firewall rules. |
731
+ | mDNS discovery finds nothing | Verify agents are on the same LAN. Check `ADP_DISABLE_MDNS` env var. |
732
+ | Signature verification fails | Ensure both sides use the same `canonicalize` function. |
733
+ | Port already in use | Use `findAvailablePort()` utility to pick a free port. |