@hanhnd/agent-kit 1.0.39 → 1.0.40

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.js CHANGED
@@ -15195,6 +15195,22 @@ async function withTimeout(promise, timeoutMs, createError) {
15195
15195
  if (timer) clearTimeout(timer);
15196
15196
  }
15197
15197
  }
15198
+ async function mapLimit(items, limit, worker) {
15199
+ if (items.length === 0) return [];
15200
+ const concurrency = Math.max(1, Math.floor(limit));
15201
+ const results = new Array(items.length);
15202
+ let nextIndex = 0;
15203
+ async function runWorker() {
15204
+ while (nextIndex < items.length) {
15205
+ const currentIndex = nextIndex;
15206
+ nextIndex++;
15207
+ results[currentIndex] = await worker(items[currentIndex], currentIndex);
15208
+ }
15209
+ }
15210
+ const runners = Array.from({ length: Math.min(concurrency, items.length) }, () => runWorker());
15211
+ await Promise.all(runners);
15212
+ return results;
15213
+ }
15198
15214
 
15199
15215
  // src/utils/paths.ts
15200
15216
  import * as os2 from "os";
@@ -15912,9 +15928,19 @@ var MemoryIndexer = class {
15912
15928
  }
15913
15929
  _ready = false;
15914
15930
  async indexFile(absolutePath) {
15915
- return this.indexFileRelativeTo(absolutePath, this.config.wikiDir);
15931
+ const mutation = await this.prepareIndexFileRelativeTo(absolutePath, this.config.wikiDir);
15932
+ return this.commitPreparedMutation(mutation);
15916
15933
  }
15917
- async indexFileRelativeTo(absolutePath, sourceRoot) {
15934
+ makeSkippedMutation(source, skipped) {
15935
+ return {
15936
+ source,
15937
+ chunksToUpsert: [],
15938
+ embeddings: [],
15939
+ idsToDelete: [],
15940
+ stats: { indexed: 0, deleted: 0, skipped }
15941
+ };
15942
+ }
15943
+ async prepareIndexFileRelativeTo(absolutePath, sourceRoot) {
15918
15944
  const source = path8.relative(sourceRoot, absolutePath);
15919
15945
  const existingChunkIds = this.store.hashesBySource(source);
15920
15946
  let text;
@@ -15922,7 +15948,7 @@ var MemoryIndexer = class {
15922
15948
  text = await fs6.promises.readFile(absolutePath, "utf8");
15923
15949
  } catch (err) {
15924
15950
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
15925
- return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
15951
+ return this.makeSkippedMutation(source, existingChunkIds.size);
15926
15952
  }
15927
15953
  let fileMtimeAt;
15928
15954
  try {
@@ -15942,21 +15968,30 @@ var MemoryIndexer = class {
15942
15968
  embeddings = await this.embedder.embed(toIndex.map((c) => c.content));
15943
15969
  } catch (err) {
15944
15970
  console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
15945
- return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
15971
+ return this.makeSkippedMutation(source, existingChunkIds.size);
15946
15972
  }
15947
15973
  }
15948
- if (toIndex.length > 0) {
15949
- this.store.upsert(toIndex, embeddings);
15950
- }
15951
- if (toDelete.length > 0) {
15952
- this.store.deleteByIds(toDelete);
15953
- }
15954
15974
  return {
15955
- indexed: toIndex.length,
15956
- deleted: toDelete.length,
15957
- skipped: allChunks.length - toIndex.length
15975
+ source,
15976
+ chunksToUpsert: toIndex,
15977
+ embeddings,
15978
+ idsToDelete: toDelete,
15979
+ stats: {
15980
+ indexed: toIndex.length,
15981
+ deleted: toDelete.length,
15982
+ skipped: allChunks.length - toIndex.length
15983
+ }
15958
15984
  };
15959
15985
  }
15986
+ commitPreparedMutation(mutation) {
15987
+ if (mutation.chunksToUpsert.length > 0) {
15988
+ this.store.upsert(mutation.chunksToUpsert, mutation.embeddings);
15989
+ }
15990
+ if (mutation.idsToDelete.length > 0) {
15991
+ this.store.deleteByIds(mutation.idsToDelete);
15992
+ }
15993
+ return mutation.stats;
15994
+ }
15960
15995
  async indexDirectory(rootDir, opts = {}) {
15961
15996
  const totals = { indexed: 0, deleted: 0, skipped: 0 };
15962
15997
  let rootExists;
@@ -15998,8 +16033,13 @@ var MemoryIndexer = class {
15998
16033
  totals.deleted += 1;
15999
16034
  }
16000
16035
  }
16001
- for (const file of files) {
16002
- const stats = await this.indexFileRelativeTo(file, relativeBase);
16036
+ const preparedMutations = await mapLimit(
16037
+ files,
16038
+ opts.fileConcurrency ?? 2,
16039
+ (file) => this.prepareIndexFileRelativeTo(file, relativeBase)
16040
+ );
16041
+ for (const mutation of preparedMutations) {
16042
+ const stats = this.commitPreparedMutation(mutation);
16003
16043
  totals.indexed += stats.indexed;
16004
16044
  totals.deleted += stats.deleted;
16005
16045
  totals.skipped += stats.skipped;
@@ -23,13 +23,13 @@ registerIntegrationTools(server);
23
23
  registerCoreTools(server);
24
24
  registerAgentTools(server);
25
25
  const workspaceRoot = getWorkspaceRoot();
26
- const memoryIndexer = registerMemoryTools(server, workspaceRoot);
26
+ const memorySubsystem = registerMemoryTools(server, workspaceRoot);
27
27
  // ═══════════════════════════════════════════════════════════════
28
28
  // START SERVER
29
29
  // ═══════════════════════════════════════════════════════════════
30
30
  const transport = new StdioServerTransport();
31
31
  await server.connect(transport);
32
32
  // Fire-and-forget startup indexing — must run after server.connect
33
- if (memoryIndexer) {
34
- void memoryIndexer.startupIndex();
33
+ if (memorySubsystem) {
34
+ void memorySubsystem.startWarmup();
35
35
  }
@@ -1,16 +1,11 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { MemoryIndexer } from '../services/memory/indexer.js';
3
- import { MemoryStore } from '../services/memory/store.js';
4
- import type { MemoryConfig } from '../services/memory/types.js';
2
+ import { type MemorySubsystem, type MemorySubsystemOverrides } from '../services/memory/subsystem.js';
5
3
  import { type ProjectSettings } from '../core/config/index.js';
6
4
  /**
7
5
  * Initializes the memory subsystem and registers all memory tools.
8
- * Returns the MemoryIndexer so the caller can fire startupIndex() after server.connect().
6
+ * Returns the MemorySubsystem so the caller can fire startWarmup() after server.connect().
9
7
  * Returns null when memory is disabled (settings.memory.enabled !== true).
10
8
  */
11
- export declare function registerMemoryTools(server: McpServer, workspaceRoot: string, overrides?: {
12
- indexer?: MemoryIndexer;
13
- store?: MemoryStore;
14
- config?: MemoryConfig;
9
+ export declare function registerMemoryTools(server: McpServer, workspaceRoot: string, overrides?: MemorySubsystemOverrides & {
15
10
  settings?: ProjectSettings;
16
- }): MemoryIndexer | null;
11
+ }): MemorySubsystem | null;
@@ -1,29 +1,33 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { z } from 'zod';
4
- import { Embedder } from '../services/memory/embedder.js';
5
- import { MemoryIndexer } from '../services/memory/indexer.js';
6
- import { MemoryStore } from '../services/memory/store.js';
4
+ import { createMemorySubsystem, } from '../services/memory/subsystem.js';
7
5
  import { SOURCE_TYPES } from '../services/memory/types.js';
8
6
  import { initializeConversationDigestModel } from '../services/digest/processor.js';
9
7
  import { DEFAULT_DIGEST_MODEL_ID } from '../services/digest/constants.js';
10
8
  import { formatError, mcpJson, mcpText } from '../utils/utils.js';
11
- import { loadGlobalSettings, resolveConversationDigestConfig, resolveMemoryConfig, } from '../core/config/index.js';
9
+ import { loadGlobalSettings, resolveConversationDigestConfig } from '../core/config/index.js';
12
10
  /**
13
- * Registers tool handlers onto an already-constructed indexer/store pair.
11
+ * Registers tool handlers against the current memory subsystem state.
14
12
  */
15
- function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoot = process.cwd()) {
13
+ function registerMemoryToolHandlers(server, subsystem, workspaceRoot = process.cwd()) {
14
+ const { config } = subsystem;
16
15
  server.tool('kit_memory_search', 'Search persistent memory for factual or semantic matches (decisions, concepts, entities). Extract concise keywords only, removing stopwords and filler words from the user request before querying. Use kit_memory_recent for temporal or recency questions.', {
17
16
  query: z.string().min(1).describe('Keyword-only search query with stopwords removed'),
18
17
  top_k: z.number().int().positive().optional().describe('Number of results to return'),
19
18
  }, async ({ query, top_k }) => {
20
19
  try {
20
+ if (!subsystem.indexer || !subsystem.store) {
21
+ return mcpText(formatInitializationFailure(subsystem));
22
+ }
23
+ const { indexer, store } = subsystem;
21
24
  const results = await indexer.search(query, top_k ?? config.topK);
25
+ const lifecycleNote = formatLifecycleNote(subsystem);
22
26
  const degradedNote = !store.vecAvailable
23
27
  ? '⚠️ Vector search unavailable — showing keyword-only results.\n\n'
24
28
  : '';
25
29
  if (results.length === 0) {
26
- return mcpText(`${degradedNote}No memories found for query: "${query}"`);
30
+ return mcpText(`${lifecycleNote}${degradedNote}No memories found for query: "${query}"`);
27
31
  }
28
32
  const formatted = results
29
33
  .map((r) => {
@@ -34,7 +38,7 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
34
38
  return `### ${displaySource} (score: ${r.score.toFixed(3)})\n${content}`;
35
39
  })
36
40
  .join('\n\n---\n\n');
37
- return mcpText(`${degradedNote}${formatted}`);
41
+ return mcpText(`${lifecycleNote}${degradedNote}${formatted}`);
38
42
  }
39
43
  catch (err) {
40
44
  return mcpText(`kit_memory_search failed: ${formatError(err)}`);
@@ -45,6 +49,10 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
45
49
  source_type: z.enum(SOURCE_TYPES).optional().describe('Optional source type filter'),
46
50
  }, async ({ n, source_type }) => {
47
51
  try {
52
+ if (!subsystem.store) {
53
+ return mcpText(formatInitializationFailure(subsystem));
54
+ }
55
+ const store = subsystem.store;
48
56
  const rows = store.getRecentSources({ limit: n ?? 5, sourceType: source_type });
49
57
  if (rows.length === 0)
50
58
  return mcpText('No recent sources found.');
@@ -71,6 +79,13 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
71
79
  content: z.string().min(1).describe('Content to save to memory'),
72
80
  }, async ({ content }) => {
73
81
  try {
82
+ if (!subsystem.indexer) {
83
+ return mcpJson({
84
+ saved: false,
85
+ error: formatInitializationFailure(subsystem),
86
+ });
87
+ }
88
+ const indexer = subsystem.indexer;
74
89
  await indexer.save(content);
75
90
  return mcpJson({
76
91
  saved: true,
@@ -109,22 +124,31 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
109
124
  }
110
125
  });
111
126
  }
127
+ function formatLifecycleNote(subsystem) {
128
+ if (subsystem.status.state === 'warming' || subsystem.status.state === 'initializing') {
129
+ return '⚠️ Memory index is warming — results may be stale until startup indexing completes.\n\n';
130
+ }
131
+ if (subsystem.status.state === 'degraded') {
132
+ return `⚠️ Memory index is degraded${subsystem.status.error ? `: ${subsystem.status.error}` : ''}.\n\n`;
133
+ }
134
+ return '';
135
+ }
136
+ function formatInitializationFailure(subsystem) {
137
+ const detail = subsystem.status.error ? `: ${subsystem.status.error}` : '';
138
+ return `Memory initialization failed${detail}`;
139
+ }
112
140
  /**
113
141
  * Initializes the memory subsystem and registers all memory tools.
114
- * Returns the MemoryIndexer so the caller can fire startupIndex() after server.connect().
142
+ * Returns the MemorySubsystem so the caller can fire startWarmup() after server.connect().
115
143
  * Returns null when memory is disabled (settings.memory.enabled !== true).
116
144
  */
117
145
  export function registerMemoryTools(server, workspaceRoot, overrides) {
118
146
  const settings = overrides?.settings ?? loadGlobalSettings();
119
147
  if (settings.memory?.enabled !== true)
120
148
  return null;
121
- const config = overrides?.config ?? resolveMemoryConfig(settings, workspaceRoot);
122
- const store = overrides?.store ?? new MemoryStore(path.join(config.wikiDir, 'index.db'), config);
123
- const indexer = overrides?.indexer ??
124
- (() => {
125
- const embedder = new Embedder(config.embeddingModel);
126
- return new MemoryIndexer(store, embedder, config);
127
- })();
128
- registerMemoryToolHandlers(server, indexer, store, config, workspaceRoot);
129
- return indexer;
149
+ const subsystem = createMemorySubsystem(workspaceRoot, { ...overrides, settings });
150
+ if (!subsystem)
151
+ return null;
152
+ registerMemoryToolHandlers(server, subsystem, workspaceRoot);
153
+ return subsystem;
130
154
  }