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/index.js
CHANGED
|
@@ -1624,6 +1624,33 @@ var SQLiteEventStore = class {
|
|
|
1624
1624
|
ids
|
|
1625
1625
|
);
|
|
1626
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Clear embedding outbox (used for embedding model migration)
|
|
1629
|
+
*/
|
|
1630
|
+
async clearEmbeddingOutbox() {
|
|
1631
|
+
await this.initialize();
|
|
1632
|
+
sqliteRun(this.db, `DELETE FROM embedding_outbox`);
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Count total events
|
|
1636
|
+
*/
|
|
1637
|
+
async countEvents() {
|
|
1638
|
+
await this.initialize();
|
|
1639
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
1640
|
+
return row?.count || 0;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
1644
|
+
*/
|
|
1645
|
+
async getEventsPage(limit = 1e3, offset = 0) {
|
|
1646
|
+
await this.initialize();
|
|
1647
|
+
const rows = sqliteAll(
|
|
1648
|
+
this.db,
|
|
1649
|
+
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
1650
|
+
[limit, offset]
|
|
1651
|
+
);
|
|
1652
|
+
return rows.map(this.rowToEvent);
|
|
1653
|
+
}
|
|
1627
1654
|
/**
|
|
1628
1655
|
* Mark outbox items as failed
|
|
1629
1656
|
*/
|
|
@@ -2589,6 +2616,23 @@ var VectorStore = class {
|
|
|
2589
2616
|
const result = await this.table.countRows();
|
|
2590
2617
|
return result;
|
|
2591
2618
|
}
|
|
2619
|
+
/**
|
|
2620
|
+
* Clear all vectors (used for embedding model migration)
|
|
2621
|
+
*/
|
|
2622
|
+
async clearAll() {
|
|
2623
|
+
await this.initialize();
|
|
2624
|
+
if (!this.db)
|
|
2625
|
+
return;
|
|
2626
|
+
try {
|
|
2627
|
+
if (typeof this.db.dropTable === "function") {
|
|
2628
|
+
await this.db.dropTable(this.tableName);
|
|
2629
|
+
} else if (typeof this.db.drop_table === "function") {
|
|
2630
|
+
await this.db.drop_table(this.tableName);
|
|
2631
|
+
}
|
|
2632
|
+
} catch {
|
|
2633
|
+
}
|
|
2634
|
+
this.table = null;
|
|
2635
|
+
}
|
|
2592
2636
|
/**
|
|
2593
2637
|
* Check if vector exists for event
|
|
2594
2638
|
*/
|
|
@@ -2601,13 +2645,15 @@ var VectorStore = class {
|
|
|
2601
2645
|
};
|
|
2602
2646
|
|
|
2603
2647
|
// src/core/embedder.ts
|
|
2604
|
-
import { pipeline } from "@
|
|
2648
|
+
import { pipeline } from "@huggingface/transformers";
|
|
2605
2649
|
var Embedder = class {
|
|
2606
2650
|
pipeline = null;
|
|
2607
2651
|
modelName;
|
|
2652
|
+
activeModelName;
|
|
2608
2653
|
initialized = false;
|
|
2609
|
-
constructor(modelName = "
|
|
2654
|
+
constructor(modelName = "jinaai/jina-embeddings-v5-text-nano-text-matching") {
|
|
2610
2655
|
this.modelName = modelName;
|
|
2656
|
+
this.activeModelName = modelName;
|
|
2611
2657
|
}
|
|
2612
2658
|
/**
|
|
2613
2659
|
* Initialize the embedding pipeline
|
|
@@ -2615,8 +2661,21 @@ var Embedder = class {
|
|
|
2615
2661
|
async initialize() {
|
|
2616
2662
|
if (this.initialized)
|
|
2617
2663
|
return;
|
|
2618
|
-
|
|
2619
|
-
|
|
2664
|
+
try {
|
|
2665
|
+
this.pipeline = await pipeline("feature-extraction", this.modelName);
|
|
2666
|
+
this.activeModelName = this.modelName;
|
|
2667
|
+
this.initialized = true;
|
|
2668
|
+
return;
|
|
2669
|
+
} catch (primaryError) {
|
|
2670
|
+
const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || "onnx-community/embeddinggemma-300m-ONNX";
|
|
2671
|
+
if (fallbackModel === this.modelName) {
|
|
2672
|
+
throw primaryError;
|
|
2673
|
+
}
|
|
2674
|
+
console.warn(`[Embedder] Primary model failed (${this.modelName}). Falling back to ${fallbackModel}`);
|
|
2675
|
+
this.pipeline = await pipeline("feature-extraction", fallbackModel);
|
|
2676
|
+
this.activeModelName = fallbackModel;
|
|
2677
|
+
this.initialized = true;
|
|
2678
|
+
}
|
|
2620
2679
|
}
|
|
2621
2680
|
/**
|
|
2622
2681
|
* Generate embedding for a single text
|
|
@@ -2633,7 +2692,7 @@ var Embedder = class {
|
|
|
2633
2692
|
const vector = Array.from(output.data);
|
|
2634
2693
|
return {
|
|
2635
2694
|
vector,
|
|
2636
|
-
model: this.
|
|
2695
|
+
model: this.activeModelName,
|
|
2637
2696
|
dimensions: vector.length
|
|
2638
2697
|
};
|
|
2639
2698
|
}
|
|
@@ -2657,7 +2716,7 @@ var Embedder = class {
|
|
|
2657
2716
|
const vector = Array.from(output.data);
|
|
2658
2717
|
results.push({
|
|
2659
2718
|
vector,
|
|
2660
|
-
model: this.
|
|
2719
|
+
model: this.activeModelName,
|
|
2661
2720
|
dimensions: vector.length
|
|
2662
2721
|
});
|
|
2663
2722
|
}
|
|
@@ -2681,13 +2740,14 @@ var Embedder = class {
|
|
|
2681
2740
|
* Get model name
|
|
2682
2741
|
*/
|
|
2683
2742
|
getModelName() {
|
|
2684
|
-
return this.
|
|
2743
|
+
return this.activeModelName;
|
|
2685
2744
|
}
|
|
2686
2745
|
};
|
|
2687
2746
|
var defaultEmbedder = null;
|
|
2688
2747
|
function getDefaultEmbedder() {
|
|
2748
|
+
const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
2689
2749
|
if (!defaultEmbedder) {
|
|
2690
|
-
defaultEmbedder = new Embedder();
|
|
2750
|
+
defaultEmbedder = new Embedder(envModel || void 0);
|
|
2691
2751
|
}
|
|
2692
2752
|
return defaultEmbedder;
|
|
2693
2753
|
}
|
|
@@ -5918,8 +5978,10 @@ var MemoryService = class {
|
|
|
5918
5978
|
readOnly;
|
|
5919
5979
|
lightweightMode;
|
|
5920
5980
|
mdMirror;
|
|
5981
|
+
storagePath;
|
|
5921
5982
|
constructor(config) {
|
|
5922
5983
|
const storagePath = this.expandPath(config.storagePath);
|
|
5984
|
+
this.storagePath = storagePath;
|
|
5923
5985
|
this.readOnly = config.readOnly ?? false;
|
|
5924
5986
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
5925
5987
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
@@ -5955,7 +6017,8 @@ var MemoryService = class {
|
|
|
5955
6017
|
);
|
|
5956
6018
|
}
|
|
5957
6019
|
this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
|
|
5958
|
-
|
|
6020
|
+
const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6021
|
+
this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
|
|
5959
6022
|
this.matcher = getDefaultMatcher();
|
|
5960
6023
|
this.retriever = createRetriever(
|
|
5961
6024
|
this.sqliteStore,
|
|
@@ -6899,6 +6962,89 @@ var MemoryService = class {
|
|
|
6899
6962
|
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
6900
6963
|
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
6901
6964
|
}
|
|
6965
|
+
getEmbeddingModelName() {
|
|
6966
|
+
return this.embedder.getModelName();
|
|
6967
|
+
}
|
|
6968
|
+
/**
|
|
6969
|
+
* Ensure embedding model metadata is in sync and optionally migrate vectors.
|
|
6970
|
+
* Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
|
|
6971
|
+
*/
|
|
6972
|
+
async ensureEmbeddingModelForImport(options) {
|
|
6973
|
+
await this.initialize();
|
|
6974
|
+
const currentModel = this.getEmbeddingModelName();
|
|
6975
|
+
const metaPath = path3.join(this.storagePath, "embedding-meta.json");
|
|
6976
|
+
let previousModel = null;
|
|
6977
|
+
try {
|
|
6978
|
+
if (fs4.existsSync(metaPath)) {
|
|
6979
|
+
const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
|
|
6980
|
+
previousModel = parsed?.model || null;
|
|
6981
|
+
}
|
|
6982
|
+
} catch {
|
|
6983
|
+
previousModel = null;
|
|
6984
|
+
}
|
|
6985
|
+
const stats = await this.getStats();
|
|
6986
|
+
const hasExistingVectors = (stats.vectorCount || 0) > 0;
|
|
6987
|
+
if (!previousModel && !hasExistingVectors) {
|
|
6988
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
6989
|
+
return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
|
|
6990
|
+
}
|
|
6991
|
+
const modelChanged = previousModel !== currentModel;
|
|
6992
|
+
const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
|
|
6993
|
+
if (!modelChanged && !legacyUnknownButVectorsExist) {
|
|
6994
|
+
return { changed: false, previousModel, currentModel, enqueued: 0 };
|
|
6995
|
+
}
|
|
6996
|
+
if (options?.autoMigrate === false) {
|
|
6997
|
+
return {
|
|
6998
|
+
changed: true,
|
|
6999
|
+
previousModel,
|
|
7000
|
+
currentModel,
|
|
7001
|
+
enqueued: 0,
|
|
7002
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7003
|
+
};
|
|
7004
|
+
}
|
|
7005
|
+
const wasRunning = this.vectorWorker?.isRunning() || false;
|
|
7006
|
+
if (wasRunning)
|
|
7007
|
+
this.vectorWorker?.stop();
|
|
7008
|
+
await this.vectorStore.clearAll();
|
|
7009
|
+
await this.sqliteStore.clearEmbeddingOutbox();
|
|
7010
|
+
const pageSize = 1e3;
|
|
7011
|
+
let offset = 0;
|
|
7012
|
+
let enqueued = 0;
|
|
7013
|
+
while (true) {
|
|
7014
|
+
const page = await this.sqliteStore.getEventsPage(pageSize, offset);
|
|
7015
|
+
if (page.length === 0)
|
|
7016
|
+
break;
|
|
7017
|
+
for (const event of page) {
|
|
7018
|
+
await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
|
|
7019
|
+
enqueued += 1;
|
|
7020
|
+
}
|
|
7021
|
+
offset += page.length;
|
|
7022
|
+
if (page.length < pageSize)
|
|
7023
|
+
break;
|
|
7024
|
+
}
|
|
7025
|
+
fs4.writeFileSync(
|
|
7026
|
+
metaPath,
|
|
7027
|
+
JSON.stringify(
|
|
7028
|
+
{
|
|
7029
|
+
model: currentModel,
|
|
7030
|
+
previousModel,
|
|
7031
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7032
|
+
enqueued
|
|
7033
|
+
},
|
|
7034
|
+
null,
|
|
7035
|
+
2
|
|
7036
|
+
)
|
|
7037
|
+
);
|
|
7038
|
+
if (wasRunning)
|
|
7039
|
+
this.vectorWorker?.start();
|
|
7040
|
+
return {
|
|
7041
|
+
changed: true,
|
|
7042
|
+
previousModel,
|
|
7043
|
+
currentModel,
|
|
7044
|
+
enqueued,
|
|
7045
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7046
|
+
};
|
|
7047
|
+
}
|
|
6902
7048
|
/**
|
|
6903
7049
|
* Backward-compatible alias used by some hooks
|
|
6904
7050
|
*/
|
|
@@ -7083,6 +7229,7 @@ eventsRouter.get("/", async (c) => {
|
|
|
7083
7229
|
const eventType = c.req.query("type");
|
|
7084
7230
|
const level = c.req.query("level");
|
|
7085
7231
|
const sort = c.req.query("sort") || "recent";
|
|
7232
|
+
const q = (c.req.query("q") || "").trim().toLowerCase();
|
|
7086
7233
|
const limit = parseInt(c.req.query("limit") || "100", 10);
|
|
7087
7234
|
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
7088
7235
|
const memoryService = getServiceFromQuery(c);
|
|
@@ -7100,6 +7247,9 @@ eventsRouter.get("/", async (c) => {
|
|
|
7100
7247
|
if (eventType) {
|
|
7101
7248
|
events = events.filter((e) => e.eventType === eventType);
|
|
7102
7249
|
}
|
|
7250
|
+
if (q) {
|
|
7251
|
+
events = events.filter((e) => (e.content || "").toLowerCase().includes(q));
|
|
7252
|
+
}
|
|
7103
7253
|
if (sort === "accessed") {
|
|
7104
7254
|
events.sort((a, b) => {
|
|
7105
7255
|
const aTime = a.last_accessed_at || "";
|