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.
@@ -1624,6 +1624,33 @@ var SQLiteEventStore = class {
1624
1624
  ids
1625
1625
  );
1626
1626
  }
1627
+ /**
1628
+ * Clear embedding outbox (used for embedding model migration)
1629
+ */
1630
+ async clearEmbeddingOutbox() {
1631
+ await this.initialize();
1632
+ sqliteRun(this.db, `DELETE FROM embedding_outbox`);
1633
+ }
1634
+ /**
1635
+ * Count total events
1636
+ */
1637
+ async countEvents() {
1638
+ await this.initialize();
1639
+ const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
1640
+ return row?.count || 0;
1641
+ }
1642
+ /**
1643
+ * Get events page in timestamp ascending order (stable migration/reindex scans)
1644
+ */
1645
+ async getEventsPage(limit = 1e3, offset = 0) {
1646
+ await this.initialize();
1647
+ const rows = sqliteAll(
1648
+ this.db,
1649
+ `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
1650
+ [limit, offset]
1651
+ );
1652
+ return rows.map(this.rowToEvent);
1653
+ }
1627
1654
  /**
1628
1655
  * Mark outbox items as failed
1629
1656
  */
@@ -2589,6 +2616,23 @@ var VectorStore = class {
2589
2616
  const result = await this.table.countRows();
2590
2617
  return result;
2591
2618
  }
2619
+ /**
2620
+ * Clear all vectors (used for embedding model migration)
2621
+ */
2622
+ async clearAll() {
2623
+ await this.initialize();
2624
+ if (!this.db)
2625
+ return;
2626
+ try {
2627
+ if (typeof this.db.dropTable === "function") {
2628
+ await this.db.dropTable(this.tableName);
2629
+ } else if (typeof this.db.drop_table === "function") {
2630
+ await this.db.drop_table(this.tableName);
2631
+ }
2632
+ } catch {
2633
+ }
2634
+ this.table = null;
2635
+ }
2592
2636
  /**
2593
2637
  * Check if vector exists for event
2594
2638
  */
@@ -2601,13 +2645,15 @@ var VectorStore = class {
2601
2645
  };
2602
2646
 
2603
2647
  // src/core/embedder.ts
2604
- import { pipeline } from "@xenova/transformers";
2648
+ import { pipeline } from "@huggingface/transformers";
2605
2649
  var Embedder = class {
2606
2650
  pipeline = null;
2607
2651
  modelName;
2652
+ activeModelName;
2608
2653
  initialized = false;
2609
- constructor(modelName = "Xenova/all-MiniLM-L6-v2") {
2654
+ constructor(modelName = "jinaai/jina-embeddings-v5-text-nano-text-matching") {
2610
2655
  this.modelName = modelName;
2656
+ this.activeModelName = modelName;
2611
2657
  }
2612
2658
  /**
2613
2659
  * Initialize the embedding pipeline
@@ -2615,8 +2661,21 @@ var Embedder = class {
2615
2661
  async initialize() {
2616
2662
  if (this.initialized)
2617
2663
  return;
2618
- this.pipeline = await pipeline("feature-extraction", this.modelName);
2619
- this.initialized = true;
2664
+ try {
2665
+ this.pipeline = await pipeline("feature-extraction", this.modelName);
2666
+ this.activeModelName = this.modelName;
2667
+ this.initialized = true;
2668
+ return;
2669
+ } catch (primaryError) {
2670
+ const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || "onnx-community/embeddinggemma-300m-ONNX";
2671
+ if (fallbackModel === this.modelName) {
2672
+ throw primaryError;
2673
+ }
2674
+ console.warn(`[Embedder] Primary model failed (${this.modelName}). Falling back to ${fallbackModel}`);
2675
+ this.pipeline = await pipeline("feature-extraction", fallbackModel);
2676
+ this.activeModelName = fallbackModel;
2677
+ this.initialized = true;
2678
+ }
2620
2679
  }
2621
2680
  /**
2622
2681
  * Generate embedding for a single text
@@ -2633,7 +2692,7 @@ var Embedder = class {
2633
2692
  const vector = Array.from(output.data);
2634
2693
  return {
2635
2694
  vector,
2636
- model: this.modelName,
2695
+ model: this.activeModelName,
2637
2696
  dimensions: vector.length
2638
2697
  };
2639
2698
  }
@@ -2657,7 +2716,7 @@ var Embedder = class {
2657
2716
  const vector = Array.from(output.data);
2658
2717
  results.push({
2659
2718
  vector,
2660
- model: this.modelName,
2719
+ model: this.activeModelName,
2661
2720
  dimensions: vector.length
2662
2721
  });
2663
2722
  }
@@ -2681,13 +2740,14 @@ var Embedder = class {
2681
2740
  * Get model name
2682
2741
  */
2683
2742
  getModelName() {
2684
- return this.modelName;
2743
+ return this.activeModelName;
2685
2744
  }
2686
2745
  };
2687
2746
  var defaultEmbedder = null;
2688
2747
  function getDefaultEmbedder() {
2748
+ const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
2689
2749
  if (!defaultEmbedder) {
2690
- defaultEmbedder = new Embedder();
2750
+ defaultEmbedder = new Embedder(envModel || void 0);
2691
2751
  }
2692
2752
  return defaultEmbedder;
2693
2753
  }
@@ -5918,8 +5978,10 @@ var MemoryService = class {
5918
5978
  readOnly;
5919
5979
  lightweightMode;
5920
5980
  mdMirror;
5981
+ storagePath;
5921
5982
  constructor(config) {
5922
5983
  const storagePath = this.expandPath(config.storagePath);
5984
+ this.storagePath = storagePath;
5923
5985
  this.readOnly = config.readOnly ?? false;
5924
5986
  this.lightweightMode = config.lightweightMode ?? false;
5925
5987
  this.mdMirror = new MarkdownMirror2(process.cwd());
@@ -5955,7 +6017,8 @@ var MemoryService = class {
5955
6017
  );
5956
6018
  }
5957
6019
  this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
5958
- this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
6020
+ const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
6021
+ this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
5959
6022
  this.matcher = getDefaultMatcher();
5960
6023
  this.retriever = createRetriever(
5961
6024
  this.sqliteStore,
@@ -6899,6 +6962,89 @@ var MemoryService = class {
6899
6962
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6900
6963
  this.graduation.recordAccess(eventId, sessionId, confidence);
6901
6964
  }
6965
+ getEmbeddingModelName() {
6966
+ return this.embedder.getModelName();
6967
+ }
6968
+ /**
6969
+ * Ensure embedding model metadata is in sync and optionally migrate vectors.
6970
+ * Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
6971
+ */
6972
+ async ensureEmbeddingModelForImport(options) {
6973
+ await this.initialize();
6974
+ const currentModel = this.getEmbeddingModelName();
6975
+ const metaPath = path3.join(this.storagePath, "embedding-meta.json");
6976
+ let previousModel = null;
6977
+ try {
6978
+ if (fs4.existsSync(metaPath)) {
6979
+ const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
6980
+ previousModel = parsed?.model || null;
6981
+ }
6982
+ } catch {
6983
+ previousModel = null;
6984
+ }
6985
+ const stats = await this.getStats();
6986
+ const hasExistingVectors = (stats.vectorCount || 0) > 0;
6987
+ if (!previousModel && !hasExistingVectors) {
6988
+ fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
6989
+ return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
6990
+ }
6991
+ const modelChanged = previousModel !== currentModel;
6992
+ const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
6993
+ if (!modelChanged && !legacyUnknownButVectorsExist) {
6994
+ return { changed: false, previousModel, currentModel, enqueued: 0 };
6995
+ }
6996
+ if (options?.autoMigrate === false) {
6997
+ return {
6998
+ changed: true,
6999
+ previousModel,
7000
+ currentModel,
7001
+ enqueued: 0,
7002
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7003
+ };
7004
+ }
7005
+ const wasRunning = this.vectorWorker?.isRunning() || false;
7006
+ if (wasRunning)
7007
+ this.vectorWorker?.stop();
7008
+ await this.vectorStore.clearAll();
7009
+ await this.sqliteStore.clearEmbeddingOutbox();
7010
+ const pageSize = 1e3;
7011
+ let offset = 0;
7012
+ let enqueued = 0;
7013
+ while (true) {
7014
+ const page = await this.sqliteStore.getEventsPage(pageSize, offset);
7015
+ if (page.length === 0)
7016
+ break;
7017
+ for (const event of page) {
7018
+ await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
7019
+ enqueued += 1;
7020
+ }
7021
+ offset += page.length;
7022
+ if (page.length < pageSize)
7023
+ break;
7024
+ }
7025
+ fs4.writeFileSync(
7026
+ metaPath,
7027
+ JSON.stringify(
7028
+ {
7029
+ model: currentModel,
7030
+ previousModel,
7031
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
7032
+ enqueued
7033
+ },
7034
+ null,
7035
+ 2
7036
+ )
7037
+ );
7038
+ if (wasRunning)
7039
+ this.vectorWorker?.start();
7040
+ return {
7041
+ changed: true,
7042
+ previousModel,
7043
+ currentModel,
7044
+ enqueued,
7045
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7046
+ };
7047
+ }
6902
7048
  /**
6903
7049
  * Backward-compatible alias used by some hooks
6904
7050
  */
@@ -7083,6 +7229,7 @@ eventsRouter.get("/", async (c) => {
7083
7229
  const eventType = c.req.query("type");
7084
7230
  const level = c.req.query("level");
7085
7231
  const sort = c.req.query("sort") || "recent";
7232
+ const q = (c.req.query("q") || "").trim().toLowerCase();
7086
7233
  const limit = parseInt(c.req.query("limit") || "100", 10);
7087
7234
  const offset = parseInt(c.req.query("offset") || "0", 10);
7088
7235
  const memoryService = getServiceFromQuery(c);
@@ -7100,6 +7247,9 @@ eventsRouter.get("/", async (c) => {
7100
7247
  if (eventType) {
7101
7248
  events = events.filter((e) => e.eventType === eventType);
7102
7249
  }
7250
+ if (q) {
7251
+ events = events.filter((e) => (e.content || "").toLowerCase().includes(q));
7252
+ }
7103
7253
  if (sort === "accessed") {
7104
7254
  events.sort((a, b) => {
7105
7255
  const aTime = a.last_accessed_at || "";