agent-mailbox-core 1.0.1 → 1.2.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/dist/lib.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Library exports for programmatic use
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { Mailbox } from "agent-mailbox-core/lib";
7
+ *
8
+ * const mailbox = new Mailbox({ dbPath: "./mailbox.db" });
9
+ * mailbox.send({ from: "a", to: "b", subject: "Hi", body: "Hello" });
10
+ * mailbox.close();
11
+ * ```
12
+ */
13
+ export { Mailbox } from "./mailbox.js";
14
+ export { formatMessages, formatThreads, formatAgents, formatDeadLetters, formatMetrics } from "./format.js";
15
+ export type { Priority, DeliveryStatus, MailboxConfig, Message, SendOptions, SendResult, InboxOptions, SearchOptions, Thread, DeadLetter, MailboxMetrics, AgentInfo, } from "./types.js";
16
+ //# sourceMappingURL=lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5G,YAAY,EACV,QAAQ,EACR,cAAc,EACd,aAAa,EACb,OAAO,EACP,WAAW,EACX,UAAU,EACV,YAAY,EACZ,aAAa,EACb,MAAM,EACN,UAAU,EACV,cAAc,EACd,SAAS,GACV,MAAM,YAAY,CAAC"}
package/dist/lib.js ADDED
@@ -0,0 +1,566 @@
1
+ // @bun
2
+ // src/database.ts
3
+ import { Database } from "bun:sqlite";
4
+ import { mkdirSync, existsSync } from "fs";
5
+ import { dirname } from "path";
6
+ var SCHEMA_VERSION = 1;
7
+ function initDatabase(config) {
8
+ if (config.dbPath !== ":memory:") {
9
+ const dir = dirname(config.dbPath);
10
+ if (!existsSync(dir)) {
11
+ mkdirSync(dir, { recursive: true });
12
+ }
13
+ }
14
+ const db = new Database(config.dbPath);
15
+ if (config.walMode) {
16
+ db.exec("PRAGMA journal_mode=WAL");
17
+ db.exec("PRAGMA synchronous=NORMAL");
18
+ }
19
+ db.exec("PRAGMA foreign_keys=ON");
20
+ db.exec(`
21
+ CREATE TABLE IF NOT EXISTS schema_version (
22
+ version INTEGER NOT NULL
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS messages (
26
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27
+ from_agent TEXT NOT NULL,
28
+ to_agent TEXT NOT NULL,
29
+ subject TEXT NOT NULL,
30
+ body TEXT NOT NULL,
31
+ thread_id TEXT NOT NULL,
32
+ priority TEXT NOT NULL DEFAULT 'normal' CHECK (priority IN ('high', 'normal', 'low')),
33
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'delivered', 'read', 'acked', 'expired', 'dead')),
34
+ ttl_seconds INTEGER NOT NULL DEFAULT 86400,
35
+ idempotency_key TEXT,
36
+ trace_id TEXT,
37
+ receive_count INTEGER NOT NULL DEFAULT 0,
38
+ visible_after TEXT,
39
+ session_id TEXT NOT NULL DEFAULT '',
40
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
41
+ read_at TEXT,
42
+ ack_at TEXT,
43
+ expires_at TEXT NOT NULL DEFAULT (datetime('now', '+86400 seconds'))
44
+ );
45
+
46
+ CREATE INDEX IF NOT EXISTS idx_messages_to_agent_status ON messages(to_agent, status);
47
+ CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages(thread_id);
48
+ CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at);
49
+ CREATE INDEX IF NOT EXISTS idx_messages_expires ON messages(expires_at);
50
+ CREATE INDEX IF NOT EXISTS idx_messages_visible ON messages(visible_after);
51
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_idempotency ON messages(idempotency_key) WHERE idempotency_key IS NOT NULL;
52
+
53
+ CREATE TABLE IF NOT EXISTS threads (
54
+ id TEXT PRIMARY KEY,
55
+ subject TEXT NOT NULL,
56
+ participants TEXT NOT NULL DEFAULT '[]',
57
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
58
+ last_message_at TEXT NOT NULL DEFAULT (datetime('now'))
59
+ );
60
+
61
+ CREATE TABLE IF NOT EXISTS dead_letters (
62
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
63
+ original_message_id INTEGER NOT NULL,
64
+ from_agent TEXT NOT NULL,
65
+ to_agent TEXT NOT NULL,
66
+ subject TEXT NOT NULL,
67
+ body TEXT NOT NULL,
68
+ thread_id TEXT NOT NULL,
69
+ reason TEXT NOT NULL,
70
+ moved_at TEXT NOT NULL DEFAULT (datetime('now'))
71
+ );
72
+
73
+ CREATE TABLE IF NOT EXISTS rate_limits (
74
+ agent TEXT NOT NULL,
75
+ window_start TEXT NOT NULL,
76
+ message_count INTEGER NOT NULL DEFAULT 1,
77
+ PRIMARY KEY (agent, window_start)
78
+ );
79
+
80
+ CREATE TABLE IF NOT EXISTS agent_registry (
81
+ name TEXT PRIMARY KEY,
82
+ role TEXT,
83
+ registered_at TEXT NOT NULL DEFAULT (datetime('now')),
84
+ last_active TEXT NOT NULL DEFAULT (datetime('now'))
85
+ );
86
+ `);
87
+ try {
88
+ db.exec(`
89
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
90
+ subject, body, content=messages, content_rowid=id
91
+ );
92
+
93
+ CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
94
+ INSERT INTO messages_fts(rowid, subject, body) VALUES (new.id, new.subject, new.body);
95
+ END;
96
+
97
+ CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
98
+ INSERT INTO messages_fts(messages_fts, rowid, subject, body) VALUES ('delete', old.id, old.subject, old.body);
99
+ END;
100
+ `);
101
+ } catch {}
102
+ const currentVersion = db.prepare("SELECT version FROM schema_version LIMIT 1").get();
103
+ if (!currentVersion) {
104
+ db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(SCHEMA_VERSION);
105
+ }
106
+ return db;
107
+ }
108
+
109
+ // src/mailbox.ts
110
+ var DEFAULT_CONFIG = {
111
+ dbPath: ":memory:",
112
+ defaultTTL: 86400,
113
+ visibilityTimeout: 300,
114
+ maxRetries: 3,
115
+ maxBodySize: 65536,
116
+ rateLimitPerMinute: 60,
117
+ walMode: true,
118
+ cleanupInterval: 300
119
+ };
120
+ function resolveConfig(config) {
121
+ return { ...DEFAULT_CONFIG, ...config };
122
+ }
123
+ function generateThreadId() {
124
+ return `thread-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
125
+ }
126
+ class Mailbox {
127
+ db;
128
+ config;
129
+ cleanupTimer = null;
130
+ stmts;
131
+ constructor(config) {
132
+ this.config = resolveConfig(config);
133
+ this.db = initDatabase(this.config);
134
+ this.stmts = this.prepareStatements();
135
+ if (this.config.cleanupInterval > 0) {
136
+ this.cleanupTimer = setInterval(() => this.cleanup(), this.config.cleanupInterval * 1000);
137
+ }
138
+ }
139
+ prepareStatements() {
140
+ return {
141
+ insertMessage: this.db.prepare(`
142
+ INSERT INTO messages (from_agent, to_agent, subject, body, thread_id, priority, ttl_seconds, idempotency_key, trace_id, session_id, expires_at)
143
+ VALUES ($from, $to, $subject, $body, $thread_id, $priority, $ttl, $idem_key, $trace_id, $session_id, datetime('now', '+' || $ttl || ' seconds'))
144
+ `),
145
+ insertThread: this.db.prepare(`
146
+ INSERT OR IGNORE INTO threads (id, subject, participants) VALUES ($id, $subject, $participants)
147
+ `),
148
+ updateThreadTimestamp: this.db.prepare(`
149
+ UPDATE threads SET last_message_at = datetime('now') WHERE id = $id
150
+ `),
151
+ getInbox: this.db.prepare(`
152
+ SELECT * FROM messages
153
+ WHERE (to_agent = $agent OR to_agent = 'broadcast')
154
+ AND from_agent != $agent
155
+ AND status IN ('pending', 'delivered')
156
+ AND (visible_after IS NULL OR visible_after <= datetime('now'))
157
+ AND expires_at > datetime('now')
158
+ ORDER BY
159
+ CASE priority WHEN 'high' THEN 0 WHEN 'normal' THEN 1 WHEN 'low' THEN 2 END,
160
+ created_at DESC
161
+ LIMIT $limit
162
+ `),
163
+ getInboxIncludeRead: this.db.prepare(`
164
+ SELECT * FROM messages
165
+ WHERE (to_agent = $agent OR to_agent = 'broadcast')
166
+ AND from_agent != $agent
167
+ AND status NOT IN ('dead', 'expired')
168
+ AND expires_at > datetime('now')
169
+ ORDER BY created_at DESC
170
+ LIMIT $limit
171
+ `),
172
+ claimMessage: this.db.prepare(`
173
+ UPDATE messages
174
+ SET status = 'delivered',
175
+ receive_count = receive_count + 1,
176
+ visible_after = datetime('now', '+' || $timeout || ' seconds')
177
+ WHERE id = $id
178
+ `),
179
+ markRead: this.db.prepare(`
180
+ UPDATE messages SET status = 'read', read_at = datetime('now'), visible_after = NULL WHERE id = $id
181
+ `),
182
+ markAcked: this.db.prepare(`
183
+ UPDATE messages SET status = 'acked', ack_at = datetime('now'), visible_after = NULL WHERE id = $id
184
+ `),
185
+ getMessage: this.db.prepare(`SELECT * FROM messages WHERE id = $id`),
186
+ searchFTS: this.db.prepare(`
187
+ SELECT m.* FROM messages m
188
+ JOIN messages_fts fts ON m.id = fts.rowid
189
+ WHERE messages_fts MATCH $query
190
+ AND m.expires_at > datetime('now')
191
+ ORDER BY m.created_at DESC
192
+ LIMIT $limit
193
+ `),
194
+ searchLIKE: this.db.prepare(`
195
+ SELECT * FROM messages
196
+ WHERE (subject LIKE $q OR body LIKE $q)
197
+ AND expires_at > datetime('now')
198
+ ORDER BY created_at DESC
199
+ LIMIT $limit
200
+ `),
201
+ listThreads: this.db.prepare(`
202
+ SELECT t.*,
203
+ COUNT(m.id) as message_count,
204
+ SUM(CASE WHEN m.status IN ('pending', 'delivered') AND (m.to_agent = $agent OR m.to_agent = 'broadcast') THEN 1 ELSE 0 END) as unread_count
205
+ FROM threads t
206
+ LEFT JOIN messages m ON m.thread_id = t.id
207
+ GROUP BY t.id
208
+ ORDER BY t.last_message_at DESC
209
+ LIMIT $limit
210
+ `),
211
+ getThreadMessages: this.db.prepare(`
212
+ SELECT * FROM messages WHERE thread_id = $thread_id ORDER BY created_at ASC
213
+ `),
214
+ getReply: this.db.prepare(`
215
+ SELECT * FROM messages
216
+ WHERE thread_id = $thread_id AND from_agent = $from AND to_agent = $to AND id > $after_id
217
+ ORDER BY created_at ASC LIMIT 1
218
+ `),
219
+ moveToDLQ: this.db.prepare(`
220
+ INSERT INTO dead_letters (original_message_id, from_agent, to_agent, subject, body, thread_id, reason)
221
+ SELECT id, from_agent, to_agent, subject, body, thread_id, $reason
222
+ FROM messages WHERE id = $id
223
+ `),
224
+ markDead: this.db.prepare(`
225
+ UPDATE messages SET status = 'dead' WHERE id = $id
226
+ `),
227
+ getDeadLetters: this.db.prepare(`
228
+ SELECT * FROM dead_letters ORDER BY moved_at DESC LIMIT $limit
229
+ `),
230
+ replayDeadLetter: this.db.prepare(`
231
+ SELECT * FROM dead_letters WHERE id = $id
232
+ `),
233
+ deleteDeadLetter: this.db.prepare(`
234
+ DELETE FROM dead_letters WHERE id = $id
235
+ `),
236
+ checkRate: this.db.prepare(`
237
+ SELECT message_count FROM rate_limits
238
+ WHERE agent = $agent AND window_start = $window
239
+ `),
240
+ upsertRate: this.db.prepare(`
241
+ INSERT INTO rate_limits (agent, window_start, message_count)
242
+ VALUES ($agent, $window, 1)
243
+ ON CONFLICT(agent, window_start)
244
+ DO UPDATE SET message_count = message_count + 1
245
+ `),
246
+ upsertAgent: this.db.prepare(`
247
+ INSERT INTO agent_registry (name, role, last_active)
248
+ VALUES ($name, $role, datetime('now'))
249
+ ON CONFLICT(name)
250
+ DO UPDATE SET role = COALESCE($role, role), last_active = datetime('now')
251
+ `),
252
+ listAgents: this.db.prepare(`
253
+ SELECT ar.name, ar.role, ar.last_active,
254
+ (SELECT COUNT(*) FROM messages WHERE from_agent = ar.name) as message_count
255
+ FROM agent_registry ar
256
+ ORDER BY ar.last_active DESC
257
+ `),
258
+ expireMessages: this.db.prepare(`
259
+ UPDATE messages SET status = 'expired' WHERE expires_at <= datetime('now') AND status NOT IN ('acked', 'expired', 'dead')
260
+ `),
261
+ requeueTimedOut: this.db.prepare(`
262
+ UPDATE messages SET status = 'pending', visible_after = NULL
263
+ WHERE status = 'delivered'
264
+ AND visible_after IS NOT NULL
265
+ AND visible_after <= datetime('now')
266
+ AND receive_count < $max_retries
267
+ `),
268
+ moveExhaustedToDLQ: this.db.prepare(`
269
+ SELECT id FROM messages
270
+ WHERE status = 'delivered'
271
+ AND visible_after IS NOT NULL
272
+ AND visible_after <= datetime('now')
273
+ AND receive_count >= $max_retries
274
+ `),
275
+ cleanRateLimits: this.db.prepare(`
276
+ DELETE FROM rate_limits WHERE window_start < $cutoff
277
+ `),
278
+ countByStatus: this.db.prepare(`
279
+ SELECT status, COUNT(*) as cnt FROM messages GROUP BY status
280
+ `),
281
+ countDeadLetters: this.db.prepare(`
282
+ SELECT COUNT(*) as cnt FROM dead_letters
283
+ `),
284
+ countActiveThreads: this.db.prepare(`
285
+ SELECT COUNT(*) as cnt FROM threads WHERE last_message_at > datetime('now', '-1 hour')
286
+ `),
287
+ messagesPerAgent: this.db.prepare(`
288
+ SELECT from_agent, COUNT(*) as cnt FROM messages GROUP BY from_agent ORDER BY cnt DESC
289
+ `),
290
+ avgDeliveryTime: this.db.prepare(`
291
+ SELECT AVG((julianday(read_at) - julianday(created_at)) * 86400000) as avg_ms
292
+ FROM messages WHERE read_at IS NOT NULL
293
+ `),
294
+ checkIdempotency: this.db.prepare(`
295
+ SELECT id, thread_id FROM messages WHERE idempotency_key = $key
296
+ `)
297
+ };
298
+ }
299
+ send(opts) {
300
+ if (Buffer.byteLength(opts.body, "utf-8") > this.config.maxBodySize) {
301
+ throw new Error(`Message body exceeds max size of ${this.config.maxBodySize} bytes`);
302
+ }
303
+ this.checkRateLimit(opts.from);
304
+ if (opts.idempotencyKey) {
305
+ const existing = this.stmts.checkIdempotency.get({ $key: opts.idempotencyKey });
306
+ if (existing) {
307
+ return { messageId: existing.id, threadId: existing.thread_id, idempotencyKey: opts.idempotencyKey };
308
+ }
309
+ }
310
+ const threadId = opts.threadId ?? generateThreadId();
311
+ const ttl = opts.ttlSeconds ?? this.config.defaultTTL;
312
+ this.stmts.insertThread.run({
313
+ $id: threadId,
314
+ $subject: opts.subject,
315
+ $participants: JSON.stringify([opts.from, opts.to])
316
+ });
317
+ this.stmts.updateThreadTimestamp.run({ $id: threadId });
318
+ const result = this.stmts.insertMessage.run({
319
+ $from: opts.from,
320
+ $to: opts.to,
321
+ $subject: opts.subject,
322
+ $body: opts.body,
323
+ $thread_id: threadId,
324
+ $priority: opts.priority ?? "normal",
325
+ $ttl: ttl,
326
+ $idem_key: opts.idempotencyKey ?? null,
327
+ $trace_id: opts.traceId ?? null,
328
+ $session_id: opts.sessionId ?? ""
329
+ });
330
+ this.stmts.upsertAgent.run({ $name: opts.from, $role: null });
331
+ return {
332
+ messageId: Number(result.lastInsertRowid),
333
+ threadId,
334
+ idempotencyKey: opts.idempotencyKey ?? null
335
+ };
336
+ }
337
+ broadcast(opts) {
338
+ return this.send({ ...opts, to: "broadcast" });
339
+ }
340
+ readInbox(opts) {
341
+ const limit = opts.limit ?? 20;
342
+ if (opts.includeRead) {
343
+ return this.stmts.getInboxIncludeRead.all({ $agent: opts.agent, $limit: limit });
344
+ }
345
+ const rows = this.stmts.getInbox.all({ $agent: opts.agent, $limit: limit });
346
+ for (const row of rows) {
347
+ this.stmts.claimMessage.run({
348
+ $id: row.id,
349
+ $timeout: this.config.visibilityTimeout
350
+ });
351
+ }
352
+ return rows;
353
+ }
354
+ markRead(messageId) {
355
+ this.stmts.markRead.run({ $id: messageId });
356
+ }
357
+ acknowledge(messageId, response) {
358
+ this.stmts.markAcked.run({ $id: messageId });
359
+ if (response) {
360
+ const original = this.stmts.getMessage.get({ $id: messageId });
361
+ if (original) {
362
+ return this.send({
363
+ from: response.from,
364
+ to: original.from_agent,
365
+ subject: `Re: ${original.subject}`,
366
+ body: response.body,
367
+ threadId: original.thread_id,
368
+ priority: "normal",
369
+ sessionId: response.sessionId
370
+ });
371
+ }
372
+ }
373
+ return null;
374
+ }
375
+ search(opts) {
376
+ const limit = opts.limit ?? 10;
377
+ try {
378
+ const rows = this.stmts.searchFTS.all({ $query: opts.query, $limit: limit });
379
+ return { messages: rows, usedFallback: false };
380
+ } catch {
381
+ const rows = this.stmts.searchLIKE.all({ $q: `%${opts.query}%`, $limit: limit });
382
+ return { messages: rows, usedFallback: true };
383
+ }
384
+ }
385
+ listThreads(agent, limit = 10) {
386
+ return this.stmts.listThreads.all({ $agent: agent, $limit: limit });
387
+ }
388
+ getThread(threadId) {
389
+ return this.stmts.getThreadMessages.all({ $thread_id: threadId });
390
+ }
391
+ async request(opts) {
392
+ const timeout = opts.timeoutMs ?? 120000;
393
+ const { messageId, threadId } = this.send({
394
+ ...opts,
395
+ priority: "high",
396
+ body: opts.body + `
397
+
398
+ ---
399
+ REPLY REQUESTED \u2014 sender is waiting.`
400
+ });
401
+ const startTime = Date.now();
402
+ let delay = 500;
403
+ while (Date.now() - startTime < timeout) {
404
+ const reply = this.stmts.getReply.get({
405
+ $thread_id: threadId,
406
+ $from: opts.to,
407
+ $to: opts.from,
408
+ $after_id: messageId
409
+ });
410
+ if (reply) {
411
+ this.markRead(reply.id);
412
+ return { reply };
413
+ }
414
+ await new Promise((r) => setTimeout(r, delay));
415
+ delay = Math.min(delay * 1.5, 1e4);
416
+ }
417
+ return { timeout: true, messageId, threadId };
418
+ }
419
+ registerAgent(name, role) {
420
+ this.stmts.upsertAgent.run({ $name: name, $role: role ?? null });
421
+ }
422
+ listAgents() {
423
+ return this.stmts.listAgents.all();
424
+ }
425
+ getDeadLetters(limit = 20) {
426
+ return this.stmts.getDeadLetters.all({ $limit: limit });
427
+ }
428
+ replayDeadLetter(dlqId) {
429
+ const dl = this.stmts.replayDeadLetter.get({ $id: dlqId });
430
+ if (!dl)
431
+ return null;
432
+ const result = this.send({
433
+ from: dl.from_agent,
434
+ to: dl.to_agent,
435
+ subject: dl.subject,
436
+ body: dl.body,
437
+ threadId: dl.thread_id
438
+ });
439
+ this.stmts.deleteDeadLetter.run({ $id: dlqId });
440
+ return result;
441
+ }
442
+ metrics() {
443
+ const statusCounts = this.stmts.countByStatus.all();
444
+ const statusMap = {};
445
+ let total = 0;
446
+ for (const row of statusCounts) {
447
+ statusMap[row.status] = row.cnt;
448
+ total += row.cnt;
449
+ }
450
+ const dlCount = this.stmts.countDeadLetters.get().cnt;
451
+ const threadCount = this.stmts.countActiveThreads.get().cnt;
452
+ const perAgent = this.stmts.messagesPerAgent.all();
453
+ const avgDel = this.stmts.avgDeliveryTime.get();
454
+ return {
455
+ totalMessages: total,
456
+ pendingMessages: statusMap["pending"] ?? 0,
457
+ deliveredMessages: statusMap["delivered"] ?? 0,
458
+ deadLetters: dlCount,
459
+ activeThreads: threadCount,
460
+ messagesPerAgent: Object.fromEntries(perAgent.map((r) => [r.from_agent, r.cnt])),
461
+ avgDeliveryTimeMs: avgDel.avg_ms
462
+ };
463
+ }
464
+ cleanup() {
465
+ const expired = this.stmts.expireMessages.run().changes;
466
+ const requeued = this.stmts.requeueTimedOut.run({ $max_retries: this.config.maxRetries }).changes;
467
+ const exhausted = this.stmts.moveExhaustedToDLQ.all({ $max_retries: this.config.maxRetries });
468
+ for (const { id } of exhausted) {
469
+ this.stmts.moveToDLQ.run({ $id: id, $reason: `Max retries (${this.config.maxRetries}) exceeded` });
470
+ this.stmts.markDead.run({ $id: id });
471
+ }
472
+ const cutoff = new Date(Date.now() - 120000).toISOString().slice(0, 16);
473
+ this.stmts.cleanRateLimits.run({ $cutoff: cutoff });
474
+ return { expired, requeued, deadLettered: exhausted.length };
475
+ }
476
+ close() {
477
+ if (this.cleanupTimer) {
478
+ clearInterval(this.cleanupTimer);
479
+ this.cleanupTimer = null;
480
+ }
481
+ this.db.close();
482
+ }
483
+ checkRateLimit(agent) {
484
+ const window = new Date().toISOString().slice(0, 16);
485
+ const current = this.stmts.checkRate.get({ $agent: agent, $window: window });
486
+ if (current && current.message_count >= this.config.rateLimitPerMinute) {
487
+ throw new Error(`Rate limit exceeded for agent '${agent}': ${this.config.rateLimitPerMinute}/min`);
488
+ }
489
+ this.stmts.upsertRate.run({ $agent: agent, $window: window });
490
+ }
491
+ }
492
+ // src/format.ts
493
+ function formatMessages(rows) {
494
+ if (!rows || rows.length === 0)
495
+ return "No messages found.";
496
+ return rows.map((m) => `[#${m.id}] ${m.priority === "high" ? "!! " : m.priority === "low" ? "-- " : ""}` + `From: @${m.from_agent} -> To: @${m.to_agent}
497
+ ` + `Subject: ${m.subject}
498
+ ` + `Thread: ${m.thread_id} | Status: ${m.status} | Receives: ${m.receive_count}
499
+ ` + `Time: ${m.created_at} | Expires: ${m.expires_at}` + `${m.read_at ? ` | Read: ${m.read_at}` : ""}` + `${m.ack_at ? ` | Acked: ${m.ack_at}` : ""}
500
+ ` + `${m.trace_id ? `Trace: ${m.trace_id}
501
+ ` : ""}` + `---
502
+ ${m.body}
503
+ `).join(`
504
+ ` + "=".repeat(50) + `
505
+
506
+ `);
507
+ }
508
+ function formatThreads(threads) {
509
+ if (!threads || threads.length === 0)
510
+ return "No active threads.";
511
+ return threads.map((t) => `[${t.id}] ${t.subject}
512
+ ` + ` Messages: ${t.message_count} | Unread: ${t.unread_count}
513
+ ` + ` Last activity: ${t.last_message_at}`).join(`
514
+
515
+ `);
516
+ }
517
+ function formatAgents(agents) {
518
+ if (!agents || agents.length === 0)
519
+ return "No registered agents.";
520
+ return agents.map((a) => `@${a.name} [${a.messageCount} msgs]` + `${a.lastActive ? ` last active: ${a.lastActive}` : " (never active)"}
521
+ ` + ` ${a.role ?? "No role defined"}`).join(`
522
+
523
+ `);
524
+ }
525
+ function formatDeadLetters(dls) {
526
+ if (!dls || dls.length === 0)
527
+ return "Dead letter queue is empty.";
528
+ return dls.map((d) => `[DLQ #${d.id}] Original: #${d.original_message_id}
529
+ ` + `From: @${d.from_agent} -> To: @${d.to_agent}
530
+ ` + `Subject: ${d.subject}
531
+ ` + `Reason: ${d.reason}
532
+ ` + `Moved: ${d.moved_at}
533
+ ` + `---
534
+ ${d.body}
535
+ `).join(`
536
+ ` + "=".repeat(50) + `
537
+
538
+ `);
539
+ }
540
+ function formatMetrics(m) {
541
+ const lines = [
542
+ `Mailbox Metrics`,
543
+ `${"=".repeat(40)}`,
544
+ `Total messages: ${m.totalMessages}`,
545
+ `Pending: ${m.pendingMessages}`,
546
+ `Delivered (in-flight): ${m.deliveredMessages}`,
547
+ `Dead letters: ${m.deadLetters}`,
548
+ `Active threads (last 1h): ${m.activeThreads}`,
549
+ `Avg delivery time: ${m.avgDeliveryTimeMs ? `${Math.round(m.avgDeliveryTimeMs)}ms` : "N/A"}`,
550
+ ``,
551
+ `Messages per agent:`,
552
+ ...Object.entries(m.messagesPerAgent).map(([a, c]) => ` @${a}: ${c}`)
553
+ ];
554
+ return lines.join(`
555
+ `);
556
+ }
557
+ export {
558
+ formatThreads,
559
+ formatMetrics,
560
+ formatMessages,
561
+ formatDeadLetters,
562
+ formatAgents,
563
+ Mailbox
564
+ };
565
+
566
+ //# debugId=0F6AC369000346F064756E2164756E21
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/database.ts", "../src/mailbox.ts", "../src/format.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Database initialization and schema management for Agent Mailbox\n */\n\nimport { Database } from \"bun:sqlite\";\nimport { mkdirSync, existsSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { ResolvedConfig } from \"./types.js\";\n\nconst SCHEMA_VERSION = 1;\n\nexport function initDatabase(config: ResolvedConfig): Database {\n // Ensure parent directory exists for non-memory databases\n if (config.dbPath !== \":memory:\") {\n const dir = dirname(config.dbPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n }\n\n const db = new Database(config.dbPath);\n\n if (config.walMode) {\n db.exec(\"PRAGMA journal_mode=WAL\");\n db.exec(\"PRAGMA synchronous=NORMAL\");\n }\n db.exec(\"PRAGMA foreign_keys=ON\");\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS messages (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n from_agent TEXT NOT NULL,\n to_agent TEXT NOT NULL,\n subject TEXT NOT NULL,\n body TEXT NOT NULL,\n thread_id TEXT NOT NULL,\n priority TEXT NOT NULL DEFAULT 'normal' CHECK (priority IN ('high', 'normal', 'low')),\n status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'delivered', 'read', 'acked', 'expired', 'dead')),\n ttl_seconds INTEGER NOT NULL DEFAULT 86400,\n idempotency_key TEXT,\n trace_id TEXT,\n receive_count INTEGER NOT NULL DEFAULT 0,\n visible_after TEXT,\n session_id TEXT NOT NULL DEFAULT '',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n read_at TEXT,\n ack_at TEXT,\n expires_at TEXT NOT NULL DEFAULT (datetime('now', '+86400 seconds'))\n );\n\n CREATE INDEX IF NOT EXISTS idx_messages_to_agent_status ON messages(to_agent, status);\n CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages(thread_id);\n CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at);\n CREATE INDEX IF NOT EXISTS idx_messages_expires ON messages(expires_at);\n CREATE INDEX IF NOT EXISTS idx_messages_visible ON messages(visible_after);\n CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_idempotency ON messages(idempotency_key) WHERE idempotency_key IS NOT NULL;\n\n CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n subject TEXT NOT NULL,\n participants TEXT NOT NULL DEFAULT '[]',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n last_message_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS dead_letters (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n original_message_id INTEGER NOT NULL,\n from_agent TEXT NOT NULL,\n to_agent TEXT NOT NULL,\n subject TEXT NOT NULL,\n body TEXT NOT NULL,\n thread_id TEXT NOT NULL,\n reason TEXT NOT NULL,\n moved_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS rate_limits (\n agent TEXT NOT NULL,\n window_start TEXT NOT NULL,\n message_count INTEGER NOT NULL DEFAULT 1,\n PRIMARY KEY (agent, window_start)\n );\n\n CREATE TABLE IF NOT EXISTS agent_registry (\n name TEXT PRIMARY KEY,\n role TEXT,\n registered_at TEXT NOT NULL DEFAULT (datetime('now')),\n last_active TEXT NOT NULL DEFAULT (datetime('now'))\n );\n `);\n\n // FTS5 for full-text search (separate try since it may already exist)\n try {\n db.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(\n subject, body, content=messages, content_rowid=id\n );\n\n CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN\n INSERT INTO messages_fts(rowid, subject, body) VALUES (new.id, new.subject, new.body);\n END;\n\n CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN\n INSERT INTO messages_fts(messages_fts, rowid, subject, body) VALUES ('delete', old.id, old.subject, old.body);\n END;\n `);\n } catch {\n // FTS5 tables may already exist\n }\n\n // Set schema version\n const currentVersion = db.prepare(\"SELECT version FROM schema_version LIMIT 1\").get() as { version: number } | null;\n if (!currentVersion) {\n db.prepare(\"INSERT INTO schema_version (version) VALUES (?)\").run(SCHEMA_VERSION);\n }\n\n return db;\n}\n",
6
+ "/**\n * Core Mailbox class — the main API for agent-mailbox\n */\n\nimport { Database } from \"bun:sqlite\";\nimport { initDatabase } from \"./database.js\";\nimport type {\n MailboxConfig,\n ResolvedConfig,\n Message,\n SendOptions,\n SendResult,\n InboxOptions,\n SearchOptions,\n Thread,\n DeadLetter,\n MailboxMetrics,\n AgentInfo,\n Priority,\n} from \"./types.js\";\n\nconst DEFAULT_CONFIG: ResolvedConfig = {\n dbPath: \":memory:\",\n defaultTTL: 86400,\n visibilityTimeout: 300,\n maxRetries: 3,\n maxBodySize: 65536,\n rateLimitPerMinute: 60,\n walMode: true,\n cleanupInterval: 300,\n};\n\nfunction resolveConfig(config?: MailboxConfig): ResolvedConfig {\n return { ...DEFAULT_CONFIG, ...config };\n}\n\nfunction generateThreadId(): string {\n return `thread-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction generateIdempotencyKey(): string {\n return `idem-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport class Mailbox {\n readonly db: Database;\n readonly config: ResolvedConfig;\n private cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\n // Prepared statements\n private stmts: ReturnType<typeof this.prepareStatements>;\n\n constructor(config?: MailboxConfig) {\n this.config = resolveConfig(config);\n this.db = initDatabase(this.config);\n this.stmts = this.prepareStatements();\n\n if (this.config.cleanupInterval > 0) {\n this.cleanupTimer = setInterval(\n () => this.cleanup(),\n this.config.cleanupInterval * 1000\n );\n }\n }\n\n private prepareStatements() {\n return {\n insertMessage: this.db.prepare(`\n INSERT INTO messages (from_agent, to_agent, subject, body, thread_id, priority, ttl_seconds, idempotency_key, trace_id, session_id, expires_at)\n VALUES ($from, $to, $subject, $body, $thread_id, $priority, $ttl, $idem_key, $trace_id, $session_id, datetime('now', '+' || $ttl || ' seconds'))\n `),\n\n insertThread: this.db.prepare(`\n INSERT OR IGNORE INTO threads (id, subject, participants) VALUES ($id, $subject, $participants)\n `),\n\n updateThreadTimestamp: this.db.prepare(`\n UPDATE threads SET last_message_at = datetime('now') WHERE id = $id\n `),\n\n // Visibility timeout: only fetch messages that are visible (visible_after IS NULL or past)\n getInbox: this.db.prepare(`\n SELECT * FROM messages\n WHERE (to_agent = $agent OR to_agent = 'broadcast')\n AND from_agent != $agent\n AND status IN ('pending', 'delivered')\n AND (visible_after IS NULL OR visible_after <= datetime('now'))\n AND expires_at > datetime('now')\n ORDER BY\n CASE priority WHEN 'high' THEN 0 WHEN 'normal' THEN 1 WHEN 'low' THEN 2 END,\n created_at DESC\n LIMIT $limit\n `),\n\n getInboxIncludeRead: this.db.prepare(`\n SELECT * FROM messages\n WHERE (to_agent = $agent OR to_agent = 'broadcast')\n AND from_agent != $agent\n AND status NOT IN ('dead', 'expired')\n AND expires_at > datetime('now')\n ORDER BY created_at DESC\n LIMIT $limit\n `),\n\n // Claim: set visibility timeout + increment receive_count\n claimMessage: this.db.prepare(`\n UPDATE messages\n SET status = 'delivered',\n receive_count = receive_count + 1,\n visible_after = datetime('now', '+' || $timeout || ' seconds')\n WHERE id = $id\n `),\n\n markRead: this.db.prepare(`\n UPDATE messages SET status = 'read', read_at = datetime('now'), visible_after = NULL WHERE id = $id\n `),\n\n markAcked: this.db.prepare(`\n UPDATE messages SET status = 'acked', ack_at = datetime('now'), visible_after = NULL WHERE id = $id\n `),\n\n getMessage: this.db.prepare(`SELECT * FROM messages WHERE id = $id`),\n\n searchFTS: this.db.prepare(`\n SELECT m.* FROM messages m\n JOIN messages_fts fts ON m.id = fts.rowid\n WHERE messages_fts MATCH $query\n AND m.expires_at > datetime('now')\n ORDER BY m.created_at DESC\n LIMIT $limit\n `),\n\n searchLIKE: this.db.prepare(`\n SELECT * FROM messages\n WHERE (subject LIKE $q OR body LIKE $q)\n AND expires_at > datetime('now')\n ORDER BY created_at DESC\n LIMIT $limit\n `),\n\n listThreads: this.db.prepare(`\n SELECT t.*,\n COUNT(m.id) as message_count,\n SUM(CASE WHEN m.status IN ('pending', 'delivered') AND (m.to_agent = $agent OR m.to_agent = 'broadcast') THEN 1 ELSE 0 END) as unread_count\n FROM threads t\n LEFT JOIN messages m ON m.thread_id = t.id\n GROUP BY t.id\n ORDER BY t.last_message_at DESC\n LIMIT $limit\n `),\n\n getThreadMessages: this.db.prepare(`\n SELECT * FROM messages WHERE thread_id = $thread_id ORDER BY created_at ASC\n `),\n\n getReply: this.db.prepare(`\n SELECT * FROM messages\n WHERE thread_id = $thread_id AND from_agent = $from AND to_agent = $to AND id > $after_id\n ORDER BY created_at ASC LIMIT 1\n `),\n\n // Dead letter operations\n moveToDLQ: this.db.prepare(`\n INSERT INTO dead_letters (original_message_id, from_agent, to_agent, subject, body, thread_id, reason)\n SELECT id, from_agent, to_agent, subject, body, thread_id, $reason\n FROM messages WHERE id = $id\n `),\n\n markDead: this.db.prepare(`\n UPDATE messages SET status = 'dead' WHERE id = $id\n `),\n\n getDeadLetters: this.db.prepare(`\n SELECT * FROM dead_letters ORDER BY moved_at DESC LIMIT $limit\n `),\n\n replayDeadLetter: this.db.prepare(`\n SELECT * FROM dead_letters WHERE id = $id\n `),\n\n deleteDeadLetter: this.db.prepare(`\n DELETE FROM dead_letters WHERE id = $id\n `),\n\n // Rate limiting\n checkRate: this.db.prepare(`\n SELECT message_count FROM rate_limits\n WHERE agent = $agent AND window_start = $window\n `),\n\n upsertRate: this.db.prepare(`\n INSERT INTO rate_limits (agent, window_start, message_count)\n VALUES ($agent, $window, 1)\n ON CONFLICT(agent, window_start)\n DO UPDATE SET message_count = message_count + 1\n `),\n\n // Agent registry\n upsertAgent: this.db.prepare(`\n INSERT INTO agent_registry (name, role, last_active)\n VALUES ($name, $role, datetime('now'))\n ON CONFLICT(name)\n DO UPDATE SET role = COALESCE($role, role), last_active = datetime('now')\n `),\n\n listAgents: this.db.prepare(`\n SELECT ar.name, ar.role, ar.last_active,\n (SELECT COUNT(*) FROM messages WHERE from_agent = ar.name) as message_count\n FROM agent_registry ar\n ORDER BY ar.last_active DESC\n `),\n\n // Cleanup\n expireMessages: this.db.prepare(`\n UPDATE messages SET status = 'expired' WHERE expires_at <= datetime('now') AND status NOT IN ('acked', 'expired', 'dead')\n `),\n\n requeueTimedOut: this.db.prepare(`\n UPDATE messages SET status = 'pending', visible_after = NULL\n WHERE status = 'delivered'\n AND visible_after IS NOT NULL\n AND visible_after <= datetime('now')\n AND receive_count < $max_retries\n `),\n\n moveExhaustedToDLQ: this.db.prepare(`\n SELECT id FROM messages\n WHERE status = 'delivered'\n AND visible_after IS NOT NULL\n AND visible_after <= datetime('now')\n AND receive_count >= $max_retries\n `),\n\n cleanRateLimits: this.db.prepare(`\n DELETE FROM rate_limits WHERE window_start < $cutoff\n `),\n\n // Metrics\n countByStatus: this.db.prepare(`\n SELECT status, COUNT(*) as cnt FROM messages GROUP BY status\n `),\n\n countDeadLetters: this.db.prepare(`\n SELECT COUNT(*) as cnt FROM dead_letters\n `),\n\n countActiveThreads: this.db.prepare(`\n SELECT COUNT(*) as cnt FROM threads WHERE last_message_at > datetime('now', '-1 hour')\n `),\n\n messagesPerAgent: this.db.prepare(`\n SELECT from_agent, COUNT(*) as cnt FROM messages GROUP BY from_agent ORDER BY cnt DESC\n `),\n\n avgDeliveryTime: this.db.prepare(`\n SELECT AVG((julianday(read_at) - julianday(created_at)) * 86400000) as avg_ms\n FROM messages WHERE read_at IS NOT NULL\n `),\n\n // Idempotency check\n checkIdempotency: this.db.prepare(`\n SELECT id, thread_id FROM messages WHERE idempotency_key = $key\n `),\n };\n }\n\n // ─── Core Operations ─────────────────────────────────────────────\n\n /** Send a message to an agent */\n send(opts: SendOptions): SendResult {\n // Validate body size\n if (Buffer.byteLength(opts.body, \"utf-8\") > this.config.maxBodySize) {\n throw new Error(`Message body exceeds max size of ${this.config.maxBodySize} bytes`);\n }\n\n // Check rate limit\n this.checkRateLimit(opts.from);\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const existing = this.stmts.checkIdempotency.get({ $key: opts.idempotencyKey }) as { id: number; thread_id: string } | null;\n if (existing) {\n return { messageId: existing.id, threadId: existing.thread_id, idempotencyKey: opts.idempotencyKey };\n }\n }\n\n const threadId = opts.threadId ?? generateThreadId();\n const ttl = opts.ttlSeconds ?? this.config.defaultTTL;\n\n // Create/update thread\n this.stmts.insertThread.run({\n $id: threadId,\n $subject: opts.subject,\n $participants: JSON.stringify([opts.from, opts.to]),\n });\n this.stmts.updateThreadTimestamp.run({ $id: threadId });\n\n // Insert message\n const result = this.stmts.insertMessage.run({\n $from: opts.from,\n $to: opts.to,\n $subject: opts.subject,\n $body: opts.body,\n $thread_id: threadId,\n $priority: opts.priority ?? \"normal\",\n $ttl: ttl,\n $idem_key: opts.idempotencyKey ?? null,\n $trace_id: opts.traceId ?? null,\n $session_id: opts.sessionId ?? \"\",\n });\n\n // Update agent registry\n this.stmts.upsertAgent.run({ $name: opts.from, $role: null });\n\n return {\n messageId: Number(result.lastInsertRowid),\n threadId,\n idempotencyKey: opts.idempotencyKey ?? null,\n };\n }\n\n /** Broadcast a message to all agents */\n broadcast(opts: Omit<SendOptions, \"to\">): SendResult {\n return this.send({ ...opts, to: \"broadcast\" });\n }\n\n /** Read inbox with visibility timeout */\n readInbox(opts: InboxOptions): Message[] {\n const limit = opts.limit ?? 20;\n\n if (opts.includeRead) {\n return this.stmts.getInboxIncludeRead.all({ $agent: opts.agent, $limit: limit }) as Message[];\n }\n\n const rows = this.stmts.getInbox.all({ $agent: opts.agent, $limit: limit }) as Message[];\n\n // Claim messages with visibility timeout\n for (const row of rows) {\n this.stmts.claimMessage.run({\n $id: row.id,\n $timeout: this.config.visibilityTimeout,\n });\n }\n\n return rows;\n }\n\n /** Mark a message as read (clears visibility timeout) */\n markRead(messageId: number): void {\n this.stmts.markRead.run({ $id: messageId });\n }\n\n /** Acknowledge a message (confirms processing complete) */\n acknowledge(messageId: number, response?: { from: string; body: string; sessionId?: string }): SendResult | null {\n this.stmts.markAcked.run({ $id: messageId });\n\n if (response) {\n const original = this.stmts.getMessage.get({ $id: messageId }) as Message | null;\n if (original) {\n return this.send({\n from: response.from,\n to: original.from_agent,\n subject: `Re: ${original.subject}`,\n body: response.body,\n threadId: original.thread_id,\n priority: \"normal\",\n sessionId: response.sessionId,\n });\n }\n }\n return null;\n }\n\n /** Search messages using FTS5 with LIKE fallback */\n search(opts: SearchOptions): { messages: Message[]; usedFallback: boolean } {\n const limit = opts.limit ?? 10;\n\n try {\n const rows = this.stmts.searchFTS.all({ $query: opts.query, $limit: limit }) as Message[];\n return { messages: rows, usedFallback: false };\n } catch {\n const rows = this.stmts.searchLIKE.all({ $q: `%${opts.query}%`, $limit: limit }) as Message[];\n return { messages: rows, usedFallback: true };\n }\n }\n\n /** List conversation threads */\n listThreads(agent: string, limit = 10): Thread[] {\n return this.stmts.listThreads.all({ $agent: agent, $limit: limit }) as Thread[];\n }\n\n /** Get all messages in a thread */\n getThread(threadId: string): Message[] {\n return this.stmts.getThreadMessages.all({ $thread_id: threadId }) as Message[];\n }\n\n /** Send a request and poll for reply with exponential backoff */\n async request(\n opts: SendOptions & { timeoutMs?: number }\n ): Promise<{ reply: Message } | { timeout: true; messageId: number; threadId: string }> {\n const timeout = opts.timeoutMs ?? 120_000;\n const { messageId, threadId } = this.send({\n ...opts,\n priority: \"high\",\n body: opts.body + \"\\n\\n---\\nREPLY REQUESTED — sender is waiting.\",\n });\n\n const startTime = Date.now();\n let delay = 500; // Start at 500ms, exponential backoff\n\n while (Date.now() - startTime < timeout) {\n const reply = this.stmts.getReply.get({\n $thread_id: threadId,\n $from: opts.to,\n $to: opts.from,\n $after_id: messageId,\n }) as Message | null;\n\n if (reply) {\n this.markRead(reply.id);\n return { reply };\n }\n\n await new Promise((r) => setTimeout(r, delay));\n delay = Math.min(delay * 1.5, 10_000); // Cap at 10s\n }\n\n return { timeout: true, messageId, threadId };\n }\n\n // ─── Agent Registry ──────────────────────────────────────────────\n\n /** Register an agent (upsert) */\n registerAgent(name: string, role?: string): void {\n this.stmts.upsertAgent.run({ $name: name, $role: role ?? null });\n }\n\n /** List all registered agents */\n listAgents(): AgentInfo[] {\n return this.stmts.listAgents.all() as AgentInfo[];\n }\n\n // ─── Dead Letter Queue ───────────────────────────────────────────\n\n /** Get messages in the dead letter queue */\n getDeadLetters(limit = 20): DeadLetter[] {\n return this.stmts.getDeadLetters.all({ $limit: limit }) as DeadLetter[];\n }\n\n /** Replay a dead letter (re-send the original message) */\n replayDeadLetter(dlqId: number): SendResult | null {\n const dl = this.stmts.replayDeadLetter.get({ $id: dlqId }) as DeadLetter | null;\n if (!dl) return null;\n\n const result = this.send({\n from: dl.from_agent,\n to: dl.to_agent,\n subject: dl.subject,\n body: dl.body,\n threadId: dl.thread_id,\n });\n\n this.stmts.deleteDeadLetter.run({ $id: dlqId });\n return result;\n }\n\n // ─── Metrics ─────────────────────────────────────────────────────\n\n /** Get mailbox metrics snapshot */\n metrics(): MailboxMetrics {\n const statusCounts = this.stmts.countByStatus.all() as { status: string; cnt: number }[];\n const statusMap: Record<string, number> = {};\n let total = 0;\n for (const row of statusCounts) {\n statusMap[row.status] = row.cnt;\n total += row.cnt;\n }\n\n const dlCount = (this.stmts.countDeadLetters.get() as { cnt: number }).cnt;\n const threadCount = (this.stmts.countActiveThreads.get() as { cnt: number }).cnt;\n const perAgent = this.stmts.messagesPerAgent.all() as { from_agent: string; cnt: number }[];\n const avgDel = this.stmts.avgDeliveryTime.get() as { avg_ms: number | null };\n\n return {\n totalMessages: total,\n pendingMessages: statusMap[\"pending\"] ?? 0,\n deliveredMessages: statusMap[\"delivered\"] ?? 0,\n deadLetters: dlCount,\n activeThreads: threadCount,\n messagesPerAgent: Object.fromEntries(perAgent.map((r) => [r.from_agent, r.cnt])),\n avgDeliveryTimeMs: avgDel.avg_ms,\n };\n }\n\n // ─── Maintenance ─────────────────────────────────────────────────\n\n /** Run cleanup: expire messages, requeue timed-out, move exhausted to DLQ */\n cleanup(): { expired: number; requeued: number; deadLettered: number } {\n // Expire old messages\n const expired = this.stmts.expireMessages.run().changes;\n\n // Requeue messages that timed out (visibility expired) but have retries left\n const requeued = this.stmts.requeueTimedOut.run({ $max_retries: this.config.maxRetries }).changes;\n\n // Move exhausted messages to DLQ\n const exhausted = this.stmts.moveExhaustedToDLQ.all({ $max_retries: this.config.maxRetries }) as { id: number }[];\n for (const { id } of exhausted) {\n this.stmts.moveToDLQ.run({ $id: id, $reason: `Max retries (${this.config.maxRetries}) exceeded` });\n this.stmts.markDead.run({ $id: id });\n }\n\n // Clean old rate limit entries\n const cutoff = new Date(Date.now() - 120_000).toISOString().slice(0, 16);\n this.stmts.cleanRateLimits.run({ $cutoff: cutoff });\n\n return { expired, requeued, deadLettered: exhausted.length };\n }\n\n /** Close the database and stop cleanup timer */\n close(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = null;\n }\n this.db.close();\n }\n\n // ─── Private ─────────────────────────────────────────────────────\n\n private checkRateLimit(agent: string): void {\n const window = new Date().toISOString().slice(0, 16); // Per-minute window\n const current = this.stmts.checkRate.get({ $agent: agent, $window: window }) as { message_count: number } | null;\n\n if (current && current.message_count >= this.config.rateLimitPerMinute) {\n throw new Error(`Rate limit exceeded for agent '${agent}': ${this.config.rateLimitPerMinute}/min`);\n }\n\n this.stmts.upsertRate.run({ $agent: agent, $window: window });\n }\n}\n",
7
+ "/**\n * Message formatting utilities\n */\n\nimport type { Message, Thread, AgentInfo, DeadLetter, MailboxMetrics } from \"./types.js\";\n\nexport function formatMessages(rows: Message[]): string {\n if (!rows || rows.length === 0) return \"No messages found.\";\n return rows\n .map(\n (m) =>\n `[#${m.id}] ${m.priority === \"high\" ? \"!! \" : m.priority === \"low\" ? \"-- \" : \"\"}` +\n `From: @${m.from_agent} -> To: @${m.to_agent}\\n` +\n `Subject: ${m.subject}\\n` +\n `Thread: ${m.thread_id} | Status: ${m.status} | Receives: ${m.receive_count}\\n` +\n `Time: ${m.created_at} | Expires: ${m.expires_at}` +\n `${m.read_at ? ` | Read: ${m.read_at}` : \"\"}` +\n `${m.ack_at ? ` | Acked: ${m.ack_at}` : \"\"}\\n` +\n `${m.trace_id ? `Trace: ${m.trace_id}\\n` : \"\"}` +\n `---\\n${m.body}\\n`\n )\n .join(\"\\n\" + \"=\".repeat(50) + \"\\n\\n\");\n}\n\nexport function formatThreads(threads: Thread[]): string {\n if (!threads || threads.length === 0) return \"No active threads.\";\n return threads\n .map(\n (t) =>\n `[${t.id}] ${t.subject}\\n` +\n ` Messages: ${t.message_count} | Unread: ${t.unread_count}\\n` +\n ` Last activity: ${t.last_message_at}`\n )\n .join(\"\\n\\n\");\n}\n\nexport function formatAgents(agents: AgentInfo[]): string {\n if (!agents || agents.length === 0) return \"No registered agents.\";\n return agents\n .map(\n (a) =>\n `@${a.name} [${a.messageCount} msgs]` +\n `${a.lastActive ? ` last active: ${a.lastActive}` : \" (never active)\"}\\n` +\n ` ${a.role ?? \"No role defined\"}`\n )\n .join(\"\\n\\n\");\n}\n\nexport function formatDeadLetters(dls: DeadLetter[]): string {\n if (!dls || dls.length === 0) return \"Dead letter queue is empty.\";\n return dls\n .map(\n (d) =>\n `[DLQ #${d.id}] Original: #${d.original_message_id}\\n` +\n `From: @${d.from_agent} -> To: @${d.to_agent}\\n` +\n `Subject: ${d.subject}\\n` +\n `Reason: ${d.reason}\\n` +\n `Moved: ${d.moved_at}\\n` +\n `---\\n${d.body}\\n`\n )\n .join(\"\\n\" + \"=\".repeat(50) + \"\\n\\n\");\n}\n\nexport function formatMetrics(m: MailboxMetrics): string {\n const lines = [\n `Mailbox Metrics`,\n `${\"=\".repeat(40)}`,\n `Total messages: ${m.totalMessages}`,\n `Pending: ${m.pendingMessages}`,\n `Delivered (in-flight): ${m.deliveredMessages}`,\n `Dead letters: ${m.deadLetters}`,\n `Active threads (last 1h): ${m.activeThreads}`,\n `Avg delivery time: ${m.avgDeliveryTimeMs ? `${Math.round(m.avgDeliveryTimeMs)}ms` : \"N/A\"}`,\n ``,\n `Messages per agent:`,\n ...Object.entries(m.messagesPerAgent).map(([a, c]) => ` @${a}: ${c}`),\n ];\n return lines.join(\"\\n\");\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "0F6AC369000346F064756E2164756E21",
11
+ "names": []
12
+ }