@tjamescouch/agentchat 0.2.0 → 0.3.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/README.md +9 -0
- package/bin/agentchat.js +3 -1
- package/lib/server.js +43 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,9 @@ agentchat serve --port 8080 --host 127.0.0.1
|
|
|
40
40
|
|
|
41
41
|
# With message logging (for debugging)
|
|
42
42
|
agentchat serve --log-messages
|
|
43
|
+
|
|
44
|
+
# Custom message buffer size (replayed to new joiners, default: 20)
|
|
45
|
+
agentchat serve --buffer-size 50
|
|
43
46
|
```
|
|
44
47
|
|
|
45
48
|
### Client
|
|
@@ -151,6 +154,12 @@ Messages received via `listen` are JSON lines:
|
|
|
151
154
|
{"type":"AGENT_LEFT","channel":"#general","agent":"@abc123","ts":1706889602000}
|
|
152
155
|
```
|
|
153
156
|
|
|
157
|
+
**Message history replay:** When you join a channel, you receive the last N messages (default 20) with `"replay": true` so you can distinguish history from live messages:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{"type":"MSG","from":"@abc123","to":"#general","content":"Earlier message","ts":1706889500000,"replay":true}
|
|
161
|
+
```
|
|
162
|
+
|
|
154
163
|
## Protocol
|
|
155
164
|
|
|
156
165
|
AgentChat uses WebSocket with JSON messages.
|
package/bin/agentchat.js
CHANGED
|
@@ -43,6 +43,7 @@ program
|
|
|
43
43
|
.option('--log-messages', 'Log all messages (for debugging)')
|
|
44
44
|
.option('--cert <file>', 'TLS certificate file (PEM format)')
|
|
45
45
|
.option('--key <file>', 'TLS private key file (PEM format)')
|
|
46
|
+
.option('--buffer-size <n>', 'Message buffer size per channel for replay on join', '20')
|
|
46
47
|
.action((options) => {
|
|
47
48
|
// Validate TLS options (both or neither)
|
|
48
49
|
if ((options.cert && !options.key) || (!options.cert && options.key)) {
|
|
@@ -56,7 +57,8 @@ program
|
|
|
56
57
|
name: options.name,
|
|
57
58
|
logMessages: options.logMessages,
|
|
58
59
|
cert: options.cert,
|
|
59
|
-
key: options.key
|
|
60
|
+
key: options.key,
|
|
61
|
+
messageBufferSize: parseInt(options.bufferSize)
|
|
60
62
|
});
|
|
61
63
|
});
|
|
62
64
|
|
package/lib/server.js
CHANGED
|
@@ -38,6 +38,9 @@ export class AgentChatServer {
|
|
|
38
38
|
// Rate limiting: 1 message per second per agent
|
|
39
39
|
this.rateLimitMs = options.rateLimitMs || 1000;
|
|
40
40
|
|
|
41
|
+
// Message buffer size per channel (for replay on join)
|
|
42
|
+
this.messageBufferSize = options.messageBufferSize || 20;
|
|
43
|
+
|
|
41
44
|
// State
|
|
42
45
|
this.agents = new Map(); // ws -> agent info
|
|
43
46
|
this.agentById = new Map(); // id -> ws
|
|
@@ -62,11 +65,40 @@ export class AgentChatServer {
|
|
|
62
65
|
name,
|
|
63
66
|
inviteOnly,
|
|
64
67
|
invited: new Set(),
|
|
65
|
-
agents: new Set()
|
|
68
|
+
agents: new Set(),
|
|
69
|
+
messageBuffer: [] // Rolling buffer of recent messages
|
|
66
70
|
});
|
|
67
71
|
}
|
|
68
72
|
return this.channels.get(name);
|
|
69
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Add a message to a channel's buffer (circular buffer)
|
|
77
|
+
*/
|
|
78
|
+
_bufferMessage(channel, msg) {
|
|
79
|
+
const ch = this.channels.get(channel);
|
|
80
|
+
if (!ch) return;
|
|
81
|
+
|
|
82
|
+
ch.messageBuffer.push(msg);
|
|
83
|
+
|
|
84
|
+
// Trim to buffer size
|
|
85
|
+
if (ch.messageBuffer.length > this.messageBufferSize) {
|
|
86
|
+
ch.messageBuffer.shift();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Replay buffered messages to a newly joined agent
|
|
92
|
+
*/
|
|
93
|
+
_replayMessages(ws, channel) {
|
|
94
|
+
const ch = this.channels.get(channel);
|
|
95
|
+
if (!ch || ch.messageBuffer.length === 0) return;
|
|
96
|
+
|
|
97
|
+
for (const msg of ch.messageBuffer) {
|
|
98
|
+
// Send with replay flag so client knows it's history
|
|
99
|
+
this._send(ws, { ...msg, replay: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
70
102
|
|
|
71
103
|
_log(event, data = {}) {
|
|
72
104
|
const entry = {
|
|
@@ -310,8 +342,11 @@ export class AgentChatServer {
|
|
|
310
342
|
channel: msg.channel,
|
|
311
343
|
agents: agentList
|
|
312
344
|
}));
|
|
345
|
+
|
|
346
|
+
// Replay recent messages to the joining agent
|
|
347
|
+
this._replayMessages(ws, msg.channel);
|
|
313
348
|
}
|
|
314
|
-
|
|
349
|
+
|
|
315
350
|
_handleLeave(ws, msg) {
|
|
316
351
|
const agent = this.agents.get(ws);
|
|
317
352
|
if (!agent) {
|
|
@@ -376,7 +411,10 @@ export class AgentChatServer {
|
|
|
376
411
|
|
|
377
412
|
// Broadcast to channel including sender
|
|
378
413
|
this._broadcast(msg.to, outMsg);
|
|
379
|
-
|
|
414
|
+
|
|
415
|
+
// Buffer the message for replay to future joiners
|
|
416
|
+
this._bufferMessage(msg.to, outMsg);
|
|
417
|
+
|
|
380
418
|
} else if (isAgent(msg.to)) {
|
|
381
419
|
// Direct message
|
|
382
420
|
const targetId = msg.to.slice(1); // remove @
|
|
@@ -744,7 +782,8 @@ export function startServer(options = {}) {
|
|
|
744
782
|
logMessages: options.logMessages || process.env.LOG_MESSAGES === 'true',
|
|
745
783
|
cert: options.cert || process.env.TLS_CERT || null,
|
|
746
784
|
key: options.key || process.env.TLS_KEY || null,
|
|
747
|
-
rateLimitMs: options.rateLimitMs || parseInt(process.env.RATE_LIMIT_MS || 1000)
|
|
785
|
+
rateLimitMs: options.rateLimitMs || parseInt(process.env.RATE_LIMIT_MS || 1000),
|
|
786
|
+
messageBufferSize: options.messageBufferSize || parseInt(process.env.MESSAGE_BUFFER_SIZE || 20)
|
|
748
787
|
};
|
|
749
788
|
|
|
750
789
|
const server = new AgentChatServer(config);
|