@tjamescouch/agentchat 0.16.4 → 0.17.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/bin/agentchat.js +2 -104
- package/lib/server.js +48 -4
- package/package.json +1 -1
package/bin/agentchat.js
CHANGED
|
@@ -201,7 +201,8 @@ program
|
|
|
201
201
|
|
|
202
202
|
console.log(`Agents in ${channel}:`);
|
|
203
203
|
for (const agent of agents) {
|
|
204
|
-
|
|
204
|
+
const status = agent.status_text ? ` - ${agent.status_text}` : '';
|
|
205
|
+
console.log(` ${agent.id} (${agent.name}) [${agent.presence}]${status}`);
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
client.disconnect();
|
|
@@ -212,109 +213,6 @@ program
|
|
|
212
213
|
}
|
|
213
214
|
});
|
|
214
215
|
|
|
215
|
-
// Interactive connect command
|
|
216
|
-
program
|
|
217
|
-
.command('connect <server>')
|
|
218
|
-
.description('Interactive connection (for debugging)')
|
|
219
|
-
.option('-n, --name <name>', 'Agent name', `agent-${process.pid}`)
|
|
220
|
-
.option('-i, --identity <file>', 'Path to identity file')
|
|
221
|
-
.option('-j, --join <channels...>', 'Channels to join automatically')
|
|
222
|
-
.action(async (server, options) => {
|
|
223
|
-
try {
|
|
224
|
-
const client = new AgentChatClient({ server, name: options.name, identity: options.identity });
|
|
225
|
-
await client.connect();
|
|
226
|
-
|
|
227
|
-
console.log(`Connected as ${client.agentId}`);
|
|
228
|
-
|
|
229
|
-
// Auto-join channels
|
|
230
|
-
if (options.join) {
|
|
231
|
-
for (const ch of options.join) {
|
|
232
|
-
await client.join(ch);
|
|
233
|
-
console.log(`Joined ${ch}`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Listen for messages
|
|
238
|
-
client.on('message', (msg) => {
|
|
239
|
-
console.log(`[${msg.to}] ${msg.from}: ${msg.content}`);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
client.on('agent_joined', (msg) => {
|
|
243
|
-
console.log(`* ${msg.agent} joined ${msg.channel}`);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
client.on('agent_left', (msg) => {
|
|
247
|
-
console.log(`* ${msg.agent} left ${msg.channel}`);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// Read from stdin
|
|
251
|
-
console.log('Type messages as: #channel message or @agent message');
|
|
252
|
-
console.log('Commands: /join #channel, /leave #channel, /channels, /quit');
|
|
253
|
-
|
|
254
|
-
const readline = await import('readline');
|
|
255
|
-
const rl = readline.createInterface({
|
|
256
|
-
input: process.stdin,
|
|
257
|
-
output: process.stdout
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
rl.on('line', async (line) => {
|
|
261
|
-
line = line.trim();
|
|
262
|
-
if (!line) return;
|
|
263
|
-
|
|
264
|
-
// Commands
|
|
265
|
-
if (line.startsWith('/')) {
|
|
266
|
-
const [cmd, ...args] = line.slice(1).split(' ');
|
|
267
|
-
|
|
268
|
-
switch (cmd) {
|
|
269
|
-
case 'join':
|
|
270
|
-
if (args[0]) {
|
|
271
|
-
await client.join(args[0]);
|
|
272
|
-
console.log(`Joined ${args[0]}`);
|
|
273
|
-
}
|
|
274
|
-
break;
|
|
275
|
-
case 'leave':
|
|
276
|
-
if (args[0]) {
|
|
277
|
-
await client.leave(args[0]);
|
|
278
|
-
console.log(`Left ${args[0]}`);
|
|
279
|
-
}
|
|
280
|
-
break;
|
|
281
|
-
case 'channels':
|
|
282
|
-
const channels = await client.listChannels();
|
|
283
|
-
for (const ch of channels) {
|
|
284
|
-
console.log(` ${ch.name} (${ch.agents})`);
|
|
285
|
-
}
|
|
286
|
-
break;
|
|
287
|
-
case 'quit':
|
|
288
|
-
case 'exit':
|
|
289
|
-
client.disconnect();
|
|
290
|
-
process.exit(0);
|
|
291
|
-
break;
|
|
292
|
-
default:
|
|
293
|
-
console.log('Unknown command');
|
|
294
|
-
}
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Messages: #channel msg or @agent msg
|
|
299
|
-
const match = line.match(/^([@#][^\s]+)\s+(.+)$/);
|
|
300
|
-
if (match) {
|
|
301
|
-
await client.send(match[1], match[2]);
|
|
302
|
-
} else {
|
|
303
|
-
console.log('Format: #channel message or @agent message');
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
rl.on('close', () => {
|
|
308
|
-
client.disconnect();
|
|
309
|
-
process.exit(0);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
} catch (err) {
|
|
313
|
-
console.error('Error:', err.message);
|
|
314
|
-
process.exit(1);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
|
|
318
216
|
// Create channel command
|
|
319
217
|
program
|
|
320
218
|
.command('create <server> <channel>')
|
package/lib/server.js
CHANGED
|
@@ -253,14 +253,36 @@ export class AgentChatServer {
|
|
|
253
253
|
this._log('server_start', { port: this.port, host: this.host, tls });
|
|
254
254
|
|
|
255
255
|
this.wss.on('connection', (ws, req) => {
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
// Get real IP (X-Forwarded-For for proxied connections like Fly.io)
|
|
257
|
+
const forwardedFor = req.headers['x-forwarded-for'];
|
|
258
|
+
const realIp = forwardedFor ? forwardedFor.split(',')[0].trim() : req.socket.remoteAddress;
|
|
259
|
+
const userAgent = req.headers['user-agent'] || 'unknown';
|
|
260
|
+
|
|
261
|
+
// Store connection metadata on ws for later logging
|
|
262
|
+
ws._connectedAt = Date.now();
|
|
263
|
+
ws._realIp = realIp;
|
|
264
|
+
ws._userAgent = userAgent;
|
|
265
|
+
|
|
266
|
+
this._log('connection', {
|
|
267
|
+
ip: realIp,
|
|
268
|
+
proxy_ip: req.socket.remoteAddress,
|
|
269
|
+
user_agent: userAgent
|
|
270
|
+
});
|
|
258
271
|
|
|
259
272
|
ws.on('message', (data) => {
|
|
260
273
|
this._handleMessage(ws, data.toString());
|
|
261
274
|
});
|
|
262
275
|
|
|
263
276
|
ws.on('close', () => {
|
|
277
|
+
// Log if connection closed without ever identifying (drive-by)
|
|
278
|
+
if (!this.agents.has(ws)) {
|
|
279
|
+
const duration = ws._connectedAt ? Math.round((Date.now() - ws._connectedAt) / 1000) : 0;
|
|
280
|
+
this._log('connection_closed_unidentified', {
|
|
281
|
+
ip: ws._realIp,
|
|
282
|
+
duration_sec: duration,
|
|
283
|
+
user_agent: ws._userAgent
|
|
284
|
+
});
|
|
285
|
+
}
|
|
264
286
|
this._handleDisconnect(ws);
|
|
265
287
|
});
|
|
266
288
|
|
|
@@ -468,7 +490,19 @@ export class AgentChatServer {
|
|
|
468
490
|
this.agents.set(ws, agent);
|
|
469
491
|
this.agentById.set(id, ws);
|
|
470
492
|
|
|
471
|
-
this
|
|
493
|
+
// Determine if this is a new or returning identity
|
|
494
|
+
const isReturning = msg.pubkey && this.pubkeyToId.has(msg.pubkey);
|
|
495
|
+
const isEphemeral = !msg.pubkey;
|
|
496
|
+
|
|
497
|
+
this._log('identify', {
|
|
498
|
+
id,
|
|
499
|
+
name: msg.name,
|
|
500
|
+
hasPubkey: !!msg.pubkey,
|
|
501
|
+
returning: isReturning,
|
|
502
|
+
ephemeral: isEphemeral,
|
|
503
|
+
ip: ws._realIp,
|
|
504
|
+
user_agent: ws._userAgent
|
|
505
|
+
});
|
|
472
506
|
|
|
473
507
|
this._send(ws, createMessage(ServerMessageType.WELCOME, {
|
|
474
508
|
agent_id: `@${id}`,
|
|
@@ -1381,7 +1415,17 @@ export class AgentChatServer {
|
|
|
1381
1415
|
const agent = this.agents.get(ws);
|
|
1382
1416
|
if (!agent) return;
|
|
1383
1417
|
|
|
1384
|
-
|
|
1418
|
+
// Calculate connection duration
|
|
1419
|
+
const duration = ws._connectedAt ? Math.round((Date.now() - ws._connectedAt) / 1000) : 0;
|
|
1420
|
+
const channelCount = agent.channels.size;
|
|
1421
|
+
|
|
1422
|
+
this._log('disconnect', {
|
|
1423
|
+
agent: agent.id,
|
|
1424
|
+
duration_sec: duration,
|
|
1425
|
+
channels_joined: channelCount,
|
|
1426
|
+
had_pubkey: !!agent.pubkey,
|
|
1427
|
+
ip: ws._realIp
|
|
1428
|
+
});
|
|
1385
1429
|
|
|
1386
1430
|
// Leave all channels
|
|
1387
1431
|
for (const channelName of agent.channels) {
|