@hanhnd/agent-kit 1.0.34 → 1.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/cli-digest-pending.test.js +19 -10
- package/dist/cli/commands/memory.js +44 -4
- package/dist/cli.js +348 -179
- package/dist/core/config/index.d.ts +3 -0
- package/dist/core/config/index.js +20 -0
- package/dist/entrypoints/server.js +1 -3
- package/dist/mcp/memory.d.ts +2 -1
- package/dist/mcp/memory.js +45 -18
- package/dist/server.js +294 -200
- package/dist/services/digest/constants.d.ts +3 -3
- package/dist/services/digest/constants.js +3 -3
- package/dist/services/digest/digest-background.test.js +19 -8
- package/dist/services/digest/digest-init.test.js +13 -2
- package/dist/services/digest/digest-pending.test.js +13 -2
- package/dist/services/digest/digest-processor.test.js +52 -0
- package/dist/services/digest/files.js +3 -3
- package/dist/services/digest/model-registry.js +2 -2
- package/dist/services/digest/processor.js +43 -13
- package/dist/services/digest/providers/llama-local.js +17 -28
- package/dist/services/digest/types.d.ts +3 -1
- package/dist/services/digest/types.js +1 -1
- package/dist/services/memory/chunker.d.ts +5 -2
- package/dist/services/memory/chunker.js +3 -1
- package/dist/services/memory/chunker.test.js +21 -11
- package/dist/services/memory/constants.js +1 -1
- package/dist/services/memory/embedder.js +1 -1
- package/dist/services/memory/index.d.ts +1 -1
- package/dist/services/memory/indexer.d.ts +2 -1
- package/dist/services/memory/indexer.js +24 -8
- package/dist/services/memory/indexer.test.js +38 -2
- package/dist/services/memory/memory.test.js +172 -15
- package/dist/services/memory/store.d.ts +9 -3
- package/dist/services/memory/store.js +132 -98
- package/dist/services/memory/store.test.js +239 -14
- package/dist/services/memory/types.d.ts +9 -1
- package/dist/services/memory/types.js +2 -1
- package/dist/utils/paths.d.ts +2 -0
- package/dist/utils/paths.js +2 -1
- package/dist/utils/utils.d.ts +1 -0
- package/dist/utils/utils.js +3 -3
- package/package.json +3 -5
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import Database from 'libsql';
|
|
1
2
|
import * as assert from 'node:assert/strict';
|
|
2
3
|
import * as fs from 'node:fs';
|
|
3
4
|
import * as os from 'node:os';
|
|
4
5
|
import * as path from 'node:path';
|
|
5
6
|
import { after, before, describe, test } from 'node:test';
|
|
6
|
-
import { MemoryStore } from './store.js';
|
|
7
|
+
import { MemoryStore, SCHEMA_VERSION } from './store.js';
|
|
7
8
|
import { EmbeddingModelName } from './types.js';
|
|
8
9
|
const TEST_CONFIG = {
|
|
9
10
|
enabled: true,
|
|
@@ -11,21 +12,42 @@ const TEST_CONFIG = {
|
|
|
11
12
|
topK: 5,
|
|
12
13
|
chunkSize: 1500,
|
|
13
14
|
overlapLines: 2,
|
|
14
|
-
embeddingModel: EmbeddingModelName.
|
|
15
|
+
embeddingModel: EmbeddingModelName.BASE,
|
|
15
16
|
vectorDimension: 384,
|
|
16
17
|
};
|
|
17
18
|
function makeChunk(overrides = {}) {
|
|
18
19
|
return {
|
|
19
20
|
id: 'test-id-0000001',
|
|
20
21
|
source: 'test.md',
|
|
22
|
+
sourceType: 'wiki',
|
|
21
23
|
heading: 'Test Heading',
|
|
22
24
|
headingLevel: 1,
|
|
23
25
|
content: 'This is test content for BM25 search.',
|
|
24
26
|
lineStart: 1,
|
|
25
27
|
lineEnd: 5,
|
|
28
|
+
fileMtimeAt: 1000,
|
|
26
29
|
...overrides,
|
|
27
30
|
};
|
|
28
31
|
}
|
|
32
|
+
function makeEmbedding(activeIndex, dimension = TEST_CONFIG.vectorDimension) {
|
|
33
|
+
const embedding = new Float32Array(dimension);
|
|
34
|
+
embedding[activeIndex] = 1;
|
|
35
|
+
return embedding;
|
|
36
|
+
}
|
|
37
|
+
function readUserVersion(db) {
|
|
38
|
+
const value = db.pragma('user_version', { simple: true });
|
|
39
|
+
if (typeof value === 'number')
|
|
40
|
+
return value;
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
const [row] = value;
|
|
43
|
+
return typeof row?.user_version === 'number' ? row.user_version : 0;
|
|
44
|
+
}
|
|
45
|
+
if (value && typeof value === 'object' && 'user_version' in value) {
|
|
46
|
+
const row = value;
|
|
47
|
+
return typeof row.user_version === 'number' ? row.user_version : 0;
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
29
51
|
describe('MemoryStore', () => {
|
|
30
52
|
let tmpDir;
|
|
31
53
|
let dbPath;
|
|
@@ -39,8 +61,50 @@ describe('MemoryStore', () => {
|
|
|
39
61
|
store.close();
|
|
40
62
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
41
63
|
});
|
|
42
|
-
test('
|
|
43
|
-
|
|
64
|
+
test('libsql supports required local driver and vector features', () => {
|
|
65
|
+
const libsqlDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memory-libsql-compat-'));
|
|
66
|
+
const libsqlDb = new Database(path.join(libsqlDir, 'index.db'));
|
|
67
|
+
try {
|
|
68
|
+
assert.equal(readUserVersion(libsqlDb), 0);
|
|
69
|
+
libsqlDb.pragma('journal_mode = WAL');
|
|
70
|
+
libsqlDb.pragma('busy_timeout = 5000');
|
|
71
|
+
libsqlDb.pragma(`user_version = ${SCHEMA_VERSION}`);
|
|
72
|
+
assert.equal(readUserVersion(libsqlDb), SCHEMA_VERSION);
|
|
73
|
+
libsqlDb.exec(`
|
|
74
|
+
CREATE TABLE vector_items (
|
|
75
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
|
+
name TEXT NOT NULL,
|
|
77
|
+
embedding F32_BLOB(3) NOT NULL
|
|
78
|
+
);
|
|
79
|
+
CREATE INDEX idx_vector_items_embedding
|
|
80
|
+
ON vector_items (libsql_vector_idx(embedding));
|
|
81
|
+
`);
|
|
82
|
+
const insert = libsqlDb.prepare(`INSERT INTO vector_items (name, embedding) VALUES (?, vector32(?))`);
|
|
83
|
+
const info = insert.run('alpha', '[1,0,0]');
|
|
84
|
+
assert.equal(info.lastInsertRowid, 1);
|
|
85
|
+
insert.run('beta', '[0,1,0]');
|
|
86
|
+
const insertGamma = libsqlDb.transaction(() => insert.run('gamma', '[0,0,1]'));
|
|
87
|
+
insertGamma();
|
|
88
|
+
const distance = libsqlDb
|
|
89
|
+
.prepare(`SELECT vector_distance_cos(embedding, vector32(?)) AS distance FROM vector_items WHERE name = ?`)
|
|
90
|
+
.get('[1,0,0]', 'alpha');
|
|
91
|
+
assert.equal(distance.distance, 0);
|
|
92
|
+
const rows = libsqlDb
|
|
93
|
+
.prepare(`SELECT vector_items.name
|
|
94
|
+
FROM vector_top_k('idx_vector_items_embedding', vector32(?), ?) AS vector_matches
|
|
95
|
+
JOIN vector_items ON vector_items.rowid = vector_matches.id
|
|
96
|
+
ORDER BY vector_distance_cos(vector_items.embedding, vector32(?))`)
|
|
97
|
+
.all('[1,0,0]', 2, '[1,0,0]');
|
|
98
|
+
assert.equal(rows[0].name, 'alpha');
|
|
99
|
+
assert.equal(rows.length, 2);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
libsqlDb.close();
|
|
103
|
+
fs.rmSync(libsqlDir, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
test('vecAvailable is true after schema creation', () => {
|
|
107
|
+
assert.equal(store.vecAvailable, true);
|
|
44
108
|
});
|
|
45
109
|
test('hashesBySource returns empty set for unknown source', () => {
|
|
46
110
|
const hashes = store.hashesBySource('nonexistent.md');
|
|
@@ -48,11 +112,14 @@ describe('MemoryStore', () => {
|
|
|
48
112
|
assert.equal(hashes.size, 0);
|
|
49
113
|
});
|
|
50
114
|
test('upsert stores chunks and hashesBySource returns their ids', () => {
|
|
51
|
-
const chunk = makeChunk({ id: 'upsert-test-0001', source: 'upsert.md' });
|
|
52
|
-
const embedding =
|
|
115
|
+
const chunk = makeChunk({ id: 'upsert-test-0001', source: 'upsert.md', sourceType: 'entity', fileMtimeAt: 2000 });
|
|
116
|
+
const embedding = makeEmbedding(0);
|
|
53
117
|
store.upsert([chunk], [embedding]);
|
|
54
118
|
const hashes = store.hashesBySource('upsert.md');
|
|
55
119
|
assert.ok(hashes.has('upsert-test-0001'));
|
|
120
|
+
const [stored] = store.getChunksByIds(['upsert-test-0001']);
|
|
121
|
+
assert.equal(stored.sourceType, 'entity');
|
|
122
|
+
assert.equal(stored.fileMtimeAt, 2000);
|
|
56
123
|
});
|
|
57
124
|
test('deleteBySource removes all chunks for that source', () => {
|
|
58
125
|
const c1 = makeChunk({
|
|
@@ -65,7 +132,7 @@ describe('MemoryStore', () => {
|
|
|
65
132
|
source: 'deleteme.md',
|
|
66
133
|
content: 'delete source chunk 2',
|
|
67
134
|
});
|
|
68
|
-
store.upsert([c1, c2], [
|
|
135
|
+
store.upsert([c1, c2], [makeEmbedding(1), makeEmbedding(2)]);
|
|
69
136
|
store.deleteBySource('deleteme.md');
|
|
70
137
|
const hashes = store.hashesBySource('deleteme.md');
|
|
71
138
|
assert.equal(hashes.size, 0);
|
|
@@ -76,7 +143,7 @@ describe('MemoryStore', () => {
|
|
|
76
143
|
source: 'bm25.md',
|
|
77
144
|
content: 'uniqueKeywordXYZ for BM25 testing',
|
|
78
145
|
});
|
|
79
|
-
store.upsert([chunk], [
|
|
146
|
+
store.upsert([chunk], [makeEmbedding(3)]);
|
|
80
147
|
const results = store.searchBm25('uniqueKeywordXYZ', 5);
|
|
81
148
|
assert.ok(results.length > 0, 'Expected at least one BM25 result');
|
|
82
149
|
assert.ok(results.some((r) => r.id === 'bm25-search-0001'), `Expected chunk id in results, got: ${results.map((r) => r.id).join(', ')}`);
|
|
@@ -95,7 +162,7 @@ describe('MemoryStore', () => {
|
|
|
95
162
|
heading: 'Open Questions',
|
|
96
163
|
content: 'How should git manage worktree lifecycle decisions?',
|
|
97
164
|
});
|
|
98
|
-
store.upsert([preference, unrelated], [
|
|
165
|
+
store.upsert([preference, unrelated], [makeEmbedding(4), makeEmbedding(5)]);
|
|
99
166
|
const results = store.searchBm25('personal likes and preferences of the user', 5);
|
|
100
167
|
assert.ok(results.length > 0, 'Expected preference query to return results');
|
|
101
168
|
assert.equal(results[0].id, 'preference-search-0001');
|
|
@@ -108,26 +175,30 @@ describe('MemoryStore', () => {
|
|
|
108
175
|
const chunk = makeChunk({
|
|
109
176
|
id: 'get-by-ids-0001',
|
|
110
177
|
source: 'metadata.md',
|
|
178
|
+
sourceType: 'concept',
|
|
111
179
|
heading: 'Metadata Section',
|
|
112
180
|
headingLevel: 2,
|
|
113
181
|
content: 'Content for metadata test',
|
|
114
182
|
lineStart: 10,
|
|
115
183
|
lineEnd: 20,
|
|
184
|
+
fileMtimeAt: 3000,
|
|
116
185
|
});
|
|
117
|
-
store.upsert([chunk], [
|
|
186
|
+
store.upsert([chunk], [makeEmbedding(6)]);
|
|
118
187
|
const results = store.getChunksByIds(['get-by-ids-0001']);
|
|
119
188
|
assert.equal(results.length, 1);
|
|
120
189
|
const [r] = results;
|
|
121
190
|
assert.equal(r.id, 'get-by-ids-0001');
|
|
122
191
|
assert.equal(r.source, 'metadata.md');
|
|
192
|
+
assert.equal(r.sourceType, 'concept');
|
|
123
193
|
assert.equal(r.heading, 'Metadata Section');
|
|
124
194
|
assert.equal(r.headingLevel, 2);
|
|
125
195
|
assert.equal(r.lineStart, 10);
|
|
126
196
|
assert.equal(r.lineEnd, 20);
|
|
197
|
+
assert.equal(r.fileMtimeAt, 3000);
|
|
127
198
|
});
|
|
128
199
|
test('getChunksByIds returns only found rows for mixed ids', () => {
|
|
129
200
|
const chunk = makeChunk({ id: 'partial-found-0001', source: 'partial.md', content: 'partial' });
|
|
130
|
-
store.upsert([chunk], [
|
|
201
|
+
store.upsert([chunk], [makeEmbedding(7)]);
|
|
131
202
|
const results = store.getChunksByIds(['partial-found-0001', 'does-not-exist-999']);
|
|
132
203
|
assert.equal(results.length, 1);
|
|
133
204
|
assert.equal(results[0].id, 'partial-found-0001');
|
|
@@ -138,14 +209,14 @@ describe('MemoryStore', () => {
|
|
|
138
209
|
source: 'indexed-source.md',
|
|
139
210
|
content: 'indexed',
|
|
140
211
|
});
|
|
141
|
-
store.upsert([chunk], [
|
|
212
|
+
store.upsert([chunk], [makeEmbedding(8)]);
|
|
142
213
|
const sources = store.indexedSources();
|
|
143
214
|
assert.ok(sources.includes('indexed-source.md'), `Expected 'indexed-source.md' in ${sources.join(', ')}`);
|
|
144
215
|
});
|
|
145
216
|
test('deleteByIds removes specific chunks', () => {
|
|
146
217
|
const c1 = makeChunk({ id: 'del-ids-0001', source: 'del-ids.md', content: 'delete by id 1' });
|
|
147
218
|
const c2 = makeChunk({ id: 'del-ids-0002', source: 'del-ids.md', content: 'delete by id 2' });
|
|
148
|
-
store.upsert([c1, c2], [
|
|
219
|
+
store.upsert([c1, c2], [makeEmbedding(9), makeEmbedding(10)]);
|
|
149
220
|
store.deleteByIds(['del-ids-0001']);
|
|
150
221
|
const hashes = store.hashesBySource('del-ids.md');
|
|
151
222
|
assert.ok(!hashes.has('del-ids-0001'), 'Deleted chunk id must be gone');
|
|
@@ -158,8 +229,162 @@ describe('MemoryStore', () => {
|
|
|
158
229
|
source: 'fts5.md',
|
|
159
230
|
content: 'degradedModeTest keyword',
|
|
160
231
|
});
|
|
161
|
-
store.upsert([chunk], [
|
|
232
|
+
store.upsert([chunk], [makeEmbedding(11)]);
|
|
162
233
|
const results = store.searchBm25('degradedModeTest', 5);
|
|
163
234
|
assert.ok(results.length > 0, 'FTS5 search must work regardless of vecAvailable');
|
|
164
235
|
});
|
|
236
|
+
test('getRecentSources returns distinct sources ordered by newest chunk mtime', () => {
|
|
237
|
+
const recentDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memory-recent-store-'));
|
|
238
|
+
const recentStore = new MemoryStore(path.join(recentDir, 'index.db'), { ...TEST_CONFIG, wikiDir: recentDir });
|
|
239
|
+
try {
|
|
240
|
+
recentStore.upsert([
|
|
241
|
+
makeChunk({
|
|
242
|
+
id: 'recent-digest-old',
|
|
243
|
+
source: 'compiled/provisional/conversation-digests/old.md',
|
|
244
|
+
sourceType: 'digest',
|
|
245
|
+
fileMtimeAt: 100,
|
|
246
|
+
content: 'old digest',
|
|
247
|
+
}),
|
|
248
|
+
makeChunk({
|
|
249
|
+
id: 'recent-digest-new',
|
|
250
|
+
source: 'compiled/provisional/conversation-digests/new.md',
|
|
251
|
+
sourceType: 'digest',
|
|
252
|
+
fileMtimeAt: 300,
|
|
253
|
+
content: 'new digest',
|
|
254
|
+
}),
|
|
255
|
+
makeChunk({
|
|
256
|
+
id: 'recent-digest-newer-same-source',
|
|
257
|
+
source: 'compiled/provisional/conversation-digests/old.md',
|
|
258
|
+
sourceType: 'digest',
|
|
259
|
+
fileMtimeAt: 400,
|
|
260
|
+
content: 'old digest update',
|
|
261
|
+
}),
|
|
262
|
+
makeChunk({
|
|
263
|
+
id: 'recent-entity',
|
|
264
|
+
source: 'compiled/entities/entity.md',
|
|
265
|
+
sourceType: 'entity',
|
|
266
|
+
fileMtimeAt: 500,
|
|
267
|
+
content: 'entity',
|
|
268
|
+
}),
|
|
269
|
+
], [makeEmbedding(12), makeEmbedding(13), makeEmbedding(14), makeEmbedding(15)]);
|
|
270
|
+
const allRows = recentStore.getRecentSources({ limit: 10 });
|
|
271
|
+
assert.deepEqual(allRows.map((row) => row.source), [
|
|
272
|
+
'compiled/entities/entity.md',
|
|
273
|
+
'compiled/provisional/conversation-digests/old.md',
|
|
274
|
+
'compiled/provisional/conversation-digests/new.md',
|
|
275
|
+
]);
|
|
276
|
+
const digestRows = recentStore.getRecentSources({ limit: 10, sourceType: 'digest' });
|
|
277
|
+
assert.deepEqual(digestRows.map((row) => row.source), ['compiled/provisional/conversation-digests/old.md', 'compiled/provisional/conversation-digests/new.md']);
|
|
278
|
+
assert.equal(digestRows[0].fileMtimeAt, 400);
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
recentStore.close();
|
|
282
|
+
fs.rmSync(recentDir, { recursive: true, force: true });
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
test('searchDense returns nearest vector with bounded scores', () => {
|
|
286
|
+
const denseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memory-dense-store-'));
|
|
287
|
+
const denseStore = new MemoryStore(path.join(denseDir, 'index.db'), { ...TEST_CONFIG, wikiDir: denseDir });
|
|
288
|
+
try {
|
|
289
|
+
denseStore.upsert([
|
|
290
|
+
makeChunk({ id: 'dense-alpha', source: 'dense.md', content: 'alpha vector' }),
|
|
291
|
+
makeChunk({ id: 'dense-beta', source: 'dense.md', content: 'beta vector' }),
|
|
292
|
+
makeChunk({ id: 'dense-gamma', source: 'dense.md', content: 'gamma vector' }),
|
|
293
|
+
], [makeEmbedding(21), makeEmbedding(22), makeEmbedding(23)]);
|
|
294
|
+
const results = denseStore.searchDense(makeEmbedding(22), 3);
|
|
295
|
+
assert.ok(results.length > 0, 'Expected dense search results');
|
|
296
|
+
assert.equal(results[0].id, 'dense-beta');
|
|
297
|
+
assert.ok(results.every((result) => result.score >= 0 && result.score <= 1));
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
denseStore.close();
|
|
301
|
+
fs.rmSync(denseDir, { recursive: true, force: true });
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
test('searchDense returns empty array for invalid query embedding', () => {
|
|
305
|
+
assert.deepEqual(store.searchDense(new Float32Array(3), 5), []);
|
|
306
|
+
});
|
|
307
|
+
test('upsert skips invalid embeddings while persisting valid chunks', () => {
|
|
308
|
+
const validChunk = makeChunk({ id: 'valid-embedding-0001', source: 'embedding-validation.md' });
|
|
309
|
+
const invalidChunk = makeChunk({ id: 'invalid-embedding-0001', source: 'embedding-validation.md' });
|
|
310
|
+
const nonFiniteChunk = makeChunk({ id: 'non-finite-embedding-0001', source: 'embedding-validation.md' });
|
|
311
|
+
const nonFiniteEmbedding = makeEmbedding(24);
|
|
312
|
+
nonFiniteEmbedding[25] = Number.NaN;
|
|
313
|
+
store.upsert([validChunk, invalidChunk, nonFiniteChunk], [makeEmbedding(24), new Float32Array(3), nonFiniteEmbedding]);
|
|
314
|
+
const hashes = store.hashesBySource('embedding-validation.md');
|
|
315
|
+
assert.ok(hashes.has('valid-embedding-0001'));
|
|
316
|
+
assert.ok(!hashes.has('invalid-embedding-0001'));
|
|
317
|
+
assert.ok(!hashes.has('non-finite-embedding-0001'));
|
|
318
|
+
});
|
|
319
|
+
test('schema migration recreates versioned schema with new columns, vector index, and working BM25', () => {
|
|
320
|
+
const migrationDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memory-migration-'));
|
|
321
|
+
const migrationDbPath = path.join(migrationDir, 'index.db');
|
|
322
|
+
const db = new Database(migrationDbPath);
|
|
323
|
+
db.exec(`
|
|
324
|
+
PRAGMA user_version = 1;
|
|
325
|
+
CREATE TABLE memory_chunks (
|
|
326
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
327
|
+
id TEXT NOT NULL UNIQUE,
|
|
328
|
+
source TEXT NOT NULL,
|
|
329
|
+
heading TEXT NOT NULL DEFAULT '',
|
|
330
|
+
heading_level INTEGER NOT NULL DEFAULT 0,
|
|
331
|
+
content TEXT NOT NULL,
|
|
332
|
+
line_start INTEGER NOT NULL,
|
|
333
|
+
line_end INTEGER NOT NULL,
|
|
334
|
+
indexed_at INTEGER NOT NULL
|
|
335
|
+
);
|
|
336
|
+
CREATE VIRTUAL TABLE memory_fts USING fts5(
|
|
337
|
+
source,
|
|
338
|
+
heading,
|
|
339
|
+
content,
|
|
340
|
+
content='memory_chunks',
|
|
341
|
+
content_rowid='rowid'
|
|
342
|
+
);
|
|
343
|
+
CREATE TRIGGER memory_chunks_ai
|
|
344
|
+
AFTER INSERT ON memory_chunks BEGIN
|
|
345
|
+
INSERT INTO memory_fts(rowid, source, heading, content)
|
|
346
|
+
VALUES (new.rowid, new.source, new.heading, new.content);
|
|
347
|
+
END;
|
|
348
|
+
CREATE TRIGGER memory_chunks_ad
|
|
349
|
+
AFTER DELETE ON memory_chunks BEGIN
|
|
350
|
+
INSERT INTO memory_fts(memory_fts, rowid, source, heading, content)
|
|
351
|
+
VALUES ('delete', old.rowid, old.source, old.heading, old.content);
|
|
352
|
+
END;
|
|
353
|
+
`);
|
|
354
|
+
db.prepare(`INSERT INTO memory_chunks
|
|
355
|
+
(id, source, heading, heading_level, content, line_start, line_end, indexed_at)
|
|
356
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run('old-row', 'old.md', '', 0, 'old content', 1, 1, 1);
|
|
357
|
+
db.close();
|
|
358
|
+
const migratedStore = new MemoryStore(migrationDbPath, { ...TEST_CONFIG, wikiDir: migrationDir });
|
|
359
|
+
try {
|
|
360
|
+
const verifyDb = new Database(migrationDbPath, { readonly: true });
|
|
361
|
+
try {
|
|
362
|
+
assert.equal(readUserVersion(verifyDb), SCHEMA_VERSION);
|
|
363
|
+
const columns = verifyDb.prepare(`PRAGMA table_info(memory_chunks)`).all();
|
|
364
|
+
assert.ok(columns.some((column) => column.name === 'source_type'));
|
|
365
|
+
assert.ok(columns.some((column) => column.name === 'file_mtime_at'));
|
|
366
|
+
assert.ok(columns.some((column) => column.name === 'embedding'));
|
|
367
|
+
const indexes = verifyDb.prepare(`PRAGMA index_list(memory_chunks)`).all();
|
|
368
|
+
assert.ok(indexes.some((index) => index.name === 'idx_memory_chunks_type_mtime'));
|
|
369
|
+
assert.ok(indexes.some((index) => index.name === 'idx_memory_chunks_embedding'));
|
|
370
|
+
const rowCount = verifyDb.prepare(`SELECT COUNT(*) AS count FROM memory_chunks`).get();
|
|
371
|
+
assert.equal(rowCount.count, 0);
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
verifyDb.close();
|
|
375
|
+
}
|
|
376
|
+
const migratedChunk = makeChunk({
|
|
377
|
+
id: 'migrated-bm25-0001',
|
|
378
|
+
source: 'migrated.md',
|
|
379
|
+
content: 'postMigrationKeyword is searchable after migration',
|
|
380
|
+
});
|
|
381
|
+
migratedStore.upsert([migratedChunk], [makeEmbedding(16)]);
|
|
382
|
+
const results = migratedStore.searchBm25('postMigrationKeyword', 5);
|
|
383
|
+
assert.ok(results.some((result) => result.id === 'migrated-bm25-0001'));
|
|
384
|
+
}
|
|
385
|
+
finally {
|
|
386
|
+
migratedStore.close();
|
|
387
|
+
fs.rmSync(migrationDir, { recursive: true, force: true });
|
|
388
|
+
}
|
|
389
|
+
});
|
|
165
390
|
});
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import type { ConversationDigestSettings } from '../digest/types.js';
|
|
2
2
|
export declare enum EmbeddingModelName {
|
|
3
3
|
TINY = "tiny",
|
|
4
|
-
|
|
4
|
+
BASE = "base",
|
|
5
5
|
LARGE = "large"
|
|
6
6
|
}
|
|
7
|
+
export declare const SOURCE_TYPES: readonly ["digest", "concept", "entity", "preference", "wiki"];
|
|
8
|
+
export type SourceType = (typeof SOURCE_TYPES)[number];
|
|
7
9
|
export interface MemoryChunk {
|
|
8
10
|
id: string;
|
|
9
11
|
source: string;
|
|
12
|
+
sourceType: SourceType;
|
|
10
13
|
heading: string;
|
|
11
14
|
headingLevel: number;
|
|
12
15
|
content: string;
|
|
13
16
|
lineStart: number;
|
|
14
17
|
lineEnd: number;
|
|
18
|
+
fileMtimeAt: number;
|
|
15
19
|
}
|
|
16
20
|
export interface SearchResult {
|
|
17
21
|
chunk: MemoryChunk;
|
|
@@ -34,3 +38,7 @@ export interface IndexStats {
|
|
|
34
38
|
deleted: number;
|
|
35
39
|
skipped: number;
|
|
36
40
|
}
|
|
41
|
+
export interface RecentSource {
|
|
42
|
+
source: string;
|
|
43
|
+
fileMtimeAt: number;
|
|
44
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export var EmbeddingModelName;
|
|
2
2
|
(function (EmbeddingModelName) {
|
|
3
3
|
EmbeddingModelName["TINY"] = "tiny";
|
|
4
|
-
EmbeddingModelName["
|
|
4
|
+
EmbeddingModelName["BASE"] = "base";
|
|
5
5
|
EmbeddingModelName["LARGE"] = "large";
|
|
6
6
|
})(EmbeddingModelName || (EmbeddingModelName = {}));
|
|
7
|
+
export const SOURCE_TYPES = ['digest', 'concept', 'entity', 'preference', 'wiki'];
|
package/dist/utils/paths.d.ts
CHANGED
package/dist/utils/paths.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as os from 'os';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
const AGENT_KIT_HOME = path.join(os.homedir(), '.agent-kit');
|
|
3
|
+
export const AGENT_KIT_HOME = path.join(os.homedir(), '.agent-kit');
|
|
4
4
|
export const CREDENTIALS_FILE = path.join(AGENT_KIT_HOME, 'credentials');
|
|
5
|
+
export const GLOBAL_SETTINGS_FILE = path.join(AGENT_KIT_HOME, 'settings.json');
|
|
5
6
|
export const MODEL_CACHE_DIR = path.join(AGENT_KIT_HOME, 'cache', 'models');
|
|
6
7
|
export const FASTEMBED_CACHE_DIR = path.join(MODEL_CACHE_DIR, 'fastembed');
|
package/dist/utils/utils.d.ts
CHANGED
package/dist/utils/utils.js
CHANGED
|
@@ -3,9 +3,6 @@ export function getWorkspaceRoot() {
|
|
|
3
3
|
process.env.CLAUDE_PROJECT_DIR ||
|
|
4
4
|
process.env.CODEX_PROJECT_DIR ||
|
|
5
5
|
process.env.GEMINI_PROJECT_DIR ||
|
|
6
|
-
process.env.CLAUDE_WORKSPACE ||
|
|
7
|
-
process.env.CODEX_WORKSPACE ||
|
|
8
|
-
process.env.GEMINI_WORKSPACE ||
|
|
9
6
|
process.env.INIT_CWD ||
|
|
10
7
|
process.env.PWD ||
|
|
11
8
|
process.cwd());
|
|
@@ -16,3 +13,6 @@ export function mcpText(text) {
|
|
|
16
13
|
export function mcpJson(obj) {
|
|
17
14
|
return mcpText(JSON.stringify(obj, null, 2));
|
|
18
15
|
}
|
|
16
|
+
export function formatError(err) {
|
|
17
|
+
return err instanceof Error ? err.message : String(err);
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanhnd/agent-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.36",
|
|
4
4
|
"description": "Agent Kit MCP server for software development agent workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "rm -rf dist && tsc && npm run bundle",
|
|
25
|
-
"bundle": "esbuild src/entrypoints/server.ts src/entrypoints/cli.ts --bundle --platform=node --format=esm --outdir=dist --external:node:* --external:
|
|
25
|
+
"bundle": "esbuild src/entrypoints/server.ts src/entrypoints/cli.ts --bundle --platform=node --format=esm --outdir=dist --external:node:* --external:libsql --external:fastembed --external:node-llama-cpp",
|
|
26
26
|
"dev": "tsc --watch",
|
|
27
27
|
"prepublishOnly": "npm run build",
|
|
28
28
|
"test": "tsc && node --test $(find dist -name '*.test.js' | tr '\\n' ' ')"
|
|
@@ -30,15 +30,13 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"node-llama-cpp": "^3.0.0",
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.13.1",
|
|
33
|
-
"better-sqlite3": "^11.0.0",
|
|
34
33
|
"dedent": "^1.5.3",
|
|
35
34
|
"fastembed": "^1.14.0",
|
|
36
|
-
"
|
|
35
|
+
"libsql": "^0.5.29",
|
|
37
36
|
"stopword": "^3.1.5",
|
|
38
37
|
"zod": "^3.24.4"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
41
|
-
"@types/better-sqlite3": "^7.6.0",
|
|
42
40
|
"@types/node": "^22.15.21",
|
|
43
41
|
"esbuild": "^0.25.4",
|
|
44
42
|
"typescript": "^5.8.3"
|