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.
- package/dist/cli/index.js +182 -12
- package/dist/cli/index.js.map +2 -2
- package/dist/core/index.js +70 -10
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +155 -9
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/session-end.js +155 -9
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +155 -9
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +155 -9
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +155 -9
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/server/api/index.js +159 -9
- package/dist/server/api/index.js.map +2 -2
- package/dist/server/index.js +159 -9
- package/dist/server/index.js.map +2 -2
- package/dist/services/memory-service.js +155 -9
- package/dist/services/memory-service.js.map +2 -2
- package/dist/ui/app.js +126 -0
- package/dist/ui/index.html +39 -0
- package/dist/ui/style.css +7 -0
- package/package.json +2 -2
- package/scripts/build.ts +1 -0
- package/src/cli/index.ts +23 -1
- package/src/core/embedder.ts +25 -8
- package/src/core/sqlite-event-store.ts +32 -0
- package/src/core/types.ts +2 -2
- package/src/core/vector-store.ts +20 -0
- package/src/server/api/events.ts +6 -0
- package/src/services/memory-service.ts +112 -2
- package/src/ui/app.js +126 -0
- package/src/ui/index.html +39 -0
- package/src/ui/style.css +7 -0
|
@@ -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 "@
|
|
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 = "
|
|
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
|
-
|
|
2594
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|
|
@@ -5897,8 +5957,10 @@ var MemoryService = class {
|
|
|
5897
5957
|
readOnly;
|
|
5898
5958
|
lightweightMode;
|
|
5899
5959
|
mdMirror;
|
|
5960
|
+
storagePath;
|
|
5900
5961
|
constructor(config) {
|
|
5901
5962
|
const storagePath = this.expandPath(config.storagePath);
|
|
5963
|
+
this.storagePath = storagePath;
|
|
5902
5964
|
this.readOnly = config.readOnly ?? false;
|
|
5903
5965
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
5904
5966
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
@@ -5934,7 +5996,8 @@ var MemoryService = class {
|
|
|
5934
5996
|
);
|
|
5935
5997
|
}
|
|
5936
5998
|
this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
|
|
5937
|
-
|
|
5999
|
+
const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6000
|
+
this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
|
|
5938
6001
|
this.matcher = getDefaultMatcher();
|
|
5939
6002
|
this.retriever = createRetriever(
|
|
5940
6003
|
this.sqliteStore,
|
|
@@ -6878,6 +6941,89 @@ var MemoryService = class {
|
|
|
6878
6941
|
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
6879
6942
|
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
6880
6943
|
}
|
|
6944
|
+
getEmbeddingModelName() {
|
|
6945
|
+
return this.embedder.getModelName();
|
|
6946
|
+
}
|
|
6947
|
+
/**
|
|
6948
|
+
* Ensure embedding model metadata is in sync and optionally migrate vectors.
|
|
6949
|
+
* Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
|
|
6950
|
+
*/
|
|
6951
|
+
async ensureEmbeddingModelForImport(options) {
|
|
6952
|
+
await this.initialize();
|
|
6953
|
+
const currentModel = this.getEmbeddingModelName();
|
|
6954
|
+
const metaPath = path3.join(this.storagePath, "embedding-meta.json");
|
|
6955
|
+
let previousModel = null;
|
|
6956
|
+
try {
|
|
6957
|
+
if (fs4.existsSync(metaPath)) {
|
|
6958
|
+
const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
|
|
6959
|
+
previousModel = parsed?.model || null;
|
|
6960
|
+
}
|
|
6961
|
+
} catch {
|
|
6962
|
+
previousModel = null;
|
|
6963
|
+
}
|
|
6964
|
+
const stats = await this.getStats();
|
|
6965
|
+
const hasExistingVectors = (stats.vectorCount || 0) > 0;
|
|
6966
|
+
if (!previousModel && !hasExistingVectors) {
|
|
6967
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
6968
|
+
return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
|
|
6969
|
+
}
|
|
6970
|
+
const modelChanged = previousModel !== currentModel;
|
|
6971
|
+
const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
|
|
6972
|
+
if (!modelChanged && !legacyUnknownButVectorsExist) {
|
|
6973
|
+
return { changed: false, previousModel, currentModel, enqueued: 0 };
|
|
6974
|
+
}
|
|
6975
|
+
if (options?.autoMigrate === false) {
|
|
6976
|
+
return {
|
|
6977
|
+
changed: true,
|
|
6978
|
+
previousModel,
|
|
6979
|
+
currentModel,
|
|
6980
|
+
enqueued: 0,
|
|
6981
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
6982
|
+
};
|
|
6983
|
+
}
|
|
6984
|
+
const wasRunning = this.vectorWorker?.isRunning() || false;
|
|
6985
|
+
if (wasRunning)
|
|
6986
|
+
this.vectorWorker?.stop();
|
|
6987
|
+
await this.vectorStore.clearAll();
|
|
6988
|
+
await this.sqliteStore.clearEmbeddingOutbox();
|
|
6989
|
+
const pageSize = 1e3;
|
|
6990
|
+
let offset = 0;
|
|
6991
|
+
let enqueued = 0;
|
|
6992
|
+
while (true) {
|
|
6993
|
+
const page = await this.sqliteStore.getEventsPage(pageSize, offset);
|
|
6994
|
+
if (page.length === 0)
|
|
6995
|
+
break;
|
|
6996
|
+
for (const event of page) {
|
|
6997
|
+
await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
|
|
6998
|
+
enqueued += 1;
|
|
6999
|
+
}
|
|
7000
|
+
offset += page.length;
|
|
7001
|
+
if (page.length < pageSize)
|
|
7002
|
+
break;
|
|
7003
|
+
}
|
|
7004
|
+
fs4.writeFileSync(
|
|
7005
|
+
metaPath,
|
|
7006
|
+
JSON.stringify(
|
|
7007
|
+
{
|
|
7008
|
+
model: currentModel,
|
|
7009
|
+
previousModel,
|
|
7010
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7011
|
+
enqueued
|
|
7012
|
+
},
|
|
7013
|
+
null,
|
|
7014
|
+
2
|
|
7015
|
+
)
|
|
7016
|
+
);
|
|
7017
|
+
if (wasRunning)
|
|
7018
|
+
this.vectorWorker?.start();
|
|
7019
|
+
return {
|
|
7020
|
+
changed: true,
|
|
7021
|
+
previousModel,
|
|
7022
|
+
currentModel,
|
|
7023
|
+
enqueued,
|
|
7024
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7025
|
+
};
|
|
7026
|
+
}
|
|
6881
7027
|
/**
|
|
6882
7028
|
* Backward-compatible alias used by some hooks
|
|
6883
7029
|
*/
|