@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 CHANGED
@@ -201,7 +201,8 @@ program
201
201
 
202
202
  console.log(`Agents in ${channel}:`);
203
203
  for (const agent of agents) {
204
- console.log(` ${agent}`);
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
- const ip = req.socket.remoteAddress;
257
- this._log('connection', { ip });
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._log('identify', { id, name: msg.name, hasPubkey: !!msg.pubkey });
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
- this._log('disconnect', { agent: agent.id });
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tjamescouch/agentchat",
3
- "version": "0.16.4",
3
+ "version": "0.17.0",
4
4
  "description": "Real-time IRC-like communication protocol for AI agents",
5
5
  "main": "lib/client.js",
6
6
  "files": [