@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/server.js CHANGED
@@ -3222,8 +3222,8 @@ var require_utils = __commonJS({
3222
3222
  }
3223
3223
  return ind;
3224
3224
  }
3225
- function removeDotSegments(path15) {
3226
- let input = path15;
3225
+ function removeDotSegments(path16) {
3226
+ let input = path16;
3227
3227
  const output = [];
3228
3228
  let nextSlash = -1;
3229
3229
  let len = 0;
@@ -3422,8 +3422,8 @@ var require_schemes = __commonJS({
3422
3422
  wsComponent.secure = void 0;
3423
3423
  }
3424
3424
  if (wsComponent.resourceName) {
3425
- const [path15, query] = wsComponent.resourceName.split("?");
3426
- wsComponent.path = path15 && path15 !== "/" ? path15 : void 0;
3425
+ const [path16, query] = wsComponent.resourceName.split("?");
3426
+ wsComponent.path = path16 && path16 !== "/" ? path16 : void 0;
3427
3427
  wsComponent.query = query;
3428
3428
  wsComponent.resourceName = void 0;
3429
3429
  }
@@ -22164,8 +22164,8 @@ function getErrorMap() {
22164
22164
 
22165
22165
  // ../node_modules/zod/v3/helpers/parseUtil.js
22166
22166
  var makeIssue = (params) => {
22167
- const { data, path: path15, errorMaps, issueData } = params;
22168
- const fullPath = [...path15, ...issueData.path || []];
22167
+ const { data, path: path16, errorMaps, issueData } = params;
22168
+ const fullPath = [...path16, ...issueData.path || []];
22169
22169
  const fullIssue = {
22170
22170
  ...issueData,
22171
22171
  path: fullPath
@@ -22281,11 +22281,11 @@ var errorUtil;
22281
22281
 
22282
22282
  // ../node_modules/zod/v3/types.js
22283
22283
  var ParseInputLazyPath = class {
22284
- constructor(parent, value, path15, key) {
22284
+ constructor(parent, value, path16, key) {
22285
22285
  this._cachedPath = [];
22286
22286
  this.parent = parent;
22287
22287
  this.data = value;
22288
- this._path = path15;
22288
+ this._path = path16;
22289
22289
  this._key = key;
22290
22290
  }
22291
22291
  get path() {
@@ -25923,10 +25923,10 @@ function assignProp(target, prop, value) {
25923
25923
  configurable: true
25924
25924
  });
25925
25925
  }
25926
- function getElementAtPath(obj, path15) {
25927
- if (!path15)
25926
+ function getElementAtPath(obj, path16) {
25927
+ if (!path16)
25928
25928
  return obj;
25929
- return path15.reduce((acc, key) => acc?.[key], obj);
25929
+ return path16.reduce((acc, key) => acc?.[key], obj);
25930
25930
  }
25931
25931
  function promiseAllObject(promisesObj) {
25932
25932
  const keys = Object.keys(promisesObj);
@@ -26246,11 +26246,11 @@ function aborted(x, startIndex = 0) {
26246
26246
  }
26247
26247
  return false;
26248
26248
  }
26249
- function prefixIssues(path15, issues) {
26249
+ function prefixIssues(path16, issues) {
26250
26250
  return issues.map((iss) => {
26251
26251
  var _a;
26252
26252
  (_a = iss).path ?? (_a.path = []);
26253
- iss.path.unshift(path15);
26253
+ iss.path.unshift(path16);
26254
26254
  return iss;
26255
26255
  });
26256
26256
  }
@@ -36777,7 +36777,10 @@ ${ticket.fields.labels?.join(", ") || "None"}`;
36777
36777
 
36778
36778
  // src/mcp/memory.ts
36779
36779
  import * as fs11 from "fs";
36780
- import * as path14 from "path";
36780
+ import * as path15 from "path";
36781
+
36782
+ // src/services/memory/subsystem.ts
36783
+ import * as path10 from "path";
36781
36784
 
36782
36785
  // src/services/memory/embedder.ts
36783
36786
  var EmbedderError = class extends Error {
@@ -36993,6 +36996,37 @@ ${sectionText}` : sectionText;
36993
36996
  return chunks;
36994
36997
  }
36995
36998
 
36999
+ // src/utils/async.ts
37000
+ async function withTimeout(promise, timeoutMs, createError) {
37001
+ let timer;
37002
+ const timeout = new Promise((_resolve, reject) => {
37003
+ timer = setTimeout(() => {
37004
+ reject(createError());
37005
+ }, timeoutMs);
37006
+ });
37007
+ try {
37008
+ return await Promise.race([promise, timeout]);
37009
+ } finally {
37010
+ if (timer) clearTimeout(timer);
37011
+ }
37012
+ }
37013
+ async function mapLimit(items, limit, worker) {
37014
+ if (items.length === 0) return [];
37015
+ const concurrency = Math.max(1, Math.floor(limit));
37016
+ const results = new Array(items.length);
37017
+ let nextIndex = 0;
37018
+ async function runWorker() {
37019
+ while (nextIndex < items.length) {
37020
+ const currentIndex = nextIndex;
37021
+ nextIndex++;
37022
+ results[currentIndex] = await worker(items[currentIndex], currentIndex);
37023
+ }
37024
+ }
37025
+ const runners = Array.from({ length: Math.min(concurrency, items.length) }, () => runWorker());
37026
+ await Promise.all(runners);
37027
+ return results;
37028
+ }
37029
+
36996
37030
  // src/services/memory/indexer.ts
36997
37031
  function deriveSourceType(source) {
36998
37032
  if (source.startsWith("compiled/provisional/conversation-digests/")) return "digest";
@@ -37009,9 +37043,19 @@ var MemoryIndexer = class {
37009
37043
  }
37010
37044
  _ready = false;
37011
37045
  async indexFile(absolutePath) {
37012
- return this.indexFileRelativeTo(absolutePath, this.config.wikiDir);
37046
+ const mutation = await this.prepareIndexFileRelativeTo(absolutePath, this.config.wikiDir);
37047
+ return this.commitPreparedMutation(mutation);
37013
37048
  }
37014
- async indexFileRelativeTo(absolutePath, sourceRoot) {
37049
+ makeSkippedMutation(source, skipped) {
37050
+ return {
37051
+ source,
37052
+ chunksToUpsert: [],
37053
+ embeddings: [],
37054
+ idsToDelete: [],
37055
+ stats: { indexed: 0, deleted: 0, skipped }
37056
+ };
37057
+ }
37058
+ async prepareIndexFileRelativeTo(absolutePath, sourceRoot) {
37015
37059
  const source = path8.relative(sourceRoot, absolutePath);
37016
37060
  const existingChunkIds = this.store.hashesBySource(source);
37017
37061
  let text;
@@ -37019,7 +37063,7 @@ var MemoryIndexer = class {
37019
37063
  text = await fs6.promises.readFile(absolutePath, "utf8");
37020
37064
  } catch (err) {
37021
37065
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
37022
- return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
37066
+ return this.makeSkippedMutation(source, existingChunkIds.size);
37023
37067
  }
37024
37068
  let fileMtimeAt;
37025
37069
  try {
@@ -37039,21 +37083,30 @@ var MemoryIndexer = class {
37039
37083
  embeddings = await this.embedder.embed(toIndex.map((c) => c.content));
37040
37084
  } catch (err) {
37041
37085
  console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
37042
- return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
37086
+ return this.makeSkippedMutation(source, existingChunkIds.size);
37043
37087
  }
37044
37088
  }
37045
- if (toIndex.length > 0) {
37046
- this.store.upsert(toIndex, embeddings);
37047
- }
37048
- if (toDelete.length > 0) {
37049
- this.store.deleteByIds(toDelete);
37050
- }
37051
37089
  return {
37052
- indexed: toIndex.length,
37053
- deleted: toDelete.length,
37054
- skipped: allChunks.length - toIndex.length
37090
+ source,
37091
+ chunksToUpsert: toIndex,
37092
+ embeddings,
37093
+ idsToDelete: toDelete,
37094
+ stats: {
37095
+ indexed: toIndex.length,
37096
+ deleted: toDelete.length,
37097
+ skipped: allChunks.length - toIndex.length
37098
+ }
37055
37099
  };
37056
37100
  }
37101
+ commitPreparedMutation(mutation) {
37102
+ if (mutation.chunksToUpsert.length > 0) {
37103
+ this.store.upsert(mutation.chunksToUpsert, mutation.embeddings);
37104
+ }
37105
+ if (mutation.idsToDelete.length > 0) {
37106
+ this.store.deleteByIds(mutation.idsToDelete);
37107
+ }
37108
+ return mutation.stats;
37109
+ }
37057
37110
  async indexDirectory(rootDir, opts = {}) {
37058
37111
  const totals = { indexed: 0, deleted: 0, skipped: 0 };
37059
37112
  let rootExists;
@@ -37095,8 +37148,13 @@ var MemoryIndexer = class {
37095
37148
  totals.deleted += 1;
37096
37149
  }
37097
37150
  }
37098
- for (const file of files) {
37099
- const stats = await this.indexFileRelativeTo(file, relativeBase);
37151
+ const preparedMutations = await mapLimit(
37152
+ files,
37153
+ opts.fileConcurrency ?? 2,
37154
+ (file) => this.prepareIndexFileRelativeTo(file, relativeBase)
37155
+ );
37156
+ for (const mutation of preparedMutations) {
37157
+ const stats = this.commitPreparedMutation(mutation);
37100
37158
  totals.indexed += stats.indexed;
37101
37159
  totals.deleted += stats.deleted;
37102
37160
  totals.skipped += stats.skipped;
@@ -37609,45 +37667,103 @@ var MemoryStore = class {
37609
37667
  }
37610
37668
  };
37611
37669
 
37670
+ // src/services/memory/subsystem.ts
37671
+ var DefaultMemorySubsystem = class {
37672
+ constructor(config2, indexer, store, initialState, initialError) {
37673
+ this.config = config2;
37674
+ this.indexer = indexer;
37675
+ this.store = store;
37676
+ this.lifecycleStatus = makeStatus(initialState, initialError);
37677
+ }
37678
+ lifecycleStatus;
37679
+ get status() {
37680
+ return this.lifecycleStatus;
37681
+ }
37682
+ async startWarmup() {
37683
+ if (!this.indexer) {
37684
+ this.lifecycleStatus = makeStatus("failed", this.lifecycleStatus.error ?? "Memory indexer is unavailable");
37685
+ return;
37686
+ }
37687
+ this.lifecycleStatus = makeStatus("warming");
37688
+ try {
37689
+ await this.indexer.startupIndex();
37690
+ this.lifecycleStatus = makeStatus("ready");
37691
+ } catch (err) {
37692
+ this.lifecycleStatus = makeStatus(this.store ? "degraded" : "failed", formatError3(err));
37693
+ }
37694
+ }
37695
+ close() {
37696
+ this.store?.close();
37697
+ }
37698
+ };
37699
+ function makeStatus(state, error2) {
37700
+ return {
37701
+ state,
37702
+ ready: state === "ready",
37703
+ warming: state === "initializing" || state === "warming",
37704
+ degraded: state === "degraded" || state === "failed",
37705
+ error: error2
37706
+ };
37707
+ }
37708
+ function formatError3(err) {
37709
+ return err instanceof Error ? err.message : String(err);
37710
+ }
37711
+ function createMemorySubsystem(workspaceRoot2, overrides = {}) {
37712
+ const config2 = overrides.config ?? resolveMemoryConfig(overrides.settings ?? {}, workspaceRoot2);
37713
+ if (config2.enabled !== true) return null;
37714
+ let store = overrides.store ?? null;
37715
+ let indexer = overrides.indexer ?? null;
37716
+ try {
37717
+ store = store ?? new MemoryStore(path10.join(config2.wikiDir, "index.db"), config2);
37718
+ if (!indexer) {
37719
+ const embedder = new Embedder(config2.embeddingModel);
37720
+ indexer = new MemoryIndexer(store, embedder, config2);
37721
+ }
37722
+ return new DefaultMemorySubsystem(config2, indexer, store, "initializing");
37723
+ } catch (err) {
37724
+ return new DefaultMemorySubsystem(config2, indexer, store, "failed", formatError3(err));
37725
+ }
37726
+ }
37727
+
37612
37728
  // src/services/digest/processor.ts
37613
37729
  import * as fs10 from "node:fs";
37614
- import * as path13 from "node:path";
37730
+ import * as path14 from "node:path";
37615
37731
 
37616
37732
  // src/services/digest/constants.ts
37617
- import * as path10 from "node:path";
37733
+ import * as path11 from "node:path";
37618
37734
  var DEFAULT_DIGEST_MODEL_ID = "base" /* BASE */;
37619
37735
  var DEFAULT_DIGEST_MAX_INPUT_CHARS = 1e4;
37620
37736
  var DEFAULT_DIGEST_TIMEOUT_MS = 12e4;
37621
37737
  var KIT_DIR = ".agent-kit";
37622
- var WIKI_DIR = path10.join(KIT_DIR, "wiki");
37623
- var WIKI_RAW_DIR = path10.join(WIKI_DIR, "raw");
37624
- var PROVISIONAL_DIGEST_DIR = path10.join(WIKI_DIR, "compiled", "provisional", "conversation-digests");
37625
- var DIGEST_WORKER_DIR = path10.join(WIKI_DIR, "digest");
37626
- var DIGEST_LOCKFILE_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.lock");
37627
- var DIGEST_WORKER_STATUS_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.status.json");
37628
- var DIGEST_WORKER_LOG_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.log");
37738
+ var WIKI_DIR = path11.join(KIT_DIR, "wiki");
37739
+ var WIKI_RAW_DIR = path11.join(WIKI_DIR, "raw");
37740
+ var PROVISIONAL_DIGEST_DIR = path11.join(WIKI_DIR, "compiled", "provisional", "conversation-digests");
37741
+ var DIGEST_WORKER_DIR = path11.join(WIKI_DIR, "digest");
37742
+ var DIGEST_LOCKFILE_REL_PATH = path11.join(DIGEST_WORKER_DIR, "digest-worker.lock");
37743
+ var DIGEST_WORKER_STATUS_REL_PATH = path11.join(DIGEST_WORKER_DIR, "digest-worker.status.json");
37744
+ var DIGEST_WORKER_LOG_REL_PATH = path11.join(DIGEST_WORKER_DIR, "digest-worker.log");
37629
37745
  var LLAMA_CONTEXT_SIZE = 8192;
37630
37746
  var LLAMA_MAX_GENERATED_TOKENS = 512;
37631
37747
  var LLAMA_TEMPERATURE = 0.1;
37632
37748
 
37633
37749
  // src/services/digest/files.ts
37634
37750
  import * as fs8 from "node:fs";
37635
- import * as path11 from "node:path";
37751
+ import * as path12 from "node:path";
37636
37752
  function defaultProvisionalDigestDir(workspaceRoot2) {
37637
- return path11.join(workspaceRoot2, PROVISIONAL_DIGEST_DIR);
37753
+ return path12.join(workspaceRoot2, PROVISIONAL_DIGEST_DIR);
37638
37754
  }
37639
37755
  function readConversationDigestInput(workspaceRoot2, inputPath) {
37640
- const absoluteInput = path11.isAbsolute(inputPath) ? inputPath : path11.resolve(workspaceRoot2, inputPath);
37756
+ const absoluteInput = path12.isAbsolute(inputPath) ? inputPath : path12.resolve(workspaceRoot2, inputPath);
37641
37757
  const content = fs8.readFileSync(absoluteInput, "utf8");
37642
37758
  return {
37643
- sourcePath: path11.relative(workspaceRoot2, absoluteInput),
37759
+ sourcePath: path12.relative(workspaceRoot2, absoluteInput),
37644
37760
  content,
37645
37761
  contentHash: sha256Hex(content)
37646
37762
  };
37647
37763
  }
37648
37764
  function provisionalDigestPath(outDir, input) {
37649
- const safeName = path11.basename(input.sourcePath).replace(/\.[^.]+$/, "").replace(/[^a-z0-9._-]+/gi, "-");
37650
- return path11.join(outDir, `${input.contentHash.slice(0, 16)}-${safeName}.md`);
37765
+ const safeName = path12.basename(input.sourcePath).replace(/\.[^.]+$/, "").replace(/[^a-z0-9._-]+/gi, "-");
37766
+ return path12.join(outDir, `${input.contentHash.slice(0, 16)}-${safeName}.md`);
37651
37767
  }
37652
37768
  function findExistingProvisionalDigest(outDir, input) {
37653
37769
  const markdownPath = provisionalDigestPath(outDir, input);
@@ -37722,25 +37838,8 @@ function getDigestModelSpec(modelId) {
37722
37838
 
37723
37839
  // src/services/digest/providers/llama-local.ts
37724
37840
  import * as fs9 from "node:fs";
37725
- import * as path12 from "node:path";
37841
+ import * as path13 from "node:path";
37726
37842
  import { getLlama, LlamaChatSession, createModelDownloader } from "node-llama-cpp";
37727
-
37728
- // src/utils/async.ts
37729
- async function withTimeout(promise, timeoutMs, createError) {
37730
- let timer;
37731
- const timeout = new Promise((_resolve, reject) => {
37732
- timer = setTimeout(() => {
37733
- reject(createError());
37734
- }, timeoutMs);
37735
- });
37736
- try {
37737
- return await Promise.race([promise, timeout]);
37738
- } finally {
37739
- if (timer) clearTimeout(timer);
37740
- }
37741
- }
37742
-
37743
- // src/services/digest/providers/llama-local.ts
37744
37843
  var LlamaLocalDigestProviderError = class extends Error {
37745
37844
  constructor(message) {
37746
37845
  super(message);
@@ -37748,7 +37847,7 @@ var LlamaLocalDigestProviderError = class extends Error {
37748
37847
  }
37749
37848
  };
37750
37849
  function titleFromSource(sourcePath) {
37751
- return path12.basename(sourcePath).replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
37850
+ return path13.basename(sourcePath).replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
37752
37851
  }
37753
37852
  function trimConversationExport(content, maxInputChars) {
37754
37853
  const trimmed = content.trim();
@@ -37791,7 +37890,7 @@ function sanitizeConversationDigestMarkdown(generatedText) {
37791
37890
  }
37792
37891
  async function createLlamaLocalDigestProvider(modelId) {
37793
37892
  const spec = getDigestModelSpec(modelId);
37794
- const modelCacheDir = path12.join(MODEL_CACHE_DIR, "llama");
37893
+ const modelCacheDir = path13.join(MODEL_CACHE_DIR, "llama");
37795
37894
  fs9.mkdirSync(modelCacheDir, { recursive: true });
37796
37895
  let modelFilePath;
37797
37896
  try {
@@ -37860,13 +37959,13 @@ ${sanitized}`;
37860
37959
 
37861
37960
  // src/services/digest/processor.ts
37862
37961
  function digestLockPath(workspaceRoot2) {
37863
- return path13.join(workspaceRoot2, DIGEST_LOCKFILE_REL_PATH);
37962
+ return path14.join(workspaceRoot2, DIGEST_LOCKFILE_REL_PATH);
37864
37963
  }
37865
37964
  function discoverPendingConversationFiles(workspaceRoot2) {
37866
- const rawDir = path13.join(workspaceRoot2, WIKI_RAW_DIR);
37965
+ const rawDir = path14.join(workspaceRoot2, WIKI_RAW_DIR);
37867
37966
  let files;
37868
37967
  try {
37869
- files = fs10.readdirSync(rawDir).filter((f) => f.startsWith("conv_") && f.endsWith(".md")).map((f) => path13.join(rawDir, f)).sort((a, b) => fs10.statSync(a).mtimeMs - fs10.statSync(b).mtimeMs);
37968
+ files = fs10.readdirSync(rawDir).filter((f) => f.startsWith("conv_") && f.endsWith(".md")).map((f) => path14.join(rawDir, f)).sort((a, b) => fs10.statSync(a).mtimeMs - fs10.statSync(b).mtimeMs);
37870
37969
  } catch (err) {
37871
37970
  if (err.code === "ENOENT") {
37872
37971
  files = [];
@@ -37894,7 +37993,7 @@ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer)
37894
37993
  const settings = loadGlobalSettings();
37895
37994
  const config2 = resolveMemoryConfig(settings, workspaceRoot2);
37896
37995
  if (config2.enabled !== true) return { indexed: false };
37897
- store = new MemoryStore(path13.join(config2.wikiDir, "index.db"), config2);
37996
+ store = new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
37898
37997
  const embedder = new Embedder(config2.embeddingModel);
37899
37998
  await embedder.initialize();
37900
37999
  await new MemoryIndexer(store, embedder, config2).indexFile(markdownPath);
@@ -37906,7 +38005,7 @@ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer)
37906
38005
  }
37907
38006
  }
37908
38007
  async function digestConversationFile(options) {
37909
- const outDir = options.outDir ? path13.resolve(options.workspaceRoot, options.outDir) : defaultProvisionalDigestDir(options.workspaceRoot);
38008
+ const outDir = options.outDir ? path14.resolve(options.workspaceRoot, options.outDir) : defaultProvisionalDigestDir(options.workspaceRoot);
37910
38009
  const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
37911
38010
  const existingPath = findExistingProvisionalDigest(outDir, input);
37912
38011
  if (existingPath) {
@@ -37961,7 +38060,7 @@ async function digestPendingConversations({
37961
38060
  let indexer;
37962
38061
  const memoryConfig = resolveMemoryConfig(loadGlobalSettings(), workspaceRoot2);
37963
38062
  if (memoryConfig.enabled === true) {
37964
- const store = new MemoryStore(path13.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
38063
+ const store = new MemoryStore(path14.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
37965
38064
  const embedder = new Embedder(memoryConfig.embeddingModel);
37966
38065
  indexer = new MemoryIndexer(store, embedder, memoryConfig);
37967
38066
  closeIndexer = () => store.close();
@@ -38042,7 +38141,8 @@ async function initializeConversationDigestModel(input) {
38042
38141
  }
38043
38142
 
38044
38143
  // src/mcp/memory.ts
38045
- function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2 = process.cwd()) {
38144
+ function registerMemoryToolHandlers(server2, subsystem, workspaceRoot2 = process.cwd()) {
38145
+ const { config: config2 } = subsystem;
38046
38146
  server2.tool(
38047
38147
  "kit_memory_search",
38048
38148
  "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.",
@@ -38052,10 +38152,15 @@ function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceR
38052
38152
  },
38053
38153
  async ({ query, top_k }) => {
38054
38154
  try {
38155
+ if (!subsystem.indexer || !subsystem.store) {
38156
+ return mcpText(formatInitializationFailure(subsystem));
38157
+ }
38158
+ const { indexer, store } = subsystem;
38055
38159
  const results = await indexer.search(query, top_k ?? config2.topK);
38160
+ const lifecycleNote = formatLifecycleNote(subsystem);
38056
38161
  const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
38057
38162
  if (results.length === 0) {
38058
- return mcpText(`${degradedNote}No memories found for query: "${query}"`);
38163
+ return mcpText(`${lifecycleNote}${degradedNote}No memories found for query: "${query}"`);
38059
38164
  }
38060
38165
  const formatted = results.map((r) => {
38061
38166
  const displaySource = r.chunk.source.replace(/^compiled\//, "");
@@ -38064,7 +38169,7 @@ ${r.chunk.content}` : r.chunk.content;
38064
38169
  return `### ${displaySource} (score: ${r.score.toFixed(3)})
38065
38170
  ${content}`;
38066
38171
  }).join("\n\n---\n\n");
38067
- return mcpText(`${degradedNote}${formatted}`);
38172
+ return mcpText(`${lifecycleNote}${degradedNote}${formatted}`);
38068
38173
  } catch (err) {
38069
38174
  return mcpText(`kit_memory_search failed: ${formatError2(err)}`);
38070
38175
  }
@@ -38079,12 +38184,16 @@ ${content}`;
38079
38184
  },
38080
38185
  async ({ n, source_type }) => {
38081
38186
  try {
38187
+ if (!subsystem.store) {
38188
+ return mcpText(formatInitializationFailure(subsystem));
38189
+ }
38190
+ const store = subsystem.store;
38082
38191
  const rows = store.getRecentSources({ limit: n ?? 5, sourceType: source_type });
38083
38192
  if (rows.length === 0) return mcpText("No recent sources found.");
38084
38193
  const blocks = [];
38085
38194
  for (const row of rows) {
38086
38195
  try {
38087
- const content = await fs11.promises.readFile(path14.join(config2.wikiDir, row.source), "utf8");
38196
+ const content = await fs11.promises.readFile(path15.join(config2.wikiDir, row.source), "utf8");
38088
38197
  const displaySource = row.source.replace(/^compiled\//, "");
38089
38198
  blocks.push(`### ${displaySource}
38090
38199
  ${content}`);
@@ -38107,6 +38216,13 @@ ${content}`);
38107
38216
  },
38108
38217
  async ({ content }) => {
38109
38218
  try {
38219
+ if (!subsystem.indexer) {
38220
+ return mcpJson({
38221
+ saved: false,
38222
+ error: formatInitializationFailure(subsystem)
38223
+ });
38224
+ }
38225
+ const indexer = subsystem.indexer;
38110
38226
  await indexer.save(content);
38111
38227
  return mcpJson({
38112
38228
  saved: true,
@@ -38149,17 +38265,28 @@ ${content}`);
38149
38265
  }
38150
38266
  );
38151
38267
  }
38268
+ function formatLifecycleNote(subsystem) {
38269
+ if (subsystem.status.state === "warming" || subsystem.status.state === "initializing") {
38270
+ return "\u26A0\uFE0F Memory index is warming \u2014 results may be stale until startup indexing completes.\n\n";
38271
+ }
38272
+ if (subsystem.status.state === "degraded") {
38273
+ return `\u26A0\uFE0F Memory index is degraded${subsystem.status.error ? `: ${subsystem.status.error}` : ""}.
38274
+
38275
+ `;
38276
+ }
38277
+ return "";
38278
+ }
38279
+ function formatInitializationFailure(subsystem) {
38280
+ const detail = subsystem.status.error ? `: ${subsystem.status.error}` : "";
38281
+ return `Memory initialization failed${detail}`;
38282
+ }
38152
38283
  function registerMemoryTools(server2, workspaceRoot2, overrides) {
38153
38284
  const settings = overrides?.settings ?? loadGlobalSettings();
38154
38285
  if (settings.memory?.enabled !== true) return null;
38155
- const config2 = overrides?.config ?? resolveMemoryConfig(settings, workspaceRoot2);
38156
- const store = overrides?.store ?? new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
38157
- const indexer = overrides?.indexer ?? (() => {
38158
- const embedder = new Embedder(config2.embeddingModel);
38159
- return new MemoryIndexer(store, embedder, config2);
38160
- })();
38161
- registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2);
38162
- return indexer;
38286
+ const subsystem = createMemorySubsystem(workspaceRoot2, { ...overrides, settings });
38287
+ if (!subsystem) return null;
38288
+ registerMemoryToolHandlers(server2, subsystem, workspaceRoot2);
38289
+ return subsystem;
38163
38290
  }
38164
38291
 
38165
38292
  // src/entrypoints/server.ts
@@ -38171,9 +38298,9 @@ registerIntegrationTools(server);
38171
38298
  registerCoreTools(server);
38172
38299
  registerAgentTools(server);
38173
38300
  var workspaceRoot = getWorkspaceRoot();
38174
- var memoryIndexer = registerMemoryTools(server, workspaceRoot);
38301
+ var memorySubsystem = registerMemoryTools(server, workspaceRoot);
38175
38302
  var transport = new StdioServerTransport();
38176
38303
  await server.connect(transport);
38177
- if (memoryIndexer) {
38178
- void memoryIndexer.startupIndex();
38304
+ if (memorySubsystem) {
38305
+ void memorySubsystem.startWarmup();
38179
38306
  }
@@ -1,5 +1,7 @@
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, SourceType, RecentSource } from './types.js';
4
+ export { createMemorySubsystem } from './subsystem.js';
5
+ export type { MemorySubsystem, MemorySubsystemOverrides } from './subsystem.js';
6
+ export type { MemoryChunk, SearchResult, MemoryConfig, IndexStats, SourceType, RecentSource, MemoryLifecycleState, MemoryLifecycleStatus, IndexDirectoryOptions, PreparedIndexMutation, } from './types.js';
5
7
  export { DEFAULT_MEMORY_CONFIG } from './constants.js';
@@ -1,4 +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 { createMemorySubsystem } from './subsystem.js';
4
5
  export { DEFAULT_MEMORY_CONFIG } from './constants.js';
@@ -1,5 +1,5 @@
1
1
  import type { MemoryStore } from './store.js';
2
- import type { IndexStats, MemoryConfig, SearchResult, SourceType } from './types.js';
2
+ import type { IndexDirectoryOptions, IndexStats, MemoryConfig, SearchResult, SourceType } from './types.js';
3
3
  interface TextEmbedder {
4
4
  embed(texts: string[]): Promise<Float32Array[]>;
5
5
  initialize(): Promise<void>;
@@ -12,11 +12,10 @@ export declare class MemoryIndexer {
12
12
  private _ready;
13
13
  constructor(store: MemoryStore, embedder: TextEmbedder, config: MemoryConfig);
14
14
  indexFile(absolutePath: string): Promise<IndexStats>;
15
- private indexFileRelativeTo;
16
- indexDirectory(rootDir: string, opts?: {
17
- relativeBase?: string;
18
- excludeFiles?: string[];
19
- }): Promise<IndexStats>;
15
+ private makeSkippedMutation;
16
+ private prepareIndexFileRelativeTo;
17
+ private commitPreparedMutation;
18
+ indexDirectory(rootDir: string, opts?: IndexDirectoryOptions): Promise<IndexStats>;
20
19
  search(query: string, topK: number): Promise<SearchResult[]>;
21
20
  save(content: string): Promise<IndexStats>;
22
21
  startupIndex(): Promise<void>;
@@ -2,6 +2,7 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { chunkMarkdown } from './chunker.js';
4
4
  import { DENSE_SCORE_FLOOR, FETCH_MULTIPLIER, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, RECENCY_WEIGHT, RRF_K, } from './constants.js';
5
+ import { mapLimit } from '../../utils/async.js';
5
6
  import { acquireLock, releaseLock } from '../../utils/files.js';
6
7
  export function deriveSourceType(source) {
7
8
  if (source.startsWith('compiled/provisional/conversation-digests/'))
@@ -25,9 +26,19 @@ export class MemoryIndexer {
25
26
  this.config = config;
26
27
  }
27
28
  async indexFile(absolutePath) {
28
- return this.indexFileRelativeTo(absolutePath, this.config.wikiDir);
29
+ const mutation = await this.prepareIndexFileRelativeTo(absolutePath, this.config.wikiDir);
30
+ return this.commitPreparedMutation(mutation);
29
31
  }
30
- async indexFileRelativeTo(absolutePath, sourceRoot) {
32
+ makeSkippedMutation(source, skipped) {
33
+ return {
34
+ source,
35
+ chunksToUpsert: [],
36
+ embeddings: [],
37
+ idsToDelete: [],
38
+ stats: { indexed: 0, deleted: 0, skipped },
39
+ };
40
+ }
41
+ async prepareIndexFileRelativeTo(absolutePath, sourceRoot) {
31
42
  const source = path.relative(sourceRoot, absolutePath);
32
43
  const existingChunkIds = this.store.hashesBySource(source);
33
44
  let text;
@@ -36,7 +47,7 @@ export class MemoryIndexer {
36
47
  }
37
48
  catch (err) {
38
49
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
39
- return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
50
+ return this.makeSkippedMutation(source, existingChunkIds.size);
40
51
  }
41
52
  let fileMtimeAt;
42
53
  try {
@@ -58,21 +69,30 @@ export class MemoryIndexer {
58
69
  }
59
70
  catch (err) {
60
71
  console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
61
- return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
72
+ return this.makeSkippedMutation(source, existingChunkIds.size);
62
73
  }
63
74
  }
64
- if (toIndex.length > 0) {
65
- this.store.upsert(toIndex, embeddings);
66
- }
67
- if (toDelete.length > 0) {
68
- this.store.deleteByIds(toDelete);
69
- }
70
75
  return {
71
- indexed: toIndex.length,
72
- deleted: toDelete.length,
73
- skipped: allChunks.length - toIndex.length,
76
+ source,
77
+ chunksToUpsert: toIndex,
78
+ embeddings,
79
+ idsToDelete: toDelete,
80
+ stats: {
81
+ indexed: toIndex.length,
82
+ deleted: toDelete.length,
83
+ skipped: allChunks.length - toIndex.length,
84
+ },
74
85
  };
75
86
  }
87
+ commitPreparedMutation(mutation) {
88
+ if (mutation.chunksToUpsert.length > 0) {
89
+ this.store.upsert(mutation.chunksToUpsert, mutation.embeddings);
90
+ }
91
+ if (mutation.idsToDelete.length > 0) {
92
+ this.store.deleteByIds(mutation.idsToDelete);
93
+ }
94
+ return mutation.stats;
95
+ }
76
96
  async indexDirectory(rootDir, opts = {}) {
77
97
  const totals = { indexed: 0, deleted: 0, skipped: 0 };
78
98
  let rootExists;
@@ -119,8 +139,9 @@ export class MemoryIndexer {
119
139
  totals.deleted += 1;
120
140
  }
121
141
  }
122
- for (const file of files) {
123
- const stats = await this.indexFileRelativeTo(file, relativeBase);
142
+ const preparedMutations = await mapLimit(files, opts.fileConcurrency ?? 2, (file) => this.prepareIndexFileRelativeTo(file, relativeBase));
143
+ for (const mutation of preparedMutations) {
144
+ const stats = this.commitPreparedMutation(mutation);
124
145
  totals.indexed += stats.indexed;
125
146
  totals.deleted += stats.deleted;
126
147
  totals.skipped += stats.skipped;