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/server/api/index.js
CHANGED
|
@@ -1615,6 +1615,33 @@ var SQLiteEventStore = class {
|
|
|
1615
1615
|
ids
|
|
1616
1616
|
);
|
|
1617
1617
|
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Clear embedding outbox (used for embedding model migration)
|
|
1620
|
+
*/
|
|
1621
|
+
async clearEmbeddingOutbox() {
|
|
1622
|
+
await this.initialize();
|
|
1623
|
+
sqliteRun(this.db, `DELETE FROM embedding_outbox`);
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Count total events
|
|
1627
|
+
*/
|
|
1628
|
+
async countEvents() {
|
|
1629
|
+
await this.initialize();
|
|
1630
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
1631
|
+
return row?.count || 0;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
1635
|
+
*/
|
|
1636
|
+
async getEventsPage(limit = 1e3, offset = 0) {
|
|
1637
|
+
await this.initialize();
|
|
1638
|
+
const rows = sqliteAll(
|
|
1639
|
+
this.db,
|
|
1640
|
+
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
1641
|
+
[limit, offset]
|
|
1642
|
+
);
|
|
1643
|
+
return rows.map(this.rowToEvent);
|
|
1644
|
+
}
|
|
1618
1645
|
/**
|
|
1619
1646
|
* Mark outbox items as failed
|
|
1620
1647
|
*/
|
|
@@ -2580,6 +2607,23 @@ var VectorStore = class {
|
|
|
2580
2607
|
const result = await this.table.countRows();
|
|
2581
2608
|
return result;
|
|
2582
2609
|
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Clear all vectors (used for embedding model migration)
|
|
2612
|
+
*/
|
|
2613
|
+
async clearAll() {
|
|
2614
|
+
await this.initialize();
|
|
2615
|
+
if (!this.db)
|
|
2616
|
+
return;
|
|
2617
|
+
try {
|
|
2618
|
+
if (typeof this.db.dropTable === "function") {
|
|
2619
|
+
await this.db.dropTable(this.tableName);
|
|
2620
|
+
} else if (typeof this.db.drop_table === "function") {
|
|
2621
|
+
await this.db.drop_table(this.tableName);
|
|
2622
|
+
}
|
|
2623
|
+
} catch {
|
|
2624
|
+
}
|
|
2625
|
+
this.table = null;
|
|
2626
|
+
}
|
|
2583
2627
|
/**
|
|
2584
2628
|
* Check if vector exists for event
|
|
2585
2629
|
*/
|
|
@@ -2592,13 +2636,15 @@ var VectorStore = class {
|
|
|
2592
2636
|
};
|
|
2593
2637
|
|
|
2594
2638
|
// src/core/embedder.ts
|
|
2595
|
-
import { pipeline } from "@
|
|
2639
|
+
import { pipeline } from "@huggingface/transformers";
|
|
2596
2640
|
var Embedder = class {
|
|
2597
2641
|
pipeline = null;
|
|
2598
2642
|
modelName;
|
|
2643
|
+
activeModelName;
|
|
2599
2644
|
initialized = false;
|
|
2600
|
-
constructor(modelName = "
|
|
2645
|
+
constructor(modelName = "jinaai/jina-embeddings-v5-text-nano-text-matching") {
|
|
2601
2646
|
this.modelName = modelName;
|
|
2647
|
+
this.activeModelName = modelName;
|
|
2602
2648
|
}
|
|
2603
2649
|
/**
|
|
2604
2650
|
* Initialize the embedding pipeline
|
|
@@ -2606,8 +2652,21 @@ var Embedder = class {
|
|
|
2606
2652
|
async initialize() {
|
|
2607
2653
|
if (this.initialized)
|
|
2608
2654
|
return;
|
|
2609
|
-
|
|
2610
|
-
|
|
2655
|
+
try {
|
|
2656
|
+
this.pipeline = await pipeline("feature-extraction", this.modelName);
|
|
2657
|
+
this.activeModelName = this.modelName;
|
|
2658
|
+
this.initialized = true;
|
|
2659
|
+
return;
|
|
2660
|
+
} catch (primaryError) {
|
|
2661
|
+
const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || "onnx-community/embeddinggemma-300m-ONNX";
|
|
2662
|
+
if (fallbackModel === this.modelName) {
|
|
2663
|
+
throw primaryError;
|
|
2664
|
+
}
|
|
2665
|
+
console.warn(`[Embedder] Primary model failed (${this.modelName}). Falling back to ${fallbackModel}`);
|
|
2666
|
+
this.pipeline = await pipeline("feature-extraction", fallbackModel);
|
|
2667
|
+
this.activeModelName = fallbackModel;
|
|
2668
|
+
this.initialized = true;
|
|
2669
|
+
}
|
|
2611
2670
|
}
|
|
2612
2671
|
/**
|
|
2613
2672
|
* Generate embedding for a single text
|
|
@@ -2624,7 +2683,7 @@ var Embedder = class {
|
|
|
2624
2683
|
const vector = Array.from(output.data);
|
|
2625
2684
|
return {
|
|
2626
2685
|
vector,
|
|
2627
|
-
model: this.
|
|
2686
|
+
model: this.activeModelName,
|
|
2628
2687
|
dimensions: vector.length
|
|
2629
2688
|
};
|
|
2630
2689
|
}
|
|
@@ -2648,7 +2707,7 @@ var Embedder = class {
|
|
|
2648
2707
|
const vector = Array.from(output.data);
|
|
2649
2708
|
results.push({
|
|
2650
2709
|
vector,
|
|
2651
|
-
model: this.
|
|
2710
|
+
model: this.activeModelName,
|
|
2652
2711
|
dimensions: vector.length
|
|
2653
2712
|
});
|
|
2654
2713
|
}
|
|
@@ -2672,13 +2731,14 @@ var Embedder = class {
|
|
|
2672
2731
|
* Get model name
|
|
2673
2732
|
*/
|
|
2674
2733
|
getModelName() {
|
|
2675
|
-
return this.
|
|
2734
|
+
return this.activeModelName;
|
|
2676
2735
|
}
|
|
2677
2736
|
};
|
|
2678
2737
|
var defaultEmbedder = null;
|
|
2679
2738
|
function getDefaultEmbedder() {
|
|
2739
|
+
const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
2680
2740
|
if (!defaultEmbedder) {
|
|
2681
|
-
defaultEmbedder = new Embedder();
|
|
2741
|
+
defaultEmbedder = new Embedder(envModel || void 0);
|
|
2682
2742
|
}
|
|
2683
2743
|
return defaultEmbedder;
|
|
2684
2744
|
}
|
|
@@ -5909,8 +5969,10 @@ var MemoryService = class {
|
|
|
5909
5969
|
readOnly;
|
|
5910
5970
|
lightweightMode;
|
|
5911
5971
|
mdMirror;
|
|
5972
|
+
storagePath;
|
|
5912
5973
|
constructor(config) {
|
|
5913
5974
|
const storagePath = this.expandPath(config.storagePath);
|
|
5975
|
+
this.storagePath = storagePath;
|
|
5914
5976
|
this.readOnly = config.readOnly ?? false;
|
|
5915
5977
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
5916
5978
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
@@ -5946,7 +6008,8 @@ var MemoryService = class {
|
|
|
5946
6008
|
);
|
|
5947
6009
|
}
|
|
5948
6010
|
this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
|
|
5949
|
-
|
|
6011
|
+
const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6012
|
+
this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
|
|
5950
6013
|
this.matcher = getDefaultMatcher();
|
|
5951
6014
|
this.retriever = createRetriever(
|
|
5952
6015
|
this.sqliteStore,
|
|
@@ -6890,6 +6953,89 @@ var MemoryService = class {
|
|
|
6890
6953
|
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
6891
6954
|
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
6892
6955
|
}
|
|
6956
|
+
getEmbeddingModelName() {
|
|
6957
|
+
return this.embedder.getModelName();
|
|
6958
|
+
}
|
|
6959
|
+
/**
|
|
6960
|
+
* Ensure embedding model metadata is in sync and optionally migrate vectors.
|
|
6961
|
+
* Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
|
|
6962
|
+
*/
|
|
6963
|
+
async ensureEmbeddingModelForImport(options) {
|
|
6964
|
+
await this.initialize();
|
|
6965
|
+
const currentModel = this.getEmbeddingModelName();
|
|
6966
|
+
const metaPath = path3.join(this.storagePath, "embedding-meta.json");
|
|
6967
|
+
let previousModel = null;
|
|
6968
|
+
try {
|
|
6969
|
+
if (fs4.existsSync(metaPath)) {
|
|
6970
|
+
const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
|
|
6971
|
+
previousModel = parsed?.model || null;
|
|
6972
|
+
}
|
|
6973
|
+
} catch {
|
|
6974
|
+
previousModel = null;
|
|
6975
|
+
}
|
|
6976
|
+
const stats = await this.getStats();
|
|
6977
|
+
const hasExistingVectors = (stats.vectorCount || 0) > 0;
|
|
6978
|
+
if (!previousModel && !hasExistingVectors) {
|
|
6979
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
6980
|
+
return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
|
|
6981
|
+
}
|
|
6982
|
+
const modelChanged = previousModel !== currentModel;
|
|
6983
|
+
const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
|
|
6984
|
+
if (!modelChanged && !legacyUnknownButVectorsExist) {
|
|
6985
|
+
return { changed: false, previousModel, currentModel, enqueued: 0 };
|
|
6986
|
+
}
|
|
6987
|
+
if (options?.autoMigrate === false) {
|
|
6988
|
+
return {
|
|
6989
|
+
changed: true,
|
|
6990
|
+
previousModel,
|
|
6991
|
+
currentModel,
|
|
6992
|
+
enqueued: 0,
|
|
6993
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
6994
|
+
};
|
|
6995
|
+
}
|
|
6996
|
+
const wasRunning = this.vectorWorker?.isRunning() || false;
|
|
6997
|
+
if (wasRunning)
|
|
6998
|
+
this.vectorWorker?.stop();
|
|
6999
|
+
await this.vectorStore.clearAll();
|
|
7000
|
+
await this.sqliteStore.clearEmbeddingOutbox();
|
|
7001
|
+
const pageSize = 1e3;
|
|
7002
|
+
let offset = 0;
|
|
7003
|
+
let enqueued = 0;
|
|
7004
|
+
while (true) {
|
|
7005
|
+
const page = await this.sqliteStore.getEventsPage(pageSize, offset);
|
|
7006
|
+
if (page.length === 0)
|
|
7007
|
+
break;
|
|
7008
|
+
for (const event of page) {
|
|
7009
|
+
await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
|
|
7010
|
+
enqueued += 1;
|
|
7011
|
+
}
|
|
7012
|
+
offset += page.length;
|
|
7013
|
+
if (page.length < pageSize)
|
|
7014
|
+
break;
|
|
7015
|
+
}
|
|
7016
|
+
fs4.writeFileSync(
|
|
7017
|
+
metaPath,
|
|
7018
|
+
JSON.stringify(
|
|
7019
|
+
{
|
|
7020
|
+
model: currentModel,
|
|
7021
|
+
previousModel,
|
|
7022
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7023
|
+
enqueued
|
|
7024
|
+
},
|
|
7025
|
+
null,
|
|
7026
|
+
2
|
|
7027
|
+
)
|
|
7028
|
+
);
|
|
7029
|
+
if (wasRunning)
|
|
7030
|
+
this.vectorWorker?.start();
|
|
7031
|
+
return {
|
|
7032
|
+
changed: true,
|
|
7033
|
+
previousModel,
|
|
7034
|
+
currentModel,
|
|
7035
|
+
enqueued,
|
|
7036
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7037
|
+
};
|
|
7038
|
+
}
|
|
6893
7039
|
/**
|
|
6894
7040
|
* Backward-compatible alias used by some hooks
|
|
6895
7041
|
*/
|
|
@@ -7074,6 +7220,7 @@ eventsRouter.get("/", async (c) => {
|
|
|
7074
7220
|
const eventType = c.req.query("type");
|
|
7075
7221
|
const level = c.req.query("level");
|
|
7076
7222
|
const sort = c.req.query("sort") || "recent";
|
|
7223
|
+
const q = (c.req.query("q") || "").trim().toLowerCase();
|
|
7077
7224
|
const limit = parseInt(c.req.query("limit") || "100", 10);
|
|
7078
7225
|
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
7079
7226
|
const memoryService = getServiceFromQuery(c);
|
|
@@ -7091,6 +7238,9 @@ eventsRouter.get("/", async (c) => {
|
|
|
7091
7238
|
if (eventType) {
|
|
7092
7239
|
events = events.filter((e) => e.eventType === eventType);
|
|
7093
7240
|
}
|
|
7241
|
+
if (q) {
|
|
7242
|
+
events = events.filter((e) => (e.content || "").toLowerCase().includes(q));
|
|
7243
|
+
}
|
|
7094
7244
|
if (sort === "accessed") {
|
|
7095
7245
|
events.sort((a, b) => {
|
|
7096
7246
|
const aTime = a.last_accessed_at || "";
|