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
|
}
|
|
@@ -6016,8 +6076,10 @@ var MemoryService = class {
|
|
|
6016
6076
|
readOnly;
|
|
6017
6077
|
lightweightMode;
|
|
6018
6078
|
mdMirror;
|
|
6079
|
+
storagePath;
|
|
6019
6080
|
constructor(config) {
|
|
6020
6081
|
const storagePath = this.expandPath(config.storagePath);
|
|
6082
|
+
this.storagePath = storagePath;
|
|
6021
6083
|
this.readOnly = config.readOnly ?? false;
|
|
6022
6084
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
6023
6085
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
@@ -6053,7 +6115,8 @@ var MemoryService = class {
|
|
|
6053
6115
|
);
|
|
6054
6116
|
}
|
|
6055
6117
|
this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
|
|
6056
|
-
|
|
6118
|
+
const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6119
|
+
this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
|
|
6057
6120
|
this.matcher = getDefaultMatcher();
|
|
6058
6121
|
this.retriever = createRetriever(
|
|
6059
6122
|
this.sqliteStore,
|
|
@@ -6997,6 +7060,89 @@ var MemoryService = class {
|
|
|
6997
7060
|
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
6998
7061
|
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
6999
7062
|
}
|
|
7063
|
+
getEmbeddingModelName() {
|
|
7064
|
+
return this.embedder.getModelName();
|
|
7065
|
+
}
|
|
7066
|
+
/**
|
|
7067
|
+
* Ensure embedding model metadata is in sync and optionally migrate vectors.
|
|
7068
|
+
* Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
|
|
7069
|
+
*/
|
|
7070
|
+
async ensureEmbeddingModelForImport(options) {
|
|
7071
|
+
await this.initialize();
|
|
7072
|
+
const currentModel = this.getEmbeddingModelName();
|
|
7073
|
+
const metaPath = path3.join(this.storagePath, "embedding-meta.json");
|
|
7074
|
+
let previousModel = null;
|
|
7075
|
+
try {
|
|
7076
|
+
if (fs4.existsSync(metaPath)) {
|
|
7077
|
+
const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
|
|
7078
|
+
previousModel = parsed?.model || null;
|
|
7079
|
+
}
|
|
7080
|
+
} catch {
|
|
7081
|
+
previousModel = null;
|
|
7082
|
+
}
|
|
7083
|
+
const stats = await this.getStats();
|
|
7084
|
+
const hasExistingVectors = (stats.vectorCount || 0) > 0;
|
|
7085
|
+
if (!previousModel && !hasExistingVectors) {
|
|
7086
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
7087
|
+
return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
|
|
7088
|
+
}
|
|
7089
|
+
const modelChanged = previousModel !== currentModel;
|
|
7090
|
+
const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
|
|
7091
|
+
if (!modelChanged && !legacyUnknownButVectorsExist) {
|
|
7092
|
+
return { changed: false, previousModel, currentModel, enqueued: 0 };
|
|
7093
|
+
}
|
|
7094
|
+
if (options?.autoMigrate === false) {
|
|
7095
|
+
return {
|
|
7096
|
+
changed: true,
|
|
7097
|
+
previousModel,
|
|
7098
|
+
currentModel,
|
|
7099
|
+
enqueued: 0,
|
|
7100
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7101
|
+
};
|
|
7102
|
+
}
|
|
7103
|
+
const wasRunning = this.vectorWorker?.isRunning() || false;
|
|
7104
|
+
if (wasRunning)
|
|
7105
|
+
this.vectorWorker?.stop();
|
|
7106
|
+
await this.vectorStore.clearAll();
|
|
7107
|
+
await this.sqliteStore.clearEmbeddingOutbox();
|
|
7108
|
+
const pageSize = 1e3;
|
|
7109
|
+
let offset = 0;
|
|
7110
|
+
let enqueued = 0;
|
|
7111
|
+
while (true) {
|
|
7112
|
+
const page = await this.sqliteStore.getEventsPage(pageSize, offset);
|
|
7113
|
+
if (page.length === 0)
|
|
7114
|
+
break;
|
|
7115
|
+
for (const event of page) {
|
|
7116
|
+
await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
|
|
7117
|
+
enqueued += 1;
|
|
7118
|
+
}
|
|
7119
|
+
offset += page.length;
|
|
7120
|
+
if (page.length < pageSize)
|
|
7121
|
+
break;
|
|
7122
|
+
}
|
|
7123
|
+
fs4.writeFileSync(
|
|
7124
|
+
metaPath,
|
|
7125
|
+
JSON.stringify(
|
|
7126
|
+
{
|
|
7127
|
+
model: currentModel,
|
|
7128
|
+
previousModel,
|
|
7129
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7130
|
+
enqueued
|
|
7131
|
+
},
|
|
7132
|
+
null,
|
|
7133
|
+
2
|
|
7134
|
+
)
|
|
7135
|
+
);
|
|
7136
|
+
if (wasRunning)
|
|
7137
|
+
this.vectorWorker?.start();
|
|
7138
|
+
return {
|
|
7139
|
+
changed: true,
|
|
7140
|
+
previousModel,
|
|
7141
|
+
currentModel,
|
|
7142
|
+
enqueued,
|
|
7143
|
+
reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
|
|
7144
|
+
};
|
|
7145
|
+
}
|
|
7000
7146
|
/**
|
|
7001
7147
|
* Backward-compatible alias used by some hooks
|
|
7002
7148
|
*/
|