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.
@@ -1615,6 +1615,33 @@ var SQLiteEventStore = class {
1615
1615
  ids
1616
1616
  );
1617
1617
  }
1618
+ /**
1619
+ * Clear embedding outbox (used for embedding model migration)
1620
+ */
1621
+ async clearEmbeddingOutbox() {
1622
+ await this.initialize();
1623
+ sqliteRun(this.db, `DELETE FROM embedding_outbox`);
1624
+ }
1625
+ /**
1626
+ * Count total events
1627
+ */
1628
+ async countEvents() {
1629
+ await this.initialize();
1630
+ const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
1631
+ return row?.count || 0;
1632
+ }
1633
+ /**
1634
+ * Get events page in timestamp ascending order (stable migration/reindex scans)
1635
+ */
1636
+ async getEventsPage(limit = 1e3, offset = 0) {
1637
+ await this.initialize();
1638
+ const rows = sqliteAll(
1639
+ this.db,
1640
+ `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
1641
+ [limit, offset]
1642
+ );
1643
+ return rows.map(this.rowToEvent);
1644
+ }
1618
1645
  /**
1619
1646
  * Mark outbox items as failed
1620
1647
  */
@@ -2580,6 +2607,23 @@ var VectorStore = class {
2580
2607
  const result = await this.table.countRows();
2581
2608
  return result;
2582
2609
  }
2610
+ /**
2611
+ * Clear all vectors (used for embedding model migration)
2612
+ */
2613
+ async clearAll() {
2614
+ await this.initialize();
2615
+ if (!this.db)
2616
+ return;
2617
+ try {
2618
+ if (typeof this.db.dropTable === "function") {
2619
+ await this.db.dropTable(this.tableName);
2620
+ } else if (typeof this.db.drop_table === "function") {
2621
+ await this.db.drop_table(this.tableName);
2622
+ }
2623
+ } catch {
2624
+ }
2625
+ this.table = null;
2626
+ }
2583
2627
  /**
2584
2628
  * Check if vector exists for event
2585
2629
  */
@@ -2597,7 +2641,7 @@ var Embedder = class {
2597
2641
  pipeline = null;
2598
2642
  modelName;
2599
2643
  initialized = false;
2600
- constructor(modelName = "Xenova/all-MiniLM-L6-v2") {
2644
+ constructor(modelName = "jinaai/jina-embeddings-v5-text-nano") {
2601
2645
  this.modelName = modelName;
2602
2646
  }
2603
2647
  /**
@@ -2677,8 +2721,9 @@ var Embedder = class {
2677
2721
  };
2678
2722
  var defaultEmbedder = null;
2679
2723
  function getDefaultEmbedder() {
2724
+ const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
2680
2725
  if (!defaultEmbedder) {
2681
- defaultEmbedder = new Embedder();
2726
+ defaultEmbedder = new Embedder(envModel || void 0);
2682
2727
  }
2683
2728
  return defaultEmbedder;
2684
2729
  }
@@ -5909,8 +5954,10 @@ var MemoryService = class {
5909
5954
  readOnly;
5910
5955
  lightweightMode;
5911
5956
  mdMirror;
5957
+ storagePath;
5912
5958
  constructor(config) {
5913
5959
  const storagePath = this.expandPath(config.storagePath);
5960
+ this.storagePath = storagePath;
5914
5961
  this.readOnly = config.readOnly ?? false;
5915
5962
  this.lightweightMode = config.lightweightMode ?? false;
5916
5963
  this.mdMirror = new MarkdownMirror2(process.cwd());
@@ -5946,7 +5993,8 @@ var MemoryService = class {
5946
5993
  );
5947
5994
  }
5948
5995
  this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
5949
- this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
5996
+ const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
5997
+ this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
5950
5998
  this.matcher = getDefaultMatcher();
5951
5999
  this.retriever = createRetriever(
5952
6000
  this.sqliteStore,
@@ -6890,6 +6938,89 @@ var MemoryService = class {
6890
6938
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6891
6939
  this.graduation.recordAccess(eventId, sessionId, confidence);
6892
6940
  }
6941
+ getEmbeddingModelName() {
6942
+ return this.embedder.getModelName();
6943
+ }
6944
+ /**
6945
+ * Ensure embedding model metadata is in sync and optionally migrate vectors.
6946
+ * Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
6947
+ */
6948
+ async ensureEmbeddingModelForImport(options) {
6949
+ await this.initialize();
6950
+ const currentModel = this.getEmbeddingModelName();
6951
+ const metaPath = path3.join(this.storagePath, "embedding-meta.json");
6952
+ let previousModel = null;
6953
+ try {
6954
+ if (fs4.existsSync(metaPath)) {
6955
+ const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
6956
+ previousModel = parsed?.model || null;
6957
+ }
6958
+ } catch {
6959
+ previousModel = null;
6960
+ }
6961
+ const stats = await this.getStats();
6962
+ const hasExistingVectors = (stats.vectorCount || 0) > 0;
6963
+ if (!previousModel && !hasExistingVectors) {
6964
+ fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
6965
+ return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
6966
+ }
6967
+ const modelChanged = previousModel !== currentModel;
6968
+ const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
6969
+ if (!modelChanged && !legacyUnknownButVectorsExist) {
6970
+ return { changed: false, previousModel, currentModel, enqueued: 0 };
6971
+ }
6972
+ if (options?.autoMigrate === false) {
6973
+ return {
6974
+ changed: true,
6975
+ previousModel,
6976
+ currentModel,
6977
+ enqueued: 0,
6978
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
6979
+ };
6980
+ }
6981
+ const wasRunning = this.vectorWorker?.isRunning() || false;
6982
+ if (wasRunning)
6983
+ this.vectorWorker?.stop();
6984
+ await this.vectorStore.clearAll();
6985
+ await this.sqliteStore.clearEmbeddingOutbox();
6986
+ const pageSize = 1e3;
6987
+ let offset = 0;
6988
+ let enqueued = 0;
6989
+ while (true) {
6990
+ const page = await this.sqliteStore.getEventsPage(pageSize, offset);
6991
+ if (page.length === 0)
6992
+ break;
6993
+ for (const event of page) {
6994
+ await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
6995
+ enqueued += 1;
6996
+ }
6997
+ offset += page.length;
6998
+ if (page.length < pageSize)
6999
+ break;
7000
+ }
7001
+ fs4.writeFileSync(
7002
+ metaPath,
7003
+ JSON.stringify(
7004
+ {
7005
+ model: currentModel,
7006
+ previousModel,
7007
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
7008
+ enqueued
7009
+ },
7010
+ null,
7011
+ 2
7012
+ )
7013
+ );
7014
+ if (wasRunning)
7015
+ this.vectorWorker?.start();
7016
+ return {
7017
+ changed: true,
7018
+ previousModel,
7019
+ currentModel,
7020
+ enqueued,
7021
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7022
+ };
7023
+ }
6893
7024
  /**
6894
7025
  * Backward-compatible alias used by some hooks
6895
7026
  */
@@ -7074,6 +7205,7 @@ eventsRouter.get("/", async (c) => {
7074
7205
  const eventType = c.req.query("type");
7075
7206
  const level = c.req.query("level");
7076
7207
  const sort = c.req.query("sort") || "recent";
7208
+ const q = (c.req.query("q") || "").trim().toLowerCase();
7077
7209
  const limit = parseInt(c.req.query("limit") || "100", 10);
7078
7210
  const offset = parseInt(c.req.query("offset") || "0", 10);
7079
7211
  const memoryService = getServiceFromQuery(c);
@@ -7091,6 +7223,9 @@ eventsRouter.get("/", async (c) => {
7091
7223
  if (eventType) {
7092
7224
  events = events.filter((e) => e.eventType === eventType);
7093
7225
  }
7226
+ if (q) {
7227
+ events = events.filter((e) => (e.content || "").toLowerCase().includes(q));
7228
+ }
7094
7229
  if (sort === "accessed") {
7095
7230
  events.sort((a, b) => {
7096
7231
  const aTime = a.last_accessed_at || "";