@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.
Files changed (41) hide show
  1. package/dist/cli/commands/cli-digest-pending.test.js +19 -10
  2. package/dist/cli/commands/memory.js +44 -4
  3. package/dist/cli.js +307 -129
  4. package/dist/core/config/index.d.ts +3 -0
  5. package/dist/core/config/index.js +20 -0
  6. package/dist/entrypoints/server.js +1 -3
  7. package/dist/mcp/memory.d.ts +2 -1
  8. package/dist/mcp/memory.js +45 -18
  9. package/dist/server.js +253 -150
  10. package/dist/services/digest/constants.d.ts +3 -3
  11. package/dist/services/digest/constants.js +3 -3
  12. package/dist/services/digest/digest-background.test.js +19 -8
  13. package/dist/services/digest/digest-init.test.js +13 -2
  14. package/dist/services/digest/digest-pending.test.js +13 -2
  15. package/dist/services/digest/digest-processor.test.js +66 -0
  16. package/dist/services/digest/files.js +3 -3
  17. package/dist/services/digest/model-registry.js +2 -2
  18. package/dist/services/digest/processor.js +43 -13
  19. package/dist/services/digest/providers/llama-local.js +17 -28
  20. package/dist/services/digest/types.d.ts +3 -1
  21. package/dist/services/digest/types.js +1 -1
  22. package/dist/services/memory/chunker.d.ts +5 -2
  23. package/dist/services/memory/chunker.js +3 -1
  24. package/dist/services/memory/chunker.test.js +21 -11
  25. package/dist/services/memory/constants.js +1 -1
  26. package/dist/services/memory/embedder.js +1 -1
  27. package/dist/services/memory/index.d.ts +1 -1
  28. package/dist/services/memory/indexer.d.ts +2 -1
  29. package/dist/services/memory/indexer.js +24 -8
  30. package/dist/services/memory/indexer.test.js +38 -2
  31. package/dist/services/memory/memory.test.js +172 -15
  32. package/dist/services/memory/store.d.ts +6 -1
  33. package/dist/services/memory/store.js +87 -47
  34. package/dist/services/memory/store.test.js +123 -3
  35. package/dist/services/memory/types.d.ts +9 -1
  36. package/dist/services/memory/types.js +2 -1
  37. package/dist/utils/paths.d.ts +2 -0
  38. package/dist/utils/paths.js +2 -1
  39. package/dist/utils/utils.d.ts +1 -0
  40. package/dist/utils/utils.js +3 -3
  41. package/package.json +2 -2
@@ -5,7 +5,7 @@ export const DEFAULT_MEMORY_CONFIG = {
5
5
  topK: 5,
6
6
  chunkSize: 1500,
7
7
  overlapLines: 2,
8
- embeddingModel: EmbeddingModelName.SMALL,
8
+ embeddingModel: EmbeddingModelName.BASE,
9
9
  vectorDimension: 384,
10
10
  };
11
11
  export const LOCK_RETRY_MS = 50;
@@ -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.SMALL]: EmbeddingModel.BGESmallENV15,
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
- const allChunks = chunkMarkdown(text, source, this.config);
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 existing = scoreMap.get(r.id);
123
- if (existing) {
124
- existing.bm25 = 1 / (RRF_K + rank + 1);
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.SMALL,
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, _desc, _schema, handler) {
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.SMALL,
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 { vecAvailable };
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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, { memory: { enabled: true } }, '/tmp', {
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<{