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
|
@@ -1605,6 +1605,33 @@ var SQLiteEventStore = class {
|
|
|
1605
1605
|
ids
|
|
1606
1606
|
);
|
|
1607
1607
|
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Clear embedding outbox (used for embedding model migration)
|
|
1610
|
+
*/
|
|
1611
|
+
async clearEmbeddingOutbox() {
|
|
1612
|
+
await this.initialize();
|
|
1613
|
+
sqliteRun(this.db, `DELETE FROM embedding_outbox`);
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Count total events
|
|
1617
|
+
*/
|
|
1618
|
+
async countEvents() {
|
|
1619
|
+
await this.initialize();
|
|
1620
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
1621
|
+
return row?.count || 0;
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
1625
|
+
*/
|
|
1626
|
+
async getEventsPage(limit = 1e3, offset = 0) {
|
|
1627
|
+
await this.initialize();
|
|
1628
|
+
const rows = sqliteAll(
|
|
1629
|
+
this.db,
|
|
1630
|
+
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
1631
|
+
[limit, offset]
|
|
1632
|
+
);
|
|
1633
|
+
return rows.map(this.rowToEvent);
|
|
1634
|
+
}
|
|
1608
1635
|
/**
|
|
1609
1636
|
* Mark outbox items as failed
|
|
1610
1637
|
*/
|
|
@@ -2570,6 +2597,23 @@ var VectorStore = class {
|
|
|
2570
2597
|
const result = await this.table.countRows();
|
|
2571
2598
|
return result;
|
|
2572
2599
|
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Clear all vectors (used for embedding model migration)
|
|
2602
|
+
*/
|
|
2603
|
+
async clearAll() {
|
|
2604
|
+
await this.initialize();
|
|
2605
|
+
if (!this.db)
|
|
2606
|
+
return;
|
|
2607
|
+
try {
|
|
2608
|
+
if (typeof this.db.dropTable === "function") {
|
|
2609
|
+
await this.db.dropTable(this.tableName);
|
|
2610
|
+
} else if (typeof this.db.drop_table === "function") {
|
|
2611
|
+
await this.db.drop_table(this.tableName);
|
|
2612
|
+
}
|
|
2613
|
+
} catch {
|
|
2614
|
+
}
|
|
2615
|
+
this.table = null;
|
|
2616
|
+
}
|
|
2573
2617
|
/**
|
|
2574
2618
|
* Check if vector exists for event
|
|
2575
2619
|
*/
|
|
@@ -2582,13 +2626,15 @@ var VectorStore = class {
|
|
|
2582
2626
|
};
|
|
2583
2627
|
|
|
2584
2628
|
// src/core/embedder.ts
|
|
2585
|
-
import { pipeline } from "@
|
|
2629
|
+
import { pipeline } from "@huggingface/transformers";
|
|
2586
2630
|
var Embedder = class {
|
|
2587
2631
|
pipeline = null;
|
|
2588
2632
|
modelName;
|
|
2633
|
+
activeModelName;
|
|
2589
2634
|
initialized = false;
|
|
2590
|
-
constructor(modelName = "
|
|
2635
|
+
constructor(modelName = "jinaai/jina-embeddings-v5-text-nano-text-matching") {
|
|
2591
2636
|
this.modelName = modelName;
|
|
2637
|
+
this.activeModelName = modelName;
|
|
2592
2638
|
}
|
|
2593
2639
|
/**
|
|
2594
2640
|
* Initialize the embedding pipeline
|
|
@@ -2596,8 +2642,21 @@ var Embedder = class {
|
|
|
2596
2642
|
async initialize() {
|
|
2597
2643
|
if (this.initialized)
|
|
2598
2644
|
return;
|
|
2599
|
-
|
|
2600
|
-
|
|
2645
|
+
try {
|
|
2646
|
+
this.pipeline = await pipeline("feature-extraction", this.modelName);
|
|
2647
|
+
this.activeModelName = this.modelName;
|
|
2648
|
+
this.initialized = true;
|
|
2649
|
+
return;
|
|
2650
|
+
} catch (primaryError) {
|
|
2651
|
+
const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || "onnx-community/embeddinggemma-300m-ONNX";
|
|
2652
|
+
if (fallbackModel === this.modelName) {
|
|
2653
|
+
throw primaryError;
|
|
2654
|
+
}
|
|
2655
|
+
console.warn(`[Embedder] Primary model failed (${this.modelName}). Falling back to ${fallbackModel}`);
|
|
2656
|
+
this.pipeline = await pipeline("feature-extraction", fallbackModel);
|
|
2657
|
+
this.activeModelName = fallbackModel;
|
|
2658
|
+
this.initialized = true;
|
|
2659
|
+
}
|
|
2601
2660
|
}
|
|
2602
2661
|
/**
|
|
2603
2662
|
* Generate embedding for a single text
|
|
@@ -2614,7 +2673,7 @@ var Embedder = class {
|
|
|
2614
2673
|
const vector = Array.from(output.data);
|
|
2615
2674
|
return {
|
|
2616
2675
|
vector,
|
|
2617
|
-
model: this.
|
|
2676
|
+
model: this.activeModelName,
|
|
2618
2677
|
dimensions: vector.length
|
|
2619
2678
|
};
|
|
2620
2679
|
}
|
|
@@ -2638,7 +2697,7 @@ var Embedder = class {
|
|
|
2638
2697
|
const vector = Array.from(output.data);
|
|
2639
2698
|
results.push({
|
|
2640
2699
|
vector,
|
|
2641
|
-
model: this.
|
|
2700
|
+
model: this.activeModelName,
|
|
2642
2701
|
dimensions: vector.length
|
|
2643
2702
|
});
|
|
2644
2703
|
}
|
|
@@ -2662,13 +2721,14 @@ var Embedder = class {
|
|
|
2662
2721
|
* Get model name
|
|
2663
2722
|
*/
|
|
2664
2723
|
getModelName() {
|
|
2665
|
-
return this.
|
|
2724
|
+
return this.activeModelName;
|
|
2666
2725
|
}
|
|
2667
2726
|
};
|
|
2668
2727
|
var defaultEmbedder = null;
|
|
2669
2728
|
function getDefaultEmbedder() {
|
|
2729
|
+
const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
2670
2730
|
if (!defaultEmbedder) {
|
|
2671
|
-
defaultEmbedder = new Embedder();
|
|
2731
|
+
defaultEmbedder = new Embedder(envModel || void 0);
|
|
2672
2732
|
}
|
|
2673
2733
|
return defaultEmbedder;
|
|
2674
2734
|
}
|
|
@@ -5903,8 +5963,10 @@ var MemoryService = class {
|
|
|
5903
5963
|
readOnly;
|
|
5904
5964
|
lightweightMode;
|
|
5905
5965
|
mdMirror;
|
|
5966
|
+
storagePath;
|
|
5906
5967
|
constructor(config) {
|
|
5907
5968
|
const storagePath = this.expandPath(config.storagePath);
|
|
5969
|
+
this.storagePath = storagePath;
|
|
5908
5970
|
this.readOnly = config.readOnly ?? false;
|
|
5909
5971
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
5910
5972
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
@@ -5940,7 +6002,8 @@ var MemoryService = class {
|
|
|
5940
6002
|
);
|
|
5941
6003
|
}
|
|
5942
6004
|
this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
|
|
5943
|
-
|
|
6005
|
+
const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6006
|
+
this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
|
|
5944
6007
|
this.matcher = getDefaultMatcher();
|
|
5945
6008
|
this.retriever = createRetriever(
|
|
5946
6009
|
this.sqliteStore,
|
|
@@ -6884,6 +6947,89 @@ var MemoryService = class {
|
|
|
6884
6947
|
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
6885
6948
|
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
6886
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
|
+
}
|
|
6887
7033
|
/**
|
|
6888
7034
|
* Backward-compatible alias used by some hooks
|
|
6889
7035
|
*/
|