@hanhnd/agent-kit 1.0.34 → 1.0.36

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 +348 -179
  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 +294 -200
  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 +52 -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 +9 -3
  33. package/dist/services/memory/store.js +132 -98
  34. package/dist/services/memory/store.test.js +239 -14
  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 +3 -5
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
  }
@@ -15322,9 +15310,11 @@ async function createLlamaLocalDigestProvider(modelId) {
15322
15310
 
15323
15311
  // src/services/memory/store.ts
15324
15312
  var import_stopword = __toESM(require_stopword_cjs(), 1);
15325
- import Database from "better-sqlite3";
15313
+ import Database from "libsql";
15326
15314
  import * as fs5 from "fs";
15327
15315
  import * as path7 from "path";
15316
+ var SCHEMA_VERSION = 3;
15317
+ var LibsqlDatabase = Database;
15328
15318
  var StoreError = class extends Error {
15329
15319
  constructor(message, cause) {
15330
15320
  super(message);
@@ -15339,13 +15329,11 @@ var MemoryStore = class {
15339
15329
  this.db = this.openDatabase(dbPath);
15340
15330
  }
15341
15331
  db;
15342
- _vecAvailable = false;
15343
15332
  openDatabase(dbPath) {
15344
15333
  try {
15345
- const db = new Database(dbPath);
15334
+ const db = new LibsqlDatabase(dbPath);
15346
15335
  db.pragma("journal_mode = WAL");
15347
15336
  db.pragma("busy_timeout = 5000");
15348
- this.loadVecExtension(db);
15349
15337
  this.createSchema(db);
15350
15338
  return db;
15351
15339
  } catch (err) {
@@ -15354,102 +15342,115 @@ var MemoryStore = class {
15354
15342
  fs5.unlinkSync(dbPath);
15355
15343
  } catch {
15356
15344
  }
15357
- const db = new Database(dbPath);
15358
- db.pragma("journal_mode = WAL");
15359
- db.pragma("busy_timeout = 5000");
15360
- this.loadVecExtension(db);
15361
- this.createSchema(db);
15362
- return db;
15363
- }
15364
- }
15365
- loadVecExtension(db) {
15366
- try {
15367
- const sqliteVec = __require("sqlite-vec");
15368
- sqliteVec.load(db);
15369
- this._vecAvailable = true;
15370
- } catch (err) {
15371
- console.warn("[memory-store] sqlite-vec unavailable, degrading to FTS5-only:", err);
15372
- this._vecAvailable = false;
15345
+ try {
15346
+ const db = new LibsqlDatabase(dbPath);
15347
+ db.pragma("journal_mode = WAL");
15348
+ db.pragma("busy_timeout = 5000");
15349
+ this.createSchema(db);
15350
+ return db;
15351
+ } catch (retryErr) {
15352
+ throw new StoreError("database open failed", retryErr);
15353
+ }
15373
15354
  }
15374
15355
  }
15375
15356
  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
- );
15388
-
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
- );
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;
15402
-
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 {
15357
+ try {
15358
+ const current = this.readUserVersion(db);
15359
+ if (current < SCHEMA_VERSION) {
15411
15360
  db.exec(`
15412
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
15413
- embedding FLOAT[${this.config.vectorDimension}]
15414
- );
15361
+ DROP TRIGGER IF EXISTS memory_chunks_ai;
15362
+ DROP TRIGGER IF EXISTS memory_chunks_ad;
15363
+ DROP TABLE IF EXISTS memory_fts;
15364
+ DROP TABLE IF EXISTS memory_vec;
15365
+ DROP INDEX IF EXISTS idx_memory_chunks_embedding;
15366
+ DROP TABLE IF EXISTS memory_chunks;
15415
15367
  `);
15416
- } catch (err) {
15417
- console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
15418
- this._vecAvailable = false;
15368
+ db.pragma(`user_version = ${SCHEMA_VERSION}`);
15419
15369
  }
15370
+ db.exec(`
15371
+ CREATE TABLE IF NOT EXISTS memory_chunks (
15372
+ rowid INTEGER PRIMARY KEY AUTOINCREMENT,
15373
+ id TEXT NOT NULL UNIQUE,
15374
+ source TEXT NOT NULL,
15375
+ source_type TEXT NOT NULL DEFAULT 'wiki',
15376
+ heading TEXT NOT NULL DEFAULT '',
15377
+ heading_level INTEGER NOT NULL DEFAULT 0,
15378
+ content TEXT NOT NULL,
15379
+ line_start INTEGER NOT NULL,
15380
+ line_end INTEGER NOT NULL,
15381
+ indexed_at INTEGER NOT NULL,
15382
+ file_mtime_at INTEGER NOT NULL DEFAULT 0,
15383
+ embedding F32_BLOB(${this.config.vectorDimension}) NOT NULL
15384
+ );
15385
+
15386
+ CREATE INDEX IF NOT EXISTS idx_memory_chunks_type_mtime
15387
+ ON memory_chunks (source_type, file_mtime_at DESC);
15388
+
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
+ );
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;
15402
+
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
+ CREATE INDEX IF NOT EXISTS idx_memory_chunks_embedding
15410
+ ON memory_chunks (libsql_vector_idx(embedding));
15411
+ `);
15412
+ } catch (err) {
15413
+ throw new StoreError("schema migration failed", err);
15420
15414
  }
15421
15415
  }
15416
+ readUserVersion(db) {
15417
+ const value = db.pragma("user_version", { simple: true });
15418
+ if (typeof value === "number") return value;
15419
+ if (Array.isArray(value)) {
15420
+ const [row] = value;
15421
+ return typeof row?.user_version === "number" ? row.user_version : 0;
15422
+ }
15423
+ if (value && typeof value === "object" && "user_version" in value) {
15424
+ const row = value;
15425
+ return typeof row.user_version === "number" ? row.user_version : 0;
15426
+ }
15427
+ return 0;
15428
+ }
15422
15429
  upsert(chunks, embeddings) {
15423
15430
  const insertChunk = this.db.prepare(`
15424
15431
  INSERT OR REPLACE INTO memory_chunks
15425
- (id, source, heading, heading_level, content, line_start, line_end, indexed_at)
15426
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
15432
+ (id, source, source_type, heading, heading_level, content, line_start, line_end, indexed_at, file_mtime_at, embedding)
15433
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?))
15427
15434
  `);
15428
- let insertVec = null;
15429
- if (this._vecAvailable) {
15430
- insertVec = this.db.prepare(`INSERT OR REPLACE INTO memory_vec (rowid, embedding) VALUES (?, ?)`);
15431
- }
15432
15435
  const upsertAll = this.db.transaction(() => {
15433
15436
  for (let i = 0; i < chunks.length; i++) {
15434
15437
  const chunk = chunks[i];
15435
15438
  try {
15439
+ const vectorBinding = this.toVectorBinding(embeddings[i]);
15436
15440
  this.db.prepare(`DELETE FROM memory_chunks WHERE id = ?`).run(chunk.id);
15437
- const info = insertChunk.run(
15441
+ insertChunk.run(
15438
15442
  chunk.id,
15439
15443
  chunk.source,
15444
+ chunk.sourceType,
15440
15445
  chunk.heading,
15441
15446
  chunk.headingLevel,
15442
15447
  chunk.content,
15443
15448
  chunk.lineStart,
15444
15449
  chunk.lineEnd,
15445
- Date.now()
15450
+ Date.now(),
15451
+ chunk.fileMtimeAt,
15452
+ vectorBinding
15446
15453
  );
15447
- if (insertVec && embeddings[i] && info.lastInsertRowid) {
15448
- const rowid = Number(info.lastInsertRowid);
15449
- const emb = embeddings[i];
15450
- const buf = Buffer.from(emb.buffer, emb.byteOffset, emb.byteLength);
15451
- insertVec.run(rowid, buf);
15452
- }
15453
15454
  } catch (err) {
15454
15455
  console.warn(`[memory-store] Failed to upsert chunk ${chunk.id}:`, err);
15455
15456
  }
@@ -15461,6 +15462,20 @@ var MemoryStore = class {
15461
15462
  throw new StoreError("Full batch upsert failed", err);
15462
15463
  }
15463
15464
  }
15465
+ toVectorBinding(embedding) {
15466
+ if (!embedding) throw new Error("missing embedding");
15467
+ if (!(embedding instanceof Float32Array)) throw new Error("missing embedding");
15468
+ if (embedding.length !== this.config.vectorDimension) throw new Error("embedding dimension mismatch");
15469
+ const values = Array.from(embedding, (value) => {
15470
+ if (!Number.isFinite(value)) throw new Error("embedding contains non-finite value");
15471
+ return value;
15472
+ });
15473
+ return JSON.stringify(values);
15474
+ }
15475
+ normalizeVectorDistance(distance) {
15476
+ if (distance <= 0) return 1;
15477
+ return Math.max(0, Math.min(1, 1 - distance));
15478
+ }
15464
15479
  hashesBySource(source) {
15465
15480
  const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
15466
15481
  return new Set(rows.map((r) => r.id));
@@ -15469,13 +15484,22 @@ var MemoryStore = class {
15469
15484
  const rows = this.db.prepare(`SELECT DISTINCT source FROM memory_chunks`).all();
15470
15485
  return rows.map((r) => r.source);
15471
15486
  }
15487
+ getRecentSources(options) {
15488
+ const limit = Math.min(Math.max(1, options.limit || 5), 50);
15489
+ const whereClause = options.sourceType ? " WHERE source_type = ?" : "";
15490
+ const params = options.sourceType ? [options.sourceType, limit] : [limit];
15491
+ try {
15492
+ const rows = this.db.prepare(
15493
+ `SELECT source, MAX(file_mtime_at) AS t FROM memory_chunks${whereClause} GROUP BY source ORDER BY t DESC LIMIT ?`
15494
+ ).all(...params);
15495
+ return rows.map((row) => ({ source: row.source, fileMtimeAt: row.t }));
15496
+ } catch (err) {
15497
+ console.warn("[memory-store] Recent source lookup failed:", err);
15498
+ return [];
15499
+ }
15500
+ }
15472
15501
  deleteBySource(source) {
15473
- const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE source = ?`).all(source);
15474
15502
  const deleteAll = this.db.transaction(() => {
15475
- if (this._vecAvailable && rows.length > 0) {
15476
- const placeholders = rows.map(() => "?").join(", ");
15477
- this.db.prepare(`DELETE FROM memory_vec WHERE rowid IN (${placeholders})`).run(...rows.map((r) => r.rowid));
15478
- }
15479
15503
  this.db.prepare(`DELETE FROM memory_chunks WHERE source = ?`).run(source);
15480
15504
  });
15481
15505
  deleteAll();
@@ -15483,30 +15507,24 @@ var MemoryStore = class {
15483
15507
  deleteByIds(ids) {
15484
15508
  if (ids.length === 0) return;
15485
15509
  const placeholders = ids.map(() => "?").join(", ");
15486
- const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE id IN (${placeholders})`).all(...ids);
15487
15510
  const deleteAll = this.db.transaction(() => {
15488
- if (this._vecAvailable && rows.length > 0) {
15489
- const rPlaceholders = rows.map(() => "?").join(", ");
15490
- this.db.prepare(`DELETE FROM memory_vec WHERE rowid IN (${rPlaceholders})`).run(...rows.map((r) => r.rowid));
15491
- }
15492
15511
  this.db.prepare(`DELETE FROM memory_chunks WHERE id IN (${placeholders})`).run(...ids);
15493
15512
  });
15494
15513
  deleteAll();
15495
15514
  }
15496
15515
  searchDense(embedding, limit) {
15497
- if (!this._vecAvailable) return [];
15498
15516
  try {
15499
- const buf = Buffer.from(embedding.buffer);
15517
+ const queryVector = this.toVectorBinding(embedding);
15500
15518
  const rows = this.db.prepare(
15501
- `SELECT mc.id, mv.distance
15502
- FROM memory_vec mv
15503
- JOIN memory_chunks mc ON mc.rowid = mv.rowid
15504
- WHERE mv.embedding MATCH ? AND k = ?
15505
- ORDER BY mv.distance`
15506
- ).all(buf, limit);
15519
+ `SELECT mc.id, vector_distance_cos(mc.embedding, vector32(?)) AS distance
15520
+ FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vector_matches
15521
+ JOIN memory_chunks mc ON mc.rowid = vector_matches.id
15522
+ ORDER BY distance
15523
+ LIMIT ?`
15524
+ ).all(queryVector, queryVector, limit, limit);
15507
15525
  return rows.map((r) => ({
15508
15526
  id: r.id,
15509
- score: Math.max(0, 1 - r.distance)
15527
+ score: this.normalizeVectorDistance(r.distance)
15510
15528
  }));
15511
15529
  } catch (err) {
15512
15530
  console.warn("[memory-store] Dense search failed:", err);
@@ -15545,21 +15563,23 @@ var MemoryStore = class {
15545
15563
  if (ids.length === 0) return [];
15546
15564
  const placeholders = ids.map(() => "?").join(", ");
15547
15565
  const rows = this.db.prepare(
15548
- `SELECT id, source, heading, heading_level, content, line_start, line_end
15566
+ `SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
15549
15567
  FROM memory_chunks WHERE id IN (${placeholders})`
15550
15568
  ).all(...ids);
15551
15569
  return rows.map((r) => ({
15552
15570
  id: r.id,
15553
15571
  source: r.source,
15572
+ sourceType: r.source_type,
15554
15573
  heading: r.heading,
15555
15574
  headingLevel: r.heading_level,
15556
15575
  content: r.content,
15557
15576
  lineStart: r.line_start,
15558
- lineEnd: r.line_end
15577
+ lineEnd: r.line_end,
15578
+ fileMtimeAt: r.file_mtime_at
15559
15579
  }));
15560
15580
  }
15561
15581
  get vecAvailable() {
15562
- return this._vecAvailable;
15582
+ return true;
15563
15583
  }
15564
15584
  close() {
15565
15585
  this.db.close();
@@ -15648,7 +15668,7 @@ function splitSection(text, maxLen) {
15648
15668
  if (byPara.every((p) => p.length <= maxLen)) return byPara;
15649
15669
  return byPara.flatMap((p) => p.length <= maxLen ? [p] : splitAtLine(p, maxLen));
15650
15670
  }
15651
- function chunkMarkdown(text, source, config) {
15671
+ function chunkMarkdown(text, source, config, meta) {
15652
15672
  if (!text || !text.trim()) return [];
15653
15673
  const { chunkSize, overlapLines } = config;
15654
15674
  const lines = text.split("\n");
@@ -15687,11 +15707,13 @@ ${sectionText}` : sectionText;
15687
15707
  chunks.push({
15688
15708
  id: computeChunkId(content),
15689
15709
  source,
15710
+ sourceType: meta.sourceType,
15690
15711
  heading: section.heading,
15691
15712
  headingLevel: section.headingLevel,
15692
15713
  content,
15693
15714
  lineStart: firstLine,
15694
- lineEnd: lastLine
15715
+ lineEnd: lastLine,
15716
+ fileMtimeAt: meta.fileMtimeAt
15695
15717
  });
15696
15718
  if (pi === parts.length - 1) {
15697
15719
  prevOverlapLines = partLines.slice(-overlapLines);
@@ -15702,6 +15724,13 @@ ${sectionText}` : sectionText;
15702
15724
  }
15703
15725
 
15704
15726
  // src/services/memory/indexer.ts
15727
+ function deriveSourceType(source) {
15728
+ if (source.startsWith("compiled/provisional/conversation-digests/")) return "digest";
15729
+ if (source.startsWith("compiled/concepts/")) return "concept";
15730
+ if (source.startsWith("compiled/entities/")) return "entity";
15731
+ if (source === "compiled/preferences.md") return "preference";
15732
+ return "wiki";
15733
+ }
15705
15734
  var MemoryIndexer = class {
15706
15735
  constructor(store, embedder, config) {
15707
15736
  this.store = store;
@@ -15722,7 +15751,15 @@ var MemoryIndexer = class {
15722
15751
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
15723
15752
  return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
15724
15753
  }
15725
- const allChunks = chunkMarkdown(text, source, this.config);
15754
+ let fileMtimeAt;
15755
+ try {
15756
+ fileMtimeAt = fs6.statSync(absolutePath).mtimeMs;
15757
+ } catch (err) {
15758
+ console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
15759
+ fileMtimeAt = Date.now();
15760
+ }
15761
+ const sourceType = deriveSourceType(source);
15762
+ const allChunks = chunkMarkdown(text, source, this.config, { sourceType, fileMtimeAt });
15726
15763
  const currentChunkIds = new Set(allChunks.map((chunk) => chunk.id));
15727
15764
  const toIndex = allChunks.filter((chunk) => !existingChunkIds.has(chunk.id));
15728
15765
  const toDelete = [...existingChunkIds].filter((id) => !currentChunkIds.has(id));
@@ -15809,12 +15846,9 @@ var MemoryIndexer = class {
15809
15846
  scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0 });
15810
15847
  });
15811
15848
  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
- }
15849
+ const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0 };
15850
+ entry.bm25 = 1 / (RRF_K + rank + 1);
15851
+ scoreMap.set(r.id, entry);
15818
15852
  });
15819
15853
  const hasDense = this.store.vecAvailable && denseResults.length > 0;
15820
15854
  const numRetrievers = hasDense ? 2 : 1;
@@ -15884,6 +15918,77 @@ ${content}
15884
15918
  }
15885
15919
  };
15886
15920
 
15921
+ // src/services/memory/embedder.ts
15922
+ var EmbedderError = class extends Error {
15923
+ constructor(message, cause) {
15924
+ super(message);
15925
+ this.name = "EmbedderError";
15926
+ if (cause instanceof Error) this.cause = cause;
15927
+ }
15928
+ };
15929
+ var Embedder = class {
15930
+ modelName;
15931
+ pipeline = null;
15932
+ _dimension;
15933
+ constructor(modelName) {
15934
+ this.modelName = modelName;
15935
+ }
15936
+ async embed(texts) {
15937
+ if (texts.length === 0) return [];
15938
+ if (!this.pipeline) {
15939
+ await this.loadModel();
15940
+ }
15941
+ try {
15942
+ const results = await this.pipeline(texts);
15943
+ return results.map((vec) => {
15944
+ const arr = new Float32Array(vec);
15945
+ if (!this._dimension) this._dimension = arr.length;
15946
+ return arr;
15947
+ });
15948
+ } catch (cause) {
15949
+ throw new EmbedderError(`Embedding failed for batch of ${texts.length} texts`, cause);
15950
+ }
15951
+ }
15952
+ get dimension() {
15953
+ return this._dimension;
15954
+ }
15955
+ isReady() {
15956
+ return this.pipeline !== null;
15957
+ }
15958
+ async initialize() {
15959
+ if (!this.pipeline) {
15960
+ await this.loadModel();
15961
+ }
15962
+ }
15963
+ async loadModel() {
15964
+ try {
15965
+ const { EmbeddingModel, FlagEmbedding } = await import("fastembed");
15966
+ const modelMap = {
15967
+ ["tiny" /* TINY */]: EmbeddingModel.AllMiniLML6V2,
15968
+ ["base" /* BASE */]: EmbeddingModel.BGESmallENV15,
15969
+ ["large" /* LARGE */]: EmbeddingModel.BGEBaseENV15
15970
+ };
15971
+ const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
15972
+ const embedder = await FlagEmbedding.init({
15973
+ model,
15974
+ cacheDir: FASTEMBED_CACHE_DIR,
15975
+ showDownloadProgress: false
15976
+ });
15977
+ this.pipeline = async (texts) => {
15978
+ const result = [];
15979
+ for await (const batch of embedder.embed(texts, texts.length)) {
15980
+ for (const vec of batch) {
15981
+ result.push(Array.from(vec));
15982
+ }
15983
+ }
15984
+ return result;
15985
+ };
15986
+ } catch (cause) {
15987
+ throw new EmbedderError(`Failed to load embedding model: ${this.modelName}`, cause);
15988
+ }
15989
+ }
15990
+ };
15991
+
15887
15992
  // src/services/digest/processor.ts
15888
15993
  function digestLockPath(workspaceRoot) {
15889
15994
  return path9.join(workspaceRoot, DIGEST_LOCKFILE_REL_PATH);
@@ -15910,19 +16015,25 @@ function discoverPendingConversationFiles(workspaceRoot) {
15910
16015
  }
15911
16016
  });
15912
16017
  }
15913
- async function indexProvisionalDigestFile(workspaceRoot, markdownPath) {
16018
+ async function indexProvisionalDigestFile(workspaceRoot, markdownPath, indexer) {
16019
+ let store;
15914
16020
  try {
15915
- const settings = loadProjectSettings(workspaceRoot);
16021
+ if (indexer) {
16022
+ await indexer.indexFile(markdownPath);
16023
+ return { indexed: true };
16024
+ }
16025
+ const settings = loadGlobalSettings();
15916
16026
  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);
16027
+ if (config.enabled !== true) return { indexed: false };
16028
+ store = new MemoryStore(path9.join(config.wikiDir, "index.db"), config);
16029
+ const embedder = new Embedder(config.embeddingModel);
16030
+ await embedder.initialize();
16031
+ await new MemoryIndexer(store, embedder, config).indexFile(markdownPath);
15922
16032
  return { indexed: true };
15923
16033
  } catch (err) {
15924
- const message = err instanceof Error ? err.message : String(err);
15925
- return { indexed: false, error: message };
16034
+ return { indexed: false, error: err instanceof Error ? err.message : String(err) };
16035
+ } finally {
16036
+ store?.close();
15926
16037
  }
15927
16038
  }
15928
16039
  async function digestConversationFile(options) {
@@ -15930,7 +16041,7 @@ async function digestConversationFile(options) {
15930
16041
  const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
15931
16042
  const existingPath = findExistingProvisionalDigest(outDir, input);
15932
16043
  if (existingPath) {
15933
- const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath);
16044
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath, options.indexer);
15934
16045
  return {
15935
16046
  markdown: existingPath,
15936
16047
  status: "provisional",
@@ -15947,7 +16058,7 @@ async function digestConversationFile(options) {
15947
16058
  timeoutMs: options.timeoutMs ?? DEFAULT_DIGEST_TIMEOUT_MS
15948
16059
  });
15949
16060
  const markdownPath = writeProvisionalDigestFile(outDir, input, markdown);
15950
- const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath);
16061
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath, options.indexer);
15951
16062
  return {
15952
16063
  markdown: markdownPath,
15953
16064
  status: "provisional",
@@ -15960,7 +16071,7 @@ async function digestConversationFile(options) {
15960
16071
  }
15961
16072
  }
15962
16073
  function summarizePendingConversations(workspaceRoot) {
15963
- const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
16074
+ const digestConfig = resolveConversationDigestConfig(loadGlobalSettings());
15964
16075
  if (!digestConfig || digestConfig.enabled === false) {
15965
16076
  return { initialized: false, pending: 0, reason: "not-initialized" };
15966
16077
  }
@@ -15975,7 +16086,7 @@ async function digestPendingConversations({
15975
16086
  digestFn = digestConversationFile,
15976
16087
  onProgress
15977
16088
  }) {
15978
- const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
16089
+ const digestConfig = resolveConversationDigestConfig(loadGlobalSettings());
15979
16090
  if (!digestConfig || digestConfig.enabled === false) {
15980
16091
  return { ok: true, initialized: false, action: "noop", reason: "not-initialized" };
15981
16092
  }
@@ -15983,11 +16094,21 @@ async function digestPendingConversations({
15983
16094
  if (!tryAcquireProcessLock(lockPath)) {
15984
16095
  return { ok: true, initialized: true, action: "noop", reason: "locked" };
15985
16096
  }
16097
+ let closeIndexer;
15986
16098
  try {
15987
16099
  const candidates = discoverPendingConversationFiles(workspaceRoot);
15988
16100
  if (candidates.length === 0) {
15989
16101
  return { ok: true, initialized: true, action: "noop", reason: "no-pending" };
15990
16102
  }
16103
+ let indexer;
16104
+ const memoryConfig = resolveMemoryConfig(loadGlobalSettings(), workspaceRoot);
16105
+ if (memoryConfig.enabled === true) {
16106
+ const store = new MemoryStore(path9.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
16107
+ const embedder = new Embedder(memoryConfig.embeddingModel);
16108
+ indexer = new MemoryIndexer(store, embedder, memoryConfig);
16109
+ closeIndexer = () => store.close();
16110
+ await embedder.initialize();
16111
+ }
15991
16112
  let count = 0;
15992
16113
  let skipped = 0;
15993
16114
  let errors = 0;
@@ -15996,7 +16117,8 @@ async function digestPendingConversations({
15996
16117
  const result = await digestFn({
15997
16118
  workspaceRoot,
15998
16119
  inputPath: filePath,
15999
- modelId: digestConfig.modelId
16120
+ modelId: digestConfig.modelId,
16121
+ indexer
16000
16122
  });
16001
16123
  if (result.skipped) {
16002
16124
  skipped++;
@@ -16013,9 +16135,17 @@ async function digestPendingConversations({
16013
16135
  return { ok: true, initialized: true, action: "digested", count, skipped, errors };
16014
16136
  } catch (err) {
16015
16137
  const message = err instanceof Error ? err.message : String(err);
16138
+ console.warn("[digest] Pending conversation digest failed:", err);
16016
16139
  return { ok: false, initialized: true, action: "error", error: message };
16017
16140
  } finally {
16018
16141
  releaseLock(lockPath);
16142
+ if (closeIndexer) {
16143
+ try {
16144
+ closeIndexer();
16145
+ } catch (err) {
16146
+ console.warn("[digest] Failed to close memory indexer store:", err);
16147
+ }
16148
+ }
16019
16149
  }
16020
16150
  }
16021
16151
  async function initializeConversationDigestModel(input) {
@@ -16385,7 +16515,7 @@ function runDigestFileInWorker(args) {
16385
16515
 
16386
16516
  // src/utils/utils.ts
16387
16517
  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();
16518
+ 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
16519
  }
16390
16520
 
16391
16521
  // src/cli/commands/memory.ts
@@ -16396,6 +16526,37 @@ function writeDigestFileResult(result, workerMode) {
16396
16526
  }
16397
16527
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
16398
16528
  }
16529
+ function formatLauncherResult(result) {
16530
+ if (!result.ok) return `Digest failed: ${result.error ?? "unknown error"}`;
16531
+ if (result.spawned) return "Background digest started";
16532
+ switch (result.status.state) {
16533
+ case "locked":
16534
+ return "Digest already running";
16535
+ case "no-pending":
16536
+ return "No conversations to digest";
16537
+ case "not-initialized":
16538
+ return "Digest not initialized";
16539
+ default:
16540
+ return `Digest skipped (${result.status.state})`;
16541
+ }
16542
+ }
16543
+ function formatPendingResult(result) {
16544
+ if (!result.ok) return `Digest failed: ${result.error ?? "unknown error"}`;
16545
+ if (result.action === "noop") {
16546
+ switch (result.reason) {
16547
+ case "locked":
16548
+ return "Digest already running";
16549
+ case "no-pending":
16550
+ return "No conversations to digest";
16551
+ case "not-initialized":
16552
+ return "Digest not initialized";
16553
+ }
16554
+ }
16555
+ const parts = [`${result.count} conversation${result.count !== 1 ? "s" : ""} digested`];
16556
+ if (result.skipped > 0) parts.push(`${result.skipped} skipped`);
16557
+ if (result.errors > 0) parts.push(`${result.errors} errors`);
16558
+ return parts.join(", ");
16559
+ }
16399
16560
  async function cmdDigestInit(args) {
16400
16561
  const parsed = parseArgs(args);
16401
16562
  const modelId = stringFlag(parsed.flags, "model") ?? DEFAULT_DIGEST_MODEL_ID;
@@ -16413,7 +16574,7 @@ async function cmdDigestFile(args) {
16413
16574
  if (workerResult !== void 0) return workerResult;
16414
16575
  const parsed = parseArgs(args);
16415
16576
  const workspaceRoot = getWorkspaceRoot();
16416
- const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
16577
+ const digestConfig = resolveConversationDigestConfig(loadGlobalSettings());
16417
16578
  const modelId = stringFlag(parsed.flags, "model") ?? digestConfig?.modelId ?? DEFAULT_DIGEST_MODEL_ID;
16418
16579
  const inputPath = stringFlag(parsed.flags, "input");
16419
16580
  if (!inputPath) {
@@ -16446,11 +16607,19 @@ async function cmdDigestPending(args) {
16446
16607
  args,
16447
16608
  entrypoint: process.argv[1]
16448
16609
  });
16449
- process.stdout.write(JSON.stringify(result2) + "\n");
16610
+ process.stdout.write(
16611
+ JSON.stringify({
16612
+ systemMessage: `[memory-kit] ${formatLauncherResult(result2)}`
16613
+ }) + "\n"
16614
+ );
16450
16615
  return 0;
16451
16616
  }
16452
16617
  const result = await digestPendingConversations({ workspaceRoot });
16453
- process.stdout.write(JSON.stringify(result) + "\n");
16618
+ process.stdout.write(
16619
+ JSON.stringify({
16620
+ systemMessage: `[memory-kit] ${formatPendingResult(result)}`
16621
+ }) + "\n"
16622
+ );
16454
16623
  return 0;
16455
16624
  }
16456
16625
  async function runMemoryCli(args, _env) {