claude-memory-layer 1.0.19 → 1.0.20

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.
@@ -1598,6 +1598,33 @@ var SQLiteEventStore = class {
1598
1598
  ids
1599
1599
  );
1600
1600
  }
1601
+ /**
1602
+ * Clear embedding outbox (used for embedding model migration)
1603
+ */
1604
+ async clearEmbeddingOutbox() {
1605
+ await this.initialize();
1606
+ sqliteRun(this.db, `DELETE FROM embedding_outbox`);
1607
+ }
1608
+ /**
1609
+ * Count total events
1610
+ */
1611
+ async countEvents() {
1612
+ await this.initialize();
1613
+ const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
1614
+ return row?.count || 0;
1615
+ }
1616
+ /**
1617
+ * Get events page in timestamp ascending order (stable migration/reindex scans)
1618
+ */
1619
+ async getEventsPage(limit = 1e3, offset = 0) {
1620
+ await this.initialize();
1621
+ const rows = sqliteAll(
1622
+ this.db,
1623
+ `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
1624
+ [limit, offset]
1625
+ );
1626
+ return rows.map(this.rowToEvent);
1627
+ }
1601
1628
  /**
1602
1629
  * Mark outbox items as failed
1603
1630
  */
@@ -2563,6 +2590,23 @@ var VectorStore = class {
2563
2590
  const result = await this.table.countRows();
2564
2591
  return result;
2565
2592
  }
2593
+ /**
2594
+ * Clear all vectors (used for embedding model migration)
2595
+ */
2596
+ async clearAll() {
2597
+ await this.initialize();
2598
+ if (!this.db)
2599
+ return;
2600
+ try {
2601
+ if (typeof this.db.dropTable === "function") {
2602
+ await this.db.dropTable(this.tableName);
2603
+ } else if (typeof this.db.drop_table === "function") {
2604
+ await this.db.drop_table(this.tableName);
2605
+ }
2606
+ } catch {
2607
+ }
2608
+ this.table = null;
2609
+ }
2566
2610
  /**
2567
2611
  * Check if vector exists for event
2568
2612
  */
@@ -2580,7 +2624,7 @@ var Embedder = class {
2580
2624
  pipeline = null;
2581
2625
  modelName;
2582
2626
  initialized = false;
2583
- constructor(modelName = "Xenova/all-MiniLM-L6-v2") {
2627
+ constructor(modelName = "jinaai/jina-embeddings-v5-text-nano") {
2584
2628
  this.modelName = modelName;
2585
2629
  }
2586
2630
  /**
@@ -2660,8 +2704,9 @@ var Embedder = class {
2660
2704
  };
2661
2705
  var defaultEmbedder = null;
2662
2706
  function getDefaultEmbedder() {
2707
+ const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
2663
2708
  if (!defaultEmbedder) {
2664
- defaultEmbedder = new Embedder();
2709
+ defaultEmbedder = new Embedder(envModel || void 0);
2665
2710
  }
2666
2711
  return defaultEmbedder;
2667
2712
  }
@@ -5921,8 +5966,10 @@ var MemoryService = class {
5921
5966
  readOnly;
5922
5967
  lightweightMode;
5923
5968
  mdMirror;
5969
+ storagePath;
5924
5970
  constructor(config) {
5925
5971
  const storagePath = this.expandPath(config.storagePath);
5972
+ this.storagePath = storagePath;
5926
5973
  this.readOnly = config.readOnly ?? false;
5927
5974
  this.lightweightMode = config.lightweightMode ?? false;
5928
5975
  this.mdMirror = new MarkdownMirror2(process.cwd());
@@ -5958,7 +6005,8 @@ var MemoryService = class {
5958
6005
  );
5959
6006
  }
5960
6007
  this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
5961
- this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
6008
+ const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
6009
+ this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
5962
6010
  this.matcher = getDefaultMatcher();
5963
6011
  this.retriever = createRetriever(
5964
6012
  this.sqliteStore,
@@ -6902,6 +6950,89 @@ var MemoryService = class {
6902
6950
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6903
6951
  this.graduation.recordAccess(eventId, sessionId, confidence);
6904
6952
  }
6953
+ getEmbeddingModelName() {
6954
+ return this.embedder.getModelName();
6955
+ }
6956
+ /**
6957
+ * Ensure embedding model metadata is in sync and optionally migrate vectors.
6958
+ * Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
6959
+ */
6960
+ async ensureEmbeddingModelForImport(options) {
6961
+ await this.initialize();
6962
+ const currentModel = this.getEmbeddingModelName();
6963
+ const metaPath = path3.join(this.storagePath, "embedding-meta.json");
6964
+ let previousModel = null;
6965
+ try {
6966
+ if (fs4.existsSync(metaPath)) {
6967
+ const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
6968
+ previousModel = parsed?.model || null;
6969
+ }
6970
+ } catch {
6971
+ previousModel = null;
6972
+ }
6973
+ const stats = await this.getStats();
6974
+ const hasExistingVectors = (stats.vectorCount || 0) > 0;
6975
+ if (!previousModel && !hasExistingVectors) {
6976
+ fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
6977
+ return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
6978
+ }
6979
+ const modelChanged = previousModel !== currentModel;
6980
+ const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
6981
+ if (!modelChanged && !legacyUnknownButVectorsExist) {
6982
+ return { changed: false, previousModel, currentModel, enqueued: 0 };
6983
+ }
6984
+ if (options?.autoMigrate === false) {
6985
+ return {
6986
+ changed: true,
6987
+ previousModel,
6988
+ currentModel,
6989
+ enqueued: 0,
6990
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
6991
+ };
6992
+ }
6993
+ const wasRunning = this.vectorWorker?.isRunning() || false;
6994
+ if (wasRunning)
6995
+ this.vectorWorker?.stop();
6996
+ await this.vectorStore.clearAll();
6997
+ await this.sqliteStore.clearEmbeddingOutbox();
6998
+ const pageSize = 1e3;
6999
+ let offset = 0;
7000
+ let enqueued = 0;
7001
+ while (true) {
7002
+ const page = await this.sqliteStore.getEventsPage(pageSize, offset);
7003
+ if (page.length === 0)
7004
+ break;
7005
+ for (const event of page) {
7006
+ await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
7007
+ enqueued += 1;
7008
+ }
7009
+ offset += page.length;
7010
+ if (page.length < pageSize)
7011
+ break;
7012
+ }
7013
+ fs4.writeFileSync(
7014
+ metaPath,
7015
+ JSON.stringify(
7016
+ {
7017
+ model: currentModel,
7018
+ previousModel,
7019
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
7020
+ enqueued
7021
+ },
7022
+ null,
7023
+ 2
7024
+ )
7025
+ );
7026
+ if (wasRunning)
7027
+ this.vectorWorker?.start();
7028
+ return {
7029
+ changed: true,
7030
+ previousModel,
7031
+ currentModel,
7032
+ enqueued,
7033
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7034
+ };
7035
+ }
6905
7036
  /**
6906
7037
  * Backward-compatible alias used by some hooks
6907
7038
  */