@hanhnd/agent-kit 1.0.34 → 1.0.35
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 +307 -129
- 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 +253 -150
- 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 +66 -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 +6 -1
- package/dist/services/memory/store.js +87 -47
- package/dist/services/memory/store.test.js +123 -3
- 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 +2 -2
|
@@ -50,7 +50,7 @@ export class Embedder {
|
|
|
50
50
|
const { EmbeddingModel, FlagEmbedding } = await import('fastembed');
|
|
51
51
|
const modelMap = {
|
|
52
52
|
[EmbeddingModelName.TINY]: EmbeddingModel.AllMiniLML6V2,
|
|
53
|
-
[EmbeddingModelName.
|
|
53
|
+
[EmbeddingModelName.BASE]: EmbeddingModel.BGESmallENV15,
|
|
54
54
|
[EmbeddingModelName.LARGE]: EmbeddingModel.BGEBaseENV15,
|
|
55
55
|
};
|
|
56
56
|
const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { MemoryStore, StoreError } from './store.js';
|
|
2
2
|
export { Embedder, EmbedderError } from './embedder.js';
|
|
3
3
|
export { MemoryIndexer } from './indexer.js';
|
|
4
|
-
export type { MemoryChunk, SearchResult, MemoryConfig, IndexStats } from './types.js';
|
|
4
|
+
export type { MemoryChunk, SearchResult, MemoryConfig, IndexStats, SourceType, RecentSource } from './types.js';
|
|
5
5
|
export { DEFAULT_MEMORY_CONFIG } from './constants.js';
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { MemoryStore } from './store.js';
|
|
2
|
-
import type { IndexStats, MemoryConfig, SearchResult } from './types.js';
|
|
2
|
+
import type { IndexStats, MemoryConfig, SearchResult, SourceType } from './types.js';
|
|
3
3
|
interface TextEmbedder {
|
|
4
4
|
embed(texts: string[]): Promise<Float32Array[]>;
|
|
5
5
|
initialize(): Promise<void>;
|
|
6
6
|
}
|
|
7
|
+
export declare function deriveSourceType(source: string): SourceType;
|
|
7
8
|
export declare class MemoryIndexer {
|
|
8
9
|
private readonly store;
|
|
9
10
|
private readonly embedder;
|
|
@@ -3,6 +3,17 @@ import * as path from 'path';
|
|
|
3
3
|
import { chunkMarkdown } from './chunker.js';
|
|
4
4
|
import { LOCK_RETRY_MS, LOCK_TIMEOUT_MS, RRF_K } from './constants.js';
|
|
5
5
|
import { acquireLock, releaseLock } from '../../utils/files.js';
|
|
6
|
+
export function deriveSourceType(source) {
|
|
7
|
+
if (source.startsWith('compiled/provisional/conversation-digests/'))
|
|
8
|
+
return 'digest';
|
|
9
|
+
if (source.startsWith('compiled/concepts/'))
|
|
10
|
+
return 'concept';
|
|
11
|
+
if (source.startsWith('compiled/entities/'))
|
|
12
|
+
return 'entity';
|
|
13
|
+
if (source === 'compiled/preferences.md')
|
|
14
|
+
return 'preference';
|
|
15
|
+
return 'wiki';
|
|
16
|
+
}
|
|
6
17
|
export class MemoryIndexer {
|
|
7
18
|
store;
|
|
8
19
|
embedder;
|
|
@@ -27,7 +38,16 @@ export class MemoryIndexer {
|
|
|
27
38
|
console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
|
|
28
39
|
return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
|
|
29
40
|
}
|
|
30
|
-
|
|
41
|
+
let fileMtimeAt;
|
|
42
|
+
try {
|
|
43
|
+
fileMtimeAt = fs.statSync(absolutePath).mtimeMs;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
|
|
47
|
+
fileMtimeAt = Date.now();
|
|
48
|
+
}
|
|
49
|
+
const sourceType = deriveSourceType(source);
|
|
50
|
+
const allChunks = chunkMarkdown(text, source, this.config, { sourceType, fileMtimeAt });
|
|
31
51
|
const currentChunkIds = new Set(allChunks.map((chunk) => chunk.id));
|
|
32
52
|
const toIndex = allChunks.filter((chunk) => !existingChunkIds.has(chunk.id));
|
|
33
53
|
const toDelete = [...existingChunkIds].filter((id) => !currentChunkIds.has(id));
|
|
@@ -119,13 +139,9 @@ export class MemoryIndexer {
|
|
|
119
139
|
scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0 });
|
|
120
140
|
});
|
|
121
141
|
bm25Results.forEach((r, rank) => {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
scoreMap.set(r.id, { dense: 0, bm25: 1 / (RRF_K + rank + 1) });
|
|
128
|
-
}
|
|
142
|
+
const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0 };
|
|
143
|
+
entry.bm25 = 1 / (RRF_K + rank + 1);
|
|
144
|
+
scoreMap.set(r.id, entry);
|
|
129
145
|
});
|
|
130
146
|
const hasDense = this.store.vecAvailable && denseResults.length > 0;
|
|
131
147
|
const numRetrievers = hasDense ? 2 : 1;
|
|
@@ -5,7 +5,7 @@ import { syncBuiltinESMExports } from 'node:module';
|
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import { after, before, describe, test } from 'node:test';
|
|
8
|
-
import { MemoryIndexer } from './indexer.js';
|
|
8
|
+
import { deriveSourceType, MemoryIndexer } from './indexer.js';
|
|
9
9
|
import { MemoryStore } from './store.js';
|
|
10
10
|
import { EmbeddingModelName } from './types.js';
|
|
11
11
|
class StubEmbedder {
|
|
@@ -23,7 +23,7 @@ function makeConfig(wikiDir) {
|
|
|
23
23
|
topK: 5,
|
|
24
24
|
chunkSize: 1500,
|
|
25
25
|
overlapLines: 2,
|
|
26
|
-
embeddingModel: EmbeddingModelName.
|
|
26
|
+
embeddingModel: EmbeddingModelName.BASE,
|
|
27
27
|
vectorDimension: 384,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -141,11 +141,13 @@ describe('MemoryIndexer', () => {
|
|
|
141
141
|
{
|
|
142
142
|
id: 'stale-daily-file-0001',
|
|
143
143
|
source: '2026-05-18.md',
|
|
144
|
+
sourceType: 'wiki',
|
|
144
145
|
heading: 'Stale',
|
|
145
146
|
headingLevel: 1,
|
|
146
147
|
content: 'pre migration content',
|
|
147
148
|
lineStart: 1,
|
|
148
149
|
lineEnd: 2,
|
|
150
|
+
fileMtimeAt: 1,
|
|
149
151
|
},
|
|
150
152
|
], [new Float32Array(384)]);
|
|
151
153
|
assert.ok(testStore.hashesBySource('2026-05-18.md').size > 0);
|
|
@@ -385,4 +387,38 @@ describe('MemoryIndexer', () => {
|
|
|
385
387
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
386
388
|
}
|
|
387
389
|
});
|
|
390
|
+
test('deriveSourceType follows documented path conventions', () => {
|
|
391
|
+
assert.equal(deriveSourceType('compiled/provisional/conversation-digests/abc.md'), 'digest');
|
|
392
|
+
assert.equal(deriveSourceType('compiled/concepts/x.md'), 'concept');
|
|
393
|
+
assert.equal(deriveSourceType('compiled/entities/foo.md'), 'entity');
|
|
394
|
+
assert.equal(deriveSourceType('compiled/preferences.md'), 'preference');
|
|
395
|
+
assert.equal(deriveSourceType('compiled/preferences/something.md'), 'wiki');
|
|
396
|
+
assert.equal(deriveSourceType('raw/conv_x.md'), 'wiki');
|
|
397
|
+
assert.equal(deriveSourceType(''), 'wiki');
|
|
398
|
+
});
|
|
399
|
+
test('indexFile stamps source type and file mtime onto stored chunks', async () => {
|
|
400
|
+
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'index-meta-'));
|
|
401
|
+
const testCfg = makeConfig(path.join(testDir, 'wiki'));
|
|
402
|
+
const testStore = new MemoryStore(path.join(testCfg.wikiDir, 'index.db'), testCfg);
|
|
403
|
+
const testIndexer = new MemoryIndexer(testStore, new StubEmbedder(), testCfg);
|
|
404
|
+
const filePath = path.join(testCfg.wikiDir, 'compiled', 'provisional', 'conversation-digests', 'digest.md');
|
|
405
|
+
try {
|
|
406
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
407
|
+
fs.writeFileSync(filePath, '# Digest\nmtime metadata unique content', 'utf8');
|
|
408
|
+
const expectedMtime = fs.statSync(filePath).mtimeMs;
|
|
409
|
+
const stats = await testIndexer.indexFile(filePath);
|
|
410
|
+
assert.ok(stats.indexed > 0, `Expected indexed > 0, got ${stats.indexed}`);
|
|
411
|
+
const ids = [...testStore.hashesBySource('compiled/provisional/conversation-digests/digest.md')];
|
|
412
|
+
assert.ok(ids.length > 0);
|
|
413
|
+
const chunks = testStore.getChunksByIds(ids);
|
|
414
|
+
for (const chunk of chunks) {
|
|
415
|
+
assert.equal(chunk.sourceType, 'digest');
|
|
416
|
+
assert.equal(chunk.fileMtimeAt, expectedMtime);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
finally {
|
|
420
|
+
testStore.close();
|
|
421
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
422
|
+
}
|
|
423
|
+
});
|
|
388
424
|
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import * as assert from 'node:assert/strict';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
2
5
|
import { describe, test } from 'node:test';
|
|
3
6
|
import { EmbeddingModelName } from './types.js';
|
|
4
7
|
import { resolveMemoryConfig } from '../../core/config/index.js';
|
|
@@ -6,22 +9,26 @@ import { registerMemoryTools } from '../../mcp/memory.js';
|
|
|
6
9
|
// Minimal McpServer stub that captures tool registrations
|
|
7
10
|
function makeMockServer() {
|
|
8
11
|
const tools = new Map();
|
|
12
|
+
const descriptions = new Map();
|
|
9
13
|
const server = {
|
|
10
|
-
tool(name,
|
|
14
|
+
tool(name, desc, _schema, handler) {
|
|
15
|
+
descriptions.set(name, desc);
|
|
11
16
|
tools.set(name, handler);
|
|
12
17
|
},
|
|
13
18
|
};
|
|
14
|
-
return { server, tools };
|
|
19
|
+
return { server, tools, descriptions };
|
|
15
20
|
}
|
|
16
21
|
function makeChunk(overrides = {}) {
|
|
17
22
|
return {
|
|
18
23
|
id: 'test-id-abc123',
|
|
19
24
|
source: '2024-01-01.md',
|
|
25
|
+
sourceType: 'wiki',
|
|
20
26
|
heading: 'My Section',
|
|
21
27
|
headingLevel: 2,
|
|
22
28
|
content: 'This is the chunk content.',
|
|
23
29
|
lineStart: 1,
|
|
24
30
|
lineEnd: 5,
|
|
31
|
+
fileMtimeAt: 1000,
|
|
25
32
|
...overrides,
|
|
26
33
|
};
|
|
27
34
|
}
|
|
@@ -31,7 +38,7 @@ const BASE_CONFIG = {
|
|
|
31
38
|
topK: 5,
|
|
32
39
|
chunkSize: 1500,
|
|
33
40
|
overlapLines: 2,
|
|
34
|
-
embeddingModel: EmbeddingModelName.
|
|
41
|
+
embeddingModel: EmbeddingModelName.BASE,
|
|
35
42
|
vectorDimension: 384,
|
|
36
43
|
};
|
|
37
44
|
function makeIndexerStub(overrides = {}) {
|
|
@@ -40,8 +47,11 @@ function makeIndexerStub(overrides = {}) {
|
|
|
40
47
|
save: overrides.save ?? (async () => ({ indexed: 1, deleted: 0, skipped: 0 })),
|
|
41
48
|
};
|
|
42
49
|
}
|
|
43
|
-
function makeStoreStub(vecAvailable) {
|
|
44
|
-
return {
|
|
50
|
+
function makeStoreStub(vecAvailable, overrides = {}) {
|
|
51
|
+
return {
|
|
52
|
+
vecAvailable,
|
|
53
|
+
getRecentSources: overrides.getRecentSources ?? (() => []),
|
|
54
|
+
};
|
|
45
55
|
}
|
|
46
56
|
function extractText(result) {
|
|
47
57
|
const r = result;
|
|
@@ -62,7 +72,8 @@ describe('kit_memory_search', () => {
|
|
|
62
72
|
const indexer = makeIndexerStub({ search: async () => [searchResult] });
|
|
63
73
|
const store = makeStoreStub(true);
|
|
64
74
|
const { server, tools } = makeMockServer();
|
|
65
|
-
registerMemoryTools(server,
|
|
75
|
+
registerMemoryTools(server, '/tmp', {
|
|
76
|
+
settings: { memory: { enabled: true } },
|
|
66
77
|
indexer,
|
|
67
78
|
store,
|
|
68
79
|
config: BASE_CONFIG,
|
|
@@ -78,7 +89,8 @@ describe('kit_memory_search', () => {
|
|
|
78
89
|
const indexer = makeIndexerStub({ search: async () => [] });
|
|
79
90
|
const store = makeStoreStub(true);
|
|
80
91
|
const { server, tools } = makeMockServer();
|
|
81
|
-
registerMemoryTools(server,
|
|
92
|
+
registerMemoryTools(server, '/tmp', {
|
|
93
|
+
settings: { memory: { enabled: true } },
|
|
82
94
|
indexer,
|
|
83
95
|
store,
|
|
84
96
|
config: BASE_CONFIG,
|
|
@@ -91,7 +103,8 @@ describe('kit_memory_search', () => {
|
|
|
91
103
|
const indexer = makeIndexerStub({ search: async () => [] });
|
|
92
104
|
const store = makeStoreStub(false);
|
|
93
105
|
const { server, tools } = makeMockServer();
|
|
94
|
-
registerMemoryTools(server,
|
|
106
|
+
registerMemoryTools(server, '/tmp', {
|
|
107
|
+
settings: { memory: { enabled: true } },
|
|
95
108
|
indexer,
|
|
96
109
|
store,
|
|
97
110
|
config: BASE_CONFIG,
|
|
@@ -111,7 +124,8 @@ describe('kit_memory_search', () => {
|
|
|
111
124
|
const indexer = makeIndexerStub({ search: async () => [searchResult] });
|
|
112
125
|
const store = makeStoreStub(false);
|
|
113
126
|
const { server, tools } = makeMockServer();
|
|
114
|
-
registerMemoryTools(server,
|
|
127
|
+
registerMemoryTools(server, '/tmp', {
|
|
128
|
+
settings: { memory: { enabled: true } },
|
|
115
129
|
indexer,
|
|
116
130
|
store,
|
|
117
131
|
config: BASE_CONFIG,
|
|
@@ -132,7 +146,8 @@ describe('kit_memory_search', () => {
|
|
|
132
146
|
const indexer = makeIndexerStub({ search: async () => [searchResult] });
|
|
133
147
|
const store = makeStoreStub(true);
|
|
134
148
|
const { server, tools } = makeMockServer();
|
|
135
|
-
registerMemoryTools(server,
|
|
149
|
+
registerMemoryTools(server, '/tmp', {
|
|
150
|
+
settings: { memory: { enabled: true } },
|
|
136
151
|
indexer,
|
|
137
152
|
store,
|
|
138
153
|
config: BASE_CONFIG,
|
|
@@ -152,7 +167,8 @@ describe('kit_memory_search', () => {
|
|
|
152
167
|
});
|
|
153
168
|
const store = makeStoreStub(true);
|
|
154
169
|
const { server, tools } = makeMockServer();
|
|
155
|
-
registerMemoryTools(server,
|
|
170
|
+
registerMemoryTools(server, '/tmp', {
|
|
171
|
+
settings: { memory: { enabled: true } },
|
|
156
172
|
indexer,
|
|
157
173
|
store,
|
|
158
174
|
config: BASE_CONFIG,
|
|
@@ -170,7 +186,8 @@ describe('kit_memory_search', () => {
|
|
|
170
186
|
});
|
|
171
187
|
const store = makeStoreStub(true);
|
|
172
188
|
const { server, tools } = makeMockServer();
|
|
173
|
-
registerMemoryTools(server,
|
|
189
|
+
registerMemoryTools(server, '/tmp', {
|
|
190
|
+
settings: { memory: { enabled: true } },
|
|
174
191
|
indexer,
|
|
175
192
|
store,
|
|
176
193
|
config: BASE_CONFIG,
|
|
@@ -186,7 +203,8 @@ describe('kit_memory_search', () => {
|
|
|
186
203
|
});
|
|
187
204
|
const store = makeStoreStub(true);
|
|
188
205
|
const { server, tools } = makeMockServer();
|
|
189
|
-
registerMemoryTools(server,
|
|
206
|
+
registerMemoryTools(server, '/tmp', {
|
|
207
|
+
settings: { memory: { enabled: true } },
|
|
190
208
|
indexer,
|
|
191
209
|
store,
|
|
192
210
|
config: BASE_CONFIG,
|
|
@@ -196,6 +214,143 @@ describe('kit_memory_search', () => {
|
|
|
196
214
|
assert.ok(text.includes('kit_memory_search failed'), `Expected error prefix, got: ${text}`);
|
|
197
215
|
assert.ok(text.includes('search exploded'), `Expected error message, got: ${text}`);
|
|
198
216
|
});
|
|
217
|
+
test('description routes factual and temporal queries distinctly', () => {
|
|
218
|
+
const indexer = makeIndexerStub();
|
|
219
|
+
const store = makeStoreStub(true);
|
|
220
|
+
const { server, descriptions } = makeMockServer();
|
|
221
|
+
registerMemoryTools(server, '/tmp', {
|
|
222
|
+
settings: { memory: { enabled: true } },
|
|
223
|
+
indexer,
|
|
224
|
+
store,
|
|
225
|
+
config: BASE_CONFIG,
|
|
226
|
+
});
|
|
227
|
+
const description = descriptions.get('kit_memory_search') ?? '';
|
|
228
|
+
assert.match(description, /factual|semantic/);
|
|
229
|
+
assert.match(description, /kit_memory_recent/);
|
|
230
|
+
assert.match(description, /keywords/);
|
|
231
|
+
assert.match(description, /stopwords/);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe('kit_memory_recent', () => {
|
|
235
|
+
test('returns recent sources ordered by store recency result', async () => {
|
|
236
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memory-recent-digest-'));
|
|
237
|
+
const wikiDir = path.join(tmpDir, 'wiki');
|
|
238
|
+
const olderSource = 'compiled/provisional/conversation-digests/older.md';
|
|
239
|
+
const newerSource = 'compiled/provisional/conversation-digests/newer.md';
|
|
240
|
+
const entitySource = 'compiled/entities/entity.md';
|
|
241
|
+
try {
|
|
242
|
+
fs.mkdirSync(path.join(wikiDir, 'compiled', 'provisional', 'conversation-digests'), { recursive: true });
|
|
243
|
+
fs.mkdirSync(path.join(wikiDir, 'compiled', 'entities'), { recursive: true });
|
|
244
|
+
fs.writeFileSync(path.join(wikiDir, olderSource), '# Older\nolder content', 'utf8');
|
|
245
|
+
fs.writeFileSync(path.join(wikiDir, newerSource), '# Newer\nnewer content', 'utf8');
|
|
246
|
+
fs.writeFileSync(path.join(wikiDir, entitySource), '# Entity\nentity content', 'utf8');
|
|
247
|
+
const calls = [];
|
|
248
|
+
const store = makeStoreStub(true, {
|
|
249
|
+
getRecentSources: (options) => {
|
|
250
|
+
calls.push(options);
|
|
251
|
+
return [
|
|
252
|
+
{ source: entitySource, fileMtimeAt: 300 },
|
|
253
|
+
{ source: newerSource, fileMtimeAt: 200 },
|
|
254
|
+
{ source: olderSource, fileMtimeAt: 100 },
|
|
255
|
+
];
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
const { server, tools } = makeMockServer();
|
|
259
|
+
registerMemoryTools(server, tmpDir, {
|
|
260
|
+
settings: { memory: { enabled: true } },
|
|
261
|
+
indexer: makeIndexerStub(),
|
|
262
|
+
store,
|
|
263
|
+
config: { ...BASE_CONFIG, wikiDir },
|
|
264
|
+
});
|
|
265
|
+
const result = await tools.get('kit_memory_recent')({ n: 2 });
|
|
266
|
+
const text = extractText(result);
|
|
267
|
+
assert.deepEqual(calls, [{ limit: 2, sourceType: undefined }]);
|
|
268
|
+
assert.ok(text.indexOf('entity.md') < text.indexOf('newer.md'));
|
|
269
|
+
assert.ok(text.includes('# Entity\nentity content'));
|
|
270
|
+
assert.ok(text.includes('# Newer\nnewer content'));
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
test('passes optional source type filter to recent lookup', async () => {
|
|
277
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memory-recent-filter-'));
|
|
278
|
+
const wikiDir = path.join(tmpDir, 'wiki');
|
|
279
|
+
const digestSource = 'compiled/provisional/conversation-digests/digest.md';
|
|
280
|
+
try {
|
|
281
|
+
fs.mkdirSync(path.join(wikiDir, 'compiled', 'provisional', 'conversation-digests'), { recursive: true });
|
|
282
|
+
fs.writeFileSync(path.join(wikiDir, digestSource), '# Digest\nfiltered content', 'utf8');
|
|
283
|
+
const calls = [];
|
|
284
|
+
const store = makeStoreStub(true, {
|
|
285
|
+
getRecentSources: (options) => {
|
|
286
|
+
calls.push(options);
|
|
287
|
+
return [{ source: digestSource, fileMtimeAt: 100 }];
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
const { server, tools } = makeMockServer();
|
|
291
|
+
registerMemoryTools(server, tmpDir, {
|
|
292
|
+
settings: { memory: { enabled: true } },
|
|
293
|
+
indexer: makeIndexerStub(),
|
|
294
|
+
store,
|
|
295
|
+
config: { ...BASE_CONFIG, wikiDir },
|
|
296
|
+
});
|
|
297
|
+
const result = await tools.get('kit_memory_recent')({ source_type: 'digest' });
|
|
298
|
+
const text = extractText(result);
|
|
299
|
+
assert.deepEqual(calls, [{ limit: 5, sourceType: 'digest' }]);
|
|
300
|
+
assert.ok(text.includes('provisional/conversation-digests/digest.md'));
|
|
301
|
+
assert.ok(text.includes('filtered content'));
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
test('skips missing files without source-unavailable warning', async () => {
|
|
308
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memory-recent-missing-'));
|
|
309
|
+
const wikiDir = path.join(tmpDir, 'wiki');
|
|
310
|
+
const presentSource = 'compiled/entities/present.md';
|
|
311
|
+
try {
|
|
312
|
+
fs.mkdirSync(path.join(wikiDir, 'compiled', 'entities'), { recursive: true });
|
|
313
|
+
fs.writeFileSync(path.join(wikiDir, presentSource), '# Present\navailable content', 'utf8');
|
|
314
|
+
const store = makeStoreStub(true, {
|
|
315
|
+
getRecentSources: () => [
|
|
316
|
+
{ source: 'compiled/entities/missing.md', fileMtimeAt: 200 },
|
|
317
|
+
{ source: presentSource, fileMtimeAt: 100 },
|
|
318
|
+
],
|
|
319
|
+
});
|
|
320
|
+
const { server, tools } = makeMockServer();
|
|
321
|
+
registerMemoryTools(server, tmpDir, {
|
|
322
|
+
settings: { memory: { enabled: true } },
|
|
323
|
+
indexer: makeIndexerStub(),
|
|
324
|
+
store,
|
|
325
|
+
config: { ...BASE_CONFIG, wikiDir },
|
|
326
|
+
});
|
|
327
|
+
const result = await tools.get('kit_memory_recent')({});
|
|
328
|
+
const text = extractText(result);
|
|
329
|
+
assert.ok(!text.includes('missing.md'));
|
|
330
|
+
assert.ok(!text.includes('Source file unavailable'));
|
|
331
|
+
assert.ok(text.includes('available content'));
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
test('is registered only when memory is enabled and description mentions temporal routing', () => {
|
|
338
|
+
const enabledServer = makeMockServer();
|
|
339
|
+
registerMemoryTools(enabledServer.server, '/tmp', {
|
|
340
|
+
settings: { memory: { enabled: true } },
|
|
341
|
+
indexer: makeIndexerStub(),
|
|
342
|
+
store: makeStoreStub(true),
|
|
343
|
+
config: BASE_CONFIG,
|
|
344
|
+
});
|
|
345
|
+
assert.ok(enabledServer.tools.has('kit_memory_recent'));
|
|
346
|
+
assert.match(enabledServer.descriptions.get('kit_memory_recent') ?? '', /last session|recent/);
|
|
347
|
+
const disabledServer = makeMockServer();
|
|
348
|
+
const result = registerMemoryTools(disabledServer.server, '/tmp', {
|
|
349
|
+
settings: { memory: { enabled: false } },
|
|
350
|
+
});
|
|
351
|
+
assert.equal(result, null);
|
|
352
|
+
assert.equal(disabledServer.tools.has('kit_memory_recent'), false);
|
|
353
|
+
});
|
|
199
354
|
});
|
|
200
355
|
describe('kit_memory_save', () => {
|
|
201
356
|
test('returns saved:true and queued-for-compile status on success', async () => {
|
|
@@ -204,7 +359,8 @@ describe('kit_memory_save', () => {
|
|
|
204
359
|
});
|
|
205
360
|
const store = makeStoreStub(true);
|
|
206
361
|
const { server, tools } = makeMockServer();
|
|
207
|
-
registerMemoryTools(server,
|
|
362
|
+
registerMemoryTools(server, '/tmp', {
|
|
363
|
+
settings: { memory: { enabled: true } },
|
|
208
364
|
indexer,
|
|
209
365
|
store,
|
|
210
366
|
config: BASE_CONFIG,
|
|
@@ -224,7 +380,8 @@ describe('kit_memory_save', () => {
|
|
|
224
380
|
});
|
|
225
381
|
const store = makeStoreStub(true);
|
|
226
382
|
const { server, tools } = makeMockServer();
|
|
227
|
-
registerMemoryTools(server,
|
|
383
|
+
registerMemoryTools(server, '/tmp', {
|
|
384
|
+
settings: { memory: { enabled: true } },
|
|
228
385
|
indexer,
|
|
229
386
|
store,
|
|
230
387
|
config: BASE_CONFIG,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { MemoryChunk, MemoryConfig } from './types.js';
|
|
1
|
+
import type { MemoryChunk, MemoryConfig, RecentSource, SourceType } from './types.js';
|
|
2
|
+
export declare const SCHEMA_VERSION = 2;
|
|
2
3
|
export declare class StoreError extends Error {
|
|
3
4
|
constructor(message: string, cause?: unknown);
|
|
4
5
|
}
|
|
@@ -13,6 +14,10 @@ export declare class MemoryStore {
|
|
|
13
14
|
upsert(chunks: MemoryChunk[], embeddings: Float32Array[]): void;
|
|
14
15
|
hashesBySource(source: string): Set<string>;
|
|
15
16
|
indexedSources(): string[];
|
|
17
|
+
getRecentSources(options: {
|
|
18
|
+
limit: number;
|
|
19
|
+
sourceType?: SourceType;
|
|
20
|
+
}): RecentSource[];
|
|
16
21
|
deleteBySource(source: string): void;
|
|
17
22
|
deleteByIds(ids: string[]): void;
|
|
18
23
|
searchDense(embedding: Float32Array, limit: number): Array<{
|