claude-memory-layer 1.0.19 → 1.0.21

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.
@@ -1599,6 +1599,33 @@ var SQLiteEventStore = class {
1599
1599
  ids
1600
1600
  );
1601
1601
  }
1602
+ /**
1603
+ * Clear embedding outbox (used for embedding model migration)
1604
+ */
1605
+ async clearEmbeddingOutbox() {
1606
+ await this.initialize();
1607
+ sqliteRun(this.db, `DELETE FROM embedding_outbox`);
1608
+ }
1609
+ /**
1610
+ * Count total events
1611
+ */
1612
+ async countEvents() {
1613
+ await this.initialize();
1614
+ const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
1615
+ return row?.count || 0;
1616
+ }
1617
+ /**
1618
+ * Get events page in timestamp ascending order (stable migration/reindex scans)
1619
+ */
1620
+ async getEventsPage(limit = 1e3, offset = 0) {
1621
+ await this.initialize();
1622
+ const rows = sqliteAll(
1623
+ this.db,
1624
+ `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
1625
+ [limit, offset]
1626
+ );
1627
+ return rows.map(this.rowToEvent);
1628
+ }
1602
1629
  /**
1603
1630
  * Mark outbox items as failed
1604
1631
  */
@@ -2564,6 +2591,23 @@ var VectorStore = class {
2564
2591
  const result = await this.table.countRows();
2565
2592
  return result;
2566
2593
  }
2594
+ /**
2595
+ * Clear all vectors (used for embedding model migration)
2596
+ */
2597
+ async clearAll() {
2598
+ await this.initialize();
2599
+ if (!this.db)
2600
+ return;
2601
+ try {
2602
+ if (typeof this.db.dropTable === "function") {
2603
+ await this.db.dropTable(this.tableName);
2604
+ } else if (typeof this.db.drop_table === "function") {
2605
+ await this.db.drop_table(this.tableName);
2606
+ }
2607
+ } catch {
2608
+ }
2609
+ this.table = null;
2610
+ }
2567
2611
  /**
2568
2612
  * Check if vector exists for event
2569
2613
  */
@@ -2576,13 +2620,15 @@ var VectorStore = class {
2576
2620
  };
2577
2621
 
2578
2622
  // src/core/embedder.ts
2579
- import { pipeline } from "@xenova/transformers";
2623
+ import { pipeline } from "@huggingface/transformers";
2580
2624
  var Embedder = class {
2581
2625
  pipeline = null;
2582
2626
  modelName;
2627
+ activeModelName;
2583
2628
  initialized = false;
2584
- constructor(modelName = "Xenova/all-MiniLM-L6-v2") {
2629
+ constructor(modelName = "jinaai/jina-embeddings-v5-text-nano-text-matching") {
2585
2630
  this.modelName = modelName;
2631
+ this.activeModelName = modelName;
2586
2632
  }
2587
2633
  /**
2588
2634
  * Initialize the embedding pipeline
@@ -2590,8 +2636,21 @@ var Embedder = class {
2590
2636
  async initialize() {
2591
2637
  if (this.initialized)
2592
2638
  return;
2593
- this.pipeline = await pipeline("feature-extraction", this.modelName);
2594
- this.initialized = true;
2639
+ try {
2640
+ this.pipeline = await pipeline("feature-extraction", this.modelName);
2641
+ this.activeModelName = this.modelName;
2642
+ this.initialized = true;
2643
+ return;
2644
+ } catch (primaryError) {
2645
+ const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || "onnx-community/embeddinggemma-300m-ONNX";
2646
+ if (fallbackModel === this.modelName) {
2647
+ throw primaryError;
2648
+ }
2649
+ console.warn(`[Embedder] Primary model failed (${this.modelName}). Falling back to ${fallbackModel}`);
2650
+ this.pipeline = await pipeline("feature-extraction", fallbackModel);
2651
+ this.activeModelName = fallbackModel;
2652
+ this.initialized = true;
2653
+ }
2595
2654
  }
2596
2655
  /**
2597
2656
  * Generate embedding for a single text
@@ -2608,7 +2667,7 @@ var Embedder = class {
2608
2667
  const vector = Array.from(output.data);
2609
2668
  return {
2610
2669
  vector,
2611
- model: this.modelName,
2670
+ model: this.activeModelName,
2612
2671
  dimensions: vector.length
2613
2672
  };
2614
2673
  }
@@ -2632,7 +2691,7 @@ var Embedder = class {
2632
2691
  const vector = Array.from(output.data);
2633
2692
  results.push({
2634
2693
  vector,
2635
- model: this.modelName,
2694
+ model: this.activeModelName,
2636
2695
  dimensions: vector.length
2637
2696
  });
2638
2697
  }
@@ -2656,13 +2715,14 @@ var Embedder = class {
2656
2715
  * Get model name
2657
2716
  */
2658
2717
  getModelName() {
2659
- return this.modelName;
2718
+ return this.activeModelName;
2660
2719
  }
2661
2720
  };
2662
2721
  var defaultEmbedder = null;
2663
2722
  function getDefaultEmbedder() {
2723
+ const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
2664
2724
  if (!defaultEmbedder) {
2665
- defaultEmbedder = new Embedder();
2725
+ defaultEmbedder = new Embedder(envModel || void 0);
2666
2726
  }
2667
2727
  return defaultEmbedder;
2668
2728
  }
@@ -6016,8 +6076,10 @@ var MemoryService = class {
6016
6076
  readOnly;
6017
6077
  lightweightMode;
6018
6078
  mdMirror;
6079
+ storagePath;
6019
6080
  constructor(config) {
6020
6081
  const storagePath = this.expandPath(config.storagePath);
6082
+ this.storagePath = storagePath;
6021
6083
  this.readOnly = config.readOnly ?? false;
6022
6084
  this.lightweightMode = config.lightweightMode ?? false;
6023
6085
  this.mdMirror = new MarkdownMirror2(process.cwd());
@@ -6053,7 +6115,8 @@ var MemoryService = class {
6053
6115
  );
6054
6116
  }
6055
6117
  this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
6056
- this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
6118
+ const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
6119
+ this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
6057
6120
  this.matcher = getDefaultMatcher();
6058
6121
  this.retriever = createRetriever(
6059
6122
  this.sqliteStore,
@@ -6997,6 +7060,89 @@ var MemoryService = class {
6997
7060
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6998
7061
  this.graduation.recordAccess(eventId, sessionId, confidence);
6999
7062
  }
7063
+ getEmbeddingModelName() {
7064
+ return this.embedder.getModelName();
7065
+ }
7066
+ /**
7067
+ * Ensure embedding model metadata is in sync and optionally migrate vectors.
7068
+ * Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
7069
+ */
7070
+ async ensureEmbeddingModelForImport(options) {
7071
+ await this.initialize();
7072
+ const currentModel = this.getEmbeddingModelName();
7073
+ const metaPath = path3.join(this.storagePath, "embedding-meta.json");
7074
+ let previousModel = null;
7075
+ try {
7076
+ if (fs4.existsSync(metaPath)) {
7077
+ const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
7078
+ previousModel = parsed?.model || null;
7079
+ }
7080
+ } catch {
7081
+ previousModel = null;
7082
+ }
7083
+ const stats = await this.getStats();
7084
+ const hasExistingVectors = (stats.vectorCount || 0) > 0;
7085
+ if (!previousModel && !hasExistingVectors) {
7086
+ fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
7087
+ return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
7088
+ }
7089
+ const modelChanged = previousModel !== currentModel;
7090
+ const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
7091
+ if (!modelChanged && !legacyUnknownButVectorsExist) {
7092
+ return { changed: false, previousModel, currentModel, enqueued: 0 };
7093
+ }
7094
+ if (options?.autoMigrate === false) {
7095
+ return {
7096
+ changed: true,
7097
+ previousModel,
7098
+ currentModel,
7099
+ enqueued: 0,
7100
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7101
+ };
7102
+ }
7103
+ const wasRunning = this.vectorWorker?.isRunning() || false;
7104
+ if (wasRunning)
7105
+ this.vectorWorker?.stop();
7106
+ await this.vectorStore.clearAll();
7107
+ await this.sqliteStore.clearEmbeddingOutbox();
7108
+ const pageSize = 1e3;
7109
+ let offset = 0;
7110
+ let enqueued = 0;
7111
+ while (true) {
7112
+ const page = await this.sqliteStore.getEventsPage(pageSize, offset);
7113
+ if (page.length === 0)
7114
+ break;
7115
+ for (const event of page) {
7116
+ await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
7117
+ enqueued += 1;
7118
+ }
7119
+ offset += page.length;
7120
+ if (page.length < pageSize)
7121
+ break;
7122
+ }
7123
+ fs4.writeFileSync(
7124
+ metaPath,
7125
+ JSON.stringify(
7126
+ {
7127
+ model: currentModel,
7128
+ previousModel,
7129
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
7130
+ enqueued
7131
+ },
7132
+ null,
7133
+ 2
7134
+ )
7135
+ );
7136
+ if (wasRunning)
7137
+ this.vectorWorker?.start();
7138
+ return {
7139
+ changed: true,
7140
+ previousModel,
7141
+ currentModel,
7142
+ enqueued,
7143
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7144
+ };
7145
+ }
7000
7146
  /**
7001
7147
  * Backward-compatible alias used by some hooks
7002
7148
  */