@tjamescouch/agentchat 0.22.0 → 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 (135) 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 → dist/lib}/jitter.js +16 -19
  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/lib/chat.py +0 -241
  109. package/lib/client.js +0 -821
  110. package/lib/daemon.js +0 -562
  111. package/lib/deploy/akash.js +0 -811
  112. package/lib/deploy/config.js +0 -128
  113. package/lib/deploy/docker.js +0 -132
  114. package/lib/deploy/index.js +0 -24
  115. package/lib/elo_swarm.py +0 -569
  116. package/lib/escrow-hooks.js +0 -237
  117. package/lib/identity.js +0 -376
  118. package/lib/proposals.js +0 -426
  119. package/lib/protocol.js +0 -484
  120. package/lib/receipts.js +0 -294
  121. package/lib/reputation.js +0 -664
  122. package/lib/security.js +0 -183
  123. package/lib/server/handlers/identity.js +0 -242
  124. package/lib/server/handlers/index.js +0 -42
  125. package/lib/server/handlers/message.js +0 -306
  126. package/lib/server/handlers/presence.js +0 -44
  127. package/lib/server/handlers/proposal.js +0 -358
  128. package/lib/server/handlers/skills.js +0 -141
  129. package/lib/server-directory.js +0 -181
  130. package/lib/server.js +0 -528
  131. package/lib/supervisor/USAGE.md +0 -110
  132. package/lib/supervisor/agent-supervisor.sh +0 -135
  133. package/lib/supervisor/agentctl.sh +0 -250
  134. package/lib/supervisor/killswitch.sh +0 -36
  135. package/lib/supervisor/notify.sh +0 -19
package/lib/server.js DELETED
@@ -1,528 +0,0 @@
1
- /**
2
- * AgentChat Server
3
- * WebSocket relay for agent-to-agent communication
4
- */
5
-
6
- import { WebSocketServer } from 'ws';
7
- import http from 'http';
8
- import https from 'https';
9
- import fs from 'fs';
10
- import {
11
- ClientMessageType,
12
- ServerMessageType,
13
- ErrorCode,
14
- createMessage,
15
- createError,
16
- validateClientMessage,
17
- serialize,
18
- } from './protocol.js';
19
- import { ProposalStore } from './proposals.js';
20
- import { ReputationStore } from './reputation.js';
21
- import { EscrowHooks } from './escrow-hooks.js';
22
-
23
- // Import extracted handlers
24
- import {
25
- handleMsg,
26
- handleJoin,
27
- handleLeave,
28
- handleListChannels,
29
- handleListAgents,
30
- handleCreateChannel,
31
- handleInvite,
32
- } from './server/handlers/message.js';
33
- import {
34
- handleProposal,
35
- handleAccept,
36
- handleReject,
37
- handleComplete,
38
- handleDispute,
39
- } from './server/handlers/proposal.js';
40
- import {
41
- handleIdentify,
42
- handleVerifyRequest,
43
- handleVerifyResponse,
44
- } from './server/handlers/identity.js';
45
- import {
46
- handleRegisterSkills,
47
- handleSearchSkills,
48
- } from './server/handlers/skills.js';
49
- import {
50
- handleSetPresence,
51
- } from './server/handlers/presence.js';
52
-
53
- export class AgentChatServer {
54
- constructor(options = {}) {
55
- this.port = options.port || 6667;
56
- this.host = options.host || '0.0.0.0';
57
- this.serverName = options.name || 'agentchat';
58
- this.logMessages = options.logMessages || false;
59
-
60
- // TLS options
61
- this.tlsCert = options.cert || null;
62
- this.tlsKey = options.key || null;
63
-
64
- // Rate limiting: 1 message per second per agent
65
- this.rateLimitMs = options.rateLimitMs || 1000;
66
-
67
- // Message buffer size per channel (for replay on join)
68
- this.messageBufferSize = options.messageBufferSize || 20;
69
-
70
- // State
71
- this.agents = new Map(); // ws -> agent info
72
- this.agentById = new Map(); // id -> ws
73
- this.channels = new Map(); // channel name -> channel info
74
- this.lastMessageTime = new Map(); // ws -> timestamp of last message
75
- this.pubkeyToId = new Map(); // pubkey -> stable agent ID (for persistent identity)
76
-
77
- // Idle prompt settings
78
- this.idleTimeoutMs = options.idleTimeoutMs || 5 * 60 * 1000; // 5 minutes default
79
- this.idleCheckInterval = null;
80
- this.channelLastActivity = new Map(); // channel name -> timestamp
81
-
82
- // Conversation starters for idle prompts
83
- this.conversationStarters = [
84
- "It's quiet here. What's everyone working on?",
85
- "Any agents want to test the proposal system? Try: PROPOSE @agent \"task\" --amount 0",
86
- "Topic: What capabilities would make agent coordination more useful?",
87
- "Looking for collaborators? Post your skills and what you're building.",
88
- "Challenge: Describe your most interesting current project in one sentence.",
89
- "Question: What's the hardest part about agent-to-agent coordination?",
90
- "Idle hands... anyone want to pair on a spec or code review?",
91
- ];
92
-
93
- // Create default channels
94
- this._createChannel('#general', false);
95
- this._createChannel('#agents', false);
96
- this._createChannel('#discovery', false); // For skill announcements
97
-
98
- // Proposal store for structured negotiations
99
- this.proposals = new ProposalStore();
100
-
101
- // Skills registry: agentId -> { skills: [], registered_at, sig }
102
- this.skillsRegistry = new Map();
103
-
104
- // Reputation store for ELO ratings
105
- this.reputationStore = new ReputationStore();
106
-
107
- // Escrow hooks for external integrations
108
- this.escrowHooks = new EscrowHooks({ logger: options.logger || console });
109
-
110
- // Register external escrow handlers if provided
111
- if (options.escrowHandlers) {
112
- for (const [event, handler] of Object.entries(options.escrowHandlers)) {
113
- this.escrowHooks.on(event, handler);
114
- }
115
- }
116
-
117
- // Pending verification requests: request_id -> { from, target, nonce, expires }
118
- this.pendingVerifications = new Map();
119
- this.verificationTimeoutMs = options.verificationTimeoutMs || 30000; // 30 seconds default
120
-
121
- this.wss = null;
122
- this.httpServer = null;
123
- this.startedAt = null; // Set on start() for uptime tracking
124
- }
125
-
126
- /**
127
- * Register a handler for escrow events
128
- * @param {string} event - Event from EscrowEvent (e.g., 'escrow:created')
129
- * @param {Function} handler - Async function(payload) to call
130
- * @returns {Function} Unsubscribe function
131
- */
132
- onEscrow(event, handler) {
133
- return this.escrowHooks.on(event, handler);
134
- }
135
-
136
- /**
137
- * Get server health status
138
- * @returns {Object} Health information
139
- */
140
- getHealth() {
141
- const now = Date.now();
142
- const uptime = this.startedAt ? Math.floor((now - this.startedAt) / 1000) : 0;
143
-
144
- return {
145
- status: 'healthy',
146
- server: this.serverName,
147
- version: process.env.npm_package_version || '0.0.0',
148
- uptime_seconds: uptime,
149
- started_at: this.startedAt ? new Date(this.startedAt).toISOString() : null,
150
- agents: {
151
- connected: this.agents.size,
152
- with_identity: Array.from(this.agents.values()).filter(a => a.pubkey).length
153
- },
154
- channels: {
155
- total: this.channels.size,
156
- public: Array.from(this.channels.values()).filter(c => !c.inviteOnly).length
157
- },
158
- proposals: this.proposals.stats(),
159
- timestamp: new Date(now).toISOString()
160
- };
161
- }
162
-
163
- _createChannel(name, inviteOnly = false) {
164
- if (!this.channels.has(name)) {
165
- this.channels.set(name, {
166
- name,
167
- inviteOnly,
168
- invited: new Set(),
169
- agents: new Set(),
170
- messageBuffer: [] // Rolling buffer of recent messages
171
- });
172
- }
173
- return this.channels.get(name);
174
- }
175
-
176
- /**
177
- * Add a message to a channel's buffer (circular buffer)
178
- */
179
- _bufferMessage(channel, msg) {
180
- const ch = this.channels.get(channel);
181
- if (!ch) return;
182
-
183
- ch.messageBuffer.push(msg);
184
-
185
- // Trim to buffer size
186
- if (ch.messageBuffer.length > this.messageBufferSize) {
187
- ch.messageBuffer.shift();
188
- }
189
- }
190
-
191
- /**
192
- * Replay buffered messages to a newly joined agent
193
- */
194
- _replayMessages(ws, channel) {
195
- const ch = this.channels.get(channel);
196
- if (!ch || ch.messageBuffer.length === 0) return;
197
-
198
- for (const msg of ch.messageBuffer) {
199
- // Send with replay flag so client knows it's history
200
- this._send(ws, { ...msg, replay: true });
201
- }
202
- }
203
-
204
- _log(event, data = {}) {
205
- const entry = {
206
- ts: new Date().toISOString(),
207
- event,
208
- ...data
209
- };
210
- console.error(JSON.stringify(entry));
211
- }
212
-
213
- _send(ws, msg) {
214
- if (ws.readyState === 1) { // OPEN
215
- ws.send(serialize(msg));
216
- }
217
- }
218
-
219
- _broadcast(channel, msg, excludeWs = null) {
220
- const ch = this.channels.get(channel);
221
- if (!ch) return;
222
-
223
- for (const ws of ch.agents) {
224
- if (ws !== excludeWs) {
225
- this._send(ws, msg);
226
- }
227
- }
228
- }
229
-
230
- _getAgentId(ws) {
231
- const agent = this.agents.get(ws);
232
- return agent ? `@${agent.id}` : null;
233
- }
234
-
235
- start() {
236
- const tls = !!(this.tlsCert && this.tlsKey);
237
- this.startedAt = Date.now();
238
-
239
- // HTTP request handler for health endpoint
240
- const httpHandler = (req, res) => {
241
- if (req.method === 'GET' && req.url === '/health') {
242
- const health = this.getHealth();
243
- res.writeHead(200, { 'Content-Type': 'application/json' });
244
- res.end(JSON.stringify(health));
245
- } else {
246
- res.writeHead(404);
247
- res.end('Not Found');
248
- }
249
- };
250
-
251
- if (tls) {
252
- // TLS mode: create HTTPS server and attach WebSocket
253
- const httpsOptions = {
254
- cert: fs.readFileSync(this.tlsCert),
255
- key: fs.readFileSync(this.tlsKey)
256
- };
257
- this.httpServer = https.createServer(httpsOptions, httpHandler);
258
- this.wss = new WebSocketServer({ server: this.httpServer });
259
- this.httpServer.listen(this.port, this.host);
260
- } else {
261
- // Plain mode: create HTTP server for health endpoint + WebSocket
262
- this.httpServer = http.createServer(httpHandler);
263
- this.wss = new WebSocketServer({ server: this.httpServer });
264
- this.httpServer.listen(this.port, this.host);
265
- }
266
-
267
- this._log('server_start', { port: this.port, host: this.host, tls });
268
-
269
- this.wss.on('connection', (ws, req) => {
270
- // Get real IP (X-Forwarded-For for proxied connections like Fly.io)
271
- const forwardedFor = req.headers['x-forwarded-for'];
272
- const realIp = forwardedFor ? forwardedFor.split(',')[0].trim() : req.socket.remoteAddress;
273
- const userAgent = req.headers['user-agent'] || 'unknown';
274
-
275
- // Store connection metadata on ws for later logging
276
- ws._connectedAt = Date.now();
277
- ws._realIp = realIp;
278
- ws._userAgent = userAgent;
279
-
280
- this._log('connection', {
281
- ip: realIp,
282
- proxy_ip: req.socket.remoteAddress,
283
- user_agent: userAgent
284
- });
285
-
286
- ws.on('message', (data) => {
287
- this._handleMessage(ws, data.toString());
288
- });
289
-
290
- ws.on('close', () => {
291
- // Log if connection closed without ever identifying (drive-by)
292
- if (!this.agents.has(ws)) {
293
- const duration = ws._connectedAt ? Math.round((Date.now() - ws._connectedAt) / 1000) : 0;
294
- this._log('connection_closed_unidentified', {
295
- ip: ws._realIp,
296
- duration_sec: duration,
297
- user_agent: ws._userAgent
298
- });
299
- }
300
- this._handleDisconnect(ws);
301
- });
302
-
303
- ws.on('error', (err) => {
304
- this._log('ws_error', { error: err.message });
305
- });
306
- });
307
-
308
- this.wss.on('error', (err) => {
309
- this._log('server_error', { error: err.message });
310
- });
311
-
312
- // Start idle channel checker
313
- this.idleCheckInterval = setInterval(() => {
314
- this._checkIdleChannels();
315
- }, 60 * 1000); // Check every minute
316
-
317
- return this;
318
- }
319
-
320
- /**
321
- * Check for idle channels and post conversation starters
322
- */
323
- _checkIdleChannels() {
324
- const now = Date.now();
325
-
326
- for (const [channelName, channel] of this.channels) {
327
- // Skip if no agents in channel
328
- if (channel.agents.size < 2) continue;
329
-
330
- const lastActivity = this.channelLastActivity.get(channelName) || 0;
331
- const idleTime = now - lastActivity;
332
-
333
- if (idleTime >= this.idleTimeoutMs) {
334
- // Pick a random conversation starter
335
- const starter = this.conversationStarters[
336
- Math.floor(Math.random() * this.conversationStarters.length)
337
- ];
338
-
339
- // Get list of agents to mention
340
- const agentMentions = [];
341
- for (const ws of channel.agents) {
342
- const agent = this.agents.get(ws);
343
- if (agent) agentMentions.push(`@${agent.id}`);
344
- }
345
-
346
- const prompt = `${agentMentions.join(', ')} - ${starter}`;
347
-
348
- // Broadcast the prompt
349
- const msg = createMessage(ServerMessageType.MSG, {
350
- from: '@server',
351
- to: channelName,
352
- content: prompt
353
- });
354
- this._broadcast(channelName, msg);
355
- this._bufferMessage(channelName, msg);
356
-
357
- // Update activity time so we don't spam
358
- this.channelLastActivity.set(channelName, now);
359
-
360
- this._log('idle_prompt', { channel: channelName, agents: agentMentions.length });
361
- }
362
- }
363
- }
364
-
365
- stop() {
366
- if (this.idleCheckInterval) {
367
- clearInterval(this.idleCheckInterval);
368
- }
369
- if (this.wss) {
370
- this.wss.close();
371
- }
372
- if (this.httpServer) {
373
- this.httpServer.close();
374
- }
375
- if (this.proposals) {
376
- this.proposals.close();
377
- }
378
- this._log('server_stop');
379
- }
380
-
381
- _handleMessage(ws, data) {
382
- const { valid, msg, error } = validateClientMessage(data);
383
-
384
- if (!valid) {
385
- this._send(ws, createError(ErrorCode.INVALID_MSG, error));
386
- return;
387
- }
388
-
389
- if (this.logMessages) {
390
- this._log('message', { type: msg.type, from: this._getAgentId(ws) });
391
- }
392
-
393
- switch (msg.type) {
394
- case ClientMessageType.IDENTIFY:
395
- handleIdentify(this, ws, msg);
396
- break;
397
- case ClientMessageType.JOIN:
398
- handleJoin(this, ws, msg);
399
- break;
400
- case ClientMessageType.LEAVE:
401
- handleLeave(this, ws, msg);
402
- break;
403
- case ClientMessageType.MSG:
404
- handleMsg(this, ws, msg);
405
- break;
406
- case ClientMessageType.LIST_CHANNELS:
407
- handleListChannels(this, ws);
408
- break;
409
- case ClientMessageType.LIST_AGENTS:
410
- handleListAgents(this, ws, msg);
411
- break;
412
- case ClientMessageType.CREATE_CHANNEL:
413
- handleCreateChannel(this, ws, msg);
414
- break;
415
- case ClientMessageType.INVITE:
416
- handleInvite(this, ws, msg);
417
- break;
418
- case ClientMessageType.PING:
419
- this._send(ws, createMessage(ServerMessageType.PONG));
420
- break;
421
- // Proposal/negotiation messages
422
- case ClientMessageType.PROPOSAL:
423
- handleProposal(this, ws, msg);
424
- break;
425
- case ClientMessageType.ACCEPT:
426
- handleAccept(this, ws, msg);
427
- break;
428
- case ClientMessageType.REJECT:
429
- handleReject(this, ws, msg);
430
- break;
431
- case ClientMessageType.COMPLETE:
432
- handleComplete(this, ws, msg);
433
- break;
434
- case ClientMessageType.DISPUTE:
435
- handleDispute(this, ws, msg);
436
- break;
437
- // Skill discovery messages
438
- case ClientMessageType.REGISTER_SKILLS:
439
- handleRegisterSkills(this, ws, msg);
440
- break;
441
- case ClientMessageType.SEARCH_SKILLS:
442
- handleSearchSkills(this, ws, msg);
443
- break;
444
- // Presence messages
445
- case ClientMessageType.SET_PRESENCE:
446
- handleSetPresence(this, ws, msg);
447
- break;
448
- // Identity verification messages
449
- case ClientMessageType.VERIFY_REQUEST:
450
- handleVerifyRequest(this, ws, msg);
451
- break;
452
- case ClientMessageType.VERIFY_RESPONSE:
453
- handleVerifyResponse(this, ws, msg);
454
- break;
455
- }
456
- }
457
-
458
-
459
- _handleDisconnect(ws) {
460
- const agent = this.agents.get(ws);
461
- if (!agent) return;
462
-
463
- // Calculate connection duration
464
- const duration = ws._connectedAt ? Math.round((Date.now() - ws._connectedAt) / 1000) : 0;
465
- const channelCount = agent.channels.size;
466
-
467
- this._log('disconnect', {
468
- agent: agent.id,
469
- duration_sec: duration,
470
- channels_joined: channelCount,
471
- had_pubkey: !!agent.pubkey,
472
- ip: ws._realIp
473
- });
474
-
475
- // Leave all channels
476
- for (const channelName of agent.channels) {
477
- const channel = this.channels.get(channelName);
478
- if (channel) {
479
- channel.agents.delete(ws);
480
- this._broadcast(channelName, createMessage(ServerMessageType.AGENT_LEFT, {
481
- channel: channelName,
482
- agent: `@${agent.id}`
483
- }));
484
- }
485
- }
486
-
487
- // Remove from state
488
- this.agentById.delete(agent.id);
489
- this.agents.delete(ws);
490
- this.lastMessageTime.delete(ws);
491
- }
492
- }
493
-
494
- // Allow running directly
495
- export function startServer(options = {}) {
496
- // Support environment variable overrides (for Docker)
497
- const config = {
498
- port: parseInt(options.port || process.env.PORT || 6667),
499
- host: options.host || process.env.HOST || '0.0.0.0',
500
- name: options.name || process.env.SERVER_NAME || 'agentchat',
501
- logMessages: options.logMessages || process.env.LOG_MESSAGES === 'true',
502
- cert: options.cert || process.env.TLS_CERT || null,
503
- key: options.key || process.env.TLS_KEY || null,
504
- rateLimitMs: options.rateLimitMs || parseInt(process.env.RATE_LIMIT_MS || 1000),
505
- messageBufferSize: options.messageBufferSize || parseInt(process.env.MESSAGE_BUFFER_SIZE || 20)
506
- };
507
-
508
- const server = new AgentChatServer(config);
509
- server.start();
510
-
511
- const protocol = (config.cert && config.key) ? 'wss' : 'ws';
512
- console.log(`AgentChat server running on ${protocol}://${server.host}:${server.port}`);
513
- console.log('Default channels: #general, #agents');
514
- if (config.cert && config.key) {
515
- console.log('TLS enabled');
516
- }
517
- console.log('Press Ctrl+C to stop');
518
-
519
- process.on('SIGINT', () => {
520
- server.stop();
521
- process.exit(0);
522
- });
523
-
524
- return server;
525
- }
526
-
527
- // Re-export EscrowEvent for consumers
528
- export { EscrowEvent } from './escrow-hooks.js';
@@ -1,110 +0,0 @@
1
- # Agent Supervisor System
2
-
3
- Robust daemon management for Claude agents with automatic restart and state persistence.
4
-
5
- ## Why This Exists
6
-
7
- Claude's built-in resume is unreliable. This system:
8
- - Saves agent state externally to files
9
- - Auto-restarts with exponential backoff
10
- - Feeds previous context back on restart
11
- - Lets agents save their own state before shutdown
12
-
13
- ## Quick Start
14
-
15
- ```bash
16
- # Add to PATH
17
- export PATH="$PATH:$HOME/dev/claude/agentchat/lib/supervisor"
18
-
19
- # Start an agent
20
- agentctl start monitor "monitor agentchat #general, respond to messages, moderate spam"
21
-
22
- # Check status
23
- agentctl status
24
-
25
- # View logs
26
- agentctl logs monitor
27
-
28
- # Stop gracefully
29
- agentctl stop monitor
30
-
31
- # Force kill
32
- agentctl kill monitor
33
-
34
- # Stop all agents
35
- agentctl stopall
36
- ```
37
-
38
- ## Agent Self-Persistence
39
-
40
- Inside your agent prompt, include instructions like:
41
-
42
- ```
43
- IMPORTANT: You are running under a supervisor that will restart you on failure.
44
-
45
- Your state directory: ~/.agentchat/agents/YOUR_NAME/
46
- - context.md: Save important state here BEFORE doing risky operations
47
- - Read this file on startup to resume your work
48
-
49
- Before any operation that might fail:
50
- 1. Write current task to context.md
51
- 2. Do the operation
52
- 3. Update context.md with result
53
-
54
- On quota warnings or before shutdown:
55
- - Save everything important to context.md
56
- - Exit gracefully (the supervisor will restart you)
57
- ```
58
-
59
- ## File Structure
60
-
61
- ```
62
- ~/.agentchat/agents/
63
- └── <agent-name>/
64
- ├── supervisor.pid # Supervisor process ID
65
- ├── state.json # Current state (managed by supervisor)
66
- ├── mission.txt # Original mission prompt
67
- ├── context.md # Agent-managed context (survives restarts)
68
- └── supervisor.log # Supervisor logs
69
- ```
70
-
71
- ## Backoff Strategy
72
-
73
- - Starts at 5 seconds
74
- - Doubles on each failure (5 → 10 → 20 → 40 → 80 → 160 → 300)
75
- - Caps at 5 minutes
76
- - Resets if agent runs for >5 minutes before crashing
77
-
78
- ## Graceful Shutdown
79
-
80
- Agents can check for stop signals:
81
- ```bash
82
- # In bash
83
- if [ -f ~/.agentchat/agents/YOUR_NAME/stop ]; then
84
- echo "Shutdown requested, saving state..."
85
- exit 0
86
- fi
87
- ```
88
-
89
- ## Multiple Agents
90
-
91
- You can run multiple specialized agents:
92
-
93
- ```bash
94
- agentctl start monitor "monitor agentchat, moderate, respond to questions"
95
- agentctl start social "manage moltx/moltbook, post updates, engage with mentions"
96
- agentctl start builder "work on assigned tasks from #tasks channel"
97
- ```
98
-
99
- ## Viewing All State
100
-
101
- ```bash
102
- # Status of all agents
103
- agentctl status
104
-
105
- # List registered agents
106
- agentctl list
107
-
108
- # View specific agent's saved context
109
- agentctl context monitor
110
- ```