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
|
}
|
|
@@ -5922,8 +5982,10 @@ var MemoryService = class {
|
|
|
5922
5982
|
readOnly;
|
|
5923
5983
|
lightweightMode;
|
|
5924
5984
|
mdMirror;
|
|
5985
|
+
storagePath;
|
|
5925
5986
|
constructor(config) {
|
|
5926
5987
|
const storagePath = this.expandPath(config.storagePath);
|
|
5988
|
+
this.storagePath = storagePath;
|
|
5927
5989
|
this.readOnly = config.readOnly ?? false;
|
|
5928
5990
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
5929
5991
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
@@ -5959,7 +6021,8 @@ var MemoryService = class {
|
|
|
5959
6021
|
);
|
|
5960
6022
|
}
|
|
5961
6023
|
this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
|
|
5962
|
-
|
|
6024
|
+
const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6025
|
+
this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
|
|
5963
6026
|
this.matcher = getDefaultMatcher();
|
|
5964
6027
|
this.retriever = createRetriever(
|
|
5965
6028
|
this.sqliteStore,
|
|
@@ -6903,6 +6966,89 @@ var MemoryService = class {
|
|
|
6903
6966
|
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
6904
6967
|
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
6905
6968
|
}
|
|
6969
|
+
getEmbeddingModelName() {
|
|
6970
|
+
return this.embedder.getModelName();
|
|
6971
|
+
}
|
|
6972
|
+
/**
|
|
6973
|
+
* Ensure embedding model metadata is in sync and optionally migrate vectors.
|
|
6974
|
+
* Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
|
|
6975
|
+
*/
|
|
6976
|
+
async ensureEmbeddingModelForImport(options) {
|
|
6977
|
+
await this.initialize();
|
|
6978
|
+
const currentModel = this.getEmbeddingModelName();
|
|
6979
|
+
const metaPath = path3.join(this.storagePath, "embedding-meta.json");
|
|
6980
|
+
let previousModel = null;
|
|
6981
|
+
try {
|
|
6982
|
+
if (fs4.existsSync(metaPath)) {
|
|
6983
|
+
const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
|
|
6984
|
+
previousModel = parsed?.model || null;
|
|
6985
|
+
}
|
|
6986
|
+
} catch {
|
|
6987
|
+
previousModel = null;
|
|
6988
|
+
}
|
|
6989
|
+
const stats = await this.getStats();
|
|
6990
|
+
const hasExistingVectors = (stats.vectorCount || 0) > 0;
|
|
6991
|
+
if (!previousModel && !hasExistingVectors) {
|
|
6992
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
6993
|
+
return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
|
|
6994
|
+
}
|
|
6995
|
+
const modelChanged = previousModel !== currentModel;
|
|
6996
|
+
const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
|
|
6997
|
+
if (!modelChanged && !legacyUnknownButVectorsExist) {
|
|
6998
|
+
return { changed: false, previousModel, currentModel, enqueued: 0 };
|
|
6999
|
+
}
|
|
7000
|
+
if (options?.autoMigrate === false) {
|
|
7001
|
+
return {
|
|
7002
|
+
changed: true,
|
|
7003
|
+
previousModel,
|
|
7004
|
+
currentModel,
|
|
7005
|
+
enqueued: 0,
|
|
7006
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7007
|
+
};
|
|
7008
|
+
}
|
|
7009
|
+
const wasRunning = this.vectorWorker?.isRunning() || false;
|
|
7010
|
+
if (wasRunning)
|
|
7011
|
+
this.vectorWorker?.stop();
|
|
7012
|
+
await this.vectorStore.clearAll();
|
|
7013
|
+
await this.sqliteStore.clearEmbeddingOutbox();
|
|
7014
|
+
const pageSize = 1e3;
|
|
7015
|
+
let offset = 0;
|
|
7016
|
+
let enqueued = 0;
|
|
7017
|
+
while (true) {
|
|
7018
|
+
const page = await this.sqliteStore.getEventsPage(pageSize, offset);
|
|
7019
|
+
if (page.length === 0)
|
|
7020
|
+
break;
|
|
7021
|
+
for (const event of page) {
|
|
7022
|
+
await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
|
|
7023
|
+
enqueued += 1;
|
|
7024
|
+
}
|
|
7025
|
+
offset += page.length;
|
|
7026
|
+
if (page.length < pageSize)
|
|
7027
|
+
break;
|
|
7028
|
+
}
|
|
7029
|
+
fs4.writeFileSync(
|
|
7030
|
+
metaPath,
|
|
7031
|
+
JSON.stringify(
|
|
7032
|
+
{
|
|
7033
|
+
model: currentModel,
|
|
7034
|
+
previousModel,
|
|
7035
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7036
|
+
enqueued
|
|
7037
|
+
},
|
|
7038
|
+
null,
|
|
7039
|
+
2
|
|
7040
|
+
)
|
|
7041
|
+
);
|
|
7042
|
+
if (wasRunning)
|
|
7043
|
+
this.vectorWorker?.start();
|
|
7044
|
+
return {
|
|
7045
|
+
changed: true,
|
|
7046
|
+
previousModel,
|
|
7047
|
+
currentModel,
|
|
7048
|
+
enqueued,
|
|
7049
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7050
|
+
};
|
|
7051
|
+
}
|
|
6906
7052
|
/**
|
|
6907
7053
|
* Backward-compatible alias used by some hooks
|
|
6908
7054
|
*/
|