@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
package/dist/server.js CHANGED
@@ -1,18 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
5
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
6
  var __getProtoOf = Object.getPrototypeOf;
8
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
10
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
11
- }) : x)(function(x) {
12
- if (typeof require !== "undefined") return require.apply(this, arguments);
13
- throw Error('Dynamic require of "' + x + '" is not supported');
14
- });
15
- var __commonJS = (cb, mod) => function __require2() {
8
+ var __commonJS = (cb, mod) => function __require() {
16
9
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
10
  };
18
11
  var __export = (target, all) => {
@@ -6792,12 +6785,12 @@ var require_dist = __commonJS({
6792
6785
  throw new Error(`Unknown format "${name}"`);
6793
6786
  return f;
6794
6787
  };
6795
- function addFormats(ajv, list, fs11, exportName) {
6788
+ function addFormats(ajv, list, fs12, exportName) {
6796
6789
  var _a;
6797
6790
  var _b;
6798
6791
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6799
6792
  for (const f of list)
6800
- ajv.addFormat(f, fs11[f]);
6793
+ ajv.addFormat(f, fs12[f]);
6801
6794
  }
6802
6795
  module.exports = exports = formatsPlugin;
6803
6796
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -35889,7 +35882,7 @@ import * as path4 from "path";
35889
35882
 
35890
35883
  // src/utils/utils.ts
35891
35884
  function getWorkspaceRoot() {
35892
- return process.env.WORKSPACE_DIR || process.env.CLAUDE_PROJECT_DIR || process.env.CODEX_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.CLAUDE_WORKSPACE || process.env.CODEX_WORKSPACE || process.env.GEMINI_WORKSPACE || process.env.INIT_CWD || process.env.PWD || process.cwd();
35885
+ return process.env.WORKSPACE_DIR || process.env.CLAUDE_PROJECT_DIR || process.env.CODEX_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.INIT_CWD || process.env.PWD || process.cwd();
35893
35886
  }
35894
35887
  function mcpText(text) {
35895
35888
  return { content: [{ type: "text", text }] };
@@ -35897,11 +35890,18 @@ function mcpText(text) {
35897
35890
  function mcpJson(obj) {
35898
35891
  return mcpText(JSON.stringify(obj, null, 2));
35899
35892
  }
35893
+ function formatError2(err) {
35894
+ return err instanceof Error ? err.message : String(err);
35895
+ }
35900
35896
 
35901
35897
  // src/core/config/index.ts
35902
35898
  import * as fs2 from "fs";
35899
+ import * as os from "os";
35903
35900
  import * as path2 from "path";
35904
35901
 
35902
+ // src/services/memory/types.ts
35903
+ var SOURCE_TYPES = ["digest", "concept", "entity", "preference", "wiki"];
35904
+
35905
35905
  // src/services/memory/constants.ts
35906
35906
  var DEFAULT_MEMORY_CONFIG = {
35907
35907
  enabled: false,
@@ -35909,7 +35909,7 @@ var DEFAULT_MEMORY_CONFIG = {
35909
35909
  topK: 5,
35910
35910
  chunkSize: 1500,
35911
35911
  overlapLines: 2,
35912
- embeddingModel: "small" /* SMALL */,
35912
+ embeddingModel: "base" /* BASE */,
35913
35913
  vectorDimension: 384
35914
35914
  };
35915
35915
  var LOCK_RETRY_MS = 50;
@@ -35983,8 +35983,8 @@ function tryAcquireProcessLock(lockPath) {
35983
35983
  }
35984
35984
 
35985
35985
  // src/core/config/index.ts
35986
- function resolveMemoryConfig(settings2, workspaceRoot2) {
35987
- const override = settings2.memory ?? {};
35986
+ function resolveMemoryConfig(settings, workspaceRoot2) {
35987
+ const override = settings.memory ?? {};
35988
35988
  return {
35989
35989
  ...DEFAULT_MEMORY_CONFIG,
35990
35990
  ...override,
@@ -35992,24 +35992,26 @@ function resolveMemoryConfig(settings2, workspaceRoot2) {
35992
35992
  wikiDir: typeof override.wikiDir === "string" && override.wikiDir.length > 0 ? override.wikiDir : path2.join(workspaceRoot2, ".agent-kit", "wiki")
35993
35993
  };
35994
35994
  }
35995
- function loadProjectSettings(projectDir) {
35996
- const settingsPath = path2.join(projectDir, ".agent-kit", "settings.json");
35997
- if (fs2.existsSync(settingsPath)) {
35995
+ function getGlobalSettingsFile() {
35996
+ return path2.join(os.homedir(), ".agent-kit", "settings.json");
35997
+ }
35998
+ function loadGlobalSettings() {
35999
+ const file = getGlobalSettingsFile();
36000
+ if (fs2.existsSync(file)) {
35998
36001
  try {
35999
- return JSON.parse(fs2.readFileSync(settingsPath, "utf-8"));
36002
+ return JSON.parse(fs2.readFileSync(file, "utf-8"));
36000
36003
  } catch (error2) {
36001
- console.error(`[agent-kit] Warning: Failed to parse ${settingsPath}:`, error2);
36004
+ console.error(`[agent-kit] Warning: Failed to parse ${file}:`, error2);
36002
36005
  }
36003
36006
  }
36004
36007
  return {};
36005
36008
  }
36006
- function writeProjectSettings(workspaceRoot2, patch) {
36007
- const settingsPath = path2.join(workspaceRoot2, ".agent-kit", "settings.json");
36008
- const current = loadProjectSettings(workspaceRoot2);
36009
- atomicWriteJsonFile(settingsPath, { ...current, ...patch });
36009
+ function writeGlobalSettings(patch) {
36010
+ const current = loadGlobalSettings();
36011
+ atomicWriteJsonFile(getGlobalSettingsFile(), { ...current, ...patch });
36010
36012
  }
36011
- function resolveConversationDigestConfig(settings2) {
36012
- const digest = settings2.memory?.conversationDigest;
36013
+ function resolveConversationDigestConfig(settings) {
36014
+ const digest = settings.memory?.conversationDigest;
36013
36015
  if (!digest?.initialized || typeof digest.modelId !== "string" || typeof digest.initializedAt !== "string") {
36014
36016
  return void 0;
36015
36017
  }
@@ -36383,10 +36385,11 @@ import { writeFileSync as writeFileSync4 } from "fs";
36383
36385
  import * as fs5 from "fs";
36384
36386
 
36385
36387
  // src/utils/paths.ts
36386
- import * as os from "os";
36388
+ import * as os2 from "os";
36387
36389
  import * as path7 from "path";
36388
- var AGENT_KIT_HOME = path7.join(os.homedir(), ".agent-kit");
36390
+ var AGENT_KIT_HOME = path7.join(os2.homedir(), ".agent-kit");
36389
36391
  var CREDENTIALS_FILE = path7.join(AGENT_KIT_HOME, "credentials");
36392
+ var GLOBAL_SETTINGS_FILE = path7.join(AGENT_KIT_HOME, "settings.json");
36390
36393
  var MODEL_CACHE_DIR = path7.join(AGENT_KIT_HOME, "cache", "models");
36391
36394
  var FASTEMBED_CACHE_DIR = path7.join(MODEL_CACHE_DIR, "fastembed");
36392
36395
 
@@ -36758,6 +36761,7 @@ ${ticket.fields.labels?.join(", ") || "None"}`;
36758
36761
  }
36759
36762
 
36760
36763
  // src/mcp/memory.ts
36764
+ import * as fs11 from "fs";
36761
36765
  import * as path14 from "path";
36762
36766
 
36763
36767
  // src/services/memory/embedder.ts
@@ -36807,7 +36811,7 @@ var Embedder = class {
36807
36811
  const { EmbeddingModel, FlagEmbedding } = await import("fastembed");
36808
36812
  const modelMap = {
36809
36813
  ["tiny" /* TINY */]: EmbeddingModel.AllMiniLML6V2,
36810
- ["small" /* SMALL */]: EmbeddingModel.BGESmallENV15,
36814
+ ["base" /* BASE */]: EmbeddingModel.BGESmallENV15,
36811
36815
  ["large" /* LARGE */]: EmbeddingModel.BGEBaseENV15
36812
36816
  };
36813
36817
  const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
@@ -36919,7 +36923,7 @@ function splitSection(text, maxLen) {
36919
36923
  if (byPara.every((p) => p.length <= maxLen)) return byPara;
36920
36924
  return byPara.flatMap((p) => p.length <= maxLen ? [p] : splitAtLine(p, maxLen));
36921
36925
  }
36922
- function chunkMarkdown(text, source, config2) {
36926
+ function chunkMarkdown(text, source, config2, meta) {
36923
36927
  if (!text || !text.trim()) return [];
36924
36928
  const { chunkSize, overlapLines } = config2;
36925
36929
  const lines = text.split("\n");
@@ -36958,11 +36962,13 @@ ${sectionText}` : sectionText;
36958
36962
  chunks.push({
36959
36963
  id: computeChunkId(content),
36960
36964
  source,
36965
+ sourceType: meta.sourceType,
36961
36966
  heading: section.heading,
36962
36967
  headingLevel: section.headingLevel,
36963
36968
  content,
36964
36969
  lineStart: firstLine,
36965
- lineEnd: lastLine
36970
+ lineEnd: lastLine,
36971
+ fileMtimeAt: meta.fileMtimeAt
36966
36972
  });
36967
36973
  if (pi === parts.length - 1) {
36968
36974
  prevOverlapLines = partLines.slice(-overlapLines);
@@ -36973,6 +36979,13 @@ ${sectionText}` : sectionText;
36973
36979
  }
36974
36980
 
36975
36981
  // src/services/memory/indexer.ts
36982
+ function deriveSourceType(source) {
36983
+ if (source.startsWith("compiled/provisional/conversation-digests/")) return "digest";
36984
+ if (source.startsWith("compiled/concepts/")) return "concept";
36985
+ if (source.startsWith("compiled/entities/")) return "entity";
36986
+ if (source === "compiled/preferences.md") return "preference";
36987
+ return "wiki";
36988
+ }
36976
36989
  var MemoryIndexer = class {
36977
36990
  constructor(store, embedder, config2) {
36978
36991
  this.store = store;
@@ -36993,7 +37006,15 @@ var MemoryIndexer = class {
36993
37006
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
36994
37007
  return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
36995
37008
  }
36996
- const allChunks = chunkMarkdown(text, source, this.config);
37009
+ let fileMtimeAt;
37010
+ try {
37011
+ fileMtimeAt = fs6.statSync(absolutePath).mtimeMs;
37012
+ } catch (err) {
37013
+ console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
37014
+ fileMtimeAt = Date.now();
37015
+ }
37016
+ const sourceType = deriveSourceType(source);
37017
+ const allChunks = chunkMarkdown(text, source, this.config, { sourceType, fileMtimeAt });
36997
37018
  const currentChunkIds = new Set(allChunks.map((chunk) => chunk.id));
36998
37019
  const toIndex = allChunks.filter((chunk) => !existingChunkIds.has(chunk.id));
36999
37020
  const toDelete = [...existingChunkIds].filter((id) => !currentChunkIds.has(id));
@@ -37080,12 +37101,9 @@ var MemoryIndexer = class {
37080
37101
  scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0 });
37081
37102
  });
37082
37103
  bm25Results.forEach((r, rank) => {
37083
- const existing = scoreMap.get(r.id);
37084
- if (existing) {
37085
- existing.bm25 = 1 / (RRF_K + rank + 1);
37086
- } else {
37087
- scoreMap.set(r.id, { dense: 0, bm25: 1 / (RRF_K + rank + 1) });
37088
- }
37104
+ const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0 };
37105
+ entry.bm25 = 1 / (RRF_K + rank + 1);
37106
+ scoreMap.set(r.id, entry);
37089
37107
  });
37090
37108
  const hasDense = this.store.vecAvailable && denseResults.length > 0;
37091
37109
  const numRetrievers = hasDense ? 2 : 1;
@@ -37159,7 +37177,10 @@ ${content}
37159
37177
  var import_stopword = __toESM(require_stopword_cjs(), 1);
37160
37178
  import Database from "better-sqlite3";
37161
37179
  import * as fs7 from "fs";
37180
+ import { createRequire } from "module";
37162
37181
  import * as path9 from "path";
37182
+ var require2 = createRequire(import.meta.url);
37183
+ var SCHEMA_VERSION = 2;
37163
37184
  var StoreError = class extends Error {
37164
37185
  constructor(message, cause) {
37165
37186
  super(message);
@@ -37199,7 +37220,7 @@ var MemoryStore = class {
37199
37220
  }
37200
37221
  loadVecExtension(db) {
37201
37222
  try {
37202
- const sqliteVec = __require("sqlite-vec");
37223
+ const sqliteVec = require2("sqlite-vec");
37203
37224
  sqliteVec.load(db);
37204
37225
  this._vecAvailable = true;
37205
37226
  } catch (err) {
@@ -37208,57 +37229,77 @@ var MemoryStore = class {
37208
37229
  }
37209
37230
  }
37210
37231
  createSchema(db) {
37211
- db.exec(`
37212
- CREATE TABLE IF NOT EXISTS memory_chunks (
37213
- rowid INTEGER PRIMARY KEY AUTOINCREMENT,
37214
- id TEXT NOT NULL UNIQUE,
37215
- source TEXT NOT NULL,
37216
- heading TEXT NOT NULL DEFAULT '',
37217
- heading_level INTEGER NOT NULL DEFAULT 0,
37218
- content TEXT NOT NULL,
37219
- line_start INTEGER NOT NULL,
37220
- line_end INTEGER NOT NULL,
37221
- indexed_at INTEGER NOT NULL
37222
- );
37232
+ try {
37233
+ const current = Number(db.pragma("user_version", { simple: true }) ?? 0);
37234
+ if (current < SCHEMA_VERSION) {
37235
+ db.exec(`
37236
+ DROP TRIGGER IF EXISTS memory_chunks_ai;
37237
+ DROP TRIGGER IF EXISTS memory_chunks_ad;
37238
+ DROP TABLE IF EXISTS memory_fts;
37239
+ DROP TABLE IF EXISTS memory_vec;
37240
+ DROP TABLE IF EXISTS memory_chunks;
37241
+ `);
37242
+ db.pragma(`user_version = ${SCHEMA_VERSION}`);
37243
+ }
37244
+ db.exec(`
37245
+ CREATE TABLE IF NOT EXISTS memory_chunks (
37246
+ rowid INTEGER PRIMARY KEY AUTOINCREMENT,
37247
+ id TEXT NOT NULL UNIQUE,
37248
+ source TEXT NOT NULL,
37249
+ source_type TEXT NOT NULL DEFAULT 'wiki',
37250
+ heading TEXT NOT NULL DEFAULT '',
37251
+ heading_level INTEGER NOT NULL DEFAULT 0,
37252
+ content TEXT NOT NULL,
37253
+ line_start INTEGER NOT NULL,
37254
+ line_end INTEGER NOT NULL,
37255
+ indexed_at INTEGER NOT NULL,
37256
+ file_mtime_at INTEGER NOT NULL DEFAULT 0
37257
+ );
37223
37258
 
37224
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
37225
- source,
37226
- heading,
37227
- content,
37228
- content='memory_chunks',
37229
- content_rowid='rowid'
37230
- );
37259
+ CREATE INDEX IF NOT EXISTS idx_memory_chunks_type_mtime
37260
+ ON memory_chunks (source_type, file_mtime_at DESC);
37231
37261
 
37232
- CREATE TRIGGER IF NOT EXISTS memory_chunks_ai
37233
- AFTER INSERT ON memory_chunks BEGIN
37234
- INSERT INTO memory_fts(rowid, source, heading, content)
37235
- VALUES (new.rowid, new.source, new.heading, new.content);
37236
- END;
37262
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
37263
+ source,
37264
+ heading,
37265
+ content,
37266
+ content='memory_chunks',
37267
+ content_rowid='rowid'
37268
+ );
37237
37269
 
37238
- CREATE TRIGGER IF NOT EXISTS memory_chunks_ad
37239
- AFTER DELETE ON memory_chunks BEGIN
37240
- INSERT INTO memory_fts(memory_fts, rowid, source, heading, content)
37241
- VALUES ('delete', old.rowid, old.source, old.heading, old.content);
37242
- END;
37243
- `);
37244
- if (this._vecAvailable) {
37245
- try {
37246
- db.exec(`
37247
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
37248
- embedding FLOAT[${this.config.vectorDimension}]
37249
- );
37250
- `);
37251
- } catch (err) {
37252
- console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
37253
- this._vecAvailable = false;
37270
+ CREATE TRIGGER IF NOT EXISTS memory_chunks_ai
37271
+ AFTER INSERT ON memory_chunks BEGIN
37272
+ INSERT INTO memory_fts(rowid, source, heading, content)
37273
+ VALUES (new.rowid, new.source, new.heading, new.content);
37274
+ END;
37275
+
37276
+ CREATE TRIGGER IF NOT EXISTS memory_chunks_ad
37277
+ AFTER DELETE ON memory_chunks BEGIN
37278
+ INSERT INTO memory_fts(memory_fts, rowid, source, heading, content)
37279
+ VALUES ('delete', old.rowid, old.source, old.heading, old.content);
37280
+ END;
37281
+ `);
37282
+ if (this._vecAvailable) {
37283
+ try {
37284
+ db.exec(`
37285
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
37286
+ embedding FLOAT[${this.config.vectorDimension}]
37287
+ );
37288
+ `);
37289
+ } catch (err) {
37290
+ console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
37291
+ this._vecAvailable = false;
37292
+ }
37254
37293
  }
37294
+ } catch (err) {
37295
+ throw new StoreError("schema migration failed", err);
37255
37296
  }
37256
37297
  }
37257
37298
  upsert(chunks, embeddings) {
37258
37299
  const insertChunk = this.db.prepare(`
37259
37300
  INSERT OR REPLACE INTO memory_chunks
37260
- (id, source, heading, heading_level, content, line_start, line_end, indexed_at)
37261
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
37301
+ (id, source, source_type, heading, heading_level, content, line_start, line_end, indexed_at, file_mtime_at)
37302
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
37262
37303
  `);
37263
37304
  let insertVec = null;
37264
37305
  if (this._vecAvailable) {
@@ -37272,15 +37313,17 @@ var MemoryStore = class {
37272
37313
  const info = insertChunk.run(
37273
37314
  chunk.id,
37274
37315
  chunk.source,
37316
+ chunk.sourceType,
37275
37317
  chunk.heading,
37276
37318
  chunk.headingLevel,
37277
37319
  chunk.content,
37278
37320
  chunk.lineStart,
37279
37321
  chunk.lineEnd,
37280
- Date.now()
37322
+ Date.now(),
37323
+ chunk.fileMtimeAt
37281
37324
  );
37282
37325
  if (insertVec && embeddings[i] && info.lastInsertRowid) {
37283
- const rowid = Number(info.lastInsertRowid);
37326
+ const rowid = typeof info.lastInsertRowid === "bigint" ? info.lastInsertRowid : BigInt(info.lastInsertRowid);
37284
37327
  const emb = embeddings[i];
37285
37328
  const buf = Buffer.from(emb.buffer, emb.byteOffset, emb.byteLength);
37286
37329
  insertVec.run(rowid, buf);
@@ -37304,6 +37347,20 @@ var MemoryStore = class {
37304
37347
  const rows = this.db.prepare(`SELECT DISTINCT source FROM memory_chunks`).all();
37305
37348
  return rows.map((r) => r.source);
37306
37349
  }
37350
+ getRecentSources(options) {
37351
+ const limit = Math.min(Math.max(1, options.limit || 5), 50);
37352
+ const whereClause = options.sourceType ? " WHERE source_type = ?" : "";
37353
+ const params = options.sourceType ? [options.sourceType, limit] : [limit];
37354
+ try {
37355
+ const rows = this.db.prepare(
37356
+ `SELECT source, MAX(file_mtime_at) AS t FROM memory_chunks${whereClause} GROUP BY source ORDER BY t DESC LIMIT ?`
37357
+ ).all(...params);
37358
+ return rows.map((row) => ({ source: row.source, fileMtimeAt: row.t }));
37359
+ } catch (err) {
37360
+ console.warn("[memory-store] Recent source lookup failed:", err);
37361
+ return [];
37362
+ }
37363
+ }
37307
37364
  deleteBySource(source) {
37308
37365
  const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE source = ?`).all(source);
37309
37366
  const deleteAll = this.db.transaction(() => {
@@ -37380,17 +37437,19 @@ var MemoryStore = class {
37380
37437
  if (ids.length === 0) return [];
37381
37438
  const placeholders = ids.map(() => "?").join(", ");
37382
37439
  const rows = this.db.prepare(
37383
- `SELECT id, source, heading, heading_level, content, line_start, line_end
37440
+ `SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
37384
37441
  FROM memory_chunks WHERE id IN (${placeholders})`
37385
37442
  ).all(...ids);
37386
37443
  return rows.map((r) => ({
37387
37444
  id: r.id,
37388
37445
  source: r.source,
37446
+ sourceType: r.source_type,
37389
37447
  heading: r.heading,
37390
37448
  headingLevel: r.heading_level,
37391
37449
  content: r.content,
37392
37450
  lineStart: r.line_start,
37393
- lineEnd: r.line_end
37451
+ lineEnd: r.line_end,
37452
+ fileMtimeAt: r.file_mtime_at
37394
37453
  }));
37395
37454
  }
37396
37455
  get vecAvailable() {
@@ -37407,8 +37466,8 @@ import * as path13 from "node:path";
37407
37466
 
37408
37467
  // src/services/digest/constants.ts
37409
37468
  import * as path10 from "node:path";
37410
- var DEFAULT_DIGEST_MODEL_ID = "default" /* DEFAULT */;
37411
- var DEFAULT_DIGEST_MAX_INPUT_CHARS = 3e3;
37469
+ var DEFAULT_DIGEST_MODEL_ID = "base" /* BASE */;
37470
+ var DEFAULT_DIGEST_MAX_INPUT_CHARS = 1e4;
37412
37471
  var DEFAULT_DIGEST_TIMEOUT_MS = 12e4;
37413
37472
  var KIT_DIR = ".agent-kit";
37414
37473
  var WIKI_DIR = path10.join(KIT_DIR, "wiki");
@@ -37418,7 +37477,7 @@ var DIGEST_WORKER_DIR = path10.join(WIKI_DIR, "digest");
37418
37477
  var DIGEST_LOCKFILE_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.lock");
37419
37478
  var DIGEST_WORKER_STATUS_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.status.json");
37420
37479
  var DIGEST_WORKER_LOG_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.log");
37421
- var LLAMA_CONTEXT_SIZE = 4096;
37480
+ var LLAMA_CONTEXT_SIZE = 8192;
37422
37481
  var LLAMA_MAX_GENERATED_TOKENS = 512;
37423
37482
  var LLAMA_TEMPERATURE = 0.1;
37424
37483
 
@@ -37451,9 +37510,9 @@ function writeProvisionalDigestFile(outDir, input, markdown) {
37451
37510
  return markdownPath;
37452
37511
  }
37453
37512
  function writeConversationDigestSettings(workspaceRoot2, digest) {
37454
- const current = loadProjectSettings(workspaceRoot2);
37513
+ const current = loadGlobalSettings();
37455
37514
  const memory = current.memory ?? {};
37456
- writeProjectSettings(workspaceRoot2, {
37515
+ writeGlobalSettings({
37457
37516
  memory: {
37458
37517
  ...memory,
37459
37518
  enabled: memory.enabled ?? true,
@@ -37478,8 +37537,8 @@ var DIGEST_MODEL_REGISTRY = {
37478
37537
  sourceUrl: "https://huggingface.co/bartowski/Qwen2.5-0.5B-Instruct-GGUF",
37479
37538
  enabled: true
37480
37539
  },
37481
- ["default" /* DEFAULT */]: {
37482
- id: "default" /* DEFAULT */,
37540
+ ["base" /* BASE */]: {
37541
+ id: "base" /* BASE */,
37483
37542
  ggufUri: "hf:bartowski/Qwen2.5-1.5B-Instruct-GGUF/Qwen2.5-1.5B-Instruct-Q4_K_M.gguf",
37484
37543
  approxSizeBytes: 1e9,
37485
37544
  license: "Apache-2.0",
@@ -37556,39 +37615,19 @@ function buildPrompt(input, maxInputChars) {
37556
37615
  {
37557
37616
  role: "system",
37558
37617
  content: [
37559
- "# ROLE",
37560
- "You write short project-memory wiki pages from developer transcripts.",
37561
- "Your output is temporary recall to prevent blind steps.",
37562
- "",
37563
- "# RULES",
37564
- "1. Read the end of the export FIRST to discover the ultimate resolution.",
37565
- "2. What the user says LAST ALWAYS overrides earlier agreements.",
37566
- "3. Capture absolute engineering conclusions, never conversational pleasantries.",
37567
- "4. Return Markdown only. Do not wrap output in code blocks (```markdown ... ```)."
37618
+ "You are a technical summarizer.",
37619
+ "Focus ONLY on the final decisions made at the end of the transcript.",
37620
+ "Never invent or guess details.",
37621
+ "Keep the output concise and strictly factual."
37568
37622
  ].join("\n")
37569
37623
  },
37570
37624
  {
37571
37625
  role: "user",
37572
37626
  content: [
37573
- "Generate a high-level project memory page. STRICTLY adhere to this architectural layout:",
37574
- "",
37575
- "<layout>",
37576
- "# Digest: " + titleFromSource(input.sourcePath),
37577
- "- **Ultimate Core Resolution**: [Describe the final working state of the feature and what problem it solves in 1-2 sentences. No conversational filler.]",
37578
- "- **Architectural Changes**: [List 3-4 high-level engineering decisions made (e.g., shifts in hook timing, data isolation, atomic locks). Focus on systemic changes, NOT individual files.]",
37579
- "- **Component State**: [Briefly state how the Plugin side and the MCP/CLI side now interact based on the final decision.]",
37580
- "- **Considered & Rejected**: [List concepts proposed but discarded (e.g., initial hooks location, mocking strategies). Omit if empty.]",
37581
- "</layout>",
37582
- "",
37583
- "# CRITICAL RULES:",
37584
- "- DO NOT list individual file paths, line numbers, or specific test cases (EXCLUDE lists of files).",
37585
- "- Focus entirely on the SYSTEM DESIGN and ARCHITECTURE that the next engineer needs to know.",
37586
- "- Keep output under 300 tokens. Stop immediately after the last valid section.",
37587
- "",
37588
- "Source path: " + input.sourcePath,
37589
- '<conversation_export format="memory-kit">',
37627
+ "Transcript:",
37590
37628
  conversationExport,
37591
- "</conversation_export>"
37629
+ "",
37630
+ "Task: Write a brief summary paragraph of the final agreed-upon decisions in the transcript above. Then, provide a simple bulleted list of the specific technical changes or outcomes."
37592
37631
  ].join("\n")
37593
37632
  }
37594
37633
  ];
@@ -37651,7 +37690,18 @@ async function createLlamaLocalDigestProvider(modelId) {
37651
37690
  options.timeoutMs,
37652
37691
  () => new LlamaLocalDigestProviderError(`Llama provider timed out after ${options.timeoutMs}ms`)
37653
37692
  );
37654
- return sanitizeConversationDigestMarkdown(response);
37693
+ const metadata = `## Digest: ${titleFromSource(input.sourcePath)}
37694
+
37695
+ | Key | Value |
37696
+ | ------ | ----- |
37697
+ | **Source** | ${input.sourcePath} |
37698
+ | **Generated** | ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} |
37699
+ | **Model** | ${modelId} |
37700
+ `;
37701
+ const sanitized = sanitizeConversationDigestMarkdown(response);
37702
+ return `${metadata}
37703
+
37704
+ ${sanitized}`;
37655
37705
  } finally {
37656
37706
  await context.dispose();
37657
37707
  }
@@ -37685,19 +37735,25 @@ function discoverPendingConversationFiles(workspaceRoot2) {
37685
37735
  }
37686
37736
  });
37687
37737
  }
37688
- async function indexProvisionalDigestFile(workspaceRoot2, markdownPath) {
37738
+ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer) {
37739
+ let store;
37689
37740
  try {
37690
- const settings2 = loadProjectSettings(workspaceRoot2);
37691
- const config2 = resolveMemoryConfig(settings2, workspaceRoot2);
37692
- const store = new MemoryStore(path13.join(config2.wikiDir, "index.db"), config2);
37693
- const embedder = { embed: async (_texts) => [], initialize: async () => {
37694
- } };
37695
- const indexer = new MemoryIndexer(store, embedder, config2);
37696
- await indexer.indexFile(markdownPath);
37741
+ if (indexer) {
37742
+ await indexer.indexFile(markdownPath);
37743
+ return { indexed: true };
37744
+ }
37745
+ const settings = loadGlobalSettings();
37746
+ const config2 = resolveMemoryConfig(settings, workspaceRoot2);
37747
+ if (config2.enabled !== true) return { indexed: false };
37748
+ store = new MemoryStore(path13.join(config2.wikiDir, "index.db"), config2);
37749
+ const embedder = new Embedder(config2.embeddingModel);
37750
+ await embedder.initialize();
37751
+ await new MemoryIndexer(store, embedder, config2).indexFile(markdownPath);
37697
37752
  return { indexed: true };
37698
37753
  } catch (err) {
37699
- const message = err instanceof Error ? err.message : String(err);
37700
- return { indexed: false, error: message };
37754
+ return { indexed: false, error: err instanceof Error ? err.message : String(err) };
37755
+ } finally {
37756
+ store?.close();
37701
37757
  }
37702
37758
  }
37703
37759
  async function digestConversationFile(options) {
@@ -37705,7 +37761,7 @@ async function digestConversationFile(options) {
37705
37761
  const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
37706
37762
  const existingPath = findExistingProvisionalDigest(outDir, input);
37707
37763
  if (existingPath) {
37708
- const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath);
37764
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath, options.indexer);
37709
37765
  return {
37710
37766
  markdown: existingPath,
37711
37767
  status: "provisional",
@@ -37722,7 +37778,7 @@ async function digestConversationFile(options) {
37722
37778
  timeoutMs: options.timeoutMs ?? DEFAULT_DIGEST_TIMEOUT_MS
37723
37779
  });
37724
37780
  const markdownPath = writeProvisionalDigestFile(outDir, input, markdown);
37725
- const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath);
37781
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath, options.indexer);
37726
37782
  return {
37727
37783
  markdown: markdownPath,
37728
37784
  status: "provisional",
@@ -37739,7 +37795,7 @@ async function digestPendingConversations({
37739
37795
  digestFn = digestConversationFile,
37740
37796
  onProgress
37741
37797
  }) {
37742
- const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot2));
37798
+ const digestConfig = resolveConversationDigestConfig(loadGlobalSettings());
37743
37799
  if (!digestConfig || digestConfig.enabled === false) {
37744
37800
  return { ok: true, initialized: false, action: "noop", reason: "not-initialized" };
37745
37801
  }
@@ -37747,11 +37803,21 @@ async function digestPendingConversations({
37747
37803
  if (!tryAcquireProcessLock(lockPath)) {
37748
37804
  return { ok: true, initialized: true, action: "noop", reason: "locked" };
37749
37805
  }
37806
+ let closeIndexer;
37750
37807
  try {
37751
37808
  const candidates = discoverPendingConversationFiles(workspaceRoot2);
37752
37809
  if (candidates.length === 0) {
37753
37810
  return { ok: true, initialized: true, action: "noop", reason: "no-pending" };
37754
37811
  }
37812
+ let indexer;
37813
+ const memoryConfig = resolveMemoryConfig(loadGlobalSettings(), workspaceRoot2);
37814
+ if (memoryConfig.enabled === true) {
37815
+ const store = new MemoryStore(path13.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
37816
+ const embedder = new Embedder(memoryConfig.embeddingModel);
37817
+ indexer = new MemoryIndexer(store, embedder, memoryConfig);
37818
+ closeIndexer = () => store.close();
37819
+ await embedder.initialize();
37820
+ }
37755
37821
  let count = 0;
37756
37822
  let skipped = 0;
37757
37823
  let errors = 0;
@@ -37760,7 +37826,8 @@ async function digestPendingConversations({
37760
37826
  const result = await digestFn({
37761
37827
  workspaceRoot: workspaceRoot2,
37762
37828
  inputPath: filePath,
37763
- modelId: digestConfig.modelId
37829
+ modelId: digestConfig.modelId,
37830
+ indexer
37764
37831
  });
37765
37832
  if (result.skipped) {
37766
37833
  skipped++;
@@ -37777,9 +37844,17 @@ async function digestPendingConversations({
37777
37844
  return { ok: true, initialized: true, action: "digested", count, skipped, errors };
37778
37845
  } catch (err) {
37779
37846
  const message = err instanceof Error ? err.message : String(err);
37847
+ console.warn("[digest] Pending conversation digest failed:", err);
37780
37848
  return { ok: false, initialized: true, action: "error", error: message };
37781
37849
  } finally {
37782
37850
  releaseLock(lockPath);
37851
+ if (closeIndexer) {
37852
+ try {
37853
+ closeIndexer();
37854
+ } catch (err) {
37855
+ console.warn("[digest] Failed to close memory indexer store:", err);
37856
+ }
37857
+ }
37783
37858
  }
37784
37859
  }
37785
37860
  async function initializeConversationDigestModel(input) {
@@ -37821,19 +37896,18 @@ async function initializeConversationDigestModel(input) {
37821
37896
  function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2 = process.cwd()) {
37822
37897
  server2.tool(
37823
37898
  "kit_memory_search",
37824
- "Search persistent memory for context relevant to the current question. Returns semantically relevant results using hybrid dense+BM25+RRF search.",
37899
+ "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.",
37825
37900
  {
37826
- query: external_exports.string().min(1).describe("Search query"),
37901
+ query: external_exports.string().min(1).describe("Keyword-only search query with stopwords removed"),
37827
37902
  top_k: external_exports.number().int().positive().optional().describe("Number of results to return")
37828
37903
  },
37829
37904
  async ({ query, top_k }) => {
37830
37905
  try {
37831
37906
  const results = await indexer.search(query, top_k ?? config2.topK);
37907
+ const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
37832
37908
  if (results.length === 0) {
37833
- const degradedNote2 = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
37834
- return mcpText(`${degradedNote2}No memories found for query: "${query}"`);
37909
+ return mcpText(`${degradedNote}No memories found for query: "${query}"`);
37835
37910
  }
37836
- const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
37837
37911
  const formatted = results.map((r) => {
37838
37912
  const displaySource = r.chunk.source.replace(/^compiled\//, "");
37839
37913
  const content = r.contentSource === "fallback" ? `\u26A0\uFE0F Source file unavailable \u2014 showing matched chunk only:
@@ -37843,8 +37917,36 @@ ${content}`;
37843
37917
  }).join("\n\n---\n\n");
37844
37918
  return mcpText(`${degradedNote}${formatted}`);
37845
37919
  } catch (err) {
37846
- const message = err instanceof Error ? err.message : String(err);
37847
- return mcpText(`kit_memory_search failed: ${message}`);
37920
+ return mcpText(`kit_memory_search failed: ${formatError2(err)}`);
37921
+ }
37922
+ }
37923
+ );
37924
+ server2.tool(
37925
+ "kit_memory_recent",
37926
+ 'Return the N most recently updated memory sources. Optionally filter by source type. Use for temporal questions like "what did we do last session" or "recent".',
37927
+ {
37928
+ n: external_exports.number().int().positive().max(50).optional().describe("How many recent sources to return (default 5)"),
37929
+ source_type: external_exports.enum(SOURCE_TYPES).optional().describe("Optional source type filter")
37930
+ },
37931
+ async ({ n, source_type }) => {
37932
+ try {
37933
+ const rows = store.getRecentSources({ limit: n ?? 5, sourceType: source_type });
37934
+ if (rows.length === 0) return mcpText("No recent sources found.");
37935
+ const blocks = [];
37936
+ for (const row of rows) {
37937
+ try {
37938
+ const content = fs11.readFileSync(path14.join(config2.wikiDir, row.source), "utf8");
37939
+ const displaySource = row.source.replace(/^compiled\//, "");
37940
+ blocks.push(`### ${displaySource}
37941
+ ${content}`);
37942
+ } catch (err) {
37943
+ console.warn(`[memory] Skipping unavailable recent source ${row.source}:`, err);
37944
+ }
37945
+ }
37946
+ if (blocks.length === 0) return mcpText("No recent sources found.");
37947
+ return mcpText(blocks.join("\n\n---\n\n"));
37948
+ } catch (err) {
37949
+ return mcpText(`kit_memory_recent failed: ${formatError2(err)}`);
37848
37950
  }
37849
37951
  }
37850
37952
  );
@@ -37863,8 +37965,10 @@ ${content}`;
37863
37965
  message: "Saved to wiki/raw \u2014 will be indexed after next /wiki compile"
37864
37966
  });
37865
37967
  } catch (err) {
37866
- const message = err instanceof Error ? err.message : String(err);
37867
- return mcpJson({ saved: false, error: message });
37968
+ return mcpJson({
37969
+ saved: false,
37970
+ error: formatError2(err)
37971
+ });
37868
37972
  }
37869
37973
  }
37870
37974
  );
@@ -37877,7 +37981,7 @@ ${content}`;
37877
37981
  },
37878
37982
  async ({ model_id, enabled }) => {
37879
37983
  try {
37880
- const existingConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot2));
37984
+ const existingConfig = resolveConversationDigestConfig(loadGlobalSettings());
37881
37985
  const result = await initializeConversationDigestModel({
37882
37986
  workspaceRoot: workspaceRoot2,
37883
37987
  modelId: model_id ?? existingConfig?.modelId ?? DEFAULT_DIGEST_MODEL_ID,
@@ -37891,15 +37995,15 @@ ${content}`;
37891
37995
  error: result.error
37892
37996
  });
37893
37997
  } catch (err) {
37894
- const message = err instanceof Error ? err.message : String(err);
37895
- return mcpJson({ initialized: false, error: message });
37998
+ return mcpJson({ initialized: false, error: formatError2(err) });
37896
37999
  }
37897
38000
  }
37898
38001
  );
37899
38002
  }
37900
- function registerMemoryTools(server2, settings2, workspaceRoot2, overrides) {
37901
- if (settings2.memory?.enabled !== true) return null;
37902
- const config2 = overrides?.config ?? resolveMemoryConfig(settings2, workspaceRoot2);
38003
+ function registerMemoryTools(server2, workspaceRoot2, overrides) {
38004
+ const settings = overrides?.settings ?? loadGlobalSettings();
38005
+ if (settings.memory?.enabled !== true) return null;
38006
+ const config2 = overrides?.config ?? resolveMemoryConfig(settings, workspaceRoot2);
37903
38007
  const store = overrides?.store ?? new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
37904
38008
  const indexer = overrides?.indexer ?? (() => {
37905
38009
  const embedder = new Embedder(config2.embeddingModel);
@@ -37918,8 +38022,7 @@ registerIntegrationTools(server);
37918
38022
  registerCoreTools(server);
37919
38023
  registerAgentTools(server);
37920
38024
  var workspaceRoot = getWorkspaceRoot();
37921
- var settings = loadProjectSettings(workspaceRoot);
37922
- var memoryIndexer = registerMemoryTools(server, settings, workspaceRoot);
38025
+ var memoryIndexer = registerMemoryTools(server, workspaceRoot);
37923
38026
  var transport = new StdioServerTransport();
37924
38027
  await server.connect(transport);
37925
38028
  if (memoryIndexer) {