@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.
Files changed (116) hide show
  1. package/README.md +250 -0
  2. package/dist/cache-manager.d.ts +134 -0
  3. package/dist/cache-manager.d.ts.map +1 -0
  4. package/dist/cache-manager.js +407 -0
  5. package/dist/cache-manager.js.map +1 -0
  6. package/dist/cli/save.d.ts +20 -0
  7. package/dist/cli/save.d.ts.map +1 -0
  8. package/dist/cli/save.js +94 -0
  9. package/dist/cli/save.js.map +1 -0
  10. package/dist/cli/setup.d.ts +18 -0
  11. package/dist/cli/setup.d.ts.map +1 -0
  12. package/dist/cli/setup.js +163 -0
  13. package/dist/cli/setup.js.map +1 -0
  14. package/dist/cli/viewer.d.ts +21 -0
  15. package/dist/cli/viewer.d.ts.map +1 -0
  16. package/dist/cli/viewer.js +182 -0
  17. package/dist/cli/viewer.js.map +1 -0
  18. package/dist/hnsw-index.d.ts +111 -0
  19. package/dist/hnsw-index.d.ts.map +1 -0
  20. package/dist/hnsw-index.js +781 -0
  21. package/dist/hnsw-index.js.map +1 -0
  22. package/dist/hooks/cli.d.ts +20 -0
  23. package/dist/hooks/cli.d.ts.map +1 -0
  24. package/dist/hooks/cli.js +102 -0
  25. package/dist/hooks/cli.js.map +1 -0
  26. package/dist/hooks/context.d.ts +31 -0
  27. package/dist/hooks/context.d.ts.map +1 -0
  28. package/dist/hooks/context.js +64 -0
  29. package/dist/hooks/context.js.map +1 -0
  30. package/dist/hooks/index.d.ts +16 -0
  31. package/dist/hooks/index.d.ts.map +1 -0
  32. package/dist/hooks/index.js +20 -0
  33. package/dist/hooks/index.js.map +1 -0
  34. package/dist/hooks/observation.d.ts +30 -0
  35. package/dist/hooks/observation.d.ts.map +1 -0
  36. package/dist/hooks/observation.js +79 -0
  37. package/dist/hooks/observation.js.map +1 -0
  38. package/dist/hooks/service.d.ts +102 -0
  39. package/dist/hooks/service.d.ts.map +1 -0
  40. package/dist/hooks/service.js +454 -0
  41. package/dist/hooks/service.js.map +1 -0
  42. package/dist/hooks/session-init.d.ts +30 -0
  43. package/dist/hooks/session-init.d.ts.map +1 -0
  44. package/dist/hooks/session-init.js +54 -0
  45. package/dist/hooks/session-init.js.map +1 -0
  46. package/dist/hooks/summarize.d.ts +30 -0
  47. package/dist/hooks/summarize.d.ts.map +1 -0
  48. package/dist/hooks/summarize.js +74 -0
  49. package/dist/hooks/summarize.js.map +1 -0
  50. package/dist/hooks/types.d.ts +193 -0
  51. package/dist/hooks/types.d.ts.map +1 -0
  52. package/dist/hooks/types.js +137 -0
  53. package/dist/hooks/types.js.map +1 -0
  54. package/dist/index.d.ts +173 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +564 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/mcp/index.d.ts +9 -0
  59. package/dist/mcp/index.d.ts.map +1 -0
  60. package/dist/mcp/index.js +9 -0
  61. package/dist/mcp/index.js.map +1 -0
  62. package/dist/mcp/server.d.ts +22 -0
  63. package/dist/mcp/server.d.ts.map +1 -0
  64. package/dist/mcp/server.js +368 -0
  65. package/dist/mcp/server.js.map +1 -0
  66. package/dist/mcp/tools.d.ts +14 -0
  67. package/dist/mcp/tools.d.ts.map +1 -0
  68. package/dist/mcp/tools.js +110 -0
  69. package/dist/mcp/tools.js.map +1 -0
  70. package/dist/mcp/types.d.ts +100 -0
  71. package/dist/mcp/types.d.ts.map +1 -0
  72. package/dist/mcp/types.js +9 -0
  73. package/dist/mcp/types.js.map +1 -0
  74. package/dist/migration.d.ts +77 -0
  75. package/dist/migration.d.ts.map +1 -0
  76. package/dist/migration.js +457 -0
  77. package/dist/migration.js.map +1 -0
  78. package/dist/sqljs-backend.d.ts +128 -0
  79. package/dist/sqljs-backend.d.ts.map +1 -0
  80. package/dist/sqljs-backend.js +623 -0
  81. package/dist/sqljs-backend.js.map +1 -0
  82. package/dist/types.d.ts +481 -0
  83. package/dist/types.d.ts.map +1 -0
  84. package/dist/types.js +73 -0
  85. package/dist/types.js.map +1 -0
  86. package/hooks.json +46 -0
  87. package/package.json +67 -0
  88. package/src/__tests__/index.test.ts +407 -0
  89. package/src/__tests__/sqljs-backend.test.ts +410 -0
  90. package/src/cache-manager.ts +515 -0
  91. package/src/cli/save.ts +109 -0
  92. package/src/cli/setup.ts +203 -0
  93. package/src/cli/viewer.ts +218 -0
  94. package/src/hnsw-index.ts +1013 -0
  95. package/src/hooks/__tests__/handlers.test.ts +298 -0
  96. package/src/hooks/__tests__/integration.test.ts +431 -0
  97. package/src/hooks/__tests__/service.test.ts +487 -0
  98. package/src/hooks/__tests__/types.test.ts +341 -0
  99. package/src/hooks/cli.ts +121 -0
  100. package/src/hooks/context.ts +77 -0
  101. package/src/hooks/index.ts +23 -0
  102. package/src/hooks/observation.ts +102 -0
  103. package/src/hooks/service.ts +582 -0
  104. package/src/hooks/session-init.ts +70 -0
  105. package/src/hooks/summarize.ts +89 -0
  106. package/src/hooks/types.ts +365 -0
  107. package/src/index.ts +755 -0
  108. package/src/mcp/__tests__/server.test.ts +181 -0
  109. package/src/mcp/index.ts +9 -0
  110. package/src/mcp/server.ts +441 -0
  111. package/src/mcp/tools.ts +113 -0
  112. package/src/mcp/types.ts +109 -0
  113. package/src/migration.ts +574 -0
  114. package/src/sql.js.d.ts +70 -0
  115. package/src/sqljs-backend.ts +789 -0
  116. 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
+ });