@tjamescouch/agentchat 0.22.1 → 0.23.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.
Files changed (153) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/bin/agentchat.d.ts +7 -0
  3. package/dist/bin/agentchat.d.ts.map +1 -0
  4. package/dist/bin/agentchat.js +1511 -0
  5. package/dist/bin/agentchat.js.map +1 -0
  6. package/dist/lib/allowlist.d.ts +77 -0
  7. package/dist/lib/allowlist.d.ts.map +1 -0
  8. package/dist/lib/allowlist.js +151 -0
  9. package/dist/lib/allowlist.js.map +1 -0
  10. package/dist/lib/client.d.ts +147 -0
  11. package/dist/lib/client.d.ts.map +1 -0
  12. package/dist/lib/client.js +704 -0
  13. package/dist/lib/client.js.map +1 -0
  14. package/dist/lib/daemon.d.ts +122 -0
  15. package/dist/lib/daemon.d.ts.map +1 -0
  16. package/dist/lib/daemon.js +523 -0
  17. package/dist/lib/daemon.js.map +1 -0
  18. package/dist/lib/deploy/akash.d.ts +271 -0
  19. package/dist/lib/deploy/akash.d.ts.map +1 -0
  20. package/dist/lib/deploy/akash.js +671 -0
  21. package/dist/lib/deploy/akash.js.map +1 -0
  22. package/dist/lib/deploy/config.d.ts +62 -0
  23. package/dist/lib/deploy/config.d.ts.map +1 -0
  24. package/dist/lib/deploy/config.js +116 -0
  25. package/dist/lib/deploy/config.js.map +1 -0
  26. package/dist/lib/deploy/docker.d.ts +37 -0
  27. package/dist/lib/deploy/docker.d.ts.map +1 -0
  28. package/dist/lib/deploy/docker.js +122 -0
  29. package/dist/lib/deploy/docker.js.map +1 -0
  30. package/dist/lib/deploy/index.d.ts +11 -0
  31. package/dist/lib/deploy/index.d.ts.map +1 -0
  32. package/dist/lib/deploy/index.js +11 -0
  33. package/dist/lib/deploy/index.js.map +1 -0
  34. package/dist/lib/escrow-hooks.d.ts +199 -0
  35. package/dist/lib/escrow-hooks.d.ts.map +1 -0
  36. package/dist/lib/escrow-hooks.js +221 -0
  37. package/dist/lib/escrow-hooks.js.map +1 -0
  38. package/dist/lib/identity.d.ts +134 -0
  39. package/dist/lib/identity.d.ts.map +1 -0
  40. package/dist/lib/identity.js +334 -0
  41. package/dist/lib/identity.js.map +1 -0
  42. package/dist/lib/jitter.d.ts +42 -0
  43. package/dist/lib/jitter.d.ts.map +1 -0
  44. package/{lib/jitter.ts → dist/lib/jitter.js} +10 -18
  45. package/dist/lib/jitter.js.map +1 -0
  46. package/dist/lib/proposals.d.ts +223 -0
  47. package/dist/lib/proposals.d.ts.map +1 -0
  48. package/dist/lib/proposals.js +379 -0
  49. package/dist/lib/proposals.js.map +1 -0
  50. package/dist/lib/protocol.d.ts +220 -0
  51. package/dist/lib/protocol.d.ts.map +1 -0
  52. package/dist/lib/protocol.js +507 -0
  53. package/dist/lib/protocol.js.map +1 -0
  54. package/dist/lib/receipts.d.ts +134 -0
  55. package/dist/lib/receipts.d.ts.map +1 -0
  56. package/dist/lib/receipts.js +270 -0
  57. package/dist/lib/receipts.js.map +1 -0
  58. package/dist/lib/reputation.d.ts +250 -0
  59. package/dist/lib/reputation.d.ts.map +1 -0
  60. package/dist/lib/reputation.js +586 -0
  61. package/dist/lib/reputation.js.map +1 -0
  62. package/dist/lib/security.d.ts +27 -0
  63. package/dist/lib/security.d.ts.map +1 -0
  64. package/dist/lib/security.js +150 -0
  65. package/dist/lib/security.js.map +1 -0
  66. package/dist/lib/server/handlers/admin.d.ts +26 -0
  67. package/dist/lib/server/handlers/admin.d.ts.map +1 -0
  68. package/dist/lib/server/handlers/admin.js +76 -0
  69. package/dist/lib/server/handlers/admin.js.map +1 -0
  70. package/dist/lib/server/handlers/identity.d.ts +36 -0
  71. package/dist/lib/server/handlers/identity.d.ts.map +1 -0
  72. package/dist/lib/server/handlers/identity.js +330 -0
  73. package/dist/lib/server/handlers/identity.js.map +1 -0
  74. package/dist/lib/server/handlers/index.d.ts +10 -0
  75. package/dist/lib/server/handlers/index.d.ts.map +1 -0
  76. package/dist/lib/server/handlers/index.js +15 -0
  77. package/dist/lib/server/handlers/index.js.map +1 -0
  78. package/dist/lib/server/handlers/message.d.ts +47 -0
  79. package/dist/lib/server/handlers/message.d.ts.map +1 -0
  80. package/dist/lib/server/handlers/message.js +265 -0
  81. package/dist/lib/server/handlers/message.js.map +1 -0
  82. package/dist/lib/server/handlers/presence.d.ts +18 -0
  83. package/dist/lib/server/handlers/presence.d.ts.map +1 -0
  84. package/dist/lib/server/handlers/presence.js +35 -0
  85. package/dist/lib/server/handlers/presence.js.map +1 -0
  86. package/dist/lib/server/handlers/proposal.d.ts +38 -0
  87. package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
  88. package/dist/lib/server/handlers/proposal.js +273 -0
  89. package/dist/lib/server/handlers/proposal.js.map +1 -0
  90. package/dist/lib/server/handlers/skills.d.ts +22 -0
  91. package/dist/lib/server/handlers/skills.d.ts.map +1 -0
  92. package/dist/lib/server/handlers/skills.js +119 -0
  93. package/dist/lib/server/handlers/skills.js.map +1 -0
  94. package/dist/lib/server-directory.d.ts +85 -0
  95. package/dist/lib/server-directory.d.ts.map +1 -0
  96. package/dist/lib/server-directory.js +177 -0
  97. package/dist/lib/server-directory.js.map +1 -0
  98. package/dist/lib/server.d.ts +162 -0
  99. package/dist/lib/server.d.ts.map +1 -0
  100. package/dist/lib/server.js +602 -0
  101. package/dist/lib/server.js.map +1 -0
  102. package/dist/lib/types.d.ts +461 -0
  103. package/dist/lib/types.d.ts.map +1 -0
  104. package/dist/lib/types.js +98 -0
  105. package/dist/lib/types.js.map +1 -0
  106. package/package.json +22 -13
  107. package/bin/agentchat.js +0 -1617
  108. package/bin/agentchat.ts +0 -1812
  109. package/lib/allowlist.js +0 -162
  110. package/lib/chat.py +0 -241
  111. package/lib/client.js +0 -821
  112. package/lib/client.ts +0 -877
  113. package/lib/daemon.js +0 -562
  114. package/lib/daemon.ts +0 -662
  115. package/lib/deploy/akash.js +0 -811
  116. package/lib/deploy/config.js +0 -128
  117. package/lib/deploy/docker.js +0 -132
  118. package/lib/deploy/index.js +0 -24
  119. package/lib/elo_swarm.py +0 -569
  120. package/lib/escrow-hooks.js +0 -237
  121. package/lib/escrow-hooks.ts +0 -391
  122. package/lib/identity.js +0 -376
  123. package/lib/identity.ts +0 -412
  124. package/lib/jitter.js +0 -54
  125. package/lib/proposals.js +0 -426
  126. package/lib/proposals.ts +0 -612
  127. package/lib/protocol.js +0 -516
  128. package/lib/receipts.js +0 -294
  129. package/lib/receipts.ts +0 -359
  130. package/lib/reputation.js +0 -664
  131. package/lib/reputation.ts +0 -790
  132. package/lib/security.js +0 -183
  133. package/lib/server/handlers/admin.js +0 -94
  134. package/lib/server/handlers/identity.js +0 -258
  135. package/lib/server/handlers/index.js +0 -42
  136. package/lib/server/handlers/message.js +0 -319
  137. package/lib/server/handlers/presence.js +0 -45
  138. package/lib/server/handlers/proposal.js +0 -358
  139. package/lib/server/handlers/skills.js +0 -141
  140. package/lib/server-directory.js +0 -190
  141. package/lib/server-directory.ts +0 -232
  142. package/lib/server.js +0 -633
  143. package/lib/server.ts +0 -698
  144. package/lib/supervisor/USAGE.md +0 -110
  145. package/lib/supervisor/agent-health.sh +0 -107
  146. package/lib/supervisor/agent-monitor.sh +0 -123
  147. package/lib/supervisor/agent-supervisor.sh +0 -135
  148. package/lib/supervisor/agentctl.sh +0 -266
  149. package/lib/supervisor/god-backup.sh +0 -126
  150. package/lib/supervisor/god-watchdog.sh +0 -107
  151. package/lib/supervisor/killswitch.sh +0 -43
  152. package/lib/supervisor/notify.sh +0 -19
  153. package/lib/types.ts +0 -433
package/lib/client.ts DELETED
@@ -1,877 +0,0 @@
1
- /**
2
- * AgentChat Client
3
- * Connect to agentchat servers from Node.js or CLI
4
- */
5
-
6
- import WebSocket from 'ws';
7
- import { EventEmitter } from 'events';
8
- import {
9
- ClientMessageType,
10
- ServerMessageType,
11
- WelcomeMessage,
12
- ServerMsgMessage,
13
- JoinedMessage,
14
- LeftMessage,
15
- AgentJoinedMessage,
16
- AgentLeftMessage,
17
- ChannelsMessage,
18
- AgentsMessage,
19
- ErrorMessage,
20
- PongMessage,
21
- ServerProposalMessage,
22
- SkillsRegisteredMessage,
23
- SearchResultsMessage,
24
- ChannelInfo,
25
- AgentInfo,
26
- ServerMessage,
27
- } from './types.js';
28
- import {
29
- createMessage,
30
- serialize,
31
- parse,
32
- generateNonce
33
- } from './protocol.js';
34
- import { Identity } from './identity.js';
35
- import {
36
- getProposalSigningContent,
37
- getAcceptSigningContent,
38
- getRejectSigningContent,
39
- getCompleteSigningContent,
40
- getDisputeSigningContent
41
- } from './proposals.js';
42
-
43
- // ============ Client-specific Types ============
44
-
45
- export interface ClientOptions {
46
- server: string;
47
- name?: string;
48
- pubkey?: string | null;
49
- identity?: string | null;
50
- }
51
-
52
- export interface ProposalOptions {
53
- task: string;
54
- amount?: number;
55
- currency?: string;
56
- payment_code?: string;
57
- terms?: string;
58
- expires?: number;
59
- elo_stake?: number;
60
- }
61
-
62
- export interface VerificationResult {
63
- verified: boolean;
64
- agent?: string;
65
- target?: string;
66
- pubkey?: string;
67
- reason?: string;
68
- request_id?: string;
69
- }
70
-
71
- type PendingRequestCallback = (result: unknown) => void;
72
-
73
- // ============ AgentChatClient Class ============
74
-
75
- export class AgentChatClient extends EventEmitter {
76
- server: string;
77
- name: string;
78
- pubkey: string | null;
79
- identityPath: string | null;
80
-
81
- private _identity: Identity | null;
82
- private ws: WebSocket | null;
83
- agentId: string | null;
84
- connected: boolean;
85
- channels: Set<string>;
86
-
87
- private _pendingRequests: Map<number, PendingRequestCallback>;
88
- private _requestId: number;
89
- private _autoVerifyHandler: ((msg: ServerMessage) => void) | null;
90
-
91
- constructor(options: ClientOptions) {
92
- super();
93
- this.server = options.server;
94
- this.name = options.name || `agent-${Date.now()}`;
95
- this.pubkey = options.pubkey || null;
96
-
97
- // Identity support
98
- this.identityPath = options.identity || null;
99
- this._identity = null;
100
-
101
- this.ws = null;
102
- this.agentId = null;
103
- this.connected = false;
104
- this.channels = new Set();
105
-
106
- this._pendingRequests = new Map();
107
- this._requestId = 0;
108
- this._autoVerifyHandler = null;
109
- }
110
-
111
- /**
112
- * Load identity from file, or create new one if it doesn't exist
113
- */
114
- private async _loadIdentity(): Promise<void> {
115
- if (this.identityPath) {
116
- try {
117
- // Check if identity file exists
118
- const exists = await Identity.exists(this.identityPath);
119
- if (exists) {
120
- this._identity = await Identity.load(this.identityPath);
121
- } else {
122
- // Generate new identity and save it
123
- this._identity = Identity.generate(this.name);
124
- await this._identity.save(this.identityPath);
125
- }
126
- this.name = this._identity.name;
127
- this.pubkey = this._identity.pubkey;
128
- } catch (err) {
129
- const error = err as Error;
130
- throw new Error(`Failed to load/create identity at ${this.identityPath}: ${error.message}`);
131
- }
132
- }
133
- }
134
-
135
- /**
136
- * Connect to the server and identify
137
- */
138
- async connect(): Promise<WelcomeMessage> {
139
- // Load identity if path provided
140
- await this._loadIdentity();
141
-
142
- return new Promise((resolve, reject) => {
143
- this.ws = new WebSocket(this.server);
144
-
145
- this.ws.on('open', () => {
146
- // Send identify
147
- this._send({
148
- type: ClientMessageType.IDENTIFY,
149
- name: this.name,
150
- pubkey: this.pubkey
151
- });
152
- });
153
-
154
- this.ws.on('message', (data: WebSocket.Data) => {
155
- this._handleMessage(data.toString());
156
- });
157
-
158
- this.ws.on('close', () => {
159
- this.connected = false;
160
- this.emit('disconnect');
161
- });
162
-
163
- this.ws.on('error', (err: Error) => {
164
- this.emit('error', err);
165
- if (!this.connected) {
166
- reject(err);
167
- }
168
- });
169
-
170
- // Wait for WELCOME
171
- this.once('welcome', (info: WelcomeMessage) => {
172
- this.connected = true;
173
- this.agentId = info.agent_id;
174
- resolve(info);
175
- });
176
-
177
- // Handle connection error
178
- this.once('error', (err: Error) => {
179
- if (!this.connected) {
180
- reject(err);
181
- }
182
- });
183
- });
184
- }
185
-
186
- /**
187
- * Disconnect from server
188
- */
189
- disconnect(): void {
190
- if (this.ws) {
191
- this.ws.close();
192
- this.ws = null;
193
- }
194
- }
195
-
196
- /**
197
- * Join a channel
198
- */
199
- async join(channel: string): Promise<JoinedMessage> {
200
- this._send({
201
- type: ClientMessageType.JOIN,
202
- channel
203
- });
204
-
205
- return new Promise((resolve, reject) => {
206
- const onJoined = (msg: JoinedMessage): void => {
207
- if (msg.channel === channel) {
208
- this.removeListener('error', onError);
209
- this.channels.add(channel);
210
- resolve(msg);
211
- }
212
- };
213
-
214
- const onError = (msg: ErrorMessage): void => {
215
- this.removeListener('joined', onJoined);
216
- reject(new Error(msg.message));
217
- };
218
-
219
- this.once('joined', onJoined);
220
- this.once('error', onError);
221
- });
222
- }
223
-
224
- /**
225
- * Leave a channel
226
- */
227
- async leave(channel: string): Promise<void> {
228
- this._send({
229
- type: ClientMessageType.LEAVE,
230
- channel
231
- });
232
- this.channels.delete(channel);
233
- }
234
-
235
- /**
236
- * Send a message to a channel or agent
237
- */
238
- async send(to: string, content: string): Promise<void> {
239
- const msg: Record<string, unknown> = {
240
- type: ClientMessageType.MSG,
241
- to,
242
- content
243
- };
244
-
245
- // Sign message if identity available
246
- if (this._identity && this._identity.privkey) {
247
- msg.ts = Date.now();
248
- const dataToSign = JSON.stringify({
249
- to: msg.to,
250
- content: msg.content,
251
- ts: msg.ts
252
- });
253
- msg.sig = this._identity.sign(dataToSign);
254
- }
255
-
256
- this._send(msg);
257
- }
258
-
259
- /**
260
- * Send a direct message (alias for send with @target)
261
- */
262
- async dm(agent: string, content: string): Promise<void> {
263
- const target = agent.startsWith('@') ? agent : `@${agent}`;
264
- return this.send(target, content);
265
- }
266
-
267
- /**
268
- * List available channels
269
- */
270
- async listChannels(): Promise<ChannelInfo[]> {
271
- this._send({
272
- type: ClientMessageType.LIST_CHANNELS
273
- });
274
-
275
- return new Promise((resolve) => {
276
- this.once('channels', (msg: ChannelsMessage) => {
277
- resolve(msg.list);
278
- });
279
- });
280
- }
281
-
282
- /**
283
- * List agents in a channel
284
- */
285
- async listAgents(channel: string): Promise<AgentInfo[]> {
286
- this._send({
287
- type: ClientMessageType.LIST_AGENTS,
288
- channel
289
- });
290
-
291
- return new Promise((resolve) => {
292
- this.once('agents', (msg: AgentsMessage) => {
293
- resolve(msg.list);
294
- });
295
- });
296
- }
297
-
298
- /**
299
- * Create a new channel
300
- */
301
- async createChannel(channel: string, inviteOnly: boolean = false): Promise<JoinedMessage> {
302
- this._send({
303
- type: ClientMessageType.CREATE_CHANNEL,
304
- channel,
305
- invite_only: inviteOnly
306
- });
307
-
308
- return new Promise((resolve, reject) => {
309
- const onJoined = (msg: JoinedMessage): void => {
310
- if (msg.channel === channel) {
311
- this.removeListener('error', onError);
312
- this.channels.add(channel);
313
- resolve(msg);
314
- }
315
- };
316
-
317
- const onError = (msg: ErrorMessage): void => {
318
- this.removeListener('joined', onJoined);
319
- reject(new Error(msg.message));
320
- };
321
-
322
- this.once('joined', onJoined);
323
- this.once('error', onError);
324
- });
325
- }
326
-
327
- /**
328
- * Invite an agent to a channel
329
- */
330
- async invite(channel: string, agent: string): Promise<void> {
331
- const target = agent.startsWith('@') ? agent : `@${agent}`;
332
- this._send({
333
- type: ClientMessageType.INVITE,
334
- channel,
335
- agent: target
336
- });
337
- }
338
-
339
- /**
340
- * Send ping to server
341
- */
342
- ping(): void {
343
- this._send({ type: ClientMessageType.PING });
344
- }
345
-
346
- // ===== PROPOSAL/NEGOTIATION METHODS =====
347
-
348
- /**
349
- * Send a proposal to another agent
350
- * Requires persistent identity for signing
351
- */
352
- async propose(to: string, proposal: ProposalOptions): Promise<ServerProposalMessage> {
353
- if (!this._identity || !this._identity.privkey) {
354
- throw new Error('Proposals require persistent identity. Use --identity flag.');
355
- }
356
-
357
- const target = to.startsWith('@') ? to : `@${to}`;
358
-
359
- const msg: Record<string, unknown> = {
360
- type: ClientMessageType.PROPOSAL,
361
- to: target,
362
- task: proposal.task,
363
- amount: proposal.amount,
364
- currency: proposal.currency,
365
- payment_code: proposal.payment_code,
366
- terms: proposal.terms,
367
- expires: proposal.expires,
368
- elo_stake: proposal.elo_stake
369
- };
370
-
371
- // Sign the proposal
372
- const sigContent = getProposalSigningContent(msg);
373
- msg.sig = this._identity.sign(sigContent);
374
-
375
- this._send(msg);
376
-
377
- // Wait for the proposal response with ID
378
- return new Promise((resolve, reject) => {
379
- const timeout = setTimeout(() => {
380
- this.removeListener('proposal', onProposal);
381
- this.removeListener('error', onError);
382
- reject(new Error('Proposal timeout'));
383
- }, 10000);
384
-
385
- const onProposal = (p: ServerProposalMessage): void => {
386
- if (p.to === target && p.from === this.agentId) {
387
- clearTimeout(timeout);
388
- this.removeListener('error', onError);
389
- resolve(p);
390
- }
391
- };
392
-
393
- const onError = (err: ErrorMessage): void => {
394
- clearTimeout(timeout);
395
- this.removeListener('proposal', onProposal);
396
- reject(new Error(err.message));
397
- };
398
-
399
- this.once('proposal', onProposal);
400
- this.once('error', onError);
401
- });
402
- }
403
-
404
- /**
405
- * Accept a proposal
406
- */
407
- async accept(proposalId: string, payment_code: string | null = null, elo_stake: number | null = null): Promise<ServerMessage> {
408
- if (!this._identity || !this._identity.privkey) {
409
- throw new Error('Accepting proposals requires persistent identity.');
410
- }
411
-
412
- const sigContent = getAcceptSigningContent(proposalId, payment_code || '', elo_stake || '');
413
- const sig = this._identity.sign(sigContent);
414
-
415
- const msg = {
416
- type: ClientMessageType.ACCEPT,
417
- proposal_id: proposalId,
418
- payment_code,
419
- elo_stake,
420
- sig
421
- };
422
-
423
- this._send(msg);
424
-
425
- return new Promise((resolve, reject) => {
426
- const timeout = setTimeout(() => {
427
- this.removeListener('accept', onAccept);
428
- this.removeListener('error', onError);
429
- reject(new Error('Accept timeout'));
430
- }, 10000);
431
-
432
- const onAccept = (response: ServerMessage & { proposal_id?: string }): void => {
433
- if (response.proposal_id === proposalId) {
434
- clearTimeout(timeout);
435
- this.removeListener('error', onError);
436
- resolve(response);
437
- }
438
- };
439
-
440
- const onError = (err: ErrorMessage): void => {
441
- clearTimeout(timeout);
442
- this.removeListener('accept', onAccept);
443
- reject(new Error(err.message));
444
- };
445
-
446
- this.once('accept', onAccept);
447
- this.once('error', onError);
448
- });
449
- }
450
-
451
- /**
452
- * Reject a proposal
453
- */
454
- async reject(proposalId: string, reason: string | null = null): Promise<ServerMessage> {
455
- if (!this._identity || !this._identity.privkey) {
456
- throw new Error('Rejecting proposals requires persistent identity.');
457
- }
458
-
459
- const sigContent = getRejectSigningContent(proposalId, reason || '');
460
- const sig = this._identity.sign(sigContent);
461
-
462
- const msg = {
463
- type: ClientMessageType.REJECT,
464
- proposal_id: proposalId,
465
- reason,
466
- sig
467
- };
468
-
469
- this._send(msg);
470
-
471
- return new Promise((resolve, reject) => {
472
- const timeout = setTimeout(() => {
473
- this.removeListener('reject', onReject);
474
- this.removeListener('error', onError);
475
- reject(new Error('Reject timeout'));
476
- }, 10000);
477
-
478
- const onReject = (response: ServerMessage & { proposal_id?: string }): void => {
479
- if (response.proposal_id === proposalId) {
480
- clearTimeout(timeout);
481
- this.removeListener('error', onError);
482
- resolve(response);
483
- }
484
- };
485
-
486
- const onError = (err: ErrorMessage): void => {
487
- clearTimeout(timeout);
488
- this.removeListener('reject', onReject);
489
- reject(new Error(err.message));
490
- };
491
-
492
- this.once('reject', onReject);
493
- this.once('error', onError);
494
- });
495
- }
496
-
497
- /**
498
- * Mark a proposal as complete
499
- */
500
- async complete(proposalId: string, proof: string | null = null): Promise<ServerMessage> {
501
- if (!this._identity || !this._identity.privkey) {
502
- throw new Error('Completing proposals requires persistent identity.');
503
- }
504
-
505
- const sigContent = getCompleteSigningContent(proposalId, proof || '');
506
- const sig = this._identity.sign(sigContent);
507
-
508
- const msg = {
509
- type: ClientMessageType.COMPLETE,
510
- proposal_id: proposalId,
511
- proof,
512
- sig
513
- };
514
-
515
- this._send(msg);
516
-
517
- return new Promise((resolve, reject) => {
518
- const timeout = setTimeout(() => {
519
- this.removeListener('complete', onComplete);
520
- this.removeListener('error', onError);
521
- reject(new Error('Complete timeout'));
522
- }, 10000);
523
-
524
- const onComplete = (response: ServerMessage & { proposal_id?: string }): void => {
525
- if (response.proposal_id === proposalId) {
526
- clearTimeout(timeout);
527
- this.removeListener('error', onError);
528
- resolve(response);
529
- }
530
- };
531
-
532
- const onError = (err: ErrorMessage): void => {
533
- clearTimeout(timeout);
534
- this.removeListener('complete', onComplete);
535
- reject(new Error(err.message));
536
- };
537
-
538
- this.once('complete', onComplete);
539
- this.once('error', onError);
540
- });
541
- }
542
-
543
- /**
544
- * Dispute a proposal
545
- */
546
- async dispute(proposalId: string, reason: string): Promise<ServerMessage> {
547
- if (!this._identity || !this._identity.privkey) {
548
- throw new Error('Disputing proposals requires persistent identity.');
549
- }
550
-
551
- if (!reason) {
552
- throw new Error('Dispute reason is required');
553
- }
554
-
555
- const sigContent = getDisputeSigningContent(proposalId, reason);
556
- const sig = this._identity.sign(sigContent);
557
-
558
- const msg = {
559
- type: ClientMessageType.DISPUTE,
560
- proposal_id: proposalId,
561
- reason,
562
- sig
563
- };
564
-
565
- this._send(msg);
566
-
567
- return new Promise((resolve, reject) => {
568
- const timeout = setTimeout(() => {
569
- this.removeListener('dispute', onDispute);
570
- this.removeListener('error', onError);
571
- reject(new Error('Dispute timeout'));
572
- }, 10000);
573
-
574
- const onDispute = (response: ServerMessage & { proposal_id?: string }): void => {
575
- if (response.proposal_id === proposalId) {
576
- clearTimeout(timeout);
577
- this.removeListener('error', onError);
578
- resolve(response);
579
- }
580
- };
581
-
582
- const onError = (err: ErrorMessage): void => {
583
- clearTimeout(timeout);
584
- this.removeListener('dispute', onDispute);
585
- reject(new Error(err.message));
586
- };
587
-
588
- this.once('dispute', onDispute);
589
- this.once('error', onError);
590
- });
591
- }
592
-
593
- // ===== IDENTITY VERIFICATION METHODS =====
594
-
595
- /**
596
- * Request identity verification from another agent
597
- * Sends a challenge nonce that the target must sign to prove they control their identity
598
- */
599
- async verify(target: string): Promise<VerificationResult> {
600
- const targetAgent = target.startsWith('@') ? target : `@${target}`;
601
- const nonce = generateNonce();
602
-
603
- const msg = {
604
- type: ClientMessageType.VERIFY_REQUEST,
605
- target: targetAgent,
606
- nonce
607
- };
608
-
609
- this._send(msg);
610
-
611
- return new Promise((resolve, reject) => {
612
- const timeout = setTimeout(() => {
613
- this.removeListener('verify_success', onSuccess);
614
- this.removeListener('verify_failed', onFailed);
615
- this.removeListener('error', onError);
616
- reject(new Error('Verification timeout'));
617
- }, 35000); // Slightly longer than server timeout
618
-
619
- const onSuccess = (response: ServerMessage & { agent?: string; target?: string; pubkey?: string; request_id?: string }): void => {
620
- if (response.agent === targetAgent || response.target === targetAgent) {
621
- clearTimeout(timeout);
622
- this.removeListener('verify_failed', onFailed);
623
- this.removeListener('error', onError);
624
- resolve({
625
- verified: true,
626
- agent: response.agent,
627
- pubkey: response.pubkey,
628
- request_id: response.request_id
629
- });
630
- }
631
- };
632
-
633
- const onFailed = (response: ServerMessage & { target?: string; reason?: string; request_id?: string }): void => {
634
- if (response.target === targetAgent) {
635
- clearTimeout(timeout);
636
- this.removeListener('verify_success', onSuccess);
637
- this.removeListener('error', onError);
638
- resolve({
639
- verified: false,
640
- target: response.target,
641
- reason: response.reason,
642
- request_id: response.request_id
643
- });
644
- }
645
- };
646
-
647
- const onError = (err: ErrorMessage): void => {
648
- clearTimeout(timeout);
649
- this.removeListener('verify_success', onSuccess);
650
- this.removeListener('verify_failed', onFailed);
651
- reject(new Error(err.message));
652
- };
653
-
654
- this.on('verify_success', onSuccess);
655
- this.on('verify_failed', onFailed);
656
- this.once('error', onError);
657
- });
658
- }
659
-
660
- /**
661
- * Respond to a verification request by signing the nonce
662
- * This is typically called automatically when a VERIFY_REQUEST is received
663
- */
664
- async respondToVerification(requestId: string, nonce: string): Promise<void> {
665
- if (!this._identity || !this._identity.privkey) {
666
- throw new Error('Responding to verification requires persistent identity.');
667
- }
668
-
669
- const sig = this._identity.sign(nonce);
670
-
671
- const msg = {
672
- type: ClientMessageType.VERIFY_RESPONSE,
673
- request_id: requestId,
674
- nonce,
675
- sig
676
- };
677
-
678
- this._send(msg);
679
- }
680
-
681
- /**
682
- * Enable automatic verification response
683
- * When enabled, the client will automatically respond to VERIFY_REQUEST messages
684
- */
685
- enableAutoVerification(enabled: boolean = true): void {
686
- if (enabled) {
687
- this._autoVerifyHandler = (msg: ServerMessage & { request_id?: string; nonce?: string; from?: string }): void => {
688
- if (msg.request_id && msg.nonce && msg.from) {
689
- this.respondToVerification(msg.request_id, msg.nonce)
690
- .catch(err => this.emit('error', { message: `Auto-verification failed: ${(err as Error).message}` }));
691
- }
692
- };
693
- this.on('verify_request', this._autoVerifyHandler);
694
- } else if (this._autoVerifyHandler) {
695
- this.removeListener('verify_request', this._autoVerifyHandler);
696
- this._autoVerifyHandler = null;
697
- }
698
- }
699
-
700
- private _send(msg: Record<string, unknown>): void {
701
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
702
- this.ws.send(serialize(msg));
703
- }
704
- }
705
-
706
- /**
707
- * Send a raw message (for protocol extensions)
708
- */
709
- sendRaw(msg: Record<string, unknown>): void {
710
- this._send(msg);
711
- }
712
-
713
- private _handleMessage(data: string): void {
714
- let msg: ServerMessage;
715
- try {
716
- msg = parse(data) as ServerMessage;
717
- } catch (e) {
718
- this.emit('error', { message: 'Invalid JSON from server' });
719
- return;
720
- }
721
-
722
- // Emit raw message
723
- this.emit('raw', msg);
724
-
725
- // Handle by type
726
- switch (msg.type) {
727
- case ServerMessageType.WELCOME:
728
- this.emit('welcome', msg);
729
- break;
730
-
731
- case ServerMessageType.MSG:
732
- this.emit('message', msg);
733
- break;
734
-
735
- case ServerMessageType.JOINED:
736
- this.emit('joined', msg);
737
- break;
738
-
739
- case ServerMessageType.LEFT:
740
- this.emit('left', msg);
741
- break;
742
-
743
- case ServerMessageType.AGENT_JOINED:
744
- this.emit('agent_joined', msg);
745
- break;
746
-
747
- case ServerMessageType.AGENT_LEFT:
748
- this.emit('agent_left', msg);
749
- break;
750
-
751
- case ServerMessageType.CHANNELS:
752
- this.emit('channels', msg);
753
- break;
754
-
755
- case ServerMessageType.AGENTS:
756
- this.emit('agents', msg);
757
- break;
758
-
759
- case ServerMessageType.ERROR:
760
- this.emit('error', msg);
761
- break;
762
-
763
- case ServerMessageType.PONG:
764
- this.emit('pong', msg);
765
- break;
766
-
767
- // Proposal/negotiation messages
768
- case ServerMessageType.PROPOSAL:
769
- this.emit('proposal', msg);
770
- break;
771
-
772
- case ServerMessageType.ACCEPT:
773
- this.emit('accept', msg);
774
- break;
775
-
776
- case ServerMessageType.REJECT:
777
- this.emit('reject', msg);
778
- break;
779
-
780
- case ServerMessageType.COMPLETE:
781
- this.emit('complete', msg);
782
- break;
783
-
784
- case ServerMessageType.DISPUTE:
785
- this.emit('dispute', msg);
786
- break;
787
-
788
- // Skills discovery messages
789
- case ServerMessageType.SKILLS_REGISTERED:
790
- this.emit('skills_registered', msg);
791
- this.emit('message', msg);
792
- break;
793
-
794
- case ServerMessageType.SEARCH_RESULTS:
795
- this.emit('search_results', msg);
796
- this.emit('message', msg);
797
- break;
798
-
799
- // Identity verification messages
800
- case ServerMessageType.VERIFY_REQUEST:
801
- this.emit('verify_request', msg);
802
- break;
803
-
804
- case ServerMessageType.VERIFY_RESPONSE:
805
- this.emit('verify_response', msg);
806
- break;
807
-
808
- case ServerMessageType.VERIFY_SUCCESS:
809
- this.emit('verify_success', msg);
810
- break;
811
-
812
- case ServerMessageType.VERIFY_FAILED:
813
- this.emit('verify_failed', msg);
814
- break;
815
- }
816
- }
817
- }
818
-
819
- /**
820
- * Quick send - connect, send message, disconnect
821
- */
822
- export async function quickSend(
823
- server: string,
824
- name: string,
825
- to: string,
826
- content: string,
827
- identityPath: string | null = null
828
- ): Promise<void> {
829
- const client = new AgentChatClient({ server, name, identity: identityPath });
830
- await client.connect();
831
-
832
- // Join channel if needed
833
- if (to.startsWith('#')) {
834
- await client.join(to);
835
- }
836
-
837
- await client.send(to, content);
838
-
839
- // Small delay to ensure message is sent
840
- await new Promise(r => setTimeout(r, 100));
841
-
842
- client.disconnect();
843
- }
844
-
845
- /**
846
- * Listen mode - connect, join channels, stream messages
847
- */
848
- export async function listen(
849
- server: string,
850
- name: string,
851
- channels: string[],
852
- callback: (msg: ServerMessage) => void,
853
- identityPath: string | null = null
854
- ): Promise<AgentChatClient> {
855
- const client = new AgentChatClient({ server, name, identity: identityPath });
856
- await client.connect();
857
-
858
- for (const channel of channels) {
859
- await client.join(channel);
860
- }
861
-
862
- client.on('message', callback);
863
- client.on('agent_joined', callback);
864
- client.on('agent_left', callback);
865
-
866
- // Also stream proposal events
867
- client.on('proposal', callback);
868
- client.on('accept', callback);
869
- client.on('reject', callback);
870
- client.on('complete', callback);
871
- client.on('dispute', callback);
872
-
873
- return client;
874
- }
875
-
876
- // Re-export security utilities
877
- export { checkDirectorySafety, enforceDirectorySafety } from './security.js';