@aitytech/agentkits-memory 1.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/README.md +250 -0
- package/dist/cache-manager.d.ts +134 -0
- package/dist/cache-manager.d.ts.map +1 -0
- package/dist/cache-manager.js +407 -0
- package/dist/cache-manager.js.map +1 -0
- package/dist/cli/save.d.ts +20 -0
- package/dist/cli/save.d.ts.map +1 -0
- package/dist/cli/save.js +94 -0
- package/dist/cli/save.js.map +1 -0
- package/dist/cli/setup.d.ts +18 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +163 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/viewer.d.ts +21 -0
- package/dist/cli/viewer.d.ts.map +1 -0
- package/dist/cli/viewer.js +182 -0
- package/dist/cli/viewer.js.map +1 -0
- package/dist/hnsw-index.d.ts +111 -0
- package/dist/hnsw-index.d.ts.map +1 -0
- package/dist/hnsw-index.js +781 -0
- package/dist/hnsw-index.js.map +1 -0
- package/dist/hooks/cli.d.ts +20 -0
- package/dist/hooks/cli.d.ts.map +1 -0
- package/dist/hooks/cli.js +102 -0
- package/dist/hooks/cli.js.map +1 -0
- package/dist/hooks/context.d.ts +31 -0
- package/dist/hooks/context.d.ts.map +1 -0
- package/dist/hooks/context.js +64 -0
- package/dist/hooks/context.js.map +1 -0
- package/dist/hooks/index.d.ts +16 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +20 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/observation.d.ts +30 -0
- package/dist/hooks/observation.d.ts.map +1 -0
- package/dist/hooks/observation.js +79 -0
- package/dist/hooks/observation.js.map +1 -0
- package/dist/hooks/service.d.ts +102 -0
- package/dist/hooks/service.d.ts.map +1 -0
- package/dist/hooks/service.js +454 -0
- package/dist/hooks/service.js.map +1 -0
- package/dist/hooks/session-init.d.ts +30 -0
- package/dist/hooks/session-init.d.ts.map +1 -0
- package/dist/hooks/session-init.js +54 -0
- package/dist/hooks/session-init.js.map +1 -0
- package/dist/hooks/summarize.d.ts +30 -0
- package/dist/hooks/summarize.d.ts.map +1 -0
- package/dist/hooks/summarize.js +74 -0
- package/dist/hooks/summarize.js.map +1 -0
- package/dist/hooks/types.d.ts +193 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +137 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +173 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +564 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +368 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +14 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +110 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +100 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +9 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/migration.d.ts +77 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +457 -0
- package/dist/migration.js.map +1 -0
- package/dist/sqljs-backend.d.ts +128 -0
- package/dist/sqljs-backend.d.ts.map +1 -0
- package/dist/sqljs-backend.js +623 -0
- package/dist/sqljs-backend.js.map +1 -0
- package/dist/types.d.ts +481 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +73 -0
- package/dist/types.js.map +1 -0
- package/hooks.json +46 -0
- package/package.json +67 -0
- package/src/__tests__/index.test.ts +407 -0
- package/src/__tests__/sqljs-backend.test.ts +410 -0
- package/src/cache-manager.ts +515 -0
- package/src/cli/save.ts +109 -0
- package/src/cli/setup.ts +203 -0
- package/src/cli/viewer.ts +218 -0
- package/src/hnsw-index.ts +1013 -0
- package/src/hooks/__tests__/handlers.test.ts +298 -0
- package/src/hooks/__tests__/integration.test.ts +431 -0
- package/src/hooks/__tests__/service.test.ts +487 -0
- package/src/hooks/__tests__/types.test.ts +341 -0
- package/src/hooks/cli.ts +121 -0
- package/src/hooks/context.ts +77 -0
- package/src/hooks/index.ts +23 -0
- package/src/hooks/observation.ts +102 -0
- package/src/hooks/service.ts +582 -0
- package/src/hooks/session-init.ts +70 -0
- package/src/hooks/summarize.ts +89 -0
- package/src/hooks/types.ts +365 -0
- package/src/index.ts +755 -0
- package/src/mcp/__tests__/server.test.ts +181 -0
- package/src/mcp/index.ts +9 -0
- package/src/mcp/server.ts +441 -0
- package/src/mcp/tools.ts +113 -0
- package/src/mcp/types.ts +109 -0
- package/src/migration.ts +574 -0
- package/src/sql.js.d.ts +70 -0
- package/src/sqljs-backend.ts +789 -0
- package/src/types.ts +715 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for Hook Handlers
|
|
3
|
+
*
|
|
4
|
+
* @module @agentkits/memory/hooks/__tests__/handlers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { existsSync, rmSync, mkdirSync } from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { NormalizedHookInput } from '../types.js';
|
|
11
|
+
import { MemoryHookService } from '../service.js';
|
|
12
|
+
import { ContextHook, createContextHook } from '../context.js';
|
|
13
|
+
import { SessionInitHook, createSessionInitHook } from '../session-init.js';
|
|
14
|
+
import { ObservationHook, createObservationHook } from '../observation.js';
|
|
15
|
+
import { SummarizeHook, createSummarizeHook } from '../summarize.js';
|
|
16
|
+
|
|
17
|
+
const TEST_DIR = path.join(process.cwd(), '.test-hook-handlers');
|
|
18
|
+
|
|
19
|
+
function createTestInput(overrides: Partial<NormalizedHookInput> = {}): NormalizedHookInput {
|
|
20
|
+
return {
|
|
21
|
+
sessionId: 'test-session-123',
|
|
22
|
+
cwd: TEST_DIR,
|
|
23
|
+
project: 'test-project',
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('Hook Handlers', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
// Clean up test directory
|
|
32
|
+
if (existsSync(TEST_DIR)) {
|
|
33
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
// Clean up test directory
|
|
40
|
+
if (existsSync(TEST_DIR)) {
|
|
41
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('ContextHook', () => {
|
|
46
|
+
it('should return no context for new project', async () => {
|
|
47
|
+
const hook = createContextHook(TEST_DIR);
|
|
48
|
+
const input = createTestInput();
|
|
49
|
+
|
|
50
|
+
const result = await hook.execute(input);
|
|
51
|
+
|
|
52
|
+
expect(result.continue).toBe(true);
|
|
53
|
+
expect(result.suppressOutput).toBe(true);
|
|
54
|
+
expect(result.additionalContext).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should return context for existing project', async () => {
|
|
58
|
+
// Set up existing data
|
|
59
|
+
const service = new MemoryHookService(TEST_DIR);
|
|
60
|
+
await service.initSession('old-session', 'test-project', 'Previous task');
|
|
61
|
+
await service.storeObservation('old-session', 'test-project', 'Read', { file_path: 'file.ts' }, {}, TEST_DIR);
|
|
62
|
+
await service.completeSession('old-session', 'Done');
|
|
63
|
+
await service.shutdown();
|
|
64
|
+
|
|
65
|
+
// Run context hook
|
|
66
|
+
const hook = createContextHook(TEST_DIR);
|
|
67
|
+
const input = createTestInput({ sessionId: 'new-session' });
|
|
68
|
+
|
|
69
|
+
const result = await hook.execute(input);
|
|
70
|
+
|
|
71
|
+
expect(result.continue).toBe(true);
|
|
72
|
+
expect(result.suppressOutput).toBe(false);
|
|
73
|
+
expect(result.additionalContext).toBeDefined();
|
|
74
|
+
expect(result.additionalContext).toContain('# Memory Context');
|
|
75
|
+
expect(result.additionalContext).toContain('test-project');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle errors gracefully', async () => {
|
|
79
|
+
// Create hook with invalid directory
|
|
80
|
+
const hook = new ContextHook({
|
|
81
|
+
initialize: async () => { throw new Error('Test error'); },
|
|
82
|
+
} as unknown as MemoryHookService);
|
|
83
|
+
|
|
84
|
+
const input = createTestInput();
|
|
85
|
+
const result = await hook.execute(input);
|
|
86
|
+
|
|
87
|
+
expect(result.continue).toBe(true);
|
|
88
|
+
expect(result.suppressOutput).toBe(true);
|
|
89
|
+
expect(result.error).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('SessionInitHook', () => {
|
|
94
|
+
it('should initialize a new session', async () => {
|
|
95
|
+
const hook = createSessionInitHook(TEST_DIR);
|
|
96
|
+
const input = createTestInput({ prompt: 'Hello Claude' });
|
|
97
|
+
|
|
98
|
+
const result = await hook.execute(input);
|
|
99
|
+
|
|
100
|
+
expect(result.continue).toBe(true);
|
|
101
|
+
expect(result.suppressOutput).toBe(true);
|
|
102
|
+
|
|
103
|
+
// Verify session was created
|
|
104
|
+
const service = new MemoryHookService(TEST_DIR);
|
|
105
|
+
await service.initialize();
|
|
106
|
+
const session = service.getSession('test-session-123');
|
|
107
|
+
await service.shutdown();
|
|
108
|
+
|
|
109
|
+
expect(session).not.toBeNull();
|
|
110
|
+
expect(session?.prompt).toBe('Hello Claude');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should not overwrite existing session', async () => {
|
|
114
|
+
// Create initial session
|
|
115
|
+
const hook1 = createSessionInitHook(TEST_DIR);
|
|
116
|
+
await hook1.execute(createTestInput({ prompt: 'First prompt' }));
|
|
117
|
+
|
|
118
|
+
// Try to re-init with different prompt
|
|
119
|
+
const hook2 = createSessionInitHook(TEST_DIR);
|
|
120
|
+
await hook2.execute(createTestInput({ prompt: 'Second prompt' }));
|
|
121
|
+
|
|
122
|
+
// Verify original prompt preserved
|
|
123
|
+
const service = new MemoryHookService(TEST_DIR);
|
|
124
|
+
await service.initialize();
|
|
125
|
+
const session = service.getSession('test-session-123');
|
|
126
|
+
await service.shutdown();
|
|
127
|
+
|
|
128
|
+
expect(session?.prompt).toBe('First prompt');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should handle errors gracefully', async () => {
|
|
132
|
+
const hook = new SessionInitHook({
|
|
133
|
+
initialize: async () => { throw new Error('Test error'); },
|
|
134
|
+
} as unknown as MemoryHookService);
|
|
135
|
+
|
|
136
|
+
const input = createTestInput();
|
|
137
|
+
const result = await hook.execute(input);
|
|
138
|
+
|
|
139
|
+
expect(result.continue).toBe(true);
|
|
140
|
+
expect(result.suppressOutput).toBe(true);
|
|
141
|
+
expect(result.error).toBeDefined();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('ObservationHook', () => {
|
|
146
|
+
it('should store observation', async () => {
|
|
147
|
+
// Initialize session first
|
|
148
|
+
const initHook = createSessionInitHook(TEST_DIR);
|
|
149
|
+
await initHook.execute(createTestInput());
|
|
150
|
+
|
|
151
|
+
// Store observation
|
|
152
|
+
const hook = createObservationHook(TEST_DIR);
|
|
153
|
+
const input = createTestInput({
|
|
154
|
+
toolName: 'Read',
|
|
155
|
+
toolInput: { file_path: '/path/to/file.ts' },
|
|
156
|
+
toolResponse: { content: 'file contents' },
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const result = await hook.execute(input);
|
|
160
|
+
|
|
161
|
+
expect(result.continue).toBe(true);
|
|
162
|
+
expect(result.suppressOutput).toBe(true);
|
|
163
|
+
|
|
164
|
+
// Verify observation was stored
|
|
165
|
+
const service = new MemoryHookService(TEST_DIR);
|
|
166
|
+
await service.initialize();
|
|
167
|
+
const observations = await service.getSessionObservations('test-session-123');
|
|
168
|
+
await service.shutdown();
|
|
169
|
+
|
|
170
|
+
expect(observations.length).toBe(1);
|
|
171
|
+
expect(observations[0].toolName).toBe('Read');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should skip if no tool name', async () => {
|
|
175
|
+
const hook = createObservationHook(TEST_DIR);
|
|
176
|
+
const input = createTestInput({ toolName: undefined });
|
|
177
|
+
|
|
178
|
+
const result = await hook.execute(input);
|
|
179
|
+
|
|
180
|
+
expect(result.continue).toBe(true);
|
|
181
|
+
expect(result.suppressOutput).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should skip internal tools', async () => {
|
|
185
|
+
// Initialize session first
|
|
186
|
+
const initHook = createSessionInitHook(TEST_DIR);
|
|
187
|
+
await initHook.execute(createTestInput());
|
|
188
|
+
|
|
189
|
+
const hook = createObservationHook(TEST_DIR);
|
|
190
|
+
|
|
191
|
+
// Test skipped tools
|
|
192
|
+
for (const tool of ['TodoWrite', 'TodoRead', 'AskFollowupQuestion', 'AttemptCompletion']) {
|
|
193
|
+
const input = createTestInput({ toolName: tool });
|
|
194
|
+
const result = await hook.execute(input);
|
|
195
|
+
|
|
196
|
+
expect(result.continue).toBe(true);
|
|
197
|
+
expect(result.suppressOutput).toBe(true);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Verify no observations stored
|
|
201
|
+
const service = new MemoryHookService(TEST_DIR);
|
|
202
|
+
await service.initialize();
|
|
203
|
+
const observations = await service.getSessionObservations('test-session-123');
|
|
204
|
+
await service.shutdown();
|
|
205
|
+
|
|
206
|
+
expect(observations.length).toBe(0);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should create session if not exists', async () => {
|
|
210
|
+
const hook = createObservationHook(TEST_DIR);
|
|
211
|
+
const input = createTestInput({
|
|
212
|
+
sessionId: 'new-session',
|
|
213
|
+
toolName: 'Read',
|
|
214
|
+
toolInput: {},
|
|
215
|
+
toolResponse: {},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const result = await hook.execute(input);
|
|
219
|
+
|
|
220
|
+
expect(result.continue).toBe(true);
|
|
221
|
+
|
|
222
|
+
// Verify session was created
|
|
223
|
+
const service = new MemoryHookService(TEST_DIR);
|
|
224
|
+
await service.initialize();
|
|
225
|
+
const session = service.getSession('new-session');
|
|
226
|
+
await service.shutdown();
|
|
227
|
+
|
|
228
|
+
expect(session).not.toBeNull();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should handle errors gracefully', async () => {
|
|
232
|
+
const hook = new ObservationHook({
|
|
233
|
+
initialize: async () => { throw new Error('Test error'); },
|
|
234
|
+
} as unknown as MemoryHookService);
|
|
235
|
+
|
|
236
|
+
const input = createTestInput({ toolName: 'Read' });
|
|
237
|
+
const result = await hook.execute(input);
|
|
238
|
+
|
|
239
|
+
expect(result.continue).toBe(true);
|
|
240
|
+
expect(result.suppressOutput).toBe(true);
|
|
241
|
+
expect(result.error).toBeDefined();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('SummarizeHook', () => {
|
|
246
|
+
it('should complete session with summary', async () => {
|
|
247
|
+
// Set up session with observations
|
|
248
|
+
const service = new MemoryHookService(TEST_DIR);
|
|
249
|
+
await service.initSession('test-session-123', 'test-project');
|
|
250
|
+
await service.storeObservation('test-session-123', 'test-project', 'Read', { file_path: 'a.ts' }, {}, TEST_DIR);
|
|
251
|
+
await service.storeObservation('test-session-123', 'test-project', 'Write', { file_path: 'b.ts' }, {}, TEST_DIR);
|
|
252
|
+
await service.shutdown();
|
|
253
|
+
|
|
254
|
+
// Run summarize hook
|
|
255
|
+
const hook = createSummarizeHook(TEST_DIR);
|
|
256
|
+
const input = createTestInput();
|
|
257
|
+
|
|
258
|
+
const result = await hook.execute(input);
|
|
259
|
+
|
|
260
|
+
expect(result.continue).toBe(true);
|
|
261
|
+
expect(result.suppressOutput).toBe(true);
|
|
262
|
+
|
|
263
|
+
// Verify session was completed
|
|
264
|
+
const service2 = new MemoryHookService(TEST_DIR);
|
|
265
|
+
await service2.initialize();
|
|
266
|
+
const session = service2.getSession('test-session-123');
|
|
267
|
+
await service2.shutdown();
|
|
268
|
+
|
|
269
|
+
expect(session?.status).toBe('completed');
|
|
270
|
+
expect(session?.summary).toBeDefined();
|
|
271
|
+
expect(session?.summary).toContain('file');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should handle non-existent session', async () => {
|
|
275
|
+
const hook = createSummarizeHook(TEST_DIR);
|
|
276
|
+
const input = createTestInput({ sessionId: 'non-existent' });
|
|
277
|
+
|
|
278
|
+
const result = await hook.execute(input);
|
|
279
|
+
|
|
280
|
+
expect(result.continue).toBe(true);
|
|
281
|
+
expect(result.suppressOutput).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should handle errors gracefully', async () => {
|
|
285
|
+
const hook = new SummarizeHook({
|
|
286
|
+
initialize: async () => { throw new Error('Test error'); },
|
|
287
|
+
shutdown: async () => {},
|
|
288
|
+
} as unknown as MemoryHookService);
|
|
289
|
+
|
|
290
|
+
const input = createTestInput();
|
|
291
|
+
const result = await hook.execute(input);
|
|
292
|
+
|
|
293
|
+
expect(result.continue).toBe(true);
|
|
294
|
+
expect(result.suppressOutput).toBe(true);
|
|
295
|
+
expect(result.error).toBeDefined();
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|