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.
@@ -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
  */
@@ -2606,7 +2650,7 @@ var Embedder = class {
2606
2650
  pipeline = null;
2607
2651
  modelName;
2608
2652
  initialized = false;
2609
- constructor(modelName = "Xenova/all-MiniLM-L6-v2") {
2653
+ constructor(modelName = "jinaai/jina-embeddings-v5-text-nano") {
2610
2654
  this.modelName = modelName;
2611
2655
  }
2612
2656
  /**
@@ -2686,8 +2730,9 @@ var Embedder = class {
2686
2730
  };
2687
2731
  var defaultEmbedder = null;
2688
2732
  function getDefaultEmbedder() {
2733
+ const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
2689
2734
  if (!defaultEmbedder) {
2690
- defaultEmbedder = new Embedder();
2735
+ defaultEmbedder = new Embedder(envModel || void 0);
2691
2736
  }
2692
2737
  return defaultEmbedder;
2693
2738
  }
@@ -5918,8 +5963,10 @@ var MemoryService = class {
5918
5963
  readOnly;
5919
5964
  lightweightMode;
5920
5965
  mdMirror;
5966
+ storagePath;
5921
5967
  constructor(config) {
5922
5968
  const storagePath = this.expandPath(config.storagePath);
5969
+ this.storagePath = storagePath;
5923
5970
  this.readOnly = config.readOnly ?? false;
5924
5971
  this.lightweightMode = config.lightweightMode ?? false;
5925
5972
  this.mdMirror = new MarkdownMirror2(process.cwd());
@@ -5955,7 +6002,8 @@ var MemoryService = class {
5955
6002
  );
5956
6003
  }
5957
6004
  this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
5958
- this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
6005
+ const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
6006
+ this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
5959
6007
  this.matcher = getDefaultMatcher();
5960
6008
  this.retriever = createRetriever(
5961
6009
  this.sqliteStore,
@@ -6899,6 +6947,89 @@ var MemoryService = class {
6899
6947
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6900
6948
  this.graduation.recordAccess(eventId, sessionId, confidence);
6901
6949
  }
6950
+ getEmbeddingModelName() {
6951
+ return this.embedder.getModelName();
6952
+ }
6953
+ /**
6954
+ * Ensure embedding model metadata is in sync and optionally migrate vectors.
6955
+ * Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
6956
+ */
6957
+ async ensureEmbeddingModelForImport(options) {
6958
+ await this.initialize();
6959
+ const currentModel = this.getEmbeddingModelName();
6960
+ const metaPath = path3.join(this.storagePath, "embedding-meta.json");
6961
+ let previousModel = null;
6962
+ try {
6963
+ if (fs4.existsSync(metaPath)) {
6964
+ const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
6965
+ previousModel = parsed?.model || null;
6966
+ }
6967
+ } catch {
6968
+ previousModel = null;
6969
+ }
6970
+ const stats = await this.getStats();
6971
+ const hasExistingVectors = (stats.vectorCount || 0) > 0;
6972
+ if (!previousModel && !hasExistingVectors) {
6973
+ fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
6974
+ return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
6975
+ }
6976
+ const modelChanged = previousModel !== currentModel;
6977
+ const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
6978
+ if (!modelChanged && !legacyUnknownButVectorsExist) {
6979
+ return { changed: false, previousModel, currentModel, enqueued: 0 };
6980
+ }
6981
+ if (options?.autoMigrate === false) {
6982
+ return {
6983
+ changed: true,
6984
+ previousModel,
6985
+ currentModel,
6986
+ enqueued: 0,
6987
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
6988
+ };
6989
+ }
6990
+ const wasRunning = this.vectorWorker?.isRunning() || false;
6991
+ if (wasRunning)
6992
+ this.vectorWorker?.stop();
6993
+ await this.vectorStore.clearAll();
6994
+ await this.sqliteStore.clearEmbeddingOutbox();
6995
+ const pageSize = 1e3;
6996
+ let offset = 0;
6997
+ let enqueued = 0;
6998
+ while (true) {
6999
+ const page = await this.sqliteStore.getEventsPage(pageSize, offset);
7000
+ if (page.length === 0)
7001
+ break;
7002
+ for (const event of page) {
7003
+ await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
7004
+ enqueued += 1;
7005
+ }
7006
+ offset += page.length;
7007
+ if (page.length < pageSize)
7008
+ break;
7009
+ }
7010
+ fs4.writeFileSync(
7011
+ metaPath,
7012
+ JSON.stringify(
7013
+ {
7014
+ model: currentModel,
7015
+ previousModel,
7016
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
7017
+ enqueued
7018
+ },
7019
+ null,
7020
+ 2
7021
+ )
7022
+ );
7023
+ if (wasRunning)
7024
+ this.vectorWorker?.start();
7025
+ return {
7026
+ changed: true,
7027
+ previousModel,
7028
+ currentModel,
7029
+ enqueued,
7030
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7031
+ };
7032
+ }
6902
7033
  /**
6903
7034
  * Backward-compatible alias used by some hooks
6904
7035
  */
@@ -7083,6 +7214,7 @@ eventsRouter.get("/", async (c) => {
7083
7214
  const eventType = c.req.query("type");
7084
7215
  const level = c.req.query("level");
7085
7216
  const sort = c.req.query("sort") || "recent";
7217
+ const q = (c.req.query("q") || "").trim().toLowerCase();
7086
7218
  const limit = parseInt(c.req.query("limit") || "100", 10);
7087
7219
  const offset = parseInt(c.req.query("offset") || "0", 10);
7088
7220
  const memoryService = getServiceFromQuery(c);
@@ -7100,6 +7232,9 @@ eventsRouter.get("/", async (c) => {
7100
7232
  if (eventType) {
7101
7233
  events = events.filter((e) => e.eventType === eventType);
7102
7234
  }
7235
+ if (q) {
7236
+ events = events.filter((e) => (e.content || "").toLowerCase().includes(q));
7237
+ }
7103
7238
  if (sort === "accessed") {
7104
7239
  events.sort((a, b) => {
7105
7240
  const aTime = a.last_accessed_at || "";