agent-inbox 0.2.1 → 0.2.3

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 (50) hide show
  1. package/bench/inbox-growth.bench.ts +224 -0
  2. package/dist/index.d.ts +12 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +26 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/jsonrpc/mail-push-types.d.ts +9 -0
  7. package/dist/jsonrpc/mail-push-types.d.ts.map +1 -1
  8. package/dist/jsonrpc/mail-push-types.js +1 -0
  9. package/dist/jsonrpc/mail-push-types.js.map +1 -1
  10. package/dist/jsonrpc/mail-server.d.ts +8 -1
  11. package/dist/jsonrpc/mail-server.d.ts.map +1 -1
  12. package/dist/jsonrpc/mail-server.js +42 -1
  13. package/dist/jsonrpc/mail-server.js.map +1 -1
  14. package/dist/push/notifier.d.ts +21 -0
  15. package/dist/push/notifier.d.ts.map +1 -1
  16. package/dist/push/notifier.js +84 -2
  17. package/dist/push/notifier.js.map +1 -1
  18. package/dist/storage/interface.d.ts +12 -0
  19. package/dist/storage/interface.d.ts.map +1 -1
  20. package/dist/storage/memory.d.ts +8 -0
  21. package/dist/storage/memory.d.ts.map +1 -1
  22. package/dist/storage/memory.js +38 -0
  23. package/dist/storage/memory.js.map +1 -1
  24. package/dist/storage/sqlite.d.ts +8 -0
  25. package/dist/storage/sqlite.d.ts.map +1 -1
  26. package/dist/storage/sqlite.js +51 -1
  27. package/dist/storage/sqlite.js.map +1 -1
  28. package/dist/traceability/traceability.d.ts.map +1 -1
  29. package/dist/traceability/traceability.js +7 -17
  30. package/dist/traceability/traceability.js.map +1 -1
  31. package/dist/types.d.ts +1 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/package.json +5 -4
  34. package/src/index.ts +38 -1
  35. package/src/jsonrpc/mail-push-types.ts +10 -0
  36. package/src/jsonrpc/mail-server.ts +48 -1
  37. package/src/push/notifier.ts +98 -2
  38. package/src/storage/interface.ts +11 -0
  39. package/src/storage/memory.ts +44 -0
  40. package/src/storage/sqlite.ts +78 -1
  41. package/src/traceability/traceability.ts +7 -16
  42. package/src/types.ts +1 -0
  43. package/test/load.test.ts +288 -0
  44. package/test/mail-presence.test.ts +149 -0
  45. package/test/mail-push.test.ts +44 -0
  46. package/test/mail-server.test.ts +25 -0
  47. package/test/push-notifier.test.ts +81 -0
  48. package/test/sqlite-storage.test.ts +106 -0
  49. package/test/storage.test.ts +92 -0
  50. 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
+ });