agent-inbox 0.0.1 → 0.1.1

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 (126) hide show
  1. package/CLAUDE.md +113 -0
  2. package/README.md +195 -1
  3. package/dist/federation/address.d.ts +24 -0
  4. package/dist/federation/address.d.ts.map +1 -0
  5. package/dist/federation/address.js +54 -0
  6. package/dist/federation/address.js.map +1 -0
  7. package/dist/federation/connection-manager.d.ts +118 -0
  8. package/dist/federation/connection-manager.d.ts.map +1 -0
  9. package/dist/federation/connection-manager.js +369 -0
  10. package/dist/federation/connection-manager.js.map +1 -0
  11. package/dist/federation/delivery-queue.d.ts +66 -0
  12. package/dist/federation/delivery-queue.d.ts.map +1 -0
  13. package/dist/federation/delivery-queue.js +199 -0
  14. package/dist/federation/delivery-queue.js.map +1 -0
  15. package/dist/federation/index.d.ts +7 -0
  16. package/dist/federation/index.d.ts.map +1 -0
  17. package/dist/federation/index.js +6 -0
  18. package/dist/federation/index.js.map +1 -0
  19. package/dist/federation/routing-engine.d.ts +74 -0
  20. package/dist/federation/routing-engine.d.ts.map +1 -0
  21. package/dist/federation/routing-engine.js +158 -0
  22. package/dist/federation/routing-engine.js.map +1 -0
  23. package/dist/federation/trust.d.ts +39 -0
  24. package/dist/federation/trust.d.ts.map +1 -0
  25. package/dist/federation/trust.js +64 -0
  26. package/dist/federation/trust.js.map +1 -0
  27. package/dist/index.d.ts +60 -2
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +217 -18
  30. package/dist/index.js.map +1 -1
  31. package/dist/ipc/ipc-server.d.ts +20 -0
  32. package/dist/ipc/ipc-server.d.ts.map +1 -0
  33. package/dist/ipc/ipc-server.js +152 -0
  34. package/dist/ipc/ipc-server.js.map +1 -0
  35. package/dist/jsonrpc/mail-server.d.ts +45 -0
  36. package/dist/jsonrpc/mail-server.d.ts.map +1 -0
  37. package/dist/jsonrpc/mail-server.js +284 -0
  38. package/dist/jsonrpc/mail-server.js.map +1 -0
  39. package/dist/map/map-client.d.ts +91 -0
  40. package/dist/map/map-client.d.ts.map +1 -0
  41. package/dist/map/map-client.js +202 -0
  42. package/dist/map/map-client.js.map +1 -0
  43. package/dist/mcp/mcp-server.d.ts +23 -0
  44. package/dist/mcp/mcp-server.d.ts.map +1 -0
  45. package/dist/mcp/mcp-server.js +226 -0
  46. package/dist/mcp/mcp-server.js.map +1 -0
  47. package/dist/push/notifier.d.ts +49 -0
  48. package/dist/push/notifier.d.ts.map +1 -0
  49. package/dist/push/notifier.js +150 -0
  50. package/dist/push/notifier.js.map +1 -0
  51. package/dist/registry/warm-registry.d.ts +63 -0
  52. package/dist/registry/warm-registry.d.ts.map +1 -0
  53. package/dist/registry/warm-registry.js +173 -0
  54. package/dist/registry/warm-registry.js.map +1 -0
  55. package/dist/router/message-router.d.ts +44 -0
  56. package/dist/router/message-router.d.ts.map +1 -0
  57. package/dist/router/message-router.js +137 -0
  58. package/dist/router/message-router.js.map +1 -0
  59. package/dist/storage/interface.d.ts +31 -0
  60. package/dist/storage/interface.d.ts.map +1 -0
  61. package/dist/storage/interface.js +2 -0
  62. package/dist/storage/interface.js.map +1 -0
  63. package/dist/storage/memory.d.ts +28 -0
  64. package/dist/storage/memory.d.ts.map +1 -0
  65. package/dist/storage/memory.js +118 -0
  66. package/dist/storage/memory.js.map +1 -0
  67. package/dist/storage/sqlite.d.ts +35 -0
  68. package/dist/storage/sqlite.d.ts.map +1 -0
  69. package/dist/storage/sqlite.js +445 -0
  70. package/dist/storage/sqlite.js.map +1 -0
  71. package/dist/traceability/traceability.d.ts +29 -0
  72. package/dist/traceability/traceability.d.ts.map +1 -0
  73. package/dist/traceability/traceability.js +150 -0
  74. package/dist/traceability/traceability.js.map +1 -0
  75. package/dist/types.d.ts +253 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +3 -0
  78. package/dist/types.js.map +1 -0
  79. package/docs/DESIGN.md +1156 -0
  80. package/docs/PLAN.md +545 -0
  81. package/hooks/inbox-hook.mjs +119 -0
  82. package/hooks/register-hook.mjs +69 -0
  83. package/package.json +33 -25
  84. package/rules/agent-inbox.md +78 -0
  85. package/src/federation/address.ts +61 -0
  86. package/src/federation/connection-manager.ts +458 -0
  87. package/src/federation/delivery-queue.ts +222 -0
  88. package/src/federation/index.ts +6 -0
  89. package/src/federation/routing-engine.ts +188 -0
  90. package/src/federation/trust.ts +71 -0
  91. package/src/index.ts +299 -0
  92. package/src/ipc/ipc-server.ts +180 -0
  93. package/src/jsonrpc/mail-server.ts +356 -0
  94. package/src/map/map-client.ts +260 -0
  95. package/src/mcp/mcp-server.ts +272 -0
  96. package/src/push/notifier.ts +192 -0
  97. package/src/registry/warm-registry.ts +210 -0
  98. package/src/router/message-router.ts +175 -0
  99. package/src/storage/interface.ts +48 -0
  100. package/src/storage/memory.ts +145 -0
  101. package/src/storage/sqlite.ts +645 -0
  102. package/src/traceability/traceability.ts +183 -0
  103. package/src/types.ts +287 -0
  104. package/test/federation/address.test.ts +101 -0
  105. package/test/federation/connection-manager.test.ts +546 -0
  106. package/test/federation/delivery-queue.test.ts +159 -0
  107. package/test/federation/integration.test.ts +823 -0
  108. package/test/federation/routing-engine.test.ts +117 -0
  109. package/test/federation/sdk-integration.test.ts +748 -0
  110. package/test/federation/trust.test.ts +89 -0
  111. package/test/ipc-jsonrpc.test.ts +113 -0
  112. package/test/ipc-server.test.ts +138 -0
  113. package/test/mail-server.test.ts +208 -0
  114. package/test/map-client.test.ts +408 -0
  115. package/test/message-router.test.ts +184 -0
  116. package/test/push-notifier.test.ts +139 -0
  117. package/test/registry/warm-registry.test.ts +171 -0
  118. package/test/sqlite-storage.test.ts +243 -0
  119. package/test/storage.test.ts +196 -0
  120. package/test/traceability.test.ts +123 -0
  121. package/tsconfig.json +20 -0
  122. package/tsup.config.ts +10 -0
  123. package/vitest.config.ts +8 -0
  124. package/dist/index.d.mts +0 -2
  125. package/dist/index.mjs +0 -1
  126. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,196 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { InMemoryStorage } from "../src/storage/memory.js";
3
+ import type { Agent, Message, Recipient, Conversation, Turn } from "../src/types.js";
4
+
5
+ function makeAgent(overrides: Partial<Agent> = {}): Agent {
6
+ return {
7
+ agent_id: "agent-1",
8
+ scope: "default",
9
+ status: "active",
10
+ metadata: {},
11
+ registered_at: "2025-01-01T00:00:00Z",
12
+ last_active_at: "2025-01-01T00:00:00Z",
13
+ ...overrides,
14
+ };
15
+ }
16
+
17
+ function makeMessage(overrides: Partial<Message> = {}): Message {
18
+ return {
19
+ id: "msg-1",
20
+ scope: "default",
21
+ sender_id: "agent-1",
22
+ recipients: [{ agent_id: "agent-2", kind: "to" }],
23
+ content: { type: "text", text: "hello" },
24
+ importance: "normal",
25
+ metadata: {},
26
+ created_at: "2025-01-01T00:00:00Z",
27
+ ...overrides,
28
+ };
29
+ }
30
+
31
+ describe("InMemoryStorage", () => {
32
+ let storage: InMemoryStorage;
33
+
34
+ beforeEach(() => {
35
+ storage = new InMemoryStorage();
36
+ });
37
+
38
+ describe("Agents", () => {
39
+ it("should put and get an agent", () => {
40
+ const agent = makeAgent();
41
+ storage.putAgent(agent);
42
+ expect(storage.getAgent("agent-1")).toEqual(agent);
43
+ });
44
+
45
+ it("should return undefined for missing agent", () => {
46
+ expect(storage.getAgent("nope")).toBeUndefined();
47
+ });
48
+
49
+ it("should list agents by scope", () => {
50
+ storage.putAgent(makeAgent({ agent_id: "a1", scope: "team-a" }));
51
+ storage.putAgent(makeAgent({ agent_id: "a2", scope: "team-b" }));
52
+ storage.putAgent(makeAgent({ agent_id: "a3", scope: "team-a" }));
53
+
54
+ expect(storage.listAgents("team-a")).toHaveLength(2);
55
+ expect(storage.listAgents("team-b")).toHaveLength(1);
56
+ expect(storage.listAgents()).toHaveLength(3);
57
+ });
58
+
59
+ it("should remove an agent", () => {
60
+ storage.putAgent(makeAgent());
61
+ expect(storage.removeAgent("agent-1")).toBe(true);
62
+ expect(storage.getAgent("agent-1")).toBeUndefined();
63
+ expect(storage.removeAgent("agent-1")).toBe(false);
64
+ });
65
+ });
66
+
67
+ describe("Messages", () => {
68
+ it("should put and get a message", () => {
69
+ const msg = makeMessage();
70
+ storage.putMessage(msg);
71
+ expect(storage.getMessage("msg-1")).toEqual(msg);
72
+ });
73
+
74
+ it("should get inbox for agent", () => {
75
+ storage.putMessage(
76
+ makeMessage({
77
+ id: "m1",
78
+ recipients: [{ agent_id: "bob", kind: "to" }],
79
+ created_at: "2025-01-01T00:00:01Z",
80
+ })
81
+ );
82
+ storage.putMessage(
83
+ makeMessage({
84
+ id: "m2",
85
+ recipients: [{ agent_id: "bob", kind: "to" }],
86
+ created_at: "2025-01-01T00:00:02Z",
87
+ })
88
+ );
89
+ storage.putMessage(
90
+ makeMessage({
91
+ id: "m3",
92
+ recipients: [{ agent_id: "alice", kind: "to" }],
93
+ })
94
+ );
95
+
96
+ const inbox = storage.getInbox("bob");
97
+ expect(inbox).toHaveLength(2);
98
+ expect(inbox[0].id).toBe("m1");
99
+ expect(inbox[1].id).toBe("m2");
100
+ });
101
+
102
+ it("should filter unread only", () => {
103
+ storage.putMessage(
104
+ makeMessage({
105
+ id: "m1",
106
+ recipients: [{ agent_id: "bob", kind: "to", read_at: "2025-01-01T00:00:05Z" }],
107
+ })
108
+ );
109
+ storage.putMessage(
110
+ makeMessage({
111
+ id: "m2",
112
+ recipients: [{ agent_id: "bob", kind: "to" }],
113
+ })
114
+ );
115
+
116
+ const unread = storage.getInbox("bob", { unreadOnly: true });
117
+ expect(unread).toHaveLength(1);
118
+ expect(unread[0].id).toBe("m2");
119
+ });
120
+
121
+ it("should get thread by tag", () => {
122
+ storage.putMessage(makeMessage({ id: "m1", thread_tag: "sprint-1", scope: "default" }));
123
+ storage.putMessage(makeMessage({ id: "m2", thread_tag: "sprint-1", scope: "default" }));
124
+ storage.putMessage(makeMessage({ id: "m3", thread_tag: "sprint-2", scope: "default" }));
125
+
126
+ const thread = storage.getThread({ threadTag: "sprint-1", scope: "default" });
127
+ expect(thread).toHaveLength(2);
128
+ });
129
+
130
+ it("should search messages by text content", () => {
131
+ storage.putMessage(makeMessage({ id: "m1", content: { type: "text", text: "fix the auth bug" } }));
132
+ storage.putMessage(makeMessage({ id: "m2", content: { type: "text", text: "deploy the app" } }));
133
+ storage.putMessage(makeMessage({ id: "m3", subject: "auth issue", content: { type: "text", text: "details" } }));
134
+
135
+ expect(storage.searchMessages("auth")).toHaveLength(2);
136
+ expect(storage.searchMessages("deploy")).toHaveLength(1);
137
+ expect(storage.searchMessages("nonexistent")).toHaveLength(0);
138
+ });
139
+
140
+ it("should get sent messages", () => {
141
+ storage.putMessage(makeMessage({ id: "m1", sender_id: "alice" }));
142
+ storage.putMessage(makeMessage({ id: "m2", sender_id: "bob" }));
143
+ storage.putMessage(makeMessage({ id: "m3", sender_id: "alice" }));
144
+
145
+ expect(storage.getSentMessages("alice")).toHaveLength(2);
146
+ expect(storage.getSentMessages("bob")).toHaveLength(1);
147
+ });
148
+ });
149
+
150
+ describe("Conversations", () => {
151
+ it("should put and get a conversation", () => {
152
+ const conv: Conversation = {
153
+ id: "conv-1",
154
+ scope: "default",
155
+ subject: "Test",
156
+ status: "active",
157
+ participants: [],
158
+ metadata: {},
159
+ created_at: "2025-01-01T00:00:00Z",
160
+ updated_at: "2025-01-01T00:00:00Z",
161
+ };
162
+ storage.putConversation(conv);
163
+ expect(storage.getConversation("conv-1")).toEqual(conv);
164
+ });
165
+
166
+ it("should list conversations by scope", () => {
167
+ storage.putConversation({
168
+ id: "c1", scope: "team-a", status: "active", participants: [],
169
+ metadata: {}, created_at: "2025-01-01T00:00:00Z", updated_at: "2025-01-01T00:00:00Z",
170
+ });
171
+ storage.putConversation({
172
+ id: "c2", scope: "team-b", status: "active", participants: [],
173
+ metadata: {}, created_at: "2025-01-01T00:00:00Z", updated_at: "2025-01-01T00:00:00Z",
174
+ });
175
+
176
+ expect(storage.listConversations("team-a")).toHaveLength(1);
177
+ expect(storage.listConversations()).toHaveLength(2);
178
+ });
179
+ });
180
+
181
+ describe("Turns", () => {
182
+ it("should add and retrieve turns", () => {
183
+ const turn: Turn = {
184
+ id: "turn-1",
185
+ conversation_id: "conv-1",
186
+ participant_id: "agent-1",
187
+ content_type: "text",
188
+ content: { type: "text", text: "hello" },
189
+ created_at: "2025-01-01T00:00:00Z",
190
+ };
191
+ storage.addTurn(turn);
192
+ expect(storage.getTurns("conv-1")).toHaveLength(1);
193
+ expect(storage.getTurns("conv-2")).toHaveLength(0);
194
+ });
195
+ });
196
+ });
@@ -0,0 +1,123 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { EventEmitter } from "node:events";
3
+ import { InMemoryStorage } from "../src/storage/memory.js";
4
+ import { MessageRouter } from "../src/router/message-router.js";
5
+ import { TraceabilityLayer } from "../src/traceability/traceability.js";
6
+
7
+ describe("TraceabilityLayer", () => {
8
+ let storage: InMemoryStorage;
9
+ let events: EventEmitter;
10
+ let router: MessageRouter;
11
+
12
+ beforeEach(() => {
13
+ storage = new InMemoryStorage();
14
+ events = new EventEmitter();
15
+ router = new MessageRouter(storage, events, "default");
16
+ // TraceabilityLayer subscribes to events on construction
17
+ new TraceabilityLayer(storage, events);
18
+ });
19
+
20
+ it("should auto-create a conversation from thread_tag", async () => {
21
+ await router.routeMessage({
22
+ from: "alice",
23
+ to: "bob",
24
+ payload: "hello",
25
+ threadTag: "sprint-1",
26
+ });
27
+
28
+ const conversations = storage.listConversations("default");
29
+ expect(conversations).toHaveLength(1);
30
+ expect(conversations[0].subject).toBe("sprint-1");
31
+ expect(conversations[0].participants).toHaveLength(2);
32
+ });
33
+
34
+ it("should reuse conversation for same thread_tag+scope", async () => {
35
+ await router.routeMessage({
36
+ from: "alice",
37
+ to: "bob",
38
+ payload: "msg 1",
39
+ threadTag: "sprint-1",
40
+ });
41
+ await router.routeMessage({
42
+ from: "bob",
43
+ to: "alice",
44
+ payload: "msg 2",
45
+ threadTag: "sprint-1",
46
+ });
47
+
48
+ const conversations = storage.listConversations("default");
49
+ expect(conversations).toHaveLength(1);
50
+
51
+ const turns = storage.getTurns(conversations[0].id);
52
+ expect(turns).toHaveLength(2);
53
+ });
54
+
55
+ it("should create catch-all conversation for untagged messages", async () => {
56
+ await router.routeMessage({
57
+ from: "alice",
58
+ to: "bob",
59
+ payload: "hi",
60
+ });
61
+
62
+ const conversations = storage.listConversations("default");
63
+ expect(conversations).toHaveLength(1);
64
+ expect(conversations[0].subject).toBe("General");
65
+ });
66
+
67
+ it("should set conversation_id on messages", async () => {
68
+ const msg = await router.routeMessage({
69
+ from: "alice",
70
+ to: "bob",
71
+ payload: "hello",
72
+ threadTag: "test",
73
+ });
74
+
75
+ // The traceability layer sets conversation_id after message.created
76
+ const updated = storage.getMessage(msg.id)!;
77
+ expect(updated.conversation_id).toBeTruthy();
78
+ });
79
+
80
+ it("should create threads from reply chains", async () => {
81
+ const msg1 = await router.routeMessage({
82
+ from: "alice",
83
+ to: "bob",
84
+ payload: "original",
85
+ threadTag: "test",
86
+ });
87
+
88
+ const msg2 = await router.routeMessage({
89
+ from: "bob",
90
+ to: "alice",
91
+ payload: "reply",
92
+ inReplyTo: msg1.id,
93
+ threadTag: "test",
94
+ });
95
+
96
+ const conversations = storage.listConversations("default");
97
+ expect(conversations).toHaveLength(1);
98
+
99
+ const threads = storage.getThreadsByConversation(conversations[0].id);
100
+ expect(threads).toHaveLength(1);
101
+
102
+ const turns = storage.getTurns(conversations[0].id);
103
+ expect(turns).toHaveLength(2);
104
+ // The reply turn should have a thread_id
105
+ const replyTurn = turns.find((t) => t.source_message_id === msg2.id);
106
+ expect(replyTurn?.thread_id).toBeTruthy();
107
+ });
108
+
109
+ it("should add participants from both sender and recipients", async () => {
110
+ await router.routeMessage({
111
+ from: "alice",
112
+ to: ["bob", "charlie"],
113
+ payload: "team update",
114
+ threadTag: "standup",
115
+ });
116
+
117
+ const conv = storage.listConversations("default")[0];
118
+ const participantIds = conv.participants.map((p) => p.agent_id);
119
+ expect(participantIds).toContain("alice");
120
+ expect(participantIds).toContain("bob");
121
+ expect(participantIds).toContain("charlie");
122
+ });
123
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "isolatedModules": true
17
+ },
18
+ "include": ["src/**/*.ts"],
19
+ "exclude": ["node_modules", "dist", "test"]
20
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ format: ["cjs", "esm"],
6
+ dts: true,
7
+ splitting: false,
8
+ sourcemap: true,
9
+ clean: true,
10
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["test/**/*.test.ts"],
6
+ testTimeout: 10_000,
7
+ },
8
+ });
package/dist/index.d.mts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { }
package/dist/index.mjs DELETED
@@ -1 +0,0 @@
1
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}