@aitytech/agentkits-memory 1.0.0 → 2.0.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/LICENSE +21 -0
- package/README.md +267 -149
- package/assets/agentkits-memory-add-memory.png +0 -0
- package/assets/agentkits-memory-memory-detail.png +0 -0
- package/assets/agentkits-memory-memory-list.png +0 -0
- package/assets/logo.svg +24 -0
- package/dist/better-sqlite3-backend.d.ts +192 -0
- package/dist/better-sqlite3-backend.d.ts.map +1 -0
- package/dist/better-sqlite3-backend.js +801 -0
- package/dist/better-sqlite3-backend.js.map +1 -0
- package/dist/cli/save.js +0 -0
- package/dist/cli/setup.d.ts +6 -2
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +289 -42
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/viewer.js +25 -56
- package/dist/cli/viewer.js.map +1 -1
- package/dist/cli/web-viewer.d.ts +14 -0
- package/dist/cli/web-viewer.d.ts.map +1 -0
- package/dist/cli/web-viewer.js +1769 -0
- package/dist/cli/web-viewer.js.map +1 -0
- package/dist/embeddings/embedding-cache.d.ts +131 -0
- package/dist/embeddings/embedding-cache.d.ts.map +1 -0
- package/dist/embeddings/embedding-cache.js +217 -0
- package/dist/embeddings/embedding-cache.js.map +1 -0
- package/dist/embeddings/index.d.ts +11 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +11 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/local-embeddings.d.ts +140 -0
- package/dist/embeddings/local-embeddings.d.ts.map +1 -0
- package/dist/embeddings/local-embeddings.js +293 -0
- package/dist/embeddings/local-embeddings.js.map +1 -0
- package/dist/hooks/context.d.ts +6 -1
- package/dist/hooks/context.d.ts.map +1 -1
- package/dist/hooks/context.js +12 -2
- package/dist/hooks/context.js.map +1 -1
- package/dist/hooks/observation.d.ts +6 -1
- package/dist/hooks/observation.d.ts.map +1 -1
- package/dist/hooks/observation.js +12 -2
- package/dist/hooks/observation.js.map +1 -1
- package/dist/hooks/service.d.ts +1 -6
- package/dist/hooks/service.d.ts.map +1 -1
- package/dist/hooks/service.js +33 -85
- package/dist/hooks/service.js.map +1 -1
- package/dist/hooks/session-init.d.ts +6 -1
- package/dist/hooks/session-init.d.ts.map +1 -1
- package/dist/hooks/session-init.js +12 -2
- package/dist/hooks/session-init.js.map +1 -1
- package/dist/hooks/summarize.d.ts +6 -1
- package/dist/hooks/summarize.d.ts.map +1 -1
- package/dist/hooks/summarize.js +12 -2
- package/dist/hooks/summarize.js.map +1 -1
- package/dist/index.d.ts +10 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +172 -94
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +17 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/migration.js +3 -3
- package/dist/migration.js.map +1 -1
- package/dist/search/hybrid-search.d.ts +262 -0
- package/dist/search/hybrid-search.d.ts.map +1 -0
- package/dist/search/hybrid-search.js +688 -0
- package/dist/search/hybrid-search.js.map +1 -0
- package/dist/search/index.d.ts +13 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +13 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/token-economics.d.ts +161 -0
- package/dist/search/token-economics.d.ts.map +1 -0
- package/dist/search/token-economics.js +239 -0
- package/dist/search/token-economics.js.map +1 -0
- package/dist/types.d.ts +0 -68
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +23 -8
- package/src/__tests__/better-sqlite3-backend.test.ts +1466 -0
- package/src/__tests__/cache-manager.test.ts +499 -0
- package/src/__tests__/embedding-integration.test.ts +481 -0
- package/src/__tests__/hnsw-index.test.ts +727 -0
- package/src/__tests__/index.test.ts +432 -0
- package/src/better-sqlite3-backend.ts +1000 -0
- package/src/cli/setup.ts +358 -47
- package/src/cli/viewer.ts +28 -63
- package/src/cli/web-viewer.ts +1956 -0
- package/src/embeddings/__tests__/embedding-cache.test.ts +269 -0
- package/src/embeddings/__tests__/local-embeddings.test.ts +495 -0
- package/src/embeddings/embedding-cache.ts +318 -0
- package/src/embeddings/index.ts +20 -0
- package/src/embeddings/local-embeddings.ts +419 -0
- package/src/hooks/__tests__/handlers.test.ts +58 -17
- package/src/hooks/__tests__/integration.test.ts +77 -26
- package/src/hooks/context.ts +13 -2
- package/src/hooks/observation.ts +13 -2
- package/src/hooks/service.ts +39 -100
- package/src/hooks/session-init.ts +13 -2
- package/src/hooks/summarize.ts +13 -2
- package/src/index.ts +210 -116
- package/src/mcp/server.ts +20 -3
- package/src/search/__tests__/hybrid-search.test.ts +669 -0
- package/src/search/__tests__/token-economics.test.ts +276 -0
- package/src/search/hybrid-search.ts +968 -0
- package/src/search/index.ts +29 -0
- package/src/search/token-economics.ts +367 -0
- package/src/types.ts +0 -96
- package/src/__tests__/sqljs-backend.test.ts +0 -410
- package/src/migration.ts +0 -574
- package/src/sql.js.d.ts +0 -70
- package/src/sqljs-backend.ts +0 -789
|
@@ -18,6 +18,15 @@ import { createSummarizeHook } from '../summarize.js';
|
|
|
18
18
|
|
|
19
19
|
const TEST_DIR = path.join(process.cwd(), '.test-integration-hooks');
|
|
20
20
|
|
|
21
|
+
// Track hooks for cleanup (needed for Windows file locking)
|
|
22
|
+
let activeHooks: Array<{ shutdown: () => Promise<void> }> = [];
|
|
23
|
+
|
|
24
|
+
// Helper to track hooks for cleanup
|
|
25
|
+
function trackHook<T extends { shutdown: () => Promise<void> }>(hook: T): T {
|
|
26
|
+
activeHooks.push(hook);
|
|
27
|
+
return hook;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
function createTestInput(overrides: Partial<NormalizedHookInput> = {}): NormalizedHookInput {
|
|
22
31
|
return {
|
|
23
32
|
sessionId: 'integration-session',
|
|
@@ -30,17 +39,39 @@ function createTestInput(overrides: Partial<NormalizedHookInput> = {}): Normaliz
|
|
|
30
39
|
|
|
31
40
|
describe('Hook System Integration', () => {
|
|
32
41
|
beforeEach(() => {
|
|
42
|
+
activeHooks = [];
|
|
33
43
|
// Clean up test directory
|
|
34
44
|
if (existsSync(TEST_DIR)) {
|
|
35
|
-
|
|
45
|
+
try {
|
|
46
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
47
|
+
} catch {
|
|
48
|
+
// Ignore errors on Windows
|
|
49
|
+
}
|
|
36
50
|
}
|
|
37
51
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
38
52
|
});
|
|
39
53
|
|
|
40
|
-
afterEach(() => {
|
|
54
|
+
afterEach(async () => {
|
|
55
|
+
// Shutdown all hooks first (releases database locks)
|
|
56
|
+
for (const hook of activeHooks) {
|
|
57
|
+
try {
|
|
58
|
+
await hook.shutdown();
|
|
59
|
+
} catch {
|
|
60
|
+
// Ignore shutdown errors
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
activeHooks = [];
|
|
64
|
+
|
|
65
|
+
// Small delay for Windows file system
|
|
66
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
67
|
+
|
|
41
68
|
// Clean up test directory
|
|
42
69
|
if (existsSync(TEST_DIR)) {
|
|
43
|
-
|
|
70
|
+
try {
|
|
71
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
72
|
+
} catch {
|
|
73
|
+
// Ignore errors on Windows - files may still be locked
|
|
74
|
+
}
|
|
44
75
|
}
|
|
45
76
|
});
|
|
46
77
|
|
|
@@ -50,24 +81,26 @@ describe('Hook System Integration', () => {
|
|
|
50
81
|
const project = 'test-project';
|
|
51
82
|
|
|
52
83
|
// 1. Session Start - Context Hook (no previous context)
|
|
53
|
-
const contextHook = createContextHook(TEST_DIR);
|
|
84
|
+
const contextHook = trackHook(createContextHook(TEST_DIR));
|
|
54
85
|
const contextResult = await contextHook.execute(
|
|
55
86
|
createTestInput({ sessionId, project })
|
|
56
87
|
);
|
|
57
88
|
|
|
58
89
|
expect(contextResult.continue).toBe(true);
|
|
59
90
|
expect(contextResult.additionalContext).toBeUndefined(); // No previous sessions
|
|
91
|
+
await contextHook.shutdown();
|
|
60
92
|
|
|
61
93
|
// 2. User Prompt Submit - Session Init Hook
|
|
62
|
-
const sessionInitHook = createSessionInitHook(TEST_DIR);
|
|
94
|
+
const sessionInitHook = trackHook(createSessionInitHook(TEST_DIR));
|
|
63
95
|
const sessionInitResult = await sessionInitHook.execute(
|
|
64
96
|
createTestInput({ sessionId, project, prompt: 'Help me implement a feature' })
|
|
65
97
|
);
|
|
66
98
|
|
|
67
99
|
expect(sessionInitResult.continue).toBe(true);
|
|
100
|
+
await sessionInitHook.shutdown();
|
|
68
101
|
|
|
69
102
|
// 3. Tool Uses - Observation Hooks
|
|
70
|
-
const observationHook = createObservationHook(TEST_DIR);
|
|
103
|
+
const observationHook = trackHook(createObservationHook(TEST_DIR));
|
|
71
104
|
|
|
72
105
|
// Simulate reading files
|
|
73
106
|
await observationHook.execute(
|
|
@@ -112,9 +145,10 @@ describe('Hook System Integration', () => {
|
|
|
112
145
|
toolResponse: { stdout: 'All tests passed' },
|
|
113
146
|
})
|
|
114
147
|
);
|
|
148
|
+
await observationHook.shutdown();
|
|
115
149
|
|
|
116
150
|
// 4. Session End - Summarize Hook
|
|
117
|
-
const summarizeHook = createSummarizeHook(TEST_DIR);
|
|
151
|
+
const summarizeHook = trackHook(createSummarizeHook(TEST_DIR));
|
|
118
152
|
const summarizeResult = await summarizeHook.execute(
|
|
119
153
|
createTestInput({ sessionId, project, stopReason: 'user_exit' })
|
|
120
154
|
);
|
|
@@ -146,11 +180,12 @@ describe('Hook System Integration', () => {
|
|
|
146
180
|
const project = 'test-project';
|
|
147
181
|
|
|
148
182
|
// Init session 1
|
|
149
|
-
const initHook1 = createSessionInitHook(TEST_DIR);
|
|
183
|
+
const initHook1 = trackHook(createSessionInitHook(TEST_DIR));
|
|
150
184
|
await initHook1.execute(createTestInput({ sessionId: session1Id, project, prompt: 'First task' }));
|
|
185
|
+
await initHook1.shutdown();
|
|
151
186
|
|
|
152
187
|
// Add observations to session 1
|
|
153
|
-
const obsHook1 = createObservationHook(TEST_DIR);
|
|
188
|
+
const obsHook1 = trackHook(createObservationHook(TEST_DIR));
|
|
154
189
|
await obsHook1.execute(createTestInput({
|
|
155
190
|
sessionId: session1Id,
|
|
156
191
|
project,
|
|
@@ -158,15 +193,16 @@ describe('Hook System Integration', () => {
|
|
|
158
193
|
toolInput: { file_path: 'src/auth.ts' },
|
|
159
194
|
toolResponse: {},
|
|
160
195
|
}));
|
|
196
|
+
await obsHook1.shutdown();
|
|
161
197
|
|
|
162
198
|
// Complete session 1
|
|
163
|
-
const sumHook1 = createSummarizeHook(TEST_DIR);
|
|
199
|
+
const sumHook1 = trackHook(createSummarizeHook(TEST_DIR));
|
|
164
200
|
await sumHook1.execute(createTestInput({ sessionId: session1Id, project }));
|
|
165
201
|
|
|
166
202
|
// Session 2: Should see context from session 1
|
|
167
203
|
const session2Id = 'current-session';
|
|
168
204
|
|
|
169
|
-
const contextHook2 = createContextHook(TEST_DIR);
|
|
205
|
+
const contextHook2 = trackHook(createContextHook(TEST_DIR));
|
|
170
206
|
const contextResult = await contextHook2.execute(
|
|
171
207
|
createTestInput({ sessionId: session2Id, project })
|
|
172
208
|
);
|
|
@@ -181,14 +217,15 @@ describe('Hook System Integration', () => {
|
|
|
181
217
|
|
|
182
218
|
it('should handle multiple projects independently', async () => {
|
|
183
219
|
// Session for project A
|
|
184
|
-
const initHookA = createSessionInitHook(TEST_DIR);
|
|
220
|
+
const initHookA = trackHook(createSessionInitHook(TEST_DIR));
|
|
185
221
|
await initHookA.execute(createTestInput({
|
|
186
222
|
sessionId: 'session-a',
|
|
187
223
|
project: 'project-a',
|
|
188
224
|
prompt: 'Task for A',
|
|
189
225
|
}));
|
|
226
|
+
await initHookA.shutdown();
|
|
190
227
|
|
|
191
|
-
const obsHookA = createObservationHook(TEST_DIR);
|
|
228
|
+
const obsHookA = trackHook(createObservationHook(TEST_DIR));
|
|
192
229
|
await obsHookA.execute(createTestInput({
|
|
193
230
|
sessionId: 'session-a',
|
|
194
231
|
project: 'project-a',
|
|
@@ -196,16 +233,18 @@ describe('Hook System Integration', () => {
|
|
|
196
233
|
toolInput: { file_path: 'a.ts' },
|
|
197
234
|
toolResponse: {},
|
|
198
235
|
}));
|
|
236
|
+
await obsHookA.shutdown();
|
|
199
237
|
|
|
200
238
|
// Session for project B
|
|
201
|
-
const initHookB = createSessionInitHook(TEST_DIR);
|
|
239
|
+
const initHookB = trackHook(createSessionInitHook(TEST_DIR));
|
|
202
240
|
await initHookB.execute(createTestInput({
|
|
203
241
|
sessionId: 'session-b',
|
|
204
242
|
project: 'project-b',
|
|
205
243
|
prompt: 'Task for B',
|
|
206
244
|
}));
|
|
245
|
+
await initHookB.shutdown();
|
|
207
246
|
|
|
208
|
-
const obsHookB = createObservationHook(TEST_DIR);
|
|
247
|
+
const obsHookB = trackHook(createObservationHook(TEST_DIR));
|
|
209
248
|
await obsHookB.execute(createTestInput({
|
|
210
249
|
sessionId: 'session-b',
|
|
211
250
|
project: 'project-b',
|
|
@@ -213,6 +252,7 @@ describe('Hook System Integration', () => {
|
|
|
213
252
|
toolInput: { file_path: 'b.ts' },
|
|
214
253
|
toolResponse: {},
|
|
215
254
|
}));
|
|
255
|
+
await obsHookB.shutdown();
|
|
216
256
|
|
|
217
257
|
// Verify isolation
|
|
218
258
|
const service = new MemoryHookService(TEST_DIR);
|
|
@@ -260,10 +300,11 @@ describe('Hook System Integration', () => {
|
|
|
260
300
|
expect(parsed.toolResponse).toEqual({ content: 'file contents here' });
|
|
261
301
|
|
|
262
302
|
// Process through observation hook
|
|
263
|
-
const observationHook = createObservationHook(TEST_DIR);
|
|
303
|
+
const observationHook = trackHook(createObservationHook(TEST_DIR));
|
|
264
304
|
const result = await observationHook.execute(parsed);
|
|
265
305
|
|
|
266
306
|
expect(result.continue).toBe(true);
|
|
307
|
+
await observationHook.shutdown();
|
|
267
308
|
|
|
268
309
|
// Verify stored
|
|
269
310
|
const service = new MemoryHookService(TEST_DIR);
|
|
@@ -282,11 +323,12 @@ describe('Hook System Integration', () => {
|
|
|
282
323
|
const project = 'test-project';
|
|
283
324
|
|
|
284
325
|
// Init session
|
|
285
|
-
const initHook = createSessionInitHook(TEST_DIR);
|
|
326
|
+
const initHook = trackHook(createSessionInitHook(TEST_DIR));
|
|
286
327
|
await initHook.execute(createTestInput({ sessionId, project }));
|
|
328
|
+
await initHook.shutdown();
|
|
287
329
|
|
|
288
330
|
// Successful observation
|
|
289
|
-
const obsHook = createObservationHook(TEST_DIR);
|
|
331
|
+
const obsHook = trackHook(createObservationHook(TEST_DIR));
|
|
290
332
|
await obsHook.execute(createTestInput({
|
|
291
333
|
sessionId,
|
|
292
334
|
project,
|
|
@@ -303,6 +345,7 @@ describe('Hook System Integration', () => {
|
|
|
303
345
|
toolInput: {},
|
|
304
346
|
toolResponse: {},
|
|
305
347
|
}));
|
|
348
|
+
await obsHook.shutdown();
|
|
306
349
|
|
|
307
350
|
// Verify both observations stored
|
|
308
351
|
const service = new MemoryHookService(TEST_DIR);
|
|
@@ -319,14 +362,16 @@ describe('Hook System Integration', () => {
|
|
|
319
362
|
const project = 'test-project';
|
|
320
363
|
|
|
321
364
|
// Start two sessions sequentially (SQLite doesn't handle concurrent writes well)
|
|
322
|
-
const initHook1 = createSessionInitHook(TEST_DIR);
|
|
365
|
+
const initHook1 = trackHook(createSessionInitHook(TEST_DIR));
|
|
323
366
|
await initHook1.execute(createTestInput({ sessionId: 'multi-1', project }));
|
|
367
|
+
await initHook1.shutdown();
|
|
324
368
|
|
|
325
|
-
const initHook2 = createSessionInitHook(TEST_DIR);
|
|
369
|
+
const initHook2 = trackHook(createSessionInitHook(TEST_DIR));
|
|
326
370
|
await initHook2.execute(createTestInput({ sessionId: 'multi-2', project }));
|
|
371
|
+
await initHook2.shutdown();
|
|
327
372
|
|
|
328
373
|
// Add observations sequentially
|
|
329
|
-
const obsHook1 = createObservationHook(TEST_DIR);
|
|
374
|
+
const obsHook1 = trackHook(createObservationHook(TEST_DIR));
|
|
330
375
|
await obsHook1.execute(createTestInput({
|
|
331
376
|
sessionId: 'multi-1',
|
|
332
377
|
project,
|
|
@@ -334,8 +379,9 @@ describe('Hook System Integration', () => {
|
|
|
334
379
|
toolInput: {},
|
|
335
380
|
toolResponse: {},
|
|
336
381
|
}));
|
|
382
|
+
await obsHook1.shutdown();
|
|
337
383
|
|
|
338
|
-
const obsHook2 = createObservationHook(TEST_DIR);
|
|
384
|
+
const obsHook2 = trackHook(createObservationHook(TEST_DIR));
|
|
339
385
|
await obsHook2.execute(createTestInput({
|
|
340
386
|
sessionId: 'multi-2',
|
|
341
387
|
project,
|
|
@@ -343,6 +389,7 @@ describe('Hook System Integration', () => {
|
|
|
343
389
|
toolInput: {},
|
|
344
390
|
toolResponse: {},
|
|
345
391
|
}));
|
|
392
|
+
await obsHook2.shutdown();
|
|
346
393
|
|
|
347
394
|
// Verify both sessions have their observations
|
|
348
395
|
const service = new MemoryHookService(TEST_DIR);
|
|
@@ -366,11 +413,12 @@ describe('Hook System Integration', () => {
|
|
|
366
413
|
const project = 'test-project';
|
|
367
414
|
|
|
368
415
|
// Init session
|
|
369
|
-
const initHook = createSessionInitHook(TEST_DIR);
|
|
416
|
+
const initHook = trackHook(createSessionInitHook(TEST_DIR));
|
|
370
417
|
await initHook.execute(createTestInput({ sessionId, project }));
|
|
418
|
+
await initHook.shutdown();
|
|
371
419
|
|
|
372
420
|
// Add many observations
|
|
373
|
-
const obsHook = createObservationHook(TEST_DIR);
|
|
421
|
+
const obsHook = trackHook(createObservationHook(TEST_DIR));
|
|
374
422
|
const observationCount = 50;
|
|
375
423
|
|
|
376
424
|
for (let i = 0; i < observationCount; i++) {
|
|
@@ -382,6 +430,7 @@ describe('Hook System Integration', () => {
|
|
|
382
430
|
toolResponse: { content: `content ${i}` },
|
|
383
431
|
}));
|
|
384
432
|
}
|
|
433
|
+
await obsHook.shutdown();
|
|
385
434
|
|
|
386
435
|
// Verify all observations stored
|
|
387
436
|
const service = new MemoryHookService(TEST_DIR);
|
|
@@ -401,11 +450,12 @@ describe('Hook System Integration', () => {
|
|
|
401
450
|
const project = 'test-project';
|
|
402
451
|
|
|
403
452
|
// Init session
|
|
404
|
-
const initHook = createSessionInitHook(TEST_DIR);
|
|
453
|
+
const initHook = trackHook(createSessionInitHook(TEST_DIR));
|
|
405
454
|
await initHook.execute(createTestInput({ sessionId, project }));
|
|
455
|
+
await initHook.shutdown();
|
|
406
456
|
|
|
407
457
|
// Add observation with large response
|
|
408
|
-
const obsHook = createObservationHook(TEST_DIR);
|
|
458
|
+
const obsHook = trackHook(createObservationHook(TEST_DIR));
|
|
409
459
|
const largeContent = 'A'.repeat(100000); // 100KB
|
|
410
460
|
|
|
411
461
|
await obsHook.execute(createTestInput({
|
|
@@ -415,6 +465,7 @@ describe('Hook System Integration', () => {
|
|
|
415
465
|
toolInput: { file_path: 'large.ts' },
|
|
416
466
|
toolResponse: { content: largeContent },
|
|
417
467
|
}));
|
|
468
|
+
await obsHook.shutdown();
|
|
418
469
|
|
|
419
470
|
// Verify response was truncated
|
|
420
471
|
const service = new MemoryHookService(TEST_DIR);
|
package/src/hooks/context.ts
CHANGED
|
@@ -23,9 +23,20 @@ import { MemoryHookService } from './service.js';
|
|
|
23
23
|
*/
|
|
24
24
|
export class ContextHook implements EventHandler {
|
|
25
25
|
private service: MemoryHookService;
|
|
26
|
+
private ownsService: boolean;
|
|
26
27
|
|
|
27
|
-
constructor(service: MemoryHookService) {
|
|
28
|
+
constructor(service: MemoryHookService, ownsService = false) {
|
|
28
29
|
this.service = service;
|
|
30
|
+
this.ownsService = ownsService;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Shutdown the hook (closes database if owned)
|
|
35
|
+
*/
|
|
36
|
+
async shutdown(): Promise<void> {
|
|
37
|
+
if (this.ownsService) {
|
|
38
|
+
await this.service.shutdown();
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
/**
|
|
@@ -71,7 +82,7 @@ export class ContextHook implements EventHandler {
|
|
|
71
82
|
*/
|
|
72
83
|
export function createContextHook(cwd: string): ContextHook {
|
|
73
84
|
const service = new MemoryHookService(cwd);
|
|
74
|
-
return new ContextHook(service);
|
|
85
|
+
return new ContextHook(service, true); // owns service
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
export default ContextHook;
|
package/src/hooks/observation.ts
CHANGED
|
@@ -32,9 +32,20 @@ const SKIP_TOOLS = new Set([
|
|
|
32
32
|
*/
|
|
33
33
|
export class ObservationHook implements EventHandler {
|
|
34
34
|
private service: MemoryHookService;
|
|
35
|
+
private ownsService: boolean;
|
|
35
36
|
|
|
36
|
-
constructor(service: MemoryHookService) {
|
|
37
|
+
constructor(service: MemoryHookService, ownsService = false) {
|
|
37
38
|
this.service = service;
|
|
39
|
+
this.ownsService = ownsService;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Shutdown the hook (closes database if owned)
|
|
44
|
+
*/
|
|
45
|
+
async shutdown(): Promise<void> {
|
|
46
|
+
if (this.ownsService) {
|
|
47
|
+
await this.service.shutdown();
|
|
48
|
+
}
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
/**
|
|
@@ -96,7 +107,7 @@ export class ObservationHook implements EventHandler {
|
|
|
96
107
|
*/
|
|
97
108
|
export function createObservationHook(cwd: string): ObservationHook {
|
|
98
109
|
const service = new MemoryHookService(cwd);
|
|
99
|
-
return new ObservationHook(service);
|
|
110
|
+
return new ObservationHook(service, true);
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
export default ObservationHook;
|
package/src/hooks/service.ts
CHANGED
|
@@ -2,18 +2,15 @@
|
|
|
2
2
|
* Memory Hook Service
|
|
3
3
|
*
|
|
4
4
|
* Lightweight service for hooks to store/retrieve memory.
|
|
5
|
-
* Direct SQLite access without HTTP worker
|
|
5
|
+
* Direct SQLite access without HTTP worker.
|
|
6
6
|
*
|
|
7
7
|
* @module @agentkits/memory/hooks/service
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { existsSync, mkdirSync
|
|
10
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
11
11
|
import * as path from 'node:path';
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
// ESM-compatible require for resolving sql.js WASM path
|
|
16
|
-
const require = createRequire(import.meta.url);
|
|
12
|
+
import Database from 'better-sqlite3';
|
|
13
|
+
import type { Database as BetterDatabase } from 'better-sqlite3';
|
|
17
14
|
import {
|
|
18
15
|
Observation,
|
|
19
16
|
SessionRecord,
|
|
@@ -60,8 +57,7 @@ const DEFAULT_CONFIG: MemoryHookServiceConfig = {
|
|
|
60
57
|
*/
|
|
61
58
|
export class MemoryHookService {
|
|
62
59
|
private config: MemoryHookServiceConfig;
|
|
63
|
-
private db:
|
|
64
|
-
private SQL: any = null;
|
|
60
|
+
private db: BetterDatabase | null = null;
|
|
65
61
|
private initialized: boolean = false;
|
|
66
62
|
private dbPath: string;
|
|
67
63
|
|
|
@@ -82,25 +78,11 @@ export class MemoryHookService {
|
|
|
82
78
|
mkdirSync(dir, { recursive: true });
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
//
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
path.dirname(require.resolve('sql.js')),
|
|
91
|
-
file
|
|
92
|
-
);
|
|
93
|
-
return localPath;
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Load or create database
|
|
98
|
-
if (existsSync(this.dbPath)) {
|
|
99
|
-
const buffer = readFileSync(this.dbPath);
|
|
100
|
-
this.db = new this.SQL.Database(new Uint8Array(buffer));
|
|
101
|
-
} else {
|
|
102
|
-
this.db = new this.SQL.Database();
|
|
103
|
-
}
|
|
81
|
+
// Open database with better-sqlite3
|
|
82
|
+
this.db = new Database(this.dbPath);
|
|
83
|
+
|
|
84
|
+
// Enable WAL mode for better performance
|
|
85
|
+
this.db.pragma('journal_mode = WAL');
|
|
104
86
|
|
|
105
87
|
// Create schema
|
|
106
88
|
this.createSchema();
|
|
@@ -108,24 +90,12 @@ export class MemoryHookService {
|
|
|
108
90
|
this.initialized = true;
|
|
109
91
|
}
|
|
110
92
|
|
|
111
|
-
/**
|
|
112
|
-
* Persist database to disk
|
|
113
|
-
*/
|
|
114
|
-
async persist(): Promise<void> {
|
|
115
|
-
if (!this.db) return;
|
|
116
|
-
|
|
117
|
-
const data = this.db.export();
|
|
118
|
-
const buffer = Buffer.from(data);
|
|
119
|
-
writeFileSync(this.dbPath, buffer);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
93
|
/**
|
|
123
94
|
* Shutdown the service
|
|
124
95
|
*/
|
|
125
96
|
async shutdown(): Promise<void> {
|
|
126
97
|
if (!this.initialized || !this.db) return;
|
|
127
98
|
|
|
128
|
-
await this.persist();
|
|
129
99
|
this.db.close();
|
|
130
100
|
this.db = null;
|
|
131
101
|
this.initialized = false;
|
|
@@ -147,15 +117,13 @@ export class MemoryHookService {
|
|
|
147
117
|
|
|
148
118
|
// Create new session
|
|
149
119
|
const now = Date.now();
|
|
150
|
-
this.db!.
|
|
120
|
+
const result = this.db!.prepare(`
|
|
151
121
|
INSERT INTO sessions (session_id, project, prompt, started_at, observation_count, status)
|
|
152
122
|
VALUES (?, ?, ?, ?, 0, 'active')
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
await this.persist();
|
|
123
|
+
`).run(sessionId, project, prompt || '', now);
|
|
156
124
|
|
|
157
125
|
return {
|
|
158
|
-
id:
|
|
126
|
+
id: Number(result.lastInsertRowid),
|
|
159
127
|
sessionId,
|
|
160
128
|
project,
|
|
161
129
|
prompt: prompt || '',
|
|
@@ -171,16 +139,12 @@ export class MemoryHookService {
|
|
|
171
139
|
getSession(sessionId: string): SessionRecord | null {
|
|
172
140
|
if (!this.db) return null;
|
|
173
141
|
|
|
174
|
-
const
|
|
175
|
-
stmt.bind([sessionId]);
|
|
142
|
+
const row = this.db.prepare('SELECT * FROM sessions WHERE session_id = ?').get(sessionId) as Record<string, unknown> | undefined;
|
|
176
143
|
|
|
177
|
-
if (
|
|
178
|
-
const row = stmt.getAsObject();
|
|
179
|
-
stmt.free();
|
|
144
|
+
if (row) {
|
|
180
145
|
return this.rowToSession(row);
|
|
181
146
|
}
|
|
182
147
|
|
|
183
|
-
stmt.free();
|
|
184
148
|
return null;
|
|
185
149
|
}
|
|
186
150
|
|
|
@@ -191,13 +155,11 @@ export class MemoryHookService {
|
|
|
191
155
|
await this.ensureInitialized();
|
|
192
156
|
|
|
193
157
|
const now = Date.now();
|
|
194
|
-
this.db!.
|
|
158
|
+
this.db!.prepare(`
|
|
195
159
|
UPDATE sessions
|
|
196
160
|
SET ended_at = ?, summary = ?, status = 'completed'
|
|
197
161
|
WHERE session_id = ?
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
await this.persist();
|
|
162
|
+
`).run(now, summary || '', sessionId);
|
|
201
163
|
}
|
|
202
164
|
|
|
203
165
|
/**
|
|
@@ -206,21 +168,14 @@ export class MemoryHookService {
|
|
|
206
168
|
async getRecentSessions(project: string, limit: number = 5): Promise<SessionRecord[]> {
|
|
207
169
|
await this.ensureInitialized();
|
|
208
170
|
|
|
209
|
-
const
|
|
171
|
+
const rows = this.db!.prepare(`
|
|
210
172
|
SELECT * FROM sessions
|
|
211
173
|
WHERE project = ?
|
|
212
174
|
ORDER BY started_at DESC
|
|
213
175
|
LIMIT ?
|
|
214
|
-
`);
|
|
215
|
-
stmt.bind([project, limit]);
|
|
216
|
-
|
|
217
|
-
const sessions: SessionRecord[] = [];
|
|
218
|
-
while (stmt.step()) {
|
|
219
|
-
sessions.push(this.rowToSession(stmt.getAsObject()));
|
|
220
|
-
}
|
|
221
|
-
stmt.free();
|
|
176
|
+
`).all(project, limit) as Record<string, unknown>[];
|
|
222
177
|
|
|
223
|
-
return
|
|
178
|
+
return rows.map(row => this.rowToSession(row));
|
|
224
179
|
}
|
|
225
180
|
|
|
226
181
|
// ===== Observation Management =====
|
|
@@ -250,19 +205,17 @@ export class MemoryHookService {
|
|
|
250
205
|
this.config.maxResponseSize
|
|
251
206
|
);
|
|
252
207
|
|
|
253
|
-
this.db!.
|
|
208
|
+
this.db!.prepare(`
|
|
254
209
|
INSERT INTO observations (id, session_id, project, tool_name, tool_input, tool_response, cwd, timestamp, type, title)
|
|
255
210
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
256
|
-
|
|
211
|
+
`).run(id, sessionId, project, toolName, inputStr, responseStr, cwd, now, type, title);
|
|
257
212
|
|
|
258
213
|
// Update session observation count
|
|
259
|
-
this.db!.
|
|
214
|
+
this.db!.prepare(`
|
|
260
215
|
UPDATE sessions
|
|
261
216
|
SET observation_count = observation_count + 1
|
|
262
217
|
WHERE session_id = ?
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
await this.persist();
|
|
218
|
+
`).run(sessionId);
|
|
266
219
|
|
|
267
220
|
return {
|
|
268
221
|
id,
|
|
@@ -284,21 +237,14 @@ export class MemoryHookService {
|
|
|
284
237
|
async getSessionObservations(sessionId: string, limit: number = 50): Promise<Observation[]> {
|
|
285
238
|
await this.ensureInitialized();
|
|
286
239
|
|
|
287
|
-
const
|
|
240
|
+
const rows = this.db!.prepare(`
|
|
288
241
|
SELECT * FROM observations
|
|
289
242
|
WHERE session_id = ?
|
|
290
243
|
ORDER BY timestamp DESC
|
|
291
244
|
LIMIT ?
|
|
292
|
-
`);
|
|
293
|
-
stmt.bind([sessionId, limit]);
|
|
245
|
+
`).all(sessionId, limit) as Record<string, unknown>[];
|
|
294
246
|
|
|
295
|
-
|
|
296
|
-
while (stmt.step()) {
|
|
297
|
-
observations.push(this.rowToObservation(stmt.getAsObject()));
|
|
298
|
-
}
|
|
299
|
-
stmt.free();
|
|
300
|
-
|
|
301
|
-
return observations;
|
|
247
|
+
return rows.map(row => this.rowToObservation(row));
|
|
302
248
|
}
|
|
303
249
|
|
|
304
250
|
/**
|
|
@@ -307,21 +253,14 @@ export class MemoryHookService {
|
|
|
307
253
|
async getRecentObservations(project: string, limit: number = 20): Promise<Observation[]> {
|
|
308
254
|
await this.ensureInitialized();
|
|
309
255
|
|
|
310
|
-
const
|
|
256
|
+
const rows = this.db!.prepare(`
|
|
311
257
|
SELECT * FROM observations
|
|
312
258
|
WHERE project = ?
|
|
313
259
|
ORDER BY timestamp DESC
|
|
314
260
|
LIMIT ?
|
|
315
|
-
`);
|
|
316
|
-
stmt.bind([project, limit]);
|
|
317
|
-
|
|
318
|
-
const observations: Observation[] = [];
|
|
319
|
-
while (stmt.step()) {
|
|
320
|
-
observations.push(this.rowToObservation(stmt.getAsObject()));
|
|
321
|
-
}
|
|
322
|
-
stmt.free();
|
|
261
|
+
`).all(project, limit) as Record<string, unknown>[];
|
|
323
262
|
|
|
324
|
-
return
|
|
263
|
+
return rows.map(row => this.rowToObservation(row));
|
|
325
264
|
}
|
|
326
265
|
|
|
327
266
|
// ===== Context Generation =====
|
|
@@ -480,7 +419,7 @@ export class MemoryHookService {
|
|
|
480
419
|
private createSchema(): void {
|
|
481
420
|
if (!this.db) return;
|
|
482
421
|
|
|
483
|
-
this.db.
|
|
422
|
+
this.db.exec(`
|
|
484
423
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
485
424
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
486
425
|
session_id TEXT UNIQUE NOT NULL,
|
|
@@ -494,7 +433,7 @@ export class MemoryHookService {
|
|
|
494
433
|
)
|
|
495
434
|
`);
|
|
496
435
|
|
|
497
|
-
this.db.
|
|
436
|
+
this.db.exec(`
|
|
498
437
|
CREATE TABLE IF NOT EXISTS observations (
|
|
499
438
|
id TEXT PRIMARY KEY,
|
|
500
439
|
session_id TEXT NOT NULL,
|
|
@@ -510,13 +449,13 @@ export class MemoryHookService {
|
|
|
510
449
|
)
|
|
511
450
|
`);
|
|
512
451
|
|
|
513
|
-
this.db.
|
|
514
|
-
this.db.
|
|
515
|
-
this.db.
|
|
516
|
-
this.db.
|
|
452
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_obs_session ON observations(session_id)');
|
|
453
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_obs_project ON observations(project)');
|
|
454
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_obs_timestamp ON observations(timestamp)');
|
|
455
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project)');
|
|
517
456
|
}
|
|
518
457
|
|
|
519
|
-
private rowToSession(row:
|
|
458
|
+
private rowToSession(row: Record<string, unknown>): SessionRecord {
|
|
520
459
|
return {
|
|
521
460
|
id: row.id as number,
|
|
522
461
|
sessionId: row.session_id as string,
|
|
@@ -530,7 +469,7 @@ export class MemoryHookService {
|
|
|
530
469
|
};
|
|
531
470
|
}
|
|
532
471
|
|
|
533
|
-
private rowToObservation(row:
|
|
472
|
+
private rowToObservation(row: Record<string, unknown>): Observation {
|
|
534
473
|
return {
|
|
535
474
|
id: row.id as string,
|
|
536
475
|
sessionId: row.session_id as string,
|
|
@@ -540,7 +479,7 @@ export class MemoryHookService {
|
|
|
540
479
|
toolResponse: row.tool_response as string,
|
|
541
480
|
cwd: row.cwd as string,
|
|
542
481
|
timestamp: row.timestamp as number,
|
|
543
|
-
type: row.type as
|
|
482
|
+
type: row.type as Observation['type'],
|
|
544
483
|
title: row.title as string | undefined,
|
|
545
484
|
};
|
|
546
485
|
}
|
|
@@ -22,9 +22,20 @@ import { MemoryHookService } from './service.js';
|
|
|
22
22
|
*/
|
|
23
23
|
export class SessionInitHook implements EventHandler {
|
|
24
24
|
private service: MemoryHookService;
|
|
25
|
+
private ownsService: boolean;
|
|
25
26
|
|
|
26
|
-
constructor(service: MemoryHookService) {
|
|
27
|
+
constructor(service: MemoryHookService, ownsService = false) {
|
|
27
28
|
this.service = service;
|
|
29
|
+
this.ownsService = ownsService;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Shutdown the hook (closes database if owned)
|
|
34
|
+
*/
|
|
35
|
+
async shutdown(): Promise<void> {
|
|
36
|
+
if (this.ownsService) {
|
|
37
|
+
await this.service.shutdown();
|
|
38
|
+
}
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
/**
|
|
@@ -64,7 +75,7 @@ export class SessionInitHook implements EventHandler {
|
|
|
64
75
|
*/
|
|
65
76
|
export function createSessionInitHook(cwd: string): SessionInitHook {
|
|
66
77
|
const service = new MemoryHookService(cwd);
|
|
67
|
-
return new SessionInitHook(service);
|
|
78
|
+
return new SessionInitHook(service, true);
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
export default SessionInitHook;
|