agent-inbox 0.2.2 → 0.2.4

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.
Files changed (149) hide show
  1. package/AGENTS.md +18 -0
  2. package/CLAUDE.md +92 -1
  3. package/README.md +73 -6
  4. package/bench/inbox-growth.bench.ts +224 -0
  5. package/dist/federation/connection-manager.d.ts +8 -0
  6. package/dist/federation/connection-manager.d.ts.map +1 -1
  7. package/dist/federation/connection-manager.js +12 -0
  8. package/dist/federation/connection-manager.js.map +1 -1
  9. package/dist/federation/delivery-queue.d.ts +11 -3
  10. package/dist/federation/delivery-queue.d.ts.map +1 -1
  11. package/dist/federation/delivery-queue.js +38 -8
  12. package/dist/federation/delivery-queue.js.map +1 -1
  13. package/dist/federation/queue-store.d.ts +42 -0
  14. package/dist/federation/queue-store.d.ts.map +1 -0
  15. package/dist/federation/queue-store.js +87 -0
  16. package/dist/federation/queue-store.js.map +1 -0
  17. package/dist/index.d.mts +2 -0
  18. package/dist/index.d.ts +29 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +124 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +1 -0
  23. package/dist/index.mjs.map +1 -0
  24. package/dist/jsonrpc/mail-push-types.d.ts +9 -0
  25. package/dist/jsonrpc/mail-push-types.d.ts.map +1 -1
  26. package/dist/jsonrpc/mail-push-types.js +1 -0
  27. package/dist/jsonrpc/mail-push-types.js.map +1 -1
  28. package/dist/jsonrpc/mail-server.d.ts +8 -1
  29. package/dist/jsonrpc/mail-server.d.ts.map +1 -1
  30. package/dist/jsonrpc/mail-server.js +42 -1
  31. package/dist/jsonrpc/mail-server.js.map +1 -1
  32. package/dist/mail/address-book.d.ts +43 -0
  33. package/dist/mail/address-book.d.ts.map +1 -0
  34. package/dist/mail/address-book.js +95 -0
  35. package/dist/mail/address-book.js.map +1 -0
  36. package/dist/mail/attachment-store.d.ts +31 -0
  37. package/dist/mail/attachment-store.d.ts.map +1 -0
  38. package/dist/mail/attachment-store.js +74 -0
  39. package/dist/mail/attachment-store.js.map +1 -0
  40. package/dist/mail/email-mapper.d.ts +41 -0
  41. package/dist/mail/email-mapper.d.ts.map +1 -0
  42. package/dist/mail/email-mapper.js +216 -0
  43. package/dist/mail/email-mapper.js.map +1 -0
  44. package/dist/mail/fs-attachment-store.d.ts +38 -0
  45. package/dist/mail/fs-attachment-store.d.ts.map +1 -0
  46. package/dist/mail/fs-attachment-store.js +165 -0
  47. package/dist/mail/fs-attachment-store.js.map +1 -0
  48. package/dist/mail/mail-gateway.d.ts +114 -0
  49. package/dist/mail/mail-gateway.d.ts.map +1 -0
  50. package/dist/mail/mail-gateway.js +402 -0
  51. package/dist/mail/mail-gateway.js.map +1 -0
  52. package/dist/mail/provider-transport.d.ts +138 -0
  53. package/dist/mail/provider-transport.d.ts.map +1 -0
  54. package/dist/mail/provider-transport.js +434 -0
  55. package/dist/mail/provider-transport.js.map +1 -0
  56. package/dist/mail/rate-limiter.d.ts +20 -0
  57. package/dist/mail/rate-limiter.d.ts.map +1 -0
  58. package/dist/mail/rate-limiter.js +56 -0
  59. package/dist/mail/rate-limiter.js.map +1 -0
  60. package/dist/mail/smtp-transport.d.ts +141 -0
  61. package/dist/mail/smtp-transport.d.ts.map +1 -0
  62. package/dist/mail/smtp-transport.js +415 -0
  63. package/dist/mail/smtp-transport.js.map +1 -0
  64. package/dist/mail/types.d.ts +177 -0
  65. package/dist/mail/types.d.ts.map +1 -0
  66. package/dist/mail/types.js +11 -0
  67. package/dist/mail/types.js.map +1 -0
  68. package/dist/push/notifier.d.ts +21 -0
  69. package/dist/push/notifier.d.ts.map +1 -1
  70. package/dist/push/notifier.js +84 -2
  71. package/dist/push/notifier.js.map +1 -1
  72. package/dist/router/destination.d.ts +69 -0
  73. package/dist/router/destination.d.ts.map +1 -0
  74. package/dist/router/destination.js +106 -0
  75. package/dist/router/destination.js.map +1 -0
  76. package/dist/router/message-router.d.ts +15 -0
  77. package/dist/router/message-router.d.ts.map +1 -1
  78. package/dist/router/message-router.js +25 -3
  79. package/dist/router/message-router.js.map +1 -1
  80. package/dist/storage/interface.d.ts +21 -0
  81. package/dist/storage/interface.d.ts.map +1 -1
  82. package/dist/storage/memory.d.ts +12 -0
  83. package/dist/storage/memory.d.ts.map +1 -1
  84. package/dist/storage/memory.js +50 -0
  85. package/dist/storage/memory.js.map +1 -1
  86. package/dist/storage/sqlite.d.ts +14 -0
  87. package/dist/storage/sqlite.d.ts.map +1 -1
  88. package/dist/storage/sqlite.js +79 -1
  89. package/dist/storage/sqlite.js.map +1 -1
  90. package/dist/traceability/traceability.d.ts.map +1 -1
  91. package/dist/traceability/traceability.js +7 -17
  92. package/dist/traceability/traceability.js.map +1 -1
  93. package/dist/types.d.ts +80 -0
  94. package/dist/types.d.ts.map +1 -1
  95. package/docs/DESIGN.md +15 -0
  96. package/docs/MAIL-INTEROP-PLAN.md +660 -0
  97. package/package.json +29 -3
  98. package/renovate.json5 +6 -0
  99. package/rules/agent-inbox.md +1 -0
  100. package/src/federation/connection-manager.ts +12 -0
  101. package/src/federation/delivery-queue.ts +38 -8
  102. package/src/federation/queue-store.ts +124 -0
  103. package/src/index.ts +186 -1
  104. package/src/jsonrpc/mail-push-types.ts +10 -0
  105. package/src/jsonrpc/mail-server.ts +48 -1
  106. package/src/mail/address-book.ts +111 -0
  107. package/src/mail/attachment-store.ts +90 -0
  108. package/src/mail/email-mapper.ts +288 -0
  109. package/src/mail/fs-attachment-store.ts +163 -0
  110. package/src/mail/mail-gateway.ts +505 -0
  111. package/src/mail/provider-transport.ts +577 -0
  112. package/src/mail/rate-limiter.ts +51 -0
  113. package/src/mail/smtp-transport.ts +589 -0
  114. package/src/mail/types.ts +221 -0
  115. package/src/push/notifier.ts +98 -2
  116. package/src/router/destination.ts +140 -0
  117. package/src/router/message-router.ts +41 -4
  118. package/src/storage/interface.ts +22 -0
  119. package/src/storage/memory.ts +59 -0
  120. package/src/storage/sqlite.ts +114 -1
  121. package/src/traceability/traceability.ts +7 -16
  122. package/src/types.ts +74 -0
  123. package/test/federation/delivery-queue-sqlite.test.ts +158 -0
  124. package/test/load.test.ts +288 -0
  125. package/test/mail/address-book.test.ts +111 -0
  126. package/test/mail/attachment-store-contract.test.ts +92 -0
  127. package/test/mail/attachment-store.test.ts +69 -0
  128. package/test/mail/destination.test.ts +115 -0
  129. package/test/mail/dsn-parse.test.ts +239 -0
  130. package/test/mail/email-mapper.test.ts +341 -0
  131. package/test/mail/external-id.test.ts +43 -0
  132. package/test/mail/fs-attachment-store.test.ts +134 -0
  133. package/test/mail/full-flow-e2e.test.ts +200 -0
  134. package/test/mail/mail-gateway.test.ts +419 -0
  135. package/test/mail/mail-transport-contract.test.ts +134 -0
  136. package/test/mail/mock-mail.ts +161 -0
  137. package/test/mail/mock-postmark.ts +66 -0
  138. package/test/mail/provider-transport.test.ts +381 -0
  139. package/test/mail/rate-limiter.test.ts +48 -0
  140. package/test/mail/router-mail-integration.test.ts +138 -0
  141. package/test/mail/smtp-e2e.test.ts +98 -0
  142. package/test/mail/smtp-transport.test.ts +138 -0
  143. package/test/mail-presence.test.ts +149 -0
  144. package/test/mail-push.test.ts +44 -0
  145. package/test/mail-server.test.ts +25 -0
  146. package/test/push-notifier.test.ts +81 -0
  147. package/test/sqlite-storage.test.ts +106 -0
  148. package/test/storage.test.ts +92 -0
  149. package/vitest.bench.config.ts +8 -0
@@ -240,4 +240,110 @@ describe("SqliteStorage", () => {
240
240
  expect(threads[0].root_turn_id).toBe("turn-1");
241
241
  });
242
242
  });
243
+
244
+ describe("setMessageConversationId / touchConversation / addParticipant", () => {
245
+ it("should set conversation_id without rewriting recipients", () => {
246
+ storage.putMessage(
247
+ makeMessage({
248
+ id: "m1",
249
+ recipients: [
250
+ { agent_id: "bob", kind: "to" },
251
+ { agent_id: "carol", kind: "cc" },
252
+ ],
253
+ })
254
+ );
255
+
256
+ storage.setMessageConversationId("m1", "conv-99");
257
+ const m = storage.getMessage("m1")!;
258
+ expect(m.conversation_id).toBe("conv-99");
259
+ expect(m.recipients).toHaveLength(2);
260
+ });
261
+
262
+ it("should touchConversation without disturbing participants", () => {
263
+ storage.putConversation({
264
+ id: "conv-1", scope: "default", status: "active",
265
+ participants: [
266
+ { agent_id: "alice", joined_at: "2025-01-01T00:00:00Z" },
267
+ ],
268
+ metadata: {}, created_at: "2025-01-01T00:00:00Z",
269
+ updated_at: "2025-01-01T00:00:00Z",
270
+ });
271
+
272
+ storage.touchConversation("conv-1", "2025-02-01T00:00:00Z");
273
+ const c = storage.getConversation("conv-1")!;
274
+ expect(c.updated_at).toBe("2025-02-01T00:00:00Z");
275
+ expect(c.participants).toHaveLength(1);
276
+ });
277
+
278
+ it("should addParticipant idempotently", () => {
279
+ storage.putConversation({
280
+ id: "conv-1", scope: "default", status: "active",
281
+ participants: [], metadata: {},
282
+ created_at: "2025-01-01T00:00:00Z",
283
+ updated_at: "2025-01-01T00:00:00Z",
284
+ });
285
+
286
+ storage.addParticipant("conv-1", {
287
+ agent_id: "alice",
288
+ joined_at: "2025-01-01T00:00:00Z",
289
+ });
290
+ storage.addParticipant("conv-1", {
291
+ agent_id: "alice",
292
+ joined_at: "2025-01-02T00:00:00Z",
293
+ });
294
+
295
+ const c = storage.getConversation("conv-1")!;
296
+ expect(c.participants).toHaveLength(1);
297
+ });
298
+ });
299
+
300
+ describe("pruneMessagesOlderThan", () => {
301
+ it("should remove old messages, recipients, turns and orphan threads", () => {
302
+ storage.putConversation({
303
+ id: "c1", scope: "default", status: "active", participants: [],
304
+ metadata: {}, created_at: "2024-01-01T00:00:00Z",
305
+ updated_at: "2026-01-01T00:00:00Z",
306
+ });
307
+ storage.putMessage(
308
+ makeMessage({ id: "old", created_at: "2024-01-01T00:00:00Z" })
309
+ );
310
+ storage.putMessage(
311
+ makeMessage({ id: "new", created_at: "2026-01-01T00:00:00Z" })
312
+ );
313
+
314
+ storage.addTurn({
315
+ id: "turn-old", conversation_id: "c1", participant_id: "alice",
316
+ source_message_id: "old", content_type: "text",
317
+ content: { type: "text", text: "x" },
318
+ created_at: "2024-01-01T00:00:00Z",
319
+ });
320
+ storage.addTurn({
321
+ id: "turn-new", conversation_id: "c1", participant_id: "alice",
322
+ source_message_id: "new", content_type: "text",
323
+ content: { type: "text", text: "y" },
324
+ created_at: "2026-01-01T00:00:00Z",
325
+ });
326
+ storage.putThread({
327
+ id: "thread-old", conversation_id: "c1",
328
+ root_turn_id: "turn-old",
329
+ created_at: "2024-01-01T00:00:00Z",
330
+ });
331
+ storage.putThread({
332
+ id: "thread-new", conversation_id: "c1",
333
+ root_turn_id: "turn-new",
334
+ created_at: "2026-01-01T00:00:00Z",
335
+ });
336
+
337
+ const removed = storage.pruneMessagesOlderThan("2025-01-01T00:00:00Z");
338
+ expect(removed).toBe(1);
339
+ expect(storage.getMessage("old")).toBeUndefined();
340
+ expect(storage.getMessage("new")).toBeDefined();
341
+
342
+ const turns = storage.getTurns("c1").map((t) => t.id);
343
+ expect(turns).toEqual(["turn-new"]);
344
+
345
+ const threads = storage.getThreadsByConversation("c1").map((t) => t.id);
346
+ expect(threads).toEqual(["thread-new"]);
347
+ });
348
+ });
243
349
  });
@@ -193,4 +193,96 @@ describe("InMemoryStorage", () => {
193
193
  expect(storage.getTurns("conv-2")).toHaveLength(0);
194
194
  });
195
195
  });
196
+
197
+ describe("setMessageConversationId", () => {
198
+ it("should set conversation_id on an existing message", () => {
199
+ storage.putMessage(makeMessage({ id: "m1" }));
200
+ storage.setMessageConversationId("m1", "conv-99");
201
+ expect(storage.getMessage("m1")?.conversation_id).toBe("conv-99");
202
+ });
203
+
204
+ it("should be a no-op for unknown message", () => {
205
+ expect(() =>
206
+ storage.setMessageConversationId("missing", "conv-99")
207
+ ).not.toThrow();
208
+ });
209
+ });
210
+
211
+ describe("touchConversation", () => {
212
+ it("should bump updated_at without disturbing participants", () => {
213
+ const conv: Conversation = {
214
+ id: "conv-1",
215
+ scope: "default",
216
+ status: "active",
217
+ participants: [
218
+ { agent_id: "alice", joined_at: "2025-01-01T00:00:00Z" },
219
+ ],
220
+ metadata: {},
221
+ created_at: "2025-01-01T00:00:00Z",
222
+ updated_at: "2025-01-01T00:00:00Z",
223
+ };
224
+ storage.putConversation(conv);
225
+
226
+ storage.touchConversation("conv-1", "2025-02-01T00:00:00Z");
227
+ const updated = storage.getConversation("conv-1");
228
+ expect(updated?.updated_at).toBe("2025-02-01T00:00:00Z");
229
+ expect(updated?.participants).toHaveLength(1);
230
+ });
231
+ });
232
+
233
+ describe("addParticipant", () => {
234
+ it("should add a participant once and ignore duplicates", () => {
235
+ storage.putConversation({
236
+ id: "conv-1", scope: "default", status: "active",
237
+ participants: [], metadata: {},
238
+ created_at: "2025-01-01T00:00:00Z",
239
+ updated_at: "2025-01-01T00:00:00Z",
240
+ });
241
+
242
+ storage.addParticipant("conv-1", {
243
+ agent_id: "alice",
244
+ joined_at: "2025-01-01T00:00:00Z",
245
+ });
246
+ storage.addParticipant("conv-1", {
247
+ agent_id: "alice",
248
+ joined_at: "2025-01-02T00:00:00Z",
249
+ });
250
+
251
+ const conv = storage.getConversation("conv-1");
252
+ expect(conv?.participants).toHaveLength(1);
253
+ expect(conv?.participants[0].agent_id).toBe("alice");
254
+ });
255
+ });
256
+
257
+ describe("pruneMessagesOlderThan", () => {
258
+ it("should remove messages and their turns older than cutoff", () => {
259
+ storage.putMessage(makeMessage({ id: "old", created_at: "2024-01-01T00:00:00Z" }));
260
+ storage.putMessage(makeMessage({ id: "new", created_at: "2026-01-01T00:00:00Z" }));
261
+ storage.addTurn({
262
+ id: "turn-old",
263
+ conversation_id: "c1",
264
+ participant_id: "alice",
265
+ source_message_id: "old",
266
+ content_type: "text",
267
+ content: { type: "text", text: "x" },
268
+ created_at: "2024-01-01T00:00:00Z",
269
+ });
270
+ storage.addTurn({
271
+ id: "turn-new",
272
+ conversation_id: "c1",
273
+ participant_id: "alice",
274
+ source_message_id: "new",
275
+ content_type: "text",
276
+ content: { type: "text", text: "y" },
277
+ created_at: "2026-01-01T00:00:00Z",
278
+ });
279
+
280
+ const removed = storage.pruneMessagesOlderThan("2025-01-01T00:00:00Z");
281
+ expect(removed).toBe(1);
282
+ expect(storage.getMessage("old")).toBeUndefined();
283
+ expect(storage.getMessage("new")).toBeDefined();
284
+ const turns = storage.getTurns("c1");
285
+ expect(turns.map((t) => t.id)).toEqual(["turn-new"]);
286
+ });
287
+ });
196
288
  });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["bench/**/*.bench.ts"],
6
+ testTimeout: 120_000,
7
+ },
8
+ });