agent-inbox 0.1.3 → 0.1.5
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/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -4
- package/dist/index.js.map +1 -1
- package/dist/jsonrpc/mail-server.d.ts.map +1 -1
- package/dist/jsonrpc/mail-server.js +15 -0
- package/dist/jsonrpc/mail-server.js.map +1 -1
- package/dist/storage/sqlite.d.ts +11 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +85 -65
- package/dist/storage/sqlite.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +19 -4
- package/src/jsonrpc/mail-server.ts +15 -0
- package/src/storage/sqlite.ts +92 -66
- package/test/mail-server.test.ts +62 -1
- package/test/sqlite-prefix.test.ts +192 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
|
+
import { SqliteStorage } from "../src/storage/sqlite.js";
|
|
4
|
+
import type { Agent, Message } from "../src/types.js";
|
|
5
|
+
|
|
6
|
+
function makeAgent(overrides: Partial<Agent> = {}): Agent {
|
|
7
|
+
return {
|
|
8
|
+
agent_id: "agent-1",
|
|
9
|
+
scope: "default",
|
|
10
|
+
status: "active",
|
|
11
|
+
metadata: {},
|
|
12
|
+
registered_at: "2025-01-01T00:00:00Z",
|
|
13
|
+
last_active_at: "2025-01-01T00:00:00Z",
|
|
14
|
+
...overrides,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeMessage(overrides: Partial<Message> = {}): Message {
|
|
19
|
+
return {
|
|
20
|
+
id: "msg-1",
|
|
21
|
+
scope: "default",
|
|
22
|
+
sender_id: "agent-1",
|
|
23
|
+
recipients: [{ agent_id: "agent-2", kind: "to" }],
|
|
24
|
+
content: { type: "text", text: "hello" },
|
|
25
|
+
importance: "normal",
|
|
26
|
+
metadata: {},
|
|
27
|
+
created_at: "2025-01-01T00:00:00Z",
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("SqliteStorage with prefix", () => {
|
|
33
|
+
let storage: SqliteStorage;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
storage = new SqliteStorage({ path: ":memory:", prefix: "inbox_" });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
storage.close();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should create prefixed tables", () => {
|
|
44
|
+
storage.putAgent(makeAgent());
|
|
45
|
+
const agent = storage.getAgent("agent-1");
|
|
46
|
+
expect(agent).toBeDefined();
|
|
47
|
+
expect(agent!.agent_id).toBe("agent-1");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should store and retrieve messages with prefix", () => {
|
|
51
|
+
storage.putMessage(makeMessage());
|
|
52
|
+
const msg = storage.getMessage("msg-1");
|
|
53
|
+
expect(msg).toBeDefined();
|
|
54
|
+
expect(msg!.recipients).toHaveLength(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should support full-text search with prefix", () => {
|
|
58
|
+
storage.putMessage(
|
|
59
|
+
makeMessage({ id: "m1", content: { type: "text", text: "fix the auth bug" } })
|
|
60
|
+
);
|
|
61
|
+
storage.putMessage(
|
|
62
|
+
makeMessage({ id: "m2", content: { type: "text", text: "deploy the app" } })
|
|
63
|
+
);
|
|
64
|
+
const results = storage.searchMessages("auth");
|
|
65
|
+
expect(results).toHaveLength(1);
|
|
66
|
+
expect(results[0].id).toBe("m1");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should support conversations and turns with prefix", () => {
|
|
70
|
+
storage.putConversation({
|
|
71
|
+
id: "conv-1",
|
|
72
|
+
scope: "default",
|
|
73
|
+
status: "active",
|
|
74
|
+
participants: [{ agent_id: "alice", joined_at: "2025-01-01T00:00:00Z" }],
|
|
75
|
+
metadata: {},
|
|
76
|
+
created_at: "2025-01-01T00:00:00Z",
|
|
77
|
+
updated_at: "2025-01-01T00:00:00Z",
|
|
78
|
+
});
|
|
79
|
+
storage.addTurn({
|
|
80
|
+
id: "turn-1",
|
|
81
|
+
conversation_id: "conv-1",
|
|
82
|
+
participant_id: "alice",
|
|
83
|
+
content_type: "text",
|
|
84
|
+
content: { type: "text", text: "hello" },
|
|
85
|
+
created_at: "2025-01-01T00:00:00Z",
|
|
86
|
+
});
|
|
87
|
+
const turns = storage.getTurns("conv-1");
|
|
88
|
+
expect(turns).toHaveLength(1);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("SqliteStorage with external DB handle", () => {
|
|
93
|
+
let externalDb: Database.Database;
|
|
94
|
+
let storage: SqliteStorage;
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
externalDb = new Database(":memory:");
|
|
98
|
+
externalDb.pragma("journal_mode = WAL");
|
|
99
|
+
externalDb.pragma("foreign_keys = ON");
|
|
100
|
+
storage = new SqliteStorage({ db: externalDb, prefix: "inbox_" });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
storage.close(); // should NOT close the external DB
|
|
105
|
+
// Verify external DB is still usable
|
|
106
|
+
expect(() => externalDb.pragma("journal_mode")).not.toThrow();
|
|
107
|
+
externalDb.close();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should use the external DB handle", () => {
|
|
111
|
+
storage.putAgent(makeAgent());
|
|
112
|
+
const agent = storage.getAgent("agent-1");
|
|
113
|
+
expect(agent).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should create prefixed tables in external DB", () => {
|
|
117
|
+
storage.putAgent(makeAgent());
|
|
118
|
+
// Query the external DB directly to verify prefixed table exists
|
|
119
|
+
const rows = externalDb
|
|
120
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'inbox_%'")
|
|
121
|
+
.all() as { name: string }[];
|
|
122
|
+
const tableNames = rows.map((r) => r.name);
|
|
123
|
+
expect(tableNames).toContain("inbox_agents");
|
|
124
|
+
expect(tableNames).toContain("inbox_messages");
|
|
125
|
+
expect(tableNames).toContain("inbox_recipients");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should not close external DB on storage.close()", () => {
|
|
129
|
+
storage.close();
|
|
130
|
+
// External DB should still work
|
|
131
|
+
const result = externalDb.prepare("SELECT 1 as val").get() as { val: number };
|
|
132
|
+
expect(result.val).toBe(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should coexist with other tables in the same DB", () => {
|
|
136
|
+
// Create a non-inbox table in the same DB
|
|
137
|
+
externalDb.exec("CREATE TABLE IF NOT EXISTS hive_posts (id TEXT PRIMARY KEY, title TEXT)");
|
|
138
|
+
externalDb.prepare("INSERT INTO hive_posts (id, title) VALUES (?, ?)").run("p1", "Hello World");
|
|
139
|
+
|
|
140
|
+
// Inbox operations should work independently
|
|
141
|
+
storage.putAgent(makeAgent());
|
|
142
|
+
storage.putMessage(makeMessage());
|
|
143
|
+
|
|
144
|
+
// Both should be accessible
|
|
145
|
+
const agent = storage.getAgent("agent-1");
|
|
146
|
+
expect(agent).toBeDefined();
|
|
147
|
+
const post = externalDb.prepare("SELECT title FROM hive_posts WHERE id = ?").get("p1") as { title: string };
|
|
148
|
+
expect(post.title).toBe("Hello World");
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("Two SqliteStorage instances sharing one DB", () => {
|
|
153
|
+
let sharedDb: Database.Database;
|
|
154
|
+
let storageA: SqliteStorage;
|
|
155
|
+
let storageB: SqliteStorage;
|
|
156
|
+
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
sharedDb = new Database(":memory:");
|
|
159
|
+
sharedDb.pragma("journal_mode = WAL");
|
|
160
|
+
sharedDb.pragma("foreign_keys = ON");
|
|
161
|
+
storageA = new SqliteStorage({ db: sharedDb, prefix: "inbox_a_" });
|
|
162
|
+
storageB = new SqliteStorage({ db: sharedDb, prefix: "inbox_b_" });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
afterEach(() => {
|
|
166
|
+
storageA.close();
|
|
167
|
+
storageB.close();
|
|
168
|
+
sharedDb.close();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should isolate data between prefixes", () => {
|
|
172
|
+
storageA.putAgent(makeAgent({ agent_id: "alice" }));
|
|
173
|
+
storageB.putAgent(makeAgent({ agent_id: "bob" }));
|
|
174
|
+
|
|
175
|
+
expect(storageA.getAgent("alice")).toBeDefined();
|
|
176
|
+
expect(storageA.getAgent("bob")).toBeUndefined();
|
|
177
|
+
|
|
178
|
+
expect(storageB.getAgent("bob")).toBeDefined();
|
|
179
|
+
expect(storageB.getAgent("alice")).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should isolate messages between prefixes", () => {
|
|
183
|
+
storageA.putMessage(makeMessage({ id: "m1", sender_id: "alice" }));
|
|
184
|
+
storageB.putMessage(makeMessage({ id: "m2", sender_id: "bob" }));
|
|
185
|
+
|
|
186
|
+
expect(storageA.getMessage("m1")).toBeDefined();
|
|
187
|
+
expect(storageA.getMessage("m2")).toBeUndefined();
|
|
188
|
+
|
|
189
|
+
expect(storageB.getMessage("m2")).toBeDefined();
|
|
190
|
+
expect(storageB.getMessage("m1")).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
});
|