@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,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for Hook Types and Utilities
|
|
3
|
+
*
|
|
4
|
+
* @module @agentkits/memory/hooks/__tests__/types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
generateObservationId,
|
|
10
|
+
getProjectName,
|
|
11
|
+
getObservationType,
|
|
12
|
+
generateObservationTitle,
|
|
13
|
+
truncate,
|
|
14
|
+
parseHookInput,
|
|
15
|
+
formatResponse,
|
|
16
|
+
STANDARD_RESPONSE,
|
|
17
|
+
} from '../types.js';
|
|
18
|
+
|
|
19
|
+
describe('Hook Types Utilities', () => {
|
|
20
|
+
describe('generateObservationId', () => {
|
|
21
|
+
it('should generate unique IDs', () => {
|
|
22
|
+
const id1 = generateObservationId();
|
|
23
|
+
const id2 = generateObservationId();
|
|
24
|
+
|
|
25
|
+
expect(id1).not.toBe(id2);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should start with obs_ prefix', () => {
|
|
29
|
+
const id = generateObservationId();
|
|
30
|
+
|
|
31
|
+
expect(id).toMatch(/^obs_/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should contain timestamp and random parts', () => {
|
|
35
|
+
const id = generateObservationId();
|
|
36
|
+
const parts = id.split('_');
|
|
37
|
+
|
|
38
|
+
expect(parts.length).toBe(3);
|
|
39
|
+
expect(parts[0]).toBe('obs');
|
|
40
|
+
expect(parts[1].length).toBeGreaterThan(0); // timestamp
|
|
41
|
+
expect(parts[2].length).toBe(4); // random
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('getProjectName', () => {
|
|
46
|
+
it('should extract project name from Unix path', () => {
|
|
47
|
+
expect(getProjectName('/home/user/projects/my-app')).toBe('my-app');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should extract project name from Windows path', () => {
|
|
51
|
+
expect(getProjectName('C:\\Users\\user\\projects\\my-app')).toBe('my-app');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle trailing slash', () => {
|
|
55
|
+
// Trailing slash results in empty string which gets mapped to 'unknown'
|
|
56
|
+
expect(getProjectName('/home/user/projects/my-app/')).toBe('unknown');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return unknown for empty path', () => {
|
|
60
|
+
expect(getProjectName('')).toBe('unknown');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should handle single directory', () => {
|
|
64
|
+
expect(getProjectName('my-app')).toBe('my-app');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('getObservationType', () => {
|
|
69
|
+
it('should classify read tools', () => {
|
|
70
|
+
expect(getObservationType('Read')).toBe('read');
|
|
71
|
+
expect(getObservationType('Glob')).toBe('read');
|
|
72
|
+
expect(getObservationType('Grep')).toBe('read');
|
|
73
|
+
expect(getObservationType('LS')).toBe('read');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should classify write tools', () => {
|
|
77
|
+
expect(getObservationType('Write')).toBe('write');
|
|
78
|
+
expect(getObservationType('Edit')).toBe('write');
|
|
79
|
+
expect(getObservationType('NotebookEdit')).toBe('write');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should classify execute tools', () => {
|
|
83
|
+
expect(getObservationType('Bash')).toBe('execute');
|
|
84
|
+
expect(getObservationType('Task')).toBe('execute');
|
|
85
|
+
expect(getObservationType('Skill')).toBe('execute');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should classify search tools', () => {
|
|
89
|
+
expect(getObservationType('WebSearch')).toBe('search');
|
|
90
|
+
expect(getObservationType('WebFetch')).toBe('search');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return other for unknown tools', () => {
|
|
94
|
+
expect(getObservationType('UnknownTool')).toBe('other');
|
|
95
|
+
expect(getObservationType('CustomTool')).toBe('other');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('generateObservationTitle', () => {
|
|
100
|
+
it('should generate title for Read tool', () => {
|
|
101
|
+
const title = generateObservationTitle('Read', { file_path: '/path/to/file.ts' });
|
|
102
|
+
expect(title).toBe('Read /path/to/file.ts');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should generate title for Write tool', () => {
|
|
106
|
+
const title = generateObservationTitle('Write', { file_path: '/path/to/file.ts' });
|
|
107
|
+
expect(title).toBe('Write /path/to/file.ts');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should generate title for Edit tool', () => {
|
|
111
|
+
const title = generateObservationTitle('Edit', { file_path: '/path/to/file.ts' });
|
|
112
|
+
expect(title).toBe('Edit /path/to/file.ts');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should generate title for Bash tool', () => {
|
|
116
|
+
const title = generateObservationTitle('Bash', { command: 'npm install' });
|
|
117
|
+
expect(title).toBe('Run: npm install');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should truncate long Bash commands', () => {
|
|
121
|
+
const longCommand = 'npm install some-very-long-package-name-that-exceeds-fifty-characters';
|
|
122
|
+
const title = generateObservationTitle('Bash', { command: longCommand });
|
|
123
|
+
expect(title).toBe(`Run: ${longCommand.substring(0, 50)}...`);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should generate title for Glob tool', () => {
|
|
127
|
+
const title = generateObservationTitle('Glob', { pattern: '**/*.ts' });
|
|
128
|
+
expect(title).toBe('Find **/*.ts');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should generate title for Grep tool', () => {
|
|
132
|
+
const title = generateObservationTitle('Grep', { pattern: 'function\\s+\\w+' });
|
|
133
|
+
expect(title).toBe('Search "function\\s+\\w+"');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should generate title for Task tool', () => {
|
|
137
|
+
const title = generateObservationTitle('Task', { description: 'explore codebase' });
|
|
138
|
+
expect(title).toBe('Task: explore codebase');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should generate title for WebSearch tool', () => {
|
|
142
|
+
const title = generateObservationTitle('WebSearch', { query: 'typescript best practices' });
|
|
143
|
+
expect(title).toBe('Search: typescript best practices');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should generate title for WebFetch tool', () => {
|
|
147
|
+
const title = generateObservationTitle('WebFetch', { url: 'https://example.com' });
|
|
148
|
+
expect(title).toBe('Fetch: https://example.com');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should handle unknown tools', () => {
|
|
152
|
+
const title = generateObservationTitle('CustomTool', { foo: 'bar' });
|
|
153
|
+
expect(title).toBe('CustomTool');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should handle Edit with path fallback', () => {
|
|
157
|
+
const title = generateObservationTitle('Edit', { path: '/path/file.ts' });
|
|
158
|
+
expect(title).toBe('Edit /path/file.ts');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should handle Edit with no path', () => {
|
|
162
|
+
const title = generateObservationTitle('Edit', {});
|
|
163
|
+
expect(title).toBe('Edit file');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle Bash with empty command', () => {
|
|
167
|
+
const title = generateObservationTitle('Bash', {});
|
|
168
|
+
expect(title).toBe('Run: ');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should handle Glob with no pattern', () => {
|
|
172
|
+
const title = generateObservationTitle('Glob', {});
|
|
173
|
+
expect(title).toBe('Find files');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should handle Grep with no pattern', () => {
|
|
177
|
+
const title = generateObservationTitle('Grep', {});
|
|
178
|
+
expect(title).toBe('Search ""');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should handle Task with no description', () => {
|
|
182
|
+
const title = generateObservationTitle('Task', {});
|
|
183
|
+
expect(title).toBe('Task: agent');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should handle WebSearch with no query', () => {
|
|
187
|
+
const title = generateObservationTitle('WebSearch', {});
|
|
188
|
+
expect(title).toBe('Search: ');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should handle WebFetch with no url', () => {
|
|
192
|
+
const title = generateObservationTitle('WebFetch', {});
|
|
193
|
+
expect(title).toBe('Fetch: ');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should handle string input', () => {
|
|
197
|
+
const title = generateObservationTitle('Read', JSON.stringify({ file_path: '/path/file.ts' }));
|
|
198
|
+
expect(title).toBe('Read /path/file.ts');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should handle null input', () => {
|
|
202
|
+
const title = generateObservationTitle('Read', null);
|
|
203
|
+
expect(title).toBe('Read file');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should handle parse errors gracefully', () => {
|
|
207
|
+
const title = generateObservationTitle('Read', 'invalid json {');
|
|
208
|
+
expect(title).toBe('Read');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('truncate', () => {
|
|
213
|
+
it('should not truncate short strings', () => {
|
|
214
|
+
const str = 'Hello World';
|
|
215
|
+
expect(truncate(str, 100)).toBe(str);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should truncate long strings', () => {
|
|
219
|
+
const str = 'A'.repeat(200);
|
|
220
|
+
const result = truncate(str, 100);
|
|
221
|
+
|
|
222
|
+
expect(result.length).toBe(100 + '...[truncated]'.length);
|
|
223
|
+
expect(result).toContain('...[truncated]');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should use default max length of 1000', () => {
|
|
227
|
+
const str = 'A'.repeat(1500);
|
|
228
|
+
const result = truncate(str);
|
|
229
|
+
|
|
230
|
+
expect(result.length).toBe(1000 + '...[truncated]'.length);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should handle exact length', () => {
|
|
234
|
+
const str = 'A'.repeat(100);
|
|
235
|
+
expect(truncate(str, 100)).toBe(str);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('parseHookInput', () => {
|
|
240
|
+
it('should parse valid JSON input', () => {
|
|
241
|
+
const input = JSON.stringify({
|
|
242
|
+
session_id: 'test-session-123',
|
|
243
|
+
cwd: '/path/to/project',
|
|
244
|
+
prompt: 'Hello Claude',
|
|
245
|
+
tool_name: 'Read',
|
|
246
|
+
tool_input: { file_path: '/path/to/file.ts' },
|
|
247
|
+
tool_result: { content: 'file contents' },
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const parsed = parseHookInput(input);
|
|
251
|
+
|
|
252
|
+
expect(parsed.sessionId).toBe('test-session-123');
|
|
253
|
+
expect(parsed.cwd).toBe('/path/to/project');
|
|
254
|
+
expect(parsed.project).toBe('project');
|
|
255
|
+
expect(parsed.prompt).toBe('Hello Claude');
|
|
256
|
+
expect(parsed.toolName).toBe('Read');
|
|
257
|
+
expect(parsed.toolInput).toEqual({ file_path: '/path/to/file.ts' });
|
|
258
|
+
expect(parsed.toolResponse).toEqual({ content: 'file contents' });
|
|
259
|
+
expect(parsed.timestamp).toBeGreaterThan(0);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should handle missing session_id', () => {
|
|
263
|
+
const input = JSON.stringify({ cwd: '/path/to/project' });
|
|
264
|
+
const parsed = parseHookInput(input);
|
|
265
|
+
|
|
266
|
+
expect(parsed.sessionId).toMatch(/^session_\d+$/);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should handle missing cwd', () => {
|
|
270
|
+
const input = JSON.stringify({ session_id: 'test' });
|
|
271
|
+
const parsed = parseHookInput(input);
|
|
272
|
+
|
|
273
|
+
expect(parsed.cwd).toBe(process.cwd());
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should handle empty input', () => {
|
|
277
|
+
const parsed = parseHookInput('');
|
|
278
|
+
|
|
279
|
+
expect(parsed.sessionId).toMatch(/^session_\d+$/);
|
|
280
|
+
expect(parsed.cwd).toBe(process.cwd());
|
|
281
|
+
expect(parsed.timestamp).toBeGreaterThan(0);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should handle invalid JSON', () => {
|
|
285
|
+
const parsed = parseHookInput('not valid json');
|
|
286
|
+
|
|
287
|
+
expect(parsed.sessionId).toMatch(/^session_\d+$/);
|
|
288
|
+
expect(parsed.cwd).toBe(process.cwd());
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should parse transcript_path and stop_reason', () => {
|
|
292
|
+
const input = JSON.stringify({
|
|
293
|
+
session_id: 'test',
|
|
294
|
+
cwd: '/path',
|
|
295
|
+
transcript_path: '/path/to/transcript.json',
|
|
296
|
+
stop_reason: 'user_exit',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const parsed = parseHookInput(input);
|
|
300
|
+
|
|
301
|
+
expect(parsed.transcriptPath).toBe('/path/to/transcript.json');
|
|
302
|
+
expect(parsed.stopReason).toBe('user_exit');
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('formatResponse', () => {
|
|
307
|
+
it('should format standard response', () => {
|
|
308
|
+
const result = {
|
|
309
|
+
continue: true,
|
|
310
|
+
suppressOutput: true,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const response = formatResponse(result);
|
|
314
|
+
const parsed = JSON.parse(response);
|
|
315
|
+
|
|
316
|
+
expect(parsed).toEqual(STANDARD_RESPONSE);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should format response with additionalContext', () => {
|
|
320
|
+
const result = {
|
|
321
|
+
continue: true,
|
|
322
|
+
suppressOutput: false,
|
|
323
|
+
additionalContext: '# Memory Context\n\nSome context here',
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const response = formatResponse(result);
|
|
327
|
+
const parsed = JSON.parse(response);
|
|
328
|
+
|
|
329
|
+
expect(parsed.hookSpecificOutput).toBeDefined();
|
|
330
|
+
expect(parsed.hookSpecificOutput.hookEventName).toBe('SessionStart');
|
|
331
|
+
expect(parsed.hookSpecificOutput.additionalContext).toBe('# Memory Context\n\nSome context here');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('STANDARD_RESPONSE', () => {
|
|
336
|
+
it('should have correct structure', () => {
|
|
337
|
+
expect(STANDARD_RESPONSE.continue).toBe(true);
|
|
338
|
+
expect(STANDARD_RESPONSE.suppressOutput).toBe(true);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
});
|
package/src/hooks/cli.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentKits Memory Hook CLI
|
|
4
|
+
*
|
|
5
|
+
* Unified CLI handler for all Claude Code hooks.
|
|
6
|
+
* Reads stdin, executes appropriate hook, outputs response.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* echo '{"session_id":"..."}' | npx agentkits-memory-hook <event>
|
|
10
|
+
*
|
|
11
|
+
* Events:
|
|
12
|
+
* context - SessionStart: inject memory context
|
|
13
|
+
* session-init - UserPromptSubmit: initialize session
|
|
14
|
+
* observation - PostToolUse: capture tool usage
|
|
15
|
+
* summarize - Stop: generate session summary
|
|
16
|
+
*
|
|
17
|
+
* @module @agentkits/memory/hooks/cli
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { parseHookInput, formatResponse, STANDARD_RESPONSE } from './types.js';
|
|
21
|
+
import { createContextHook } from './context.js';
|
|
22
|
+
import { createSessionInitHook } from './session-init.js';
|
|
23
|
+
import { createObservationHook } from './observation.js';
|
|
24
|
+
import { createSummarizeHook } from './summarize.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read stdin until EOF
|
|
28
|
+
*/
|
|
29
|
+
async function readStdin(): Promise<string> {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
let data = '';
|
|
32
|
+
|
|
33
|
+
// Set encoding
|
|
34
|
+
process.stdin.setEncoding('utf8');
|
|
35
|
+
|
|
36
|
+
// Handle data
|
|
37
|
+
process.stdin.on('data', (chunk) => {
|
|
38
|
+
data += chunk;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Handle end
|
|
42
|
+
process.stdin.on('end', () => {
|
|
43
|
+
resolve(data);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Handle error
|
|
47
|
+
process.stdin.on('error', () => {
|
|
48
|
+
resolve('');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// If stdin is already closed
|
|
52
|
+
if (process.stdin.isTTY) {
|
|
53
|
+
resolve('');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Main CLI handler
|
|
60
|
+
*/
|
|
61
|
+
async function main(): Promise<void> {
|
|
62
|
+
try {
|
|
63
|
+
// Get event type from args
|
|
64
|
+
const event = process.argv[2];
|
|
65
|
+
|
|
66
|
+
if (!event) {
|
|
67
|
+
console.error('Usage: agentkits-memory-hook <event>');
|
|
68
|
+
console.error('Events: context, session-init, observation, summarize');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Read stdin
|
|
73
|
+
const stdin = await readStdin();
|
|
74
|
+
|
|
75
|
+
// Parse input
|
|
76
|
+
const input = parseHookInput(stdin);
|
|
77
|
+
|
|
78
|
+
// Select and execute handler
|
|
79
|
+
let result;
|
|
80
|
+
|
|
81
|
+
switch (event) {
|
|
82
|
+
case 'context':
|
|
83
|
+
result = await createContextHook(input.cwd).execute(input);
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'session-init':
|
|
87
|
+
result = await createSessionInitHook(input.cwd).execute(input);
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'observation':
|
|
91
|
+
result = await createObservationHook(input.cwd).execute(input);
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
case 'summarize':
|
|
95
|
+
result = await createSummarizeHook(input.cwd).execute(input);
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
default:
|
|
99
|
+
console.error(`Unknown event: ${event}`);
|
|
100
|
+
console.log(JSON.stringify(STANDARD_RESPONSE));
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Output response
|
|
105
|
+
console.log(formatResponse(result));
|
|
106
|
+
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Log error to stderr
|
|
109
|
+
console.error('[AgentKits Memory] CLI error:', error);
|
|
110
|
+
|
|
111
|
+
// Output standard response (don't block Claude)
|
|
112
|
+
console.log(JSON.stringify(STANDARD_RESPONSE));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Run
|
|
117
|
+
main().catch((error) => {
|
|
118
|
+
console.error('[AgentKits Memory] Fatal error:', error);
|
|
119
|
+
console.log(JSON.stringify(STANDARD_RESPONSE));
|
|
120
|
+
process.exit(0);
|
|
121
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Hook Handler (SessionStart)
|
|
3
|
+
*
|
|
4
|
+
* Injects memory context at the start of a Claude Code session.
|
|
5
|
+
* Provides previous session history and relevant observations.
|
|
6
|
+
*
|
|
7
|
+
* @module @agentkits/memory/hooks/context
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
NormalizedHookInput,
|
|
12
|
+
HookResult,
|
|
13
|
+
EventHandler,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
import { MemoryHookService } from './service.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Context Hook - SessionStart Event
|
|
19
|
+
*
|
|
20
|
+
* Called when a new Claude Code session starts.
|
|
21
|
+
* Retrieves and injects previous context to help Claude
|
|
22
|
+
* understand the project history.
|
|
23
|
+
*/
|
|
24
|
+
export class ContextHook implements EventHandler {
|
|
25
|
+
private service: MemoryHookService;
|
|
26
|
+
|
|
27
|
+
constructor(service: MemoryHookService) {
|
|
28
|
+
this.service = service;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Execute the context hook
|
|
33
|
+
*/
|
|
34
|
+
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
|
35
|
+
try {
|
|
36
|
+
// Initialize service
|
|
37
|
+
await this.service.initialize();
|
|
38
|
+
|
|
39
|
+
// Get context for this project
|
|
40
|
+
const context = await this.service.getContext(input.project);
|
|
41
|
+
|
|
42
|
+
// No context to inject
|
|
43
|
+
if (!context.markdown || context.markdown.includes('No previous session context')) {
|
|
44
|
+
return {
|
|
45
|
+
continue: true,
|
|
46
|
+
suppressOutput: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Inject context as additional context
|
|
51
|
+
return {
|
|
52
|
+
continue: true,
|
|
53
|
+
suppressOutput: false,
|
|
54
|
+
additionalContext: context.markdown,
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Log error but don't block session
|
|
58
|
+
console.error('[AgentKits Memory] Context hook error:', error);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
continue: true,
|
|
62
|
+
suppressOutput: true,
|
|
63
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create context hook handler
|
|
71
|
+
*/
|
|
72
|
+
export function createContextHook(cwd: string): ContextHook {
|
|
73
|
+
const service = new MemoryHookService(cwd);
|
|
74
|
+
return new ContextHook(service);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default ContextHook;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentKits Memory Hooks
|
|
3
|
+
*
|
|
4
|
+
* Lightweight hook system for auto-capturing Claude Code sessions.
|
|
5
|
+
* Direct SQLite access without HTTP worker overhead.
|
|
6
|
+
*
|
|
7
|
+
* @module @agentkits/memory/hooks
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Types
|
|
11
|
+
export * from './types.js';
|
|
12
|
+
|
|
13
|
+
// Service
|
|
14
|
+
export { MemoryHookService, createHookService } from './service.js';
|
|
15
|
+
|
|
16
|
+
// Handlers
|
|
17
|
+
export { ContextHook, createContextHook } from './context.js';
|
|
18
|
+
export { SessionInitHook, createSessionInitHook } from './session-init.js';
|
|
19
|
+
export { ObservationHook, createObservationHook } from './observation.js';
|
|
20
|
+
export { SummarizeHook, createSummarizeHook } from './summarize.js';
|
|
21
|
+
|
|
22
|
+
// Re-export default service
|
|
23
|
+
export { default } from './service.js';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observation Hook Handler (PostToolUse)
|
|
3
|
+
*
|
|
4
|
+
* Captures tool usage observations after Claude executes a tool.
|
|
5
|
+
* Stores file reads, writes, commands, and searches for context.
|
|
6
|
+
*
|
|
7
|
+
* @module @agentkits/memory/hooks/observation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
NormalizedHookInput,
|
|
12
|
+
HookResult,
|
|
13
|
+
EventHandler,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
import { MemoryHookService } from './service.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tools to skip capturing (internal/noisy tools)
|
|
19
|
+
*/
|
|
20
|
+
const SKIP_TOOLS = new Set([
|
|
21
|
+
'TodoWrite',
|
|
22
|
+
'TodoRead',
|
|
23
|
+
'AskFollowupQuestion',
|
|
24
|
+
'AttemptCompletion',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Observation Hook - PostToolUse Event
|
|
29
|
+
*
|
|
30
|
+
* Called after Claude executes a tool.
|
|
31
|
+
* Captures the tool usage for future context.
|
|
32
|
+
*/
|
|
33
|
+
export class ObservationHook implements EventHandler {
|
|
34
|
+
private service: MemoryHookService;
|
|
35
|
+
|
|
36
|
+
constructor(service: MemoryHookService) {
|
|
37
|
+
this.service = service;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Execute the observation hook
|
|
42
|
+
*/
|
|
43
|
+
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
|
44
|
+
try {
|
|
45
|
+
// Skip if no tool name
|
|
46
|
+
if (!input.toolName) {
|
|
47
|
+
return {
|
|
48
|
+
continue: true,
|
|
49
|
+
suppressOutput: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Skip internal tools
|
|
54
|
+
if (SKIP_TOOLS.has(input.toolName)) {
|
|
55
|
+
return {
|
|
56
|
+
continue: true,
|
|
57
|
+
suppressOutput: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Initialize service
|
|
62
|
+
await this.service.initialize();
|
|
63
|
+
|
|
64
|
+
// Ensure session exists (create if not)
|
|
65
|
+
await this.service.initSession(input.sessionId, input.project);
|
|
66
|
+
|
|
67
|
+
// Store the observation
|
|
68
|
+
await this.service.storeObservation(
|
|
69
|
+
input.sessionId,
|
|
70
|
+
input.project,
|
|
71
|
+
input.toolName,
|
|
72
|
+
input.toolInput,
|
|
73
|
+
input.toolResponse,
|
|
74
|
+
input.cwd
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
continue: true,
|
|
79
|
+
suppressOutput: true,
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Log error but don't block tool execution
|
|
83
|
+
console.error('[AgentKits Memory] Observation hook error:', error);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
continue: true,
|
|
87
|
+
suppressOutput: true,
|
|
88
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create observation hook handler
|
|
96
|
+
*/
|
|
97
|
+
export function createObservationHook(cwd: string): ObservationHook {
|
|
98
|
+
const service = new MemoryHookService(cwd);
|
|
99
|
+
return new ObservationHook(service);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default ObservationHook;
|