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
|
@@ -1598,6 +1598,33 @@ var SQLiteEventStore = class {
|
|
|
1598
1598
|
ids
|
|
1599
1599
|
);
|
|
1600
1600
|
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Clear embedding outbox (used for embedding model migration)
|
|
1603
|
+
*/
|
|
1604
|
+
async clearEmbeddingOutbox() {
|
|
1605
|
+
await this.initialize();
|
|
1606
|
+
sqliteRun(this.db, `DELETE FROM embedding_outbox`);
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Count total events
|
|
1610
|
+
*/
|
|
1611
|
+
async countEvents() {
|
|
1612
|
+
await this.initialize();
|
|
1613
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
1614
|
+
return row?.count || 0;
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
1618
|
+
*/
|
|
1619
|
+
async getEventsPage(limit = 1e3, offset = 0) {
|
|
1620
|
+
await this.initialize();
|
|
1621
|
+
const rows = sqliteAll(
|
|
1622
|
+
this.db,
|
|
1623
|
+
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
1624
|
+
[limit, offset]
|
|
1625
|
+
);
|
|
1626
|
+
return rows.map(this.rowToEvent);
|
|
1627
|
+
}
|
|
1601
1628
|
/**
|
|
1602
1629
|
* Mark outbox items as failed
|
|
1603
1630
|
*/
|
|
@@ -2563,6 +2590,23 @@ var VectorStore = class {
|
|
|
2563
2590
|
const result = await this.table.countRows();
|
|
2564
2591
|
return result;
|
|
2565
2592
|
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Clear all vectors (used for embedding model migration)
|
|
2595
|
+
*/
|
|
2596
|
+
async clearAll() {
|
|
2597
|
+
await this.initialize();
|
|
2598
|
+
if (!this.db)
|
|
2599
|
+
return;
|
|
2600
|
+
try {
|
|
2601
|
+
if (typeof this.db.dropTable === "function") {
|
|
2602
|
+
await this.db.dropTable(this.tableName);
|
|
2603
|
+
} else if (typeof this.db.drop_table === "function") {
|
|
2604
|
+
await this.db.drop_table(this.tableName);
|
|
2605
|
+
}
|
|
2606
|
+
} catch {
|
|
2607
|
+
}
|
|
2608
|
+
this.table = null;
|
|
2609
|
+
}
|
|
2566
2610
|
/**
|
|
2567
2611
|
* Check if vector exists for event
|
|
2568
2612
|
*/
|
|
@@ -2575,13 +2619,15 @@ var VectorStore = class {
|
|
|
2575
2619
|
};
|
|
2576
2620
|
|
|
2577
2621
|
// src/core/embedder.ts
|
|
2578
|
-
import { pipeline } from "@
|
|
2622
|
+
import { pipeline } from "@huggingface/transformers";
|
|
2579
2623
|
var Embedder = class {
|
|
2580
2624
|
pipeline = null;
|
|
2581
2625
|
modelName;
|
|
2626
|
+
activeModelName;
|
|
2582
2627
|
initialized = false;
|
|
2583
|
-
constructor(modelName = "
|
|
2628
|
+
constructor(modelName = "jinaai/jina-embeddings-v5-text-nano-text-matching") {
|
|
2584
2629
|
this.modelName = modelName;
|
|
2630
|
+
this.activeModelName = modelName;
|
|
2585
2631
|
}
|
|
2586
2632
|
/**
|
|
2587
2633
|
* Initialize the embedding pipeline
|
|
@@ -2589,8 +2635,21 @@ var Embedder = class {
|
|
|
2589
2635
|
async initialize() {
|
|
2590
2636
|
if (this.initialized)
|
|
2591
2637
|
return;
|
|
2592
|
-
|
|
2593
|
-
|
|
2638
|
+
try {
|
|
2639
|
+
this.pipeline = await pipeline("feature-extraction", this.modelName);
|
|
2640
|
+
this.activeModelName = this.modelName;
|
|
2641
|
+
this.initialized = true;
|
|
2642
|
+
return;
|
|
2643
|
+
} catch (primaryError) {
|
|
2644
|
+
const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || "onnx-community/embeddinggemma-300m-ONNX";
|
|
2645
|
+
if (fallbackModel === this.modelName) {
|
|
2646
|
+
throw primaryError;
|
|
2647
|
+
}
|
|
2648
|
+
console.warn(`[Embedder] Primary model failed (${this.modelName}). Falling back to ${fallbackModel}`);
|
|
2649
|
+
this.pipeline = await pipeline("feature-extraction", fallbackModel);
|
|
2650
|
+
this.activeModelName = fallbackModel;
|
|
2651
|
+
this.initialized = true;
|
|
2652
|
+
}
|
|
2594
2653
|
}
|
|
2595
2654
|
/**
|
|
2596
2655
|
* Generate embedding for a single text
|
|
@@ -2607,7 +2666,7 @@ var Embedder = class {
|
|
|
2607
2666
|
const vector = Array.from(output.data);
|
|
2608
2667
|
return {
|
|
2609
2668
|
vector,
|
|
2610
|
-
model: this.
|
|
2669
|
+
model: this.activeModelName,
|
|
2611
2670
|
dimensions: vector.length
|
|
2612
2671
|
};
|
|
2613
2672
|
}
|
|
@@ -2631,7 +2690,7 @@ var Embedder = class {
|
|
|
2631
2690
|
const vector = Array.from(output.data);
|
|
2632
2691
|
results.push({
|
|
2633
2692
|
vector,
|
|
2634
|
-
model: this.
|
|
2693
|
+
model: this.activeModelName,
|
|
2635
2694
|
dimensions: vector.length
|
|
2636
2695
|
});
|
|
2637
2696
|
}
|
|
@@ -2655,13 +2714,14 @@ var Embedder = class {
|
|
|
2655
2714
|
* Get model name
|
|
2656
2715
|
*/
|
|
2657
2716
|
getModelName() {
|
|
2658
|
-
return this.
|
|
2717
|
+
return this.activeModelName;
|
|
2659
2718
|
}
|
|
2660
2719
|
};
|
|
2661
2720
|
var defaultEmbedder = null;
|
|
2662
2721
|
function getDefaultEmbedder() {
|
|
2722
|
+
const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
2663
2723
|
if (!defaultEmbedder) {
|
|
2664
|
-
defaultEmbedder = new Embedder();
|
|
2724
|
+
defaultEmbedder = new Embedder(envModel || void 0);
|
|
2665
2725
|
}
|
|
2666
2726
|
return defaultEmbedder;
|
|
2667
2727
|
}
|
|
@@ -5921,8 +5981,10 @@ var MemoryService = class {
|
|
|
5921
5981
|
readOnly;
|
|
5922
5982
|
lightweightMode;
|
|
5923
5983
|
mdMirror;
|
|
5984
|
+
storagePath;
|
|
5924
5985
|
constructor(config) {
|
|
5925
5986
|
const storagePath = this.expandPath(config.storagePath);
|
|
5987
|
+
this.storagePath = storagePath;
|
|
5926
5988
|
this.readOnly = config.readOnly ?? false;
|
|
5927
5989
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
5928
5990
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
@@ -5958,7 +6020,8 @@ var MemoryService = class {
|
|
|
5958
6020
|
);
|
|
5959
6021
|
}
|
|
5960
6022
|
this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
|
|
5961
|
-
|
|
6023
|
+
const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6024
|
+
this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
|
|
5962
6025
|
this.matcher = getDefaultMatcher();
|
|
5963
6026
|
this.retriever = createRetriever(
|
|
5964
6027
|
this.sqliteStore,
|
|
@@ -6902,6 +6965,89 @@ var MemoryService = class {
|
|
|
6902
6965
|
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
6903
6966
|
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
6904
6967
|
}
|
|
6968
|
+
getEmbeddingModelName() {
|
|
6969
|
+
return this.embedder.getModelName();
|
|
6970
|
+
}
|
|
6971
|
+
/**
|
|
6972
|
+
* Ensure embedding model metadata is in sync and optionally migrate vectors.
|
|
6973
|
+
* Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
|
|
6974
|
+
*/
|
|
6975
|
+
async ensureEmbeddingModelForImport(options) {
|
|
6976
|
+
await this.initialize();
|
|
6977
|
+
const currentModel = this.getEmbeddingModelName();
|
|
6978
|
+
const metaPath = path3.join(this.storagePath, "embedding-meta.json");
|
|
6979
|
+
let previousModel = null;
|
|
6980
|
+
try {
|
|
6981
|
+
if (fs4.existsSync(metaPath)) {
|
|
6982
|
+
const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
|
|
6983
|
+
previousModel = parsed?.model || null;
|
|
6984
|
+
}
|
|
6985
|
+
} catch {
|
|
6986
|
+
previousModel = null;
|
|
6987
|
+
}
|
|
6988
|
+
const stats = await this.getStats();
|
|
6989
|
+
const hasExistingVectors = (stats.vectorCount || 0) > 0;
|
|
6990
|
+
if (!previousModel && !hasExistingVectors) {
|
|
6991
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
6992
|
+
return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
|
|
6993
|
+
}
|
|
6994
|
+
const modelChanged = previousModel !== currentModel;
|
|
6995
|
+
const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
|
|
6996
|
+
if (!modelChanged && !legacyUnknownButVectorsExist) {
|
|
6997
|
+
return { changed: false, previousModel, currentModel, enqueued: 0 };
|
|
6998
|
+
}
|
|
6999
|
+
if (options?.autoMigrate === false) {
|
|
7000
|
+
return {
|
|
7001
|
+
changed: true,
|
|
7002
|
+
previousModel,
|
|
7003
|
+
currentModel,
|
|
7004
|
+
enqueued: 0,
|
|
7005
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7006
|
+
};
|
|
7007
|
+
}
|
|
7008
|
+
const wasRunning = this.vectorWorker?.isRunning() || false;
|
|
7009
|
+
if (wasRunning)
|
|
7010
|
+
this.vectorWorker?.stop();
|
|
7011
|
+
await this.vectorStore.clearAll();
|
|
7012
|
+
await this.sqliteStore.clearEmbeddingOutbox();
|
|
7013
|
+
const pageSize = 1e3;
|
|
7014
|
+
let offset = 0;
|
|
7015
|
+
let enqueued = 0;
|
|
7016
|
+
while (true) {
|
|
7017
|
+
const page = await this.sqliteStore.getEventsPage(pageSize, offset);
|
|
7018
|
+
if (page.length === 0)
|
|
7019
|
+
break;
|
|
7020
|
+
for (const event of page) {
|
|
7021
|
+
await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
|
|
7022
|
+
enqueued += 1;
|
|
7023
|
+
}
|
|
7024
|
+
offset += page.length;
|
|
7025
|
+
if (page.length < pageSize)
|
|
7026
|
+
break;
|
|
7027
|
+
}
|
|
7028
|
+
fs4.writeFileSync(
|
|
7029
|
+
metaPath,
|
|
7030
|
+
JSON.stringify(
|
|
7031
|
+
{
|
|
7032
|
+
model: currentModel,
|
|
7033
|
+
previousModel,
|
|
7034
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7035
|
+
enqueued
|
|
7036
|
+
},
|
|
7037
|
+
null,
|
|
7038
|
+
2
|
|
7039
|
+
)
|
|
7040
|
+
);
|
|
7041
|
+
if (wasRunning)
|
|
7042
|
+
this.vectorWorker?.start();
|
|
7043
|
+
return {
|
|
7044
|
+
changed: true,
|
|
7045
|
+
previousModel,
|
|
7046
|
+
currentModel,
|
|
7047
|
+
enqueued,
|
|
7048
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7049
|
+
};
|
|
7050
|
+
}
|
|
6905
7051
|
/**
|
|
6906
7052
|
* Backward-compatible alias used by some hooks
|
|
6907
7053
|
*/
|