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
package/dist/hooks/stop.js
CHANGED
|
@@ -1603,6 +1603,33 @@ var SQLiteEventStore = class {
|
|
|
1603
1603
|
ids
|
|
1604
1604
|
);
|
|
1605
1605
|
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Clear embedding outbox (used for embedding model migration)
|
|
1608
|
+
*/
|
|
1609
|
+
async clearEmbeddingOutbox() {
|
|
1610
|
+
await this.initialize();
|
|
1611
|
+
sqliteRun(this.db, `DELETE FROM embedding_outbox`);
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Count total events
|
|
1615
|
+
*/
|
|
1616
|
+
async countEvents() {
|
|
1617
|
+
await this.initialize();
|
|
1618
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
1619
|
+
return row?.count || 0;
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
1623
|
+
*/
|
|
1624
|
+
async getEventsPage(limit = 1e3, offset = 0) {
|
|
1625
|
+
await this.initialize();
|
|
1626
|
+
const rows = sqliteAll(
|
|
1627
|
+
this.db,
|
|
1628
|
+
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
1629
|
+
[limit, offset]
|
|
1630
|
+
);
|
|
1631
|
+
return rows.map(this.rowToEvent);
|
|
1632
|
+
}
|
|
1606
1633
|
/**
|
|
1607
1634
|
* Mark outbox items as failed
|
|
1608
1635
|
*/
|
|
@@ -2568,6 +2595,23 @@ var VectorStore = class {
|
|
|
2568
2595
|
const result = await this.table.countRows();
|
|
2569
2596
|
return result;
|
|
2570
2597
|
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Clear all vectors (used for embedding model migration)
|
|
2600
|
+
*/
|
|
2601
|
+
async clearAll() {
|
|
2602
|
+
await this.initialize();
|
|
2603
|
+
if (!this.db)
|
|
2604
|
+
return;
|
|
2605
|
+
try {
|
|
2606
|
+
if (typeof this.db.dropTable === "function") {
|
|
2607
|
+
await this.db.dropTable(this.tableName);
|
|
2608
|
+
} else if (typeof this.db.drop_table === "function") {
|
|
2609
|
+
await this.db.drop_table(this.tableName);
|
|
2610
|
+
}
|
|
2611
|
+
} catch {
|
|
2612
|
+
}
|
|
2613
|
+
this.table = null;
|
|
2614
|
+
}
|
|
2571
2615
|
/**
|
|
2572
2616
|
* Check if vector exists for event
|
|
2573
2617
|
*/
|
|
@@ -2580,13 +2624,15 @@ var VectorStore = class {
|
|
|
2580
2624
|
};
|
|
2581
2625
|
|
|
2582
2626
|
// src/core/embedder.ts
|
|
2583
|
-
import { pipeline } from "@
|
|
2627
|
+
import { pipeline } from "@huggingface/transformers";
|
|
2584
2628
|
var Embedder = class {
|
|
2585
2629
|
pipeline = null;
|
|
2586
2630
|
modelName;
|
|
2631
|
+
activeModelName;
|
|
2587
2632
|
initialized = false;
|
|
2588
|
-
constructor(modelName = "
|
|
2633
|
+
constructor(modelName = "jinaai/jina-embeddings-v5-text-nano-text-matching") {
|
|
2589
2634
|
this.modelName = modelName;
|
|
2635
|
+
this.activeModelName = modelName;
|
|
2590
2636
|
}
|
|
2591
2637
|
/**
|
|
2592
2638
|
* Initialize the embedding pipeline
|
|
@@ -2594,8 +2640,21 @@ var Embedder = class {
|
|
|
2594
2640
|
async initialize() {
|
|
2595
2641
|
if (this.initialized)
|
|
2596
2642
|
return;
|
|
2597
|
-
|
|
2598
|
-
|
|
2643
|
+
try {
|
|
2644
|
+
this.pipeline = await pipeline("feature-extraction", this.modelName);
|
|
2645
|
+
this.activeModelName = this.modelName;
|
|
2646
|
+
this.initialized = true;
|
|
2647
|
+
return;
|
|
2648
|
+
} catch (primaryError) {
|
|
2649
|
+
const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || "onnx-community/embeddinggemma-300m-ONNX";
|
|
2650
|
+
if (fallbackModel === this.modelName) {
|
|
2651
|
+
throw primaryError;
|
|
2652
|
+
}
|
|
2653
|
+
console.warn(`[Embedder] Primary model failed (${this.modelName}). Falling back to ${fallbackModel}`);
|
|
2654
|
+
this.pipeline = await pipeline("feature-extraction", fallbackModel);
|
|
2655
|
+
this.activeModelName = fallbackModel;
|
|
2656
|
+
this.initialized = true;
|
|
2657
|
+
}
|
|
2599
2658
|
}
|
|
2600
2659
|
/**
|
|
2601
2660
|
* Generate embedding for a single text
|
|
@@ -2612,7 +2671,7 @@ var Embedder = class {
|
|
|
2612
2671
|
const vector = Array.from(output.data);
|
|
2613
2672
|
return {
|
|
2614
2673
|
vector,
|
|
2615
|
-
model: this.
|
|
2674
|
+
model: this.activeModelName,
|
|
2616
2675
|
dimensions: vector.length
|
|
2617
2676
|
};
|
|
2618
2677
|
}
|
|
@@ -2636,7 +2695,7 @@ var Embedder = class {
|
|
|
2636
2695
|
const vector = Array.from(output.data);
|
|
2637
2696
|
results.push({
|
|
2638
2697
|
vector,
|
|
2639
|
-
model: this.
|
|
2698
|
+
model: this.activeModelName,
|
|
2640
2699
|
dimensions: vector.length
|
|
2641
2700
|
});
|
|
2642
2701
|
}
|
|
@@ -2660,13 +2719,14 @@ var Embedder = class {
|
|
|
2660
2719
|
* Get model name
|
|
2661
2720
|
*/
|
|
2662
2721
|
getModelName() {
|
|
2663
|
-
return this.
|
|
2722
|
+
return this.activeModelName;
|
|
2664
2723
|
}
|
|
2665
2724
|
};
|
|
2666
2725
|
var defaultEmbedder = null;
|
|
2667
2726
|
function getDefaultEmbedder() {
|
|
2727
|
+
const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
2668
2728
|
if (!defaultEmbedder) {
|
|
2669
|
-
defaultEmbedder = new Embedder();
|
|
2729
|
+
defaultEmbedder = new Embedder(envModel || void 0);
|
|
2670
2730
|
}
|
|
2671
2731
|
return defaultEmbedder;
|
|
2672
2732
|
}
|
|
@@ -5901,8 +5961,10 @@ var MemoryService = class {
|
|
|
5901
5961
|
readOnly;
|
|
5902
5962
|
lightweightMode;
|
|
5903
5963
|
mdMirror;
|
|
5964
|
+
storagePath;
|
|
5904
5965
|
constructor(config) {
|
|
5905
5966
|
const storagePath = this.expandPath(config.storagePath);
|
|
5967
|
+
this.storagePath = storagePath;
|
|
5906
5968
|
this.readOnly = config.readOnly ?? false;
|
|
5907
5969
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
5908
5970
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
@@ -5938,7 +6000,8 @@ var MemoryService = class {
|
|
|
5938
6000
|
);
|
|
5939
6001
|
}
|
|
5940
6002
|
this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
|
|
5941
|
-
|
|
6003
|
+
const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6004
|
+
this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
|
|
5942
6005
|
this.matcher = getDefaultMatcher();
|
|
5943
6006
|
this.retriever = createRetriever(
|
|
5944
6007
|
this.sqliteStore,
|
|
@@ -6882,6 +6945,89 @@ var MemoryService = class {
|
|
|
6882
6945
|
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
6883
6946
|
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
6884
6947
|
}
|
|
6948
|
+
getEmbeddingModelName() {
|
|
6949
|
+
return this.embedder.getModelName();
|
|
6950
|
+
}
|
|
6951
|
+
/**
|
|
6952
|
+
* Ensure embedding model metadata is in sync and optionally migrate vectors.
|
|
6953
|
+
* Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
|
|
6954
|
+
*/
|
|
6955
|
+
async ensureEmbeddingModelForImport(options) {
|
|
6956
|
+
await this.initialize();
|
|
6957
|
+
const currentModel = this.getEmbeddingModelName();
|
|
6958
|
+
const metaPath = path3.join(this.storagePath, "embedding-meta.json");
|
|
6959
|
+
let previousModel = null;
|
|
6960
|
+
try {
|
|
6961
|
+
if (fs4.existsSync(metaPath)) {
|
|
6962
|
+
const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
|
|
6963
|
+
previousModel = parsed?.model || null;
|
|
6964
|
+
}
|
|
6965
|
+
} catch {
|
|
6966
|
+
previousModel = null;
|
|
6967
|
+
}
|
|
6968
|
+
const stats = await this.getStats();
|
|
6969
|
+
const hasExistingVectors = (stats.vectorCount || 0) > 0;
|
|
6970
|
+
if (!previousModel && !hasExistingVectors) {
|
|
6971
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
6972
|
+
return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
|
|
6973
|
+
}
|
|
6974
|
+
const modelChanged = previousModel !== currentModel;
|
|
6975
|
+
const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
|
|
6976
|
+
if (!modelChanged && !legacyUnknownButVectorsExist) {
|
|
6977
|
+
return { changed: false, previousModel, currentModel, enqueued: 0 };
|
|
6978
|
+
}
|
|
6979
|
+
if (options?.autoMigrate === false) {
|
|
6980
|
+
return {
|
|
6981
|
+
changed: true,
|
|
6982
|
+
previousModel,
|
|
6983
|
+
currentModel,
|
|
6984
|
+
enqueued: 0,
|
|
6985
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
6986
|
+
};
|
|
6987
|
+
}
|
|
6988
|
+
const wasRunning = this.vectorWorker?.isRunning() || false;
|
|
6989
|
+
if (wasRunning)
|
|
6990
|
+
this.vectorWorker?.stop();
|
|
6991
|
+
await this.vectorStore.clearAll();
|
|
6992
|
+
await this.sqliteStore.clearEmbeddingOutbox();
|
|
6993
|
+
const pageSize = 1e3;
|
|
6994
|
+
let offset = 0;
|
|
6995
|
+
let enqueued = 0;
|
|
6996
|
+
while (true) {
|
|
6997
|
+
const page = await this.sqliteStore.getEventsPage(pageSize, offset);
|
|
6998
|
+
if (page.length === 0)
|
|
6999
|
+
break;
|
|
7000
|
+
for (const event of page) {
|
|
7001
|
+
await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
|
|
7002
|
+
enqueued += 1;
|
|
7003
|
+
}
|
|
7004
|
+
offset += page.length;
|
|
7005
|
+
if (page.length < pageSize)
|
|
7006
|
+
break;
|
|
7007
|
+
}
|
|
7008
|
+
fs4.writeFileSync(
|
|
7009
|
+
metaPath,
|
|
7010
|
+
JSON.stringify(
|
|
7011
|
+
{
|
|
7012
|
+
model: currentModel,
|
|
7013
|
+
previousModel,
|
|
7014
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7015
|
+
enqueued
|
|
7016
|
+
},
|
|
7017
|
+
null,
|
|
7018
|
+
2
|
|
7019
|
+
)
|
|
7020
|
+
);
|
|
7021
|
+
if (wasRunning)
|
|
7022
|
+
this.vectorWorker?.start();
|
|
7023
|
+
return {
|
|
7024
|
+
changed: true,
|
|
7025
|
+
previousModel,
|
|
7026
|
+
currentModel,
|
|
7027
|
+
enqueued,
|
|
7028
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7029
|
+
};
|
|
7030
|
+
}
|
|
6885
7031
|
/**
|
|
6886
7032
|
* Backward-compatible alias used by some hooks
|
|
6887
7033
|
*/
|