@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.
- package/Dockerfile +1 -1
- package/dist/bin/agentchat.d.ts +7 -0
- package/dist/bin/agentchat.d.ts.map +1 -0
- package/dist/bin/agentchat.js +1511 -0
- package/dist/bin/agentchat.js.map +1 -0
- package/dist/lib/allowlist.d.ts +77 -0
- package/dist/lib/allowlist.d.ts.map +1 -0
- package/dist/lib/allowlist.js +151 -0
- package/dist/lib/allowlist.js.map +1 -0
- package/dist/lib/client.d.ts +147 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +704 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/daemon.d.ts +122 -0
- package/dist/lib/daemon.d.ts.map +1 -0
- package/dist/lib/daemon.js +523 -0
- package/dist/lib/daemon.js.map +1 -0
- package/dist/lib/deploy/akash.d.ts +271 -0
- package/dist/lib/deploy/akash.d.ts.map +1 -0
- package/dist/lib/deploy/akash.js +671 -0
- package/dist/lib/deploy/akash.js.map +1 -0
- package/dist/lib/deploy/config.d.ts +62 -0
- package/dist/lib/deploy/config.d.ts.map +1 -0
- package/dist/lib/deploy/config.js +116 -0
- package/dist/lib/deploy/config.js.map +1 -0
- package/dist/lib/deploy/docker.d.ts +37 -0
- package/dist/lib/deploy/docker.d.ts.map +1 -0
- package/dist/lib/deploy/docker.js +122 -0
- package/dist/lib/deploy/docker.js.map +1 -0
- package/dist/lib/deploy/index.d.ts +11 -0
- package/dist/lib/deploy/index.d.ts.map +1 -0
- package/dist/lib/deploy/index.js +11 -0
- package/dist/lib/deploy/index.js.map +1 -0
- package/dist/lib/escrow-hooks.d.ts +199 -0
- package/dist/lib/escrow-hooks.d.ts.map +1 -0
- package/dist/lib/escrow-hooks.js +221 -0
- package/dist/lib/escrow-hooks.js.map +1 -0
- package/dist/lib/identity.d.ts +134 -0
- package/dist/lib/identity.d.ts.map +1 -0
- package/dist/lib/identity.js +334 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/jitter.d.ts +42 -0
- package/dist/lib/jitter.d.ts.map +1 -0
- package/{lib → dist/lib}/jitter.js +16 -19
- package/dist/lib/jitter.js.map +1 -0
- package/dist/lib/proposals.d.ts +223 -0
- package/dist/lib/proposals.d.ts.map +1 -0
- package/dist/lib/proposals.js +379 -0
- package/dist/lib/proposals.js.map +1 -0
- package/dist/lib/protocol.d.ts +220 -0
- package/dist/lib/protocol.d.ts.map +1 -0
- package/dist/lib/protocol.js +507 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/receipts.d.ts +134 -0
- package/dist/lib/receipts.d.ts.map +1 -0
- package/dist/lib/receipts.js +270 -0
- package/dist/lib/receipts.js.map +1 -0
- package/dist/lib/reputation.d.ts +250 -0
- package/dist/lib/reputation.d.ts.map +1 -0
- package/dist/lib/reputation.js +586 -0
- package/dist/lib/reputation.js.map +1 -0
- package/dist/lib/security.d.ts +27 -0
- package/dist/lib/security.d.ts.map +1 -0
- package/dist/lib/security.js +150 -0
- package/dist/lib/security.js.map +1 -0
- package/dist/lib/server/handlers/admin.d.ts +26 -0
- package/dist/lib/server/handlers/admin.d.ts.map +1 -0
- package/dist/lib/server/handlers/admin.js +76 -0
- package/dist/lib/server/handlers/admin.js.map +1 -0
- package/dist/lib/server/handlers/identity.d.ts +36 -0
- package/dist/lib/server/handlers/identity.d.ts.map +1 -0
- package/dist/lib/server/handlers/identity.js +330 -0
- package/dist/lib/server/handlers/identity.js.map +1 -0
- package/dist/lib/server/handlers/index.d.ts +10 -0
- package/dist/lib/server/handlers/index.d.ts.map +1 -0
- package/dist/lib/server/handlers/index.js +15 -0
- package/dist/lib/server/handlers/index.js.map +1 -0
- package/dist/lib/server/handlers/message.d.ts +47 -0
- package/dist/lib/server/handlers/message.d.ts.map +1 -0
- package/dist/lib/server/handlers/message.js +265 -0
- package/dist/lib/server/handlers/message.js.map +1 -0
- package/dist/lib/server/handlers/presence.d.ts +18 -0
- package/dist/lib/server/handlers/presence.d.ts.map +1 -0
- package/dist/lib/server/handlers/presence.js +35 -0
- package/dist/lib/server/handlers/presence.js.map +1 -0
- package/dist/lib/server/handlers/proposal.d.ts +38 -0
- package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
- package/dist/lib/server/handlers/proposal.js +273 -0
- package/dist/lib/server/handlers/proposal.js.map +1 -0
- package/dist/lib/server/handlers/skills.d.ts +22 -0
- package/dist/lib/server/handlers/skills.d.ts.map +1 -0
- package/dist/lib/server/handlers/skills.js +119 -0
- package/dist/lib/server/handlers/skills.js.map +1 -0
- package/dist/lib/server-directory.d.ts +85 -0
- package/dist/lib/server-directory.d.ts.map +1 -0
- package/dist/lib/server-directory.js +177 -0
- package/dist/lib/server-directory.js.map +1 -0
- package/dist/lib/server.d.ts +162 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/server.js +602 -0
- package/dist/lib/server.js.map +1 -0
- package/dist/lib/types.d.ts +461 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +98 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +22 -13
- package/bin/agentchat.js +0 -1617
- package/lib/chat.py +0 -241
- package/lib/client.js +0 -821
- package/lib/daemon.js +0 -562
- package/lib/deploy/akash.js +0 -811
- package/lib/deploy/config.js +0 -128
- package/lib/deploy/docker.js +0 -132
- package/lib/deploy/index.js +0 -24
- package/lib/elo_swarm.py +0 -569
- package/lib/escrow-hooks.js +0 -237
- package/lib/identity.js +0 -376
- package/lib/proposals.js +0 -426
- package/lib/protocol.js +0 -484
- package/lib/receipts.js +0 -294
- package/lib/reputation.js +0 -664
- package/lib/security.js +0 -183
- package/lib/server/handlers/identity.js +0 -242
- package/lib/server/handlers/index.js +0 -42
- package/lib/server/handlers/message.js +0 -306
- package/lib/server/handlers/presence.js +0 -44
- package/lib/server/handlers/proposal.js +0 -358
- package/lib/server/handlers/skills.js +0 -141
- package/lib/server-directory.js +0 -181
- package/lib/server.js +0 -528
- package/lib/supervisor/USAGE.md +0 -110
- package/lib/supervisor/agent-supervisor.sh +0 -135
- package/lib/supervisor/agentctl.sh +0 -250
- package/lib/supervisor/killswitch.sh +0 -36
- 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';
|
package/lib/supervisor/USAGE.md
DELETED
|
@@ -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
|
-
```
|