@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/cli.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 __copyProps = (to, from, except, desc) => {
@@ -14926,8 +14919,8 @@ import * as path9 from "node:path";
14926
14919
 
14927
14920
  // src/services/digest/constants.ts
14928
14921
  import * as path from "node:path";
14929
- var DEFAULT_DIGEST_MODEL_ID = "default" /* DEFAULT */;
14930
- var DEFAULT_DIGEST_MAX_INPUT_CHARS = 3e3;
14922
+ var DEFAULT_DIGEST_MODEL_ID = "base" /* BASE */;
14923
+ var DEFAULT_DIGEST_MAX_INPUT_CHARS = 1e4;
14931
14924
  var DEFAULT_DIGEST_TIMEOUT_MS = 12e4;
14932
14925
  var DIGEST_TIMEOUT_GRACE_MS = 5e3;
14933
14926
  var DIGEST_WORKER_FLAG = "__agent-kit-digest-worker";
@@ -14940,7 +14933,7 @@ var DIGEST_WORKER_DIR = path.join(WIKI_DIR, "digest");
14940
14933
  var DIGEST_LOCKFILE_REL_PATH = path.join(DIGEST_WORKER_DIR, "digest-worker.lock");
14941
14934
  var DIGEST_WORKER_STATUS_REL_PATH = path.join(DIGEST_WORKER_DIR, "digest-worker.status.json");
14942
14935
  var DIGEST_WORKER_LOG_REL_PATH = path.join(DIGEST_WORKER_DIR, "digest-worker.log");
14943
- var LLAMA_CONTEXT_SIZE = 4096;
14936
+ var LLAMA_CONTEXT_SIZE = 8192;
14944
14937
  var LLAMA_MAX_GENERATED_TOKENS = 512;
14945
14938
  var LLAMA_TEMPERATURE = 0.1;
14946
14939
 
@@ -15022,6 +15015,7 @@ function sha256Hex(content) {
15022
15015
 
15023
15016
  // src/core/config/index.ts
15024
15017
  import * as fs2 from "fs";
15018
+ import * as os from "os";
15025
15019
  import * as path3 from "path";
15026
15020
 
15027
15021
  // src/services/memory/constants.ts
@@ -15031,7 +15025,7 @@ var DEFAULT_MEMORY_CONFIG = {
15031
15025
  topK: 5,
15032
15026
  chunkSize: 1500,
15033
15027
  overlapLines: 2,
15034
- embeddingModel: "small" /* SMALL */,
15028
+ embeddingModel: "base" /* BASE */,
15035
15029
  vectorDimension: 384
15036
15030
  };
15037
15031
  var LOCK_RETRY_MS = 50;
@@ -15048,21 +15042,23 @@ function resolveMemoryConfig(settings, workspaceRoot) {
15048
15042
  wikiDir: typeof override.wikiDir === "string" && override.wikiDir.length > 0 ? override.wikiDir : path3.join(workspaceRoot, ".agent-kit", "wiki")
15049
15043
  };
15050
15044
  }
15051
- function loadProjectSettings(projectDir) {
15052
- const settingsPath = path3.join(projectDir, ".agent-kit", "settings.json");
15053
- if (fs2.existsSync(settingsPath)) {
15045
+ function getGlobalSettingsFile() {
15046
+ return path3.join(os.homedir(), ".agent-kit", "settings.json");
15047
+ }
15048
+ function loadGlobalSettings() {
15049
+ const file = getGlobalSettingsFile();
15050
+ if (fs2.existsSync(file)) {
15054
15051
  try {
15055
- return JSON.parse(fs2.readFileSync(settingsPath, "utf-8"));
15052
+ return JSON.parse(fs2.readFileSync(file, "utf-8"));
15056
15053
  } catch (error) {
15057
- console.error(`[agent-kit] Warning: Failed to parse ${settingsPath}:`, error);
15054
+ console.error(`[agent-kit] Warning: Failed to parse ${file}:`, error);
15058
15055
  }
15059
15056
  }
15060
15057
  return {};
15061
15058
  }
15062
- function writeProjectSettings(workspaceRoot, patch) {
15063
- const settingsPath = path3.join(workspaceRoot, ".agent-kit", "settings.json");
15064
- const current = loadProjectSettings(workspaceRoot);
15065
- atomicWriteJsonFile(settingsPath, { ...current, ...patch });
15059
+ function writeGlobalSettings(patch) {
15060
+ const current = loadGlobalSettings();
15061
+ atomicWriteJsonFile(getGlobalSettingsFile(), { ...current, ...patch });
15066
15062
  }
15067
15063
  function resolveConversationDigestConfig(settings) {
15068
15064
  const digest = settings.memory?.conversationDigest;
@@ -15104,9 +15100,9 @@ function writeProvisionalDigestFile(outDir, input, markdown) {
15104
15100
  return markdownPath;
15105
15101
  }
15106
15102
  function writeConversationDigestSettings(workspaceRoot, digest) {
15107
- const current = loadProjectSettings(workspaceRoot);
15103
+ const current = loadGlobalSettings();
15108
15104
  const memory = current.memory ?? {};
15109
- writeProjectSettings(workspaceRoot, {
15105
+ writeGlobalSettings({
15110
15106
  memory: {
15111
15107
  ...memory,
15112
15108
  enabled: memory.enabled ?? true,
@@ -15131,8 +15127,8 @@ var DIGEST_MODEL_REGISTRY = {
15131
15127
  sourceUrl: "https://huggingface.co/bartowski/Qwen2.5-0.5B-Instruct-GGUF",
15132
15128
  enabled: true
15133
15129
  },
15134
- ["default" /* DEFAULT */]: {
15135
- id: "default" /* DEFAULT */,
15130
+ ["base" /* BASE */]: {
15131
+ id: "base" /* BASE */,
15136
15132
  ggufUri: "hf:bartowski/Qwen2.5-1.5B-Instruct-GGUF/Qwen2.5-1.5B-Instruct-Q4_K_M.gguf",
15137
15133
  approxSizeBytes: 1e9,
15138
15134
  license: "Apache-2.0",
@@ -15186,10 +15182,11 @@ async function withTimeout(promise, timeoutMs, createError) {
15186
15182
  }
15187
15183
 
15188
15184
  // src/utils/paths.ts
15189
- import * as os from "os";
15185
+ import * as os2 from "os";
15190
15186
  import * as path5 from "path";
15191
- var AGENT_KIT_HOME = path5.join(os.homedir(), ".agent-kit");
15187
+ var AGENT_KIT_HOME = path5.join(os2.homedir(), ".agent-kit");
15192
15188
  var CREDENTIALS_FILE = path5.join(AGENT_KIT_HOME, "credentials");
15189
+ var GLOBAL_SETTINGS_FILE = path5.join(AGENT_KIT_HOME, "settings.json");
15193
15190
  var MODEL_CACHE_DIR = path5.join(AGENT_KIT_HOME, "cache", "models");
15194
15191
  var FASTEMBED_CACHE_DIR = path5.join(MODEL_CACHE_DIR, "fastembed");
15195
15192
 
@@ -15217,39 +15214,19 @@ function buildPrompt(input, maxInputChars) {
15217
15214
  {
15218
15215
  role: "system",
15219
15216
  content: [
15220
- "# ROLE",
15221
- "You write short project-memory wiki pages from developer transcripts.",
15222
- "Your output is temporary recall to prevent blind steps.",
15223
- "",
15224
- "# RULES",
15225
- "1. Read the end of the export FIRST to discover the ultimate resolution.",
15226
- "2. What the user says LAST ALWAYS overrides earlier agreements.",
15227
- "3. Capture absolute engineering conclusions, never conversational pleasantries.",
15228
- "4. Return Markdown only. Do not wrap output in code blocks (```markdown ... ```)."
15217
+ "You are a technical summarizer.",
15218
+ "Focus ONLY on the final decisions made at the end of the transcript.",
15219
+ "Never invent or guess details.",
15220
+ "Keep the output concise and strictly factual."
15229
15221
  ].join("\n")
15230
15222
  },
15231
15223
  {
15232
15224
  role: "user",
15233
15225
  content: [
15234
- "Generate a high-level project memory page. STRICTLY adhere to this architectural layout:",
15235
- "",
15236
- "<layout>",
15237
- "# Digest: " + titleFromSource(input.sourcePath),
15238
- "- **Ultimate Core Resolution**: [Describe the final working state of the feature and what problem it solves in 1-2 sentences. No conversational filler.]",
15239
- "- **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.]",
15240
- "- **Component State**: [Briefly state how the Plugin side and the MCP/CLI side now interact based on the final decision.]",
15241
- "- **Considered & Rejected**: [List concepts proposed but discarded (e.g., initial hooks location, mocking strategies). Omit if empty.]",
15242
- "</layout>",
15243
- "",
15244
- "# CRITICAL RULES:",
15245
- "- DO NOT list individual file paths, line numbers, or specific test cases (EXCLUDE lists of files).",
15246
- "- Focus entirely on the SYSTEM DESIGN and ARCHITECTURE that the next engineer needs to know.",
15247
- "- Keep output under 300 tokens. Stop immediately after the last valid section.",
15248
- "",
15249
- "Source path: " + input.sourcePath,
15250
- '<conversation_export format="memory-kit">',
15226
+ "Transcript:",
15251
15227
  conversationExport,
15252
- "</conversation_export>"
15228
+ "",
15229
+ "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."
15253
15230
  ].join("\n")
15254
15231
  }
15255
15232
  ];
@@ -15312,7 +15289,18 @@ async function createLlamaLocalDigestProvider(modelId) {
15312
15289
  options.timeoutMs,
15313
15290
  () => new LlamaLocalDigestProviderError(`Llama provider timed out after ${options.timeoutMs}ms`)
15314
15291
  );
15315
- return sanitizeConversationDigestMarkdown(response);
15292
+ const metadata = `## Digest: ${titleFromSource(input.sourcePath)}
15293
+
15294
+ | Key | Value |
15295
+ | ------ | ----- |
15296
+ | **Source** | ${input.sourcePath} |
15297
+ | **Generated** | ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} |
15298
+ | **Model** | ${modelId} |
15299
+ `;
15300
+ const sanitized = sanitizeConversationDigestMarkdown(response);
15301
+ return `${metadata}
15302
+
15303
+ ${sanitized}`;
15316
15304
  } finally {
15317
15305
  await context.dispose();
15318
15306
  }
@@ -15324,7 +15312,10 @@ async function createLlamaLocalDigestProvider(modelId) {
15324
15312
  var import_stopword = __toESM(require_stopword_cjs(), 1);
15325
15313
  import Database from "better-sqlite3";
15326
15314
  import * as fs5 from "fs";
15315
+ import { createRequire } from "module";
15327
15316
  import * as path7 from "path";
15317
+ var require2 = createRequire(import.meta.url);
15318
+ var SCHEMA_VERSION = 2;
15328
15319
  var StoreError = class extends Error {
15329
15320
  constructor(message, cause) {
15330
15321
  super(message);
@@ -15364,7 +15355,7 @@ var MemoryStore = class {
15364
15355
  }
15365
15356
  loadVecExtension(db) {
15366
15357
  try {
15367
- const sqliteVec = __require("sqlite-vec");
15358
+ const sqliteVec = require2("sqlite-vec");
15368
15359
  sqliteVec.load(db);
15369
15360
  this._vecAvailable = true;
15370
15361
  } catch (err) {
@@ -15373,57 +15364,77 @@ var MemoryStore = class {
15373
15364
  }
15374
15365
  }
15375
15366
  createSchema(db) {
15376
- db.exec(`
15377
- CREATE TABLE IF NOT EXISTS memory_chunks (
15378
- rowid INTEGER PRIMARY KEY AUTOINCREMENT,
15379
- id TEXT NOT NULL UNIQUE,
15380
- source TEXT NOT NULL,
15381
- heading TEXT NOT NULL DEFAULT '',
15382
- heading_level INTEGER NOT NULL DEFAULT 0,
15383
- content TEXT NOT NULL,
15384
- line_start INTEGER NOT NULL,
15385
- line_end INTEGER NOT NULL,
15386
- indexed_at INTEGER NOT NULL
15387
- );
15367
+ try {
15368
+ const current = Number(db.pragma("user_version", { simple: true }) ?? 0);
15369
+ if (current < SCHEMA_VERSION) {
15370
+ db.exec(`
15371
+ DROP TRIGGER IF EXISTS memory_chunks_ai;
15372
+ DROP TRIGGER IF EXISTS memory_chunks_ad;
15373
+ DROP TABLE IF EXISTS memory_fts;
15374
+ DROP TABLE IF EXISTS memory_vec;
15375
+ DROP TABLE IF EXISTS memory_chunks;
15376
+ `);
15377
+ db.pragma(`user_version = ${SCHEMA_VERSION}`);
15378
+ }
15379
+ db.exec(`
15380
+ CREATE TABLE IF NOT EXISTS memory_chunks (
15381
+ rowid INTEGER PRIMARY KEY AUTOINCREMENT,
15382
+ id TEXT NOT NULL UNIQUE,
15383
+ source TEXT NOT NULL,
15384
+ source_type TEXT NOT NULL DEFAULT 'wiki',
15385
+ heading TEXT NOT NULL DEFAULT '',
15386
+ heading_level INTEGER NOT NULL DEFAULT 0,
15387
+ content TEXT NOT NULL,
15388
+ line_start INTEGER NOT NULL,
15389
+ line_end INTEGER NOT NULL,
15390
+ indexed_at INTEGER NOT NULL,
15391
+ file_mtime_at INTEGER NOT NULL DEFAULT 0
15392
+ );
15388
15393
 
15389
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
15390
- source,
15391
- heading,
15392
- content,
15393
- content='memory_chunks',
15394
- content_rowid='rowid'
15395
- );
15394
+ CREATE INDEX IF NOT EXISTS idx_memory_chunks_type_mtime
15395
+ ON memory_chunks (source_type, file_mtime_at DESC);
15396
15396
 
15397
- CREATE TRIGGER IF NOT EXISTS memory_chunks_ai
15398
- AFTER INSERT ON memory_chunks BEGIN
15399
- INSERT INTO memory_fts(rowid, source, heading, content)
15400
- VALUES (new.rowid, new.source, new.heading, new.content);
15401
- END;
15397
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
15398
+ source,
15399
+ heading,
15400
+ content,
15401
+ content='memory_chunks',
15402
+ content_rowid='rowid'
15403
+ );
15402
15404
 
15403
- CREATE TRIGGER IF NOT EXISTS memory_chunks_ad
15404
- AFTER DELETE ON memory_chunks BEGIN
15405
- INSERT INTO memory_fts(memory_fts, rowid, source, heading, content)
15406
- VALUES ('delete', old.rowid, old.source, old.heading, old.content);
15407
- END;
15408
- `);
15409
- if (this._vecAvailable) {
15410
- try {
15411
- db.exec(`
15412
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
15413
- embedding FLOAT[${this.config.vectorDimension}]
15414
- );
15415
- `);
15416
- } catch (err) {
15417
- console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
15418
- this._vecAvailable = false;
15405
+ CREATE TRIGGER IF NOT EXISTS memory_chunks_ai
15406
+ AFTER INSERT ON memory_chunks BEGIN
15407
+ INSERT INTO memory_fts(rowid, source, heading, content)
15408
+ VALUES (new.rowid, new.source, new.heading, new.content);
15409
+ END;
15410
+
15411
+ CREATE TRIGGER IF NOT EXISTS memory_chunks_ad
15412
+ AFTER DELETE ON memory_chunks BEGIN
15413
+ INSERT INTO memory_fts(memory_fts, rowid, source, heading, content)
15414
+ VALUES ('delete', old.rowid, old.source, old.heading, old.content);
15415
+ END;
15416
+ `);
15417
+ if (this._vecAvailable) {
15418
+ try {
15419
+ db.exec(`
15420
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
15421
+ embedding FLOAT[${this.config.vectorDimension}]
15422
+ );
15423
+ `);
15424
+ } catch (err) {
15425
+ console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
15426
+ this._vecAvailable = false;
15427
+ }
15419
15428
  }
15429
+ } catch (err) {
15430
+ throw new StoreError("schema migration failed", err);
15420
15431
  }
15421
15432
  }
15422
15433
  upsert(chunks, embeddings) {
15423
15434
  const insertChunk = this.db.prepare(`
15424
15435
  INSERT OR REPLACE INTO memory_chunks
15425
- (id, source, heading, heading_level, content, line_start, line_end, indexed_at)
15426
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
15436
+ (id, source, source_type, heading, heading_level, content, line_start, line_end, indexed_at, file_mtime_at)
15437
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15427
15438
  `);
15428
15439
  let insertVec = null;
15429
15440
  if (this._vecAvailable) {
@@ -15437,15 +15448,17 @@ var MemoryStore = class {
15437
15448
  const info = insertChunk.run(
15438
15449
  chunk.id,
15439
15450
  chunk.source,
15451
+ chunk.sourceType,
15440
15452
  chunk.heading,
15441
15453
  chunk.headingLevel,
15442
15454
  chunk.content,
15443
15455
  chunk.lineStart,
15444
15456
  chunk.lineEnd,
15445
- Date.now()
15457
+ Date.now(),
15458
+ chunk.fileMtimeAt
15446
15459
  );
15447
15460
  if (insertVec && embeddings[i] && info.lastInsertRowid) {
15448
- const rowid = Number(info.lastInsertRowid);
15461
+ const rowid = typeof info.lastInsertRowid === "bigint" ? info.lastInsertRowid : BigInt(info.lastInsertRowid);
15449
15462
  const emb = embeddings[i];
15450
15463
  const buf = Buffer.from(emb.buffer, emb.byteOffset, emb.byteLength);
15451
15464
  insertVec.run(rowid, buf);
@@ -15469,6 +15482,20 @@ var MemoryStore = class {
15469
15482
  const rows = this.db.prepare(`SELECT DISTINCT source FROM memory_chunks`).all();
15470
15483
  return rows.map((r) => r.source);
15471
15484
  }
15485
+ getRecentSources(options) {
15486
+ const limit = Math.min(Math.max(1, options.limit || 5), 50);
15487
+ const whereClause = options.sourceType ? " WHERE source_type = ?" : "";
15488
+ const params = options.sourceType ? [options.sourceType, limit] : [limit];
15489
+ try {
15490
+ const rows = this.db.prepare(
15491
+ `SELECT source, MAX(file_mtime_at) AS t FROM memory_chunks${whereClause} GROUP BY source ORDER BY t DESC LIMIT ?`
15492
+ ).all(...params);
15493
+ return rows.map((row) => ({ source: row.source, fileMtimeAt: row.t }));
15494
+ } catch (err) {
15495
+ console.warn("[memory-store] Recent source lookup failed:", err);
15496
+ return [];
15497
+ }
15498
+ }
15472
15499
  deleteBySource(source) {
15473
15500
  const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE source = ?`).all(source);
15474
15501
  const deleteAll = this.db.transaction(() => {
@@ -15545,17 +15572,19 @@ var MemoryStore = class {
15545
15572
  if (ids.length === 0) return [];
15546
15573
  const placeholders = ids.map(() => "?").join(", ");
15547
15574
  const rows = this.db.prepare(
15548
- `SELECT id, source, heading, heading_level, content, line_start, line_end
15575
+ `SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
15549
15576
  FROM memory_chunks WHERE id IN (${placeholders})`
15550
15577
  ).all(...ids);
15551
15578
  return rows.map((r) => ({
15552
15579
  id: r.id,
15553
15580
  source: r.source,
15581
+ sourceType: r.source_type,
15554
15582
  heading: r.heading,
15555
15583
  headingLevel: r.heading_level,
15556
15584
  content: r.content,
15557
15585
  lineStart: r.line_start,
15558
- lineEnd: r.line_end
15586
+ lineEnd: r.line_end,
15587
+ fileMtimeAt: r.file_mtime_at
15559
15588
  }));
15560
15589
  }
15561
15590
  get vecAvailable() {
@@ -15648,7 +15677,7 @@ function splitSection(text, maxLen) {
15648
15677
  if (byPara.every((p) => p.length <= maxLen)) return byPara;
15649
15678
  return byPara.flatMap((p) => p.length <= maxLen ? [p] : splitAtLine(p, maxLen));
15650
15679
  }
15651
- function chunkMarkdown(text, source, config) {
15680
+ function chunkMarkdown(text, source, config, meta) {
15652
15681
  if (!text || !text.trim()) return [];
15653
15682
  const { chunkSize, overlapLines } = config;
15654
15683
  const lines = text.split("\n");
@@ -15687,11 +15716,13 @@ ${sectionText}` : sectionText;
15687
15716
  chunks.push({
15688
15717
  id: computeChunkId(content),
15689
15718
  source,
15719
+ sourceType: meta.sourceType,
15690
15720
  heading: section.heading,
15691
15721
  headingLevel: section.headingLevel,
15692
15722
  content,
15693
15723
  lineStart: firstLine,
15694
- lineEnd: lastLine
15724
+ lineEnd: lastLine,
15725
+ fileMtimeAt: meta.fileMtimeAt
15695
15726
  });
15696
15727
  if (pi === parts.length - 1) {
15697
15728
  prevOverlapLines = partLines.slice(-overlapLines);
@@ -15702,6 +15733,13 @@ ${sectionText}` : sectionText;
15702
15733
  }
15703
15734
 
15704
15735
  // src/services/memory/indexer.ts
15736
+ function deriveSourceType(source) {
15737
+ if (source.startsWith("compiled/provisional/conversation-digests/")) return "digest";
15738
+ if (source.startsWith("compiled/concepts/")) return "concept";
15739
+ if (source.startsWith("compiled/entities/")) return "entity";
15740
+ if (source === "compiled/preferences.md") return "preference";
15741
+ return "wiki";
15742
+ }
15705
15743
  var MemoryIndexer = class {
15706
15744
  constructor(store, embedder, config) {
15707
15745
  this.store = store;
@@ -15722,7 +15760,15 @@ var MemoryIndexer = class {
15722
15760
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
15723
15761
  return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
15724
15762
  }
15725
- const allChunks = chunkMarkdown(text, source, this.config);
15763
+ let fileMtimeAt;
15764
+ try {
15765
+ fileMtimeAt = fs6.statSync(absolutePath).mtimeMs;
15766
+ } catch (err) {
15767
+ console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
15768
+ fileMtimeAt = Date.now();
15769
+ }
15770
+ const sourceType = deriveSourceType(source);
15771
+ const allChunks = chunkMarkdown(text, source, this.config, { sourceType, fileMtimeAt });
15726
15772
  const currentChunkIds = new Set(allChunks.map((chunk) => chunk.id));
15727
15773
  const toIndex = allChunks.filter((chunk) => !existingChunkIds.has(chunk.id));
15728
15774
  const toDelete = [...existingChunkIds].filter((id) => !currentChunkIds.has(id));
@@ -15809,12 +15855,9 @@ var MemoryIndexer = class {
15809
15855
  scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0 });
15810
15856
  });
15811
15857
  bm25Results.forEach((r, rank) => {
15812
- const existing = scoreMap.get(r.id);
15813
- if (existing) {
15814
- existing.bm25 = 1 / (RRF_K + rank + 1);
15815
- } else {
15816
- scoreMap.set(r.id, { dense: 0, bm25: 1 / (RRF_K + rank + 1) });
15817
- }
15858
+ const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0 };
15859
+ entry.bm25 = 1 / (RRF_K + rank + 1);
15860
+ scoreMap.set(r.id, entry);
15818
15861
  });
15819
15862
  const hasDense = this.store.vecAvailable && denseResults.length > 0;
15820
15863
  const numRetrievers = hasDense ? 2 : 1;
@@ -15884,6 +15927,77 @@ ${content}
15884
15927
  }
15885
15928
  };
15886
15929
 
15930
+ // src/services/memory/embedder.ts
15931
+ var EmbedderError = class extends Error {
15932
+ constructor(message, cause) {
15933
+ super(message);
15934
+ this.name = "EmbedderError";
15935
+ if (cause instanceof Error) this.cause = cause;
15936
+ }
15937
+ };
15938
+ var Embedder = class {
15939
+ modelName;
15940
+ pipeline = null;
15941
+ _dimension;
15942
+ constructor(modelName) {
15943
+ this.modelName = modelName;
15944
+ }
15945
+ async embed(texts) {
15946
+ if (texts.length === 0) return [];
15947
+ if (!this.pipeline) {
15948
+ await this.loadModel();
15949
+ }
15950
+ try {
15951
+ const results = await this.pipeline(texts);
15952
+ return results.map((vec) => {
15953
+ const arr = new Float32Array(vec);
15954
+ if (!this._dimension) this._dimension = arr.length;
15955
+ return arr;
15956
+ });
15957
+ } catch (cause) {
15958
+ throw new EmbedderError(`Embedding failed for batch of ${texts.length} texts`, cause);
15959
+ }
15960
+ }
15961
+ get dimension() {
15962
+ return this._dimension;
15963
+ }
15964
+ isReady() {
15965
+ return this.pipeline !== null;
15966
+ }
15967
+ async initialize() {
15968
+ if (!this.pipeline) {
15969
+ await this.loadModel();
15970
+ }
15971
+ }
15972
+ async loadModel() {
15973
+ try {
15974
+ const { EmbeddingModel, FlagEmbedding } = await import("fastembed");
15975
+ const modelMap = {
15976
+ ["tiny" /* TINY */]: EmbeddingModel.AllMiniLML6V2,
15977
+ ["base" /* BASE */]: EmbeddingModel.BGESmallENV15,
15978
+ ["large" /* LARGE */]: EmbeddingModel.BGEBaseENV15
15979
+ };
15980
+ const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
15981
+ const embedder = await FlagEmbedding.init({
15982
+ model,
15983
+ cacheDir: FASTEMBED_CACHE_DIR,
15984
+ showDownloadProgress: false
15985
+ });
15986
+ this.pipeline = async (texts) => {
15987
+ const result = [];
15988
+ for await (const batch of embedder.embed(texts, texts.length)) {
15989
+ for (const vec of batch) {
15990
+ result.push(Array.from(vec));
15991
+ }
15992
+ }
15993
+ return result;
15994
+ };
15995
+ } catch (cause) {
15996
+ throw new EmbedderError(`Failed to load embedding model: ${this.modelName}`, cause);
15997
+ }
15998
+ }
15999
+ };
16000
+
15887
16001
  // src/services/digest/processor.ts
15888
16002
  function digestLockPath(workspaceRoot) {
15889
16003
  return path9.join(workspaceRoot, DIGEST_LOCKFILE_REL_PATH);
@@ -15910,19 +16024,25 @@ function discoverPendingConversationFiles(workspaceRoot) {
15910
16024
  }
15911
16025
  });
15912
16026
  }
15913
- async function indexProvisionalDigestFile(workspaceRoot, markdownPath) {
16027
+ async function indexProvisionalDigestFile(workspaceRoot, markdownPath, indexer) {
16028
+ let store;
15914
16029
  try {
15915
- const settings = loadProjectSettings(workspaceRoot);
16030
+ if (indexer) {
16031
+ await indexer.indexFile(markdownPath);
16032
+ return { indexed: true };
16033
+ }
16034
+ const settings = loadGlobalSettings();
15916
16035
  const config = resolveMemoryConfig(settings, workspaceRoot);
15917
- const store = new MemoryStore(path9.join(config.wikiDir, "index.db"), config);
15918
- const embedder = { embed: async (_texts) => [], initialize: async () => {
15919
- } };
15920
- const indexer = new MemoryIndexer(store, embedder, config);
15921
- await indexer.indexFile(markdownPath);
16036
+ if (config.enabled !== true) return { indexed: false };
16037
+ store = new MemoryStore(path9.join(config.wikiDir, "index.db"), config);
16038
+ const embedder = new Embedder(config.embeddingModel);
16039
+ await embedder.initialize();
16040
+ await new MemoryIndexer(store, embedder, config).indexFile(markdownPath);
15922
16041
  return { indexed: true };
15923
16042
  } catch (err) {
15924
- const message = err instanceof Error ? err.message : String(err);
15925
- return { indexed: false, error: message };
16043
+ return { indexed: false, error: err instanceof Error ? err.message : String(err) };
16044
+ } finally {
16045
+ store?.close();
15926
16046
  }
15927
16047
  }
15928
16048
  async function digestConversationFile(options) {
@@ -15930,7 +16050,7 @@ async function digestConversationFile(options) {
15930
16050
  const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
15931
16051
  const existingPath = findExistingProvisionalDigest(outDir, input);
15932
16052
  if (existingPath) {
15933
- const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath);
16053
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath, options.indexer);
15934
16054
  return {
15935
16055
  markdown: existingPath,
15936
16056
  status: "provisional",
@@ -15947,7 +16067,7 @@ async function digestConversationFile(options) {
15947
16067
  timeoutMs: options.timeoutMs ?? DEFAULT_DIGEST_TIMEOUT_MS
15948
16068
  });
15949
16069
  const markdownPath = writeProvisionalDigestFile(outDir, input, markdown);
15950
- const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath);
16070
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath, options.indexer);
15951
16071
  return {
15952
16072
  markdown: markdownPath,
15953
16073
  status: "provisional",
@@ -15960,7 +16080,7 @@ async function digestConversationFile(options) {
15960
16080
  }
15961
16081
  }
15962
16082
  function summarizePendingConversations(workspaceRoot) {
15963
- const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
16083
+ const digestConfig = resolveConversationDigestConfig(loadGlobalSettings());
15964
16084
  if (!digestConfig || digestConfig.enabled === false) {
15965
16085
  return { initialized: false, pending: 0, reason: "not-initialized" };
15966
16086
  }
@@ -15975,7 +16095,7 @@ async function digestPendingConversations({
15975
16095
  digestFn = digestConversationFile,
15976
16096
  onProgress
15977
16097
  }) {
15978
- const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
16098
+ const digestConfig = resolveConversationDigestConfig(loadGlobalSettings());
15979
16099
  if (!digestConfig || digestConfig.enabled === false) {
15980
16100
  return { ok: true, initialized: false, action: "noop", reason: "not-initialized" };
15981
16101
  }
@@ -15983,11 +16103,21 @@ async function digestPendingConversations({
15983
16103
  if (!tryAcquireProcessLock(lockPath)) {
15984
16104
  return { ok: true, initialized: true, action: "noop", reason: "locked" };
15985
16105
  }
16106
+ let closeIndexer;
15986
16107
  try {
15987
16108
  const candidates = discoverPendingConversationFiles(workspaceRoot);
15988
16109
  if (candidates.length === 0) {
15989
16110
  return { ok: true, initialized: true, action: "noop", reason: "no-pending" };
15990
16111
  }
16112
+ let indexer;
16113
+ const memoryConfig = resolveMemoryConfig(loadGlobalSettings(), workspaceRoot);
16114
+ if (memoryConfig.enabled === true) {
16115
+ const store = new MemoryStore(path9.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
16116
+ const embedder = new Embedder(memoryConfig.embeddingModel);
16117
+ indexer = new MemoryIndexer(store, embedder, memoryConfig);
16118
+ closeIndexer = () => store.close();
16119
+ await embedder.initialize();
16120
+ }
15991
16121
  let count = 0;
15992
16122
  let skipped = 0;
15993
16123
  let errors = 0;
@@ -15996,7 +16126,8 @@ async function digestPendingConversations({
15996
16126
  const result = await digestFn({
15997
16127
  workspaceRoot,
15998
16128
  inputPath: filePath,
15999
- modelId: digestConfig.modelId
16129
+ modelId: digestConfig.modelId,
16130
+ indexer
16000
16131
  });
16001
16132
  if (result.skipped) {
16002
16133
  skipped++;
@@ -16013,9 +16144,17 @@ async function digestPendingConversations({
16013
16144
  return { ok: true, initialized: true, action: "digested", count, skipped, errors };
16014
16145
  } catch (err) {
16015
16146
  const message = err instanceof Error ? err.message : String(err);
16147
+ console.warn("[digest] Pending conversation digest failed:", err);
16016
16148
  return { ok: false, initialized: true, action: "error", error: message };
16017
16149
  } finally {
16018
16150
  releaseLock(lockPath);
16151
+ if (closeIndexer) {
16152
+ try {
16153
+ closeIndexer();
16154
+ } catch (err) {
16155
+ console.warn("[digest] Failed to close memory indexer store:", err);
16156
+ }
16157
+ }
16019
16158
  }
16020
16159
  }
16021
16160
  async function initializeConversationDigestModel(input) {
@@ -16385,7 +16524,7 @@ function runDigestFileInWorker(args) {
16385
16524
 
16386
16525
  // src/utils/utils.ts
16387
16526
  function getWorkspaceRoot() {
16388
- 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();
16527
+ 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();
16389
16528
  }
16390
16529
 
16391
16530
  // src/cli/commands/memory.ts
@@ -16396,6 +16535,37 @@ function writeDigestFileResult(result, workerMode) {
16396
16535
  }
16397
16536
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
16398
16537
  }
16538
+ function formatLauncherResult(result) {
16539
+ if (!result.ok) return `Digest failed: ${result.error ?? "unknown error"}`;
16540
+ if (result.spawned) return "Background digest started";
16541
+ switch (result.status.state) {
16542
+ case "locked":
16543
+ return "Digest already running";
16544
+ case "no-pending":
16545
+ return "No conversations to digest";
16546
+ case "not-initialized":
16547
+ return "Digest not initialized";
16548
+ default:
16549
+ return `Digest skipped (${result.status.state})`;
16550
+ }
16551
+ }
16552
+ function formatPendingResult(result) {
16553
+ if (!result.ok) return `Digest failed: ${result.error ?? "unknown error"}`;
16554
+ if (result.action === "noop") {
16555
+ switch (result.reason) {
16556
+ case "locked":
16557
+ return "Digest already running";
16558
+ case "no-pending":
16559
+ return "No conversations to digest";
16560
+ case "not-initialized":
16561
+ return "Digest not initialized";
16562
+ }
16563
+ }
16564
+ const parts = [`${result.count} conversation${result.count !== 1 ? "s" : ""} digested`];
16565
+ if (result.skipped > 0) parts.push(`${result.skipped} skipped`);
16566
+ if (result.errors > 0) parts.push(`${result.errors} errors`);
16567
+ return parts.join(", ");
16568
+ }
16399
16569
  async function cmdDigestInit(args) {
16400
16570
  const parsed = parseArgs(args);
16401
16571
  const modelId = stringFlag(parsed.flags, "model") ?? DEFAULT_DIGEST_MODEL_ID;
@@ -16413,7 +16583,7 @@ async function cmdDigestFile(args) {
16413
16583
  if (workerResult !== void 0) return workerResult;
16414
16584
  const parsed = parseArgs(args);
16415
16585
  const workspaceRoot = getWorkspaceRoot();
16416
- const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
16586
+ const digestConfig = resolveConversationDigestConfig(loadGlobalSettings());
16417
16587
  const modelId = stringFlag(parsed.flags, "model") ?? digestConfig?.modelId ?? DEFAULT_DIGEST_MODEL_ID;
16418
16588
  const inputPath = stringFlag(parsed.flags, "input");
16419
16589
  if (!inputPath) {
@@ -16446,11 +16616,19 @@ async function cmdDigestPending(args) {
16446
16616
  args,
16447
16617
  entrypoint: process.argv[1]
16448
16618
  });
16449
- process.stdout.write(JSON.stringify(result2) + "\n");
16619
+ process.stdout.write(
16620
+ JSON.stringify({
16621
+ systemMessage: `[memory-kit] ${formatLauncherResult(result2)}`
16622
+ }) + "\n"
16623
+ );
16450
16624
  return 0;
16451
16625
  }
16452
16626
  const result = await digestPendingConversations({ workspaceRoot });
16453
- process.stdout.write(JSON.stringify(result) + "\n");
16627
+ process.stdout.write(
16628
+ JSON.stringify({
16629
+ systemMessage: `[memory-kit] ${formatPendingResult(result)}`
16630
+ }) + "\n"
16631
+ );
16454
16632
  return 0;
16455
16633
  }
16456
16634
  async function runMemoryCli(args, _env) {