@hanhnd/agent-kit 1.0.38 → 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,15 +25923,15 @@ 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);
25933
- const promises = keys.map((key) => promisesObj[key]);
25934
- return Promise.all(promises).then((results) => {
25933
+ const promises3 = keys.map((key) => promisesObj[key]);
25934
+ return Promise.all(promises3).then((results) => {
25935
25935
  const resolvedObj = {};
25936
25936
  for (let i = 0; i < keys.length; i++) {
25937
25937
  resolvedObj[keys[i]] = results[i];
@@ -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,21 +37043,31 @@ 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);
37048
+ }
37049
+ makeSkippedMutation(source, skipped) {
37050
+ return {
37051
+ source,
37052
+ chunksToUpsert: [],
37053
+ embeddings: [],
37054
+ idsToDelete: [],
37055
+ stats: { indexed: 0, deleted: 0, skipped }
37056
+ };
37013
37057
  }
37014
- async indexFileRelativeTo(absolutePath, sourceRoot) {
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;
37018
37062
  try {
37019
- text = fs6.readFileSync(absolutePath, "utf8");
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 {
37026
- fileMtimeAt = fs6.statSync(absolutePath).mtimeMs;
37070
+ fileMtimeAt = (await fs6.promises.stat(absolutePath)).mtimeMs;
37027
37071
  } catch (err) {
37028
37072
  console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
37029
37073
  fileMtimeAt = Date.now();
@@ -37039,39 +37083,56 @@ 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
- if (!fs6.existsSync(rootDir)) return totals;
37112
+ let rootExists;
37113
+ try {
37114
+ await fs6.promises.stat(rootDir);
37115
+ rootExists = true;
37116
+ } catch {
37117
+ rootExists = false;
37118
+ }
37119
+ if (!rootExists) return totals;
37060
37120
  const relativeBase = opts.relativeBase ?? this.config.wikiDir;
37061
37121
  const excludeFiles = new Set(opts.excludeFiles ?? []);
37062
37122
  const files = [];
37063
- const walk = (dirPath) => {
37123
+ const walk = async (dirPath) => {
37064
37124
  let entries;
37065
37125
  try {
37066
- entries = fs6.readdirSync(dirPath, { withFileTypes: true });
37126
+ entries = await fs6.promises.readdir(dirPath, { withFileTypes: true });
37067
37127
  } catch (err) {
37068
37128
  console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
37069
37129
  return;
37070
37130
  }
37131
+ entries.sort((a, b) => a.name.localeCompare(b.name));
37071
37132
  for (const entry of entries) {
37072
37133
  const entryPath = path8.join(dirPath, entry.name);
37073
37134
  if (entry.isDirectory()) {
37074
- walk(entryPath);
37135
+ await walk(entryPath);
37075
37136
  continue;
37076
37137
  }
37077
37138
  if (entry.isFile() && /\.md$/i.test(entry.name) && !excludeFiles.has(entry.name)) {
@@ -37079,7 +37140,7 @@ var MemoryIndexer = class {
37079
37140
  }
37080
37141
  }
37081
37142
  };
37082
- walk(rootDir);
37143
+ await walk(rootDir);
37083
37144
  const currentSources = new Set(files.map((file) => path8.relative(relativeBase, file)));
37084
37145
  for (const stale of this.store.indexedSources()) {
37085
37146
  if (!currentSources.has(stale)) {
@@ -37087,8 +37148,13 @@ var MemoryIndexer = class {
37087
37148
  totals.deleted += 1;
37088
37149
  }
37089
37150
  }
37090
- for (const file of files) {
37091
- 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);
37092
37158
  totals.indexed += stats.indexed;
37093
37159
  totals.deleted += stats.deleted;
37094
37160
  totals.skipped += stats.skipped;
@@ -37097,77 +37163,34 @@ var MemoryIndexer = class {
37097
37163
  }
37098
37164
  async search(query, topK) {
37099
37165
  const fetchLimit = topK * FETCH_MULTIPLIER;
37100
- let denseResults = [];
37166
+ let embedding;
37101
37167
  if (this.store.vecAvailable) {
37102
37168
  try {
37103
- const queryEmbedding = await this.embedder.embed([query]);
37104
- denseResults = this.store.searchDense(queryEmbedding[0], fetchLimit);
37169
+ const embeddings = await this.embedder.embed([query]);
37170
+ embedding = embeddings[0];
37105
37171
  } catch (err) {
37106
37172
  console.warn("[memory-indexer] Dense search embedding failed:", err);
37107
37173
  }
37108
37174
  }
37109
- const bm25Results = this.store.searchBm25(query, fetchLimit);
37110
- const denseIds = new Set(denseResults.map((r) => r.id));
37111
- const bm25Ids = new Set(bm25Results.map((r) => r.id));
37112
- const unionIds = /* @__PURE__ */ new Set([...denseIds, ...bm25Ids]);
37113
- const denseRetrievalScore = new Map(denseResults.map((r) => [r.id, r.score]));
37114
- const unionArray = [...unionIds];
37115
- const chunks = this.store.getChunksByIds(unionArray);
37116
- const chunkMap = new Map(chunks.map((c) => [c.id, c]));
37117
- const unionIndexOf = new Map(unionArray.map((id, idx) => [id, idx]));
37118
- const recencyOrdered = [...unionArray].sort((a, b) => {
37119
- const mtimeA = chunkMap.get(a)?.fileMtimeAt ?? 0;
37120
- const mtimeB = chunkMap.get(b)?.fileMtimeAt ?? 0;
37121
- if (mtimeB !== mtimeA) return mtimeB - mtimeA;
37122
- return (unionIndexOf.get(a) ?? 0) - (unionIndexOf.get(b) ?? 0);
37175
+ const rows = this.store.searchHybrid({
37176
+ query,
37177
+ embedding,
37178
+ topK,
37179
+ fetchLimit,
37180
+ denseScoreFloor: DENSE_SCORE_FLOOR,
37181
+ recencyWeight: RECENCY_WEIGHT,
37182
+ rrfK: RRF_K
37123
37183
  });
37124
- const scoreMap = /* @__PURE__ */ new Map();
37125
- denseResults.forEach((r, rank) => {
37126
- scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0, recency: 0 });
37127
- });
37128
- bm25Results.forEach((r, rank) => {
37129
- const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0, recency: 0 };
37130
- entry.bm25 = 1 / (RRF_K + rank + 1);
37131
- scoreMap.set(r.id, entry);
37132
- });
37133
- recencyOrdered.forEach((id, rank) => {
37134
- const entry = scoreMap.get(id);
37135
- if (!entry) return;
37136
- entry.recency = RECENCY_WEIGHT * (1 / (RRF_K + rank + 1));
37137
- });
37138
- const denseActive = denseResults.length > 0;
37139
- const bm25Active = bm25Results.length > 0;
37140
- const recencyActive = unionIds.size > 0;
37141
- const maxScore = ((denseActive ? 1 : 0) + (bm25Active ? 1 : 0) + (recencyActive ? RECENCY_WEIGHT : 0)) / (RRF_K + 1);
37142
- const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
37143
- id,
37144
- totalScore: scores.dense + scores.bm25 + scores.recency,
37145
- hasDense: scores.dense > 0,
37146
- hasBm25: scores.bm25 > 0,
37147
- isDenseOnly: denseIds.has(id) && !bm25Ids.has(id)
37148
- })).filter((candidate) => {
37149
- if (!candidate.isDenseOnly) return true;
37150
- const retrieval = denseRetrievalScore.get(candidate.id);
37151
- if (retrieval === void 0) return true;
37152
- return retrieval >= DENSE_SCORE_FLOOR;
37153
- }).sort((a, b) => b.totalScore - a.totalScore);
37184
+ if (rows.length === 0) return [];
37154
37185
  const results = [];
37155
- const seenSources = /* @__PURE__ */ new Set();
37156
- for (const r of ranked) {
37157
- if (results.length >= topK) break;
37158
- const chunk = chunkMap.get(r.id);
37159
- if (!chunk) continue;
37160
- if (seenSources.has(chunk.source)) continue;
37161
- seenSources.add(chunk.source);
37162
- const normalizedScore = maxScore > 0 ? r.totalScore / maxScore : 0;
37163
- const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
37186
+ for (const row of rows) {
37164
37187
  let contentSource = "file";
37165
37188
  try {
37166
- chunk.content = fs6.readFileSync(path8.join(this.config.wikiDir, chunk.source), "utf8");
37189
+ row.chunk.content = await fs6.promises.readFile(path8.join(this.config.wikiDir, row.chunk.source), "utf8");
37167
37190
  } catch {
37168
37191
  contentSource = "fallback";
37169
37192
  }
37170
- results.push({ chunk, score: normalizedScore, retriever, contentSource });
37193
+ results.push({ chunk: row.chunk, score: row.score, retriever: row.retriever, contentSource });
37171
37194
  }
37172
37195
  return results;
37173
37196
  }
@@ -37176,13 +37199,13 @@ var MemoryIndexer = class {
37176
37199
  const rawDir = path8.join(this.config.wikiDir, "raw");
37177
37200
  const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
37178
37201
  const lockPath = `${savePath}.lock`;
37179
- fs6.mkdirSync(rawDir, { recursive: true });
37202
+ await fs6.promises.mkdir(rawDir, { recursive: true });
37180
37203
  const acquired = await acquireLock(lockPath, LOCK_TIMEOUT_MS, LOCK_RETRY_MS);
37181
37204
  if (!acquired) {
37182
37205
  console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
37183
37206
  }
37184
37207
  try {
37185
- fs6.appendFileSync(savePath, `
37208
+ await fs6.promises.appendFile(savePath, `
37186
37209
  ${content}
37187
37210
  `, "utf8");
37188
37211
  } finally {
@@ -37389,10 +37412,6 @@ var MemoryStore = class {
37389
37412
  }
37390
37413
  return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
37391
37414
  }
37392
- normalizeVectorDistance(distance) {
37393
- if (distance <= 0) return 1;
37394
- return Math.max(0, Math.min(1, 1 - distance));
37395
- }
37396
37415
  hashesBySource(source) {
37397
37416
  const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
37398
37417
  return new Set(rows.map((r) => r.id));
@@ -37429,61 +37448,12 @@ var MemoryStore = class {
37429
37448
  });
37430
37449
  deleteAll();
37431
37450
  }
37432
- searchDense(embedding, limit) {
37433
- try {
37434
- const queryVector = this.toVectorBinding(embedding);
37435
- const rows = this.db.prepare(
37436
- `SELECT mc.id, vector_distance_cos(mc.embedding, vector32(?)) AS distance
37437
- FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vector_matches
37438
- JOIN memory_chunks mc ON mc.rowid = vector_matches.id
37439
- ORDER BY distance
37440
- LIMIT ?`
37441
- ).all(queryVector, queryVector, limit, limit);
37442
- return rows.map((r) => ({
37443
- id: r.id,
37444
- score: this.normalizeVectorDistance(r.distance)
37445
- }));
37446
- } catch (err) {
37447
- console.warn("[memory-store] Dense search failed:", err);
37448
- return [];
37449
- }
37450
- }
37451
- searchBm25(query, limit) {
37452
- if (!query.trim()) return [];
37451
+ buildFtsQuery(query) {
37453
37452
  const tokens = query.trim().split(/\s+/).map((t) => t.replace(/[^a-zA-Z0-9_]/g, "").toLowerCase()).filter((t) => t.length > 1);
37454
- const ftsQuery = (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
37455
- if (!ftsQuery) return [];
37456
- try {
37457
- const rows = this.db.prepare(
37458
- `SELECT mc.id, bm25(memory_fts, 0.2, 2.0, 5.0) AS rank
37459
- FROM memory_fts
37460
- JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
37461
- WHERE memory_fts MATCH ?
37462
- ORDER BY rank
37463
- LIMIT ?`
37464
- ).all(ftsQuery, limit);
37465
- if (rows.length === 0) return [];
37466
- const ranks = rows.map((r) => r.rank);
37467
- const minRank = Math.min(...ranks);
37468
- const maxRank = Math.max(...ranks);
37469
- const range = maxRank - minRank;
37470
- return rows.map((r) => ({
37471
- id: r.id,
37472
- score: range > 0 ? (maxRank - r.rank) / range : 1
37473
- }));
37474
- } catch (err) {
37475
- console.warn("[memory-store] BM25 search failed:", err);
37476
- return [];
37477
- }
37453
+ return (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
37478
37454
  }
37479
- getChunksByIds(ids) {
37480
- if (ids.length === 0) return [];
37481
- const placeholders = ids.map(() => "?").join(", ");
37482
- const rows = this.db.prepare(
37483
- `SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
37484
- FROM memory_chunks WHERE id IN (${placeholders})`
37485
- ).all(...ids);
37486
- return rows.map((r) => ({
37455
+ mapMemoryChunkRow(r) {
37456
+ return {
37487
37457
  id: r.id,
37488
37458
  source: r.source,
37489
37459
  sourceType: r.source_type,
@@ -37493,7 +37463,201 @@ var MemoryStore = class {
37493
37463
  lineStart: r.line_start,
37494
37464
  lineEnd: r.line_end,
37495
37465
  fileMtimeAt: r.file_mtime_at
37496
- }));
37466
+ };
37467
+ }
37468
+ searchHybrid(options) {
37469
+ const {
37470
+ query,
37471
+ embedding,
37472
+ topK,
37473
+ fetchLimit,
37474
+ denseScoreFloor,
37475
+ recencyWeight,
37476
+ rrfK,
37477
+ sourceType,
37478
+ sinceMtimeAt,
37479
+ includeDebug
37480
+ } = options;
37481
+ const ftsQuery = this.buildFtsQuery(query);
37482
+ const hasDense = embedding !== void 0;
37483
+ const hasBm25 = ftsQuery.length > 0;
37484
+ if (!hasDense && !hasBm25) return [];
37485
+ const hasFilters = sourceType !== void 0 || sinceMtimeAt !== void 0;
37486
+ try {
37487
+ const params = [];
37488
+ let denseCte = "";
37489
+ let denseRankedCte = "";
37490
+ if (hasDense) {
37491
+ const queryBuf = this.toVectorBinding(embedding);
37492
+ if (hasFilters) {
37493
+ const filterClauses = [];
37494
+ if (sourceType) {
37495
+ filterClauses.push("mc.source_type = ?");
37496
+ params.push(sourceType);
37497
+ }
37498
+ if (sinceMtimeAt !== void 0) {
37499
+ filterClauses.push("mc.file_mtime_at >= ?");
37500
+ params.push(sinceMtimeAt);
37501
+ }
37502
+ const whereClause = filterClauses.length > 0 ? `WHERE ${filterClauses.join(" AND ")}` : "";
37503
+ params.push(queryBuf);
37504
+ params.push(fetchLimit);
37505
+ denseCte = `dense_data AS (
37506
+ SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
37507
+ mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
37508
+ vector_distance_cos(mc.embedding, vector32(?)) AS dense_dist
37509
+ FROM memory_chunks mc
37510
+ ${whereClause}
37511
+ ORDER BY dense_dist
37512
+ LIMIT ?
37513
+ )`;
37514
+ } else {
37515
+ params.push(queryBuf);
37516
+ params.push(queryBuf);
37517
+ params.push(fetchLimit);
37518
+ denseCte = `dense_data AS (
37519
+ SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
37520
+ mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
37521
+ vector_distance_cos(mc.embedding, vector32(?)) AS dense_dist
37522
+ FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vtk
37523
+ JOIN memory_chunks mc ON mc.rowid = vtk.id
37524
+ )`;
37525
+ }
37526
+ denseRankedCte = `dense_ranked AS (
37527
+ SELECT *, ROW_NUMBER() OVER (ORDER BY dense_dist, id) AS dense_rank
37528
+ FROM dense_data
37529
+ )`;
37530
+ }
37531
+ let bm25Cte = "";
37532
+ let bm25RankedCte = "";
37533
+ if (hasBm25) {
37534
+ const bm25FilterClauses = [];
37535
+ if (sourceType) bm25FilterClauses.push("mc.source_type = ?");
37536
+ if (sinceMtimeAt !== void 0) bm25FilterClauses.push("mc.file_mtime_at >= ?");
37537
+ const bm25WhereExtra = bm25FilterClauses.length > 0 ? ` AND ${bm25FilterClauses.join(" AND ")}` : "";
37538
+ params.push(ftsQuery);
37539
+ if (sourceType) params.push(sourceType);
37540
+ if (sinceMtimeAt !== void 0) params.push(sinceMtimeAt);
37541
+ params.push(fetchLimit);
37542
+ bm25Cte = `bm25_data AS (
37543
+ SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
37544
+ mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
37545
+ bm25(memory_fts, 0.2, 2.0, 5.0) AS bm25_neg
37546
+ FROM memory_fts
37547
+ JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
37548
+ WHERE memory_fts MATCH ?${bm25WhereExtra}
37549
+ LIMIT ?
37550
+ )`;
37551
+ bm25RankedCte = `bm25_ranked AS (
37552
+ SELECT *, ROW_NUMBER() OVER (ORDER BY bm25_neg, id) AS bm25_rank
37553
+ FROM bm25_data
37554
+ )`;
37555
+ }
37556
+ let candidatesCte = "";
37557
+ if (hasDense && hasBm25) {
37558
+ candidatesCte = `all_candidates AS (
37559
+ SELECT d.id, d.source, d.source_type, d.file_mtime_at,
37560
+ d.heading, d.heading_level, d.content, d.line_start, d.line_end,
37561
+ CAST(d.dense_rank AS INTEGER) AS dense_rank, d.dense_dist,
37562
+ b.bm25_rank,
37563
+ CASE WHEN b.id IS NOT NULL THEN 'both' ELSE 'dense' END AS retriever
37564
+ FROM dense_ranked d
37565
+ LEFT JOIN bm25_ranked b ON b.id = d.id
37566
+ UNION ALL
37567
+ SELECT b.id, b.source, b.source_type, b.file_mtime_at,
37568
+ b.heading, b.heading_level, b.content, b.line_start, b.line_end,
37569
+ NULL AS dense_rank, NULL AS dense_dist,
37570
+ CAST(b.bm25_rank AS INTEGER) AS bm25_rank,
37571
+ 'bm25' AS retriever
37572
+ FROM bm25_ranked b
37573
+ LEFT JOIN dense_ranked d ON d.id = b.id
37574
+ WHERE d.id IS NULL
37575
+ )`;
37576
+ } else if (hasDense) {
37577
+ candidatesCte = `all_candidates AS (
37578
+ SELECT id, source, source_type, file_mtime_at,
37579
+ heading, heading_level, content, line_start, line_end,
37580
+ CAST(dense_rank AS INTEGER) AS dense_rank, dense_dist,
37581
+ NULL AS bm25_rank, 'dense' AS retriever
37582
+ FROM dense_ranked
37583
+ )`;
37584
+ } else {
37585
+ candidatesCte = `all_candidates AS (
37586
+ SELECT id, source, source_type, file_mtime_at,
37587
+ heading, heading_level, content, line_start, line_end,
37588
+ NULL AS dense_rank, NULL AS dense_dist,
37589
+ CAST(bm25_rank AS INTEGER) AS bm25_rank, 'bm25' AS retriever
37590
+ FROM bm25_ranked
37591
+ )`;
37592
+ }
37593
+ params.push(rrfK, rrfK, recencyWeight, rrfK, denseScoreFloor, topK);
37594
+ const sql = `
37595
+ WITH
37596
+ ${[denseCte, denseRankedCte, bm25Cte, bm25RankedCte, candidatesCte].filter(Boolean).join(",\n")}
37597
+ ,
37598
+ recency_ranked AS (
37599
+ SELECT id, ROW_NUMBER() OVER (ORDER BY file_mtime_at DESC, source, id) AS recency_rank
37600
+ FROM all_candidates
37601
+ ),
37602
+ scored AS (
37603
+ SELECT c.id, c.source, c.source_type, c.file_mtime_at,
37604
+ c.heading, c.heading_level, c.content, c.line_start, c.line_end,
37605
+ c.dense_rank, c.dense_dist, c.bm25_rank, r.recency_rank,
37606
+ c.retriever,
37607
+ COALESCE(1.0 / (? + c.dense_rank), 0.0) +
37608
+ COALESCE(1.0 / (? + c.bm25_rank), 0.0) +
37609
+ ? * (1.0 / (? + r.recency_rank)) AS total_score
37610
+ FROM all_candidates c
37611
+ JOIN recency_ranked r ON r.id = c.id
37612
+ WHERE c.retriever != 'dense' OR (1.0 - c.dense_dist) >= ?
37613
+ ),
37614
+ best_per_source AS (
37615
+ SELECT *,
37616
+ ROW_NUMBER() OVER (PARTITION BY source ORDER BY total_score DESC, source, id) AS source_rank
37617
+ FROM scored
37618
+ )
37619
+ SELECT id, source, source_type, file_mtime_at, heading, heading_level, content, line_start, line_end,
37620
+ dense_rank, dense_dist, bm25_rank, recency_rank, total_score, retriever
37621
+ FROM best_per_source
37622
+ WHERE source_rank = 1
37623
+ ORDER BY total_score DESC, source, id
37624
+ LIMIT ?
37625
+ `;
37626
+ const rows = this.db.prepare(sql).all(...params);
37627
+ if (rows.length === 0) return [];
37628
+ const denseActive = hasDense;
37629
+ const bm25Active = hasBm25;
37630
+ const recencyActive = rows.length > 0;
37631
+ const maxScore = ((denseActive ? 1 : 0) + (bm25Active ? 1 : 0) + (recencyActive ? recencyWeight : 0)) / (rrfK + 1);
37632
+ return rows.map((r) => {
37633
+ const chunk = this.mapMemoryChunkRow(r);
37634
+ const normalizedScore = maxScore > 0 ? r.total_score / maxScore : 0;
37635
+ const row = {
37636
+ chunk,
37637
+ score: normalizedScore,
37638
+ retriever: r.retriever
37639
+ };
37640
+ if (includeDebug) {
37641
+ const denseScore = r.dense_dist !== null ? Math.max(0, Math.min(1, 1 - r.dense_dist)) : void 0;
37642
+ const bm25Score = r.bm25_rank !== null ? 1 / (rrfK + r.bm25_rank) : void 0;
37643
+ const recencyScore = recencyWeight * (1 / (rrfK + r.recency_rank));
37644
+ const debug = {
37645
+ denseRank: r.dense_rank ?? void 0,
37646
+ denseScore,
37647
+ bm25Rank: r.bm25_rank ?? void 0,
37648
+ bm25Score,
37649
+ recencyRank: r.recency_rank,
37650
+ recencyScore,
37651
+ totalScore: r.total_score
37652
+ };
37653
+ row.debug = debug;
37654
+ }
37655
+ return row;
37656
+ });
37657
+ } catch (err) {
37658
+ console.warn("[memory-store] Hybrid search failed:", err);
37659
+ return [];
37660
+ }
37497
37661
  }
37498
37662
  get vecAvailable() {
37499
37663
  return true;
@@ -37503,45 +37667,103 @@ var MemoryStore = class {
37503
37667
  }
37504
37668
  };
37505
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
+
37506
37728
  // src/services/digest/processor.ts
37507
37729
  import * as fs10 from "node:fs";
37508
- import * as path13 from "node:path";
37730
+ import * as path14 from "node:path";
37509
37731
 
37510
37732
  // src/services/digest/constants.ts
37511
- import * as path10 from "node:path";
37733
+ import * as path11 from "node:path";
37512
37734
  var DEFAULT_DIGEST_MODEL_ID = "base" /* BASE */;
37513
37735
  var DEFAULT_DIGEST_MAX_INPUT_CHARS = 1e4;
37514
37736
  var DEFAULT_DIGEST_TIMEOUT_MS = 12e4;
37515
37737
  var KIT_DIR = ".agent-kit";
37516
- var WIKI_DIR = path10.join(KIT_DIR, "wiki");
37517
- var WIKI_RAW_DIR = path10.join(WIKI_DIR, "raw");
37518
- var PROVISIONAL_DIGEST_DIR = path10.join(WIKI_DIR, "compiled", "provisional", "conversation-digests");
37519
- var DIGEST_WORKER_DIR = path10.join(WIKI_DIR, "digest");
37520
- var DIGEST_LOCKFILE_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.lock");
37521
- var DIGEST_WORKER_STATUS_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.status.json");
37522
- 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");
37523
37745
  var LLAMA_CONTEXT_SIZE = 8192;
37524
37746
  var LLAMA_MAX_GENERATED_TOKENS = 512;
37525
37747
  var LLAMA_TEMPERATURE = 0.1;
37526
37748
 
37527
37749
  // src/services/digest/files.ts
37528
37750
  import * as fs8 from "node:fs";
37529
- import * as path11 from "node:path";
37751
+ import * as path12 from "node:path";
37530
37752
  function defaultProvisionalDigestDir(workspaceRoot2) {
37531
- return path11.join(workspaceRoot2, PROVISIONAL_DIGEST_DIR);
37753
+ return path12.join(workspaceRoot2, PROVISIONAL_DIGEST_DIR);
37532
37754
  }
37533
37755
  function readConversationDigestInput(workspaceRoot2, inputPath) {
37534
- const absoluteInput = path11.isAbsolute(inputPath) ? inputPath : path11.resolve(workspaceRoot2, inputPath);
37756
+ const absoluteInput = path12.isAbsolute(inputPath) ? inputPath : path12.resolve(workspaceRoot2, inputPath);
37535
37757
  const content = fs8.readFileSync(absoluteInput, "utf8");
37536
37758
  return {
37537
- sourcePath: path11.relative(workspaceRoot2, absoluteInput),
37759
+ sourcePath: path12.relative(workspaceRoot2, absoluteInput),
37538
37760
  content,
37539
37761
  contentHash: sha256Hex(content)
37540
37762
  };
37541
37763
  }
37542
37764
  function provisionalDigestPath(outDir, input) {
37543
- const safeName = path11.basename(input.sourcePath).replace(/\.[^.]+$/, "").replace(/[^a-z0-9._-]+/gi, "-");
37544
- 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`);
37545
37767
  }
37546
37768
  function findExistingProvisionalDigest(outDir, input) {
37547
37769
  const markdownPath = provisionalDigestPath(outDir, input);
@@ -37616,25 +37838,8 @@ function getDigestModelSpec(modelId) {
37616
37838
 
37617
37839
  // src/services/digest/providers/llama-local.ts
37618
37840
  import * as fs9 from "node:fs";
37619
- import * as path12 from "node:path";
37841
+ import * as path13 from "node:path";
37620
37842
  import { getLlama, LlamaChatSession, createModelDownloader } from "node-llama-cpp";
37621
-
37622
- // src/utils/async.ts
37623
- async function withTimeout(promise, timeoutMs, createError) {
37624
- let timer;
37625
- const timeout = new Promise((_resolve, reject) => {
37626
- timer = setTimeout(() => {
37627
- reject(createError());
37628
- }, timeoutMs);
37629
- });
37630
- try {
37631
- return await Promise.race([promise, timeout]);
37632
- } finally {
37633
- if (timer) clearTimeout(timer);
37634
- }
37635
- }
37636
-
37637
- // src/services/digest/providers/llama-local.ts
37638
37843
  var LlamaLocalDigestProviderError = class extends Error {
37639
37844
  constructor(message) {
37640
37845
  super(message);
@@ -37642,7 +37847,7 @@ var LlamaLocalDigestProviderError = class extends Error {
37642
37847
  }
37643
37848
  };
37644
37849
  function titleFromSource(sourcePath) {
37645
- return path12.basename(sourcePath).replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
37850
+ return path13.basename(sourcePath).replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
37646
37851
  }
37647
37852
  function trimConversationExport(content, maxInputChars) {
37648
37853
  const trimmed = content.trim();
@@ -37685,7 +37890,7 @@ function sanitizeConversationDigestMarkdown(generatedText) {
37685
37890
  }
37686
37891
  async function createLlamaLocalDigestProvider(modelId) {
37687
37892
  const spec = getDigestModelSpec(modelId);
37688
- const modelCacheDir = path12.join(MODEL_CACHE_DIR, "llama");
37893
+ const modelCacheDir = path13.join(MODEL_CACHE_DIR, "llama");
37689
37894
  fs9.mkdirSync(modelCacheDir, { recursive: true });
37690
37895
  let modelFilePath;
37691
37896
  try {
@@ -37754,13 +37959,13 @@ ${sanitized}`;
37754
37959
 
37755
37960
  // src/services/digest/processor.ts
37756
37961
  function digestLockPath(workspaceRoot2) {
37757
- return path13.join(workspaceRoot2, DIGEST_LOCKFILE_REL_PATH);
37962
+ return path14.join(workspaceRoot2, DIGEST_LOCKFILE_REL_PATH);
37758
37963
  }
37759
37964
  function discoverPendingConversationFiles(workspaceRoot2) {
37760
- const rawDir = path13.join(workspaceRoot2, WIKI_RAW_DIR);
37965
+ const rawDir = path14.join(workspaceRoot2, WIKI_RAW_DIR);
37761
37966
  let files;
37762
37967
  try {
37763
- 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);
37764
37969
  } catch (err) {
37765
37970
  if (err.code === "ENOENT") {
37766
37971
  files = [];
@@ -37788,7 +37993,7 @@ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer)
37788
37993
  const settings = loadGlobalSettings();
37789
37994
  const config2 = resolveMemoryConfig(settings, workspaceRoot2);
37790
37995
  if (config2.enabled !== true) return { indexed: false };
37791
- store = new MemoryStore(path13.join(config2.wikiDir, "index.db"), config2);
37996
+ store = new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
37792
37997
  const embedder = new Embedder(config2.embeddingModel);
37793
37998
  await embedder.initialize();
37794
37999
  await new MemoryIndexer(store, embedder, config2).indexFile(markdownPath);
@@ -37800,7 +38005,7 @@ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer)
37800
38005
  }
37801
38006
  }
37802
38007
  async function digestConversationFile(options) {
37803
- 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);
37804
38009
  const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
37805
38010
  const existingPath = findExistingProvisionalDigest(outDir, input);
37806
38011
  if (existingPath) {
@@ -37855,7 +38060,7 @@ async function digestPendingConversations({
37855
38060
  let indexer;
37856
38061
  const memoryConfig = resolveMemoryConfig(loadGlobalSettings(), workspaceRoot2);
37857
38062
  if (memoryConfig.enabled === true) {
37858
- const store = new MemoryStore(path13.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
38063
+ const store = new MemoryStore(path14.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
37859
38064
  const embedder = new Embedder(memoryConfig.embeddingModel);
37860
38065
  indexer = new MemoryIndexer(store, embedder, memoryConfig);
37861
38066
  closeIndexer = () => store.close();
@@ -37936,7 +38141,8 @@ async function initializeConversationDigestModel(input) {
37936
38141
  }
37937
38142
 
37938
38143
  // src/mcp/memory.ts
37939
- function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2 = process.cwd()) {
38144
+ function registerMemoryToolHandlers(server2, subsystem, workspaceRoot2 = process.cwd()) {
38145
+ const { config: config2 } = subsystem;
37940
38146
  server2.tool(
37941
38147
  "kit_memory_search",
37942
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.",
@@ -37946,10 +38152,15 @@ function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceR
37946
38152
  },
37947
38153
  async ({ query, top_k }) => {
37948
38154
  try {
38155
+ if (!subsystem.indexer || !subsystem.store) {
38156
+ return mcpText(formatInitializationFailure(subsystem));
38157
+ }
38158
+ const { indexer, store } = subsystem;
37949
38159
  const results = await indexer.search(query, top_k ?? config2.topK);
38160
+ const lifecycleNote = formatLifecycleNote(subsystem);
37950
38161
  const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
37951
38162
  if (results.length === 0) {
37952
- return mcpText(`${degradedNote}No memories found for query: "${query}"`);
38163
+ return mcpText(`${lifecycleNote}${degradedNote}No memories found for query: "${query}"`);
37953
38164
  }
37954
38165
  const formatted = results.map((r) => {
37955
38166
  const displaySource = r.chunk.source.replace(/^compiled\//, "");
@@ -37958,7 +38169,7 @@ ${r.chunk.content}` : r.chunk.content;
37958
38169
  return `### ${displaySource} (score: ${r.score.toFixed(3)})
37959
38170
  ${content}`;
37960
38171
  }).join("\n\n---\n\n");
37961
- return mcpText(`${degradedNote}${formatted}`);
38172
+ return mcpText(`${lifecycleNote}${degradedNote}${formatted}`);
37962
38173
  } catch (err) {
37963
38174
  return mcpText(`kit_memory_search failed: ${formatError2(err)}`);
37964
38175
  }
@@ -37973,12 +38184,16 @@ ${content}`;
37973
38184
  },
37974
38185
  async ({ n, source_type }) => {
37975
38186
  try {
38187
+ if (!subsystem.store) {
38188
+ return mcpText(formatInitializationFailure(subsystem));
38189
+ }
38190
+ const store = subsystem.store;
37976
38191
  const rows = store.getRecentSources({ limit: n ?? 5, sourceType: source_type });
37977
38192
  if (rows.length === 0) return mcpText("No recent sources found.");
37978
38193
  const blocks = [];
37979
38194
  for (const row of rows) {
37980
38195
  try {
37981
- const content = fs11.readFileSync(path14.join(config2.wikiDir, row.source), "utf8");
38196
+ const content = await fs11.promises.readFile(path15.join(config2.wikiDir, row.source), "utf8");
37982
38197
  const displaySource = row.source.replace(/^compiled\//, "");
37983
38198
  blocks.push(`### ${displaySource}
37984
38199
  ${content}`);
@@ -38001,6 +38216,13 @@ ${content}`);
38001
38216
  },
38002
38217
  async ({ content }) => {
38003
38218
  try {
38219
+ if (!subsystem.indexer) {
38220
+ return mcpJson({
38221
+ saved: false,
38222
+ error: formatInitializationFailure(subsystem)
38223
+ });
38224
+ }
38225
+ const indexer = subsystem.indexer;
38004
38226
  await indexer.save(content);
38005
38227
  return mcpJson({
38006
38228
  saved: true,
@@ -38043,17 +38265,28 @@ ${content}`);
38043
38265
  }
38044
38266
  );
38045
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
+ }
38046
38283
  function registerMemoryTools(server2, workspaceRoot2, overrides) {
38047
38284
  const settings = overrides?.settings ?? loadGlobalSettings();
38048
38285
  if (settings.memory?.enabled !== true) return null;
38049
- const config2 = overrides?.config ?? resolveMemoryConfig(settings, workspaceRoot2);
38050
- const store = overrides?.store ?? new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
38051
- const indexer = overrides?.indexer ?? (() => {
38052
- const embedder = new Embedder(config2.embeddingModel);
38053
- return new MemoryIndexer(store, embedder, config2);
38054
- })();
38055
- registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2);
38056
- return indexer;
38286
+ const subsystem = createMemorySubsystem(workspaceRoot2, { ...overrides, settings });
38287
+ if (!subsystem) return null;
38288
+ registerMemoryToolHandlers(server2, subsystem, workspaceRoot2);
38289
+ return subsystem;
38057
38290
  }
38058
38291
 
38059
38292
  // src/entrypoints/server.ts
@@ -38065,9 +38298,9 @@ registerIntegrationTools(server);
38065
38298
  registerCoreTools(server);
38066
38299
  registerAgentTools(server);
38067
38300
  var workspaceRoot = getWorkspaceRoot();
38068
- var memoryIndexer = registerMemoryTools(server, workspaceRoot);
38301
+ var memorySubsystem = registerMemoryTools(server, workspaceRoot);
38069
38302
  var transport = new StdioServerTransport();
38070
38303
  await server.connect(transport);
38071
- if (memoryIndexer) {
38072
- void memoryIndexer.startupIndex();
38304
+ if (memorySubsystem) {
38305
+ void memorySubsystem.startWarmup();
38073
38306
  }