claude-memory-layer 1.0.23 → 1.0.24
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/.claude/settings.local.json +11 -0
- package/README.md +2 -0
- package/dist/cli/index.js +85 -17
- package/dist/cli/index.js.map +2 -2
- package/dist/core/index.js +28 -5
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +115 -18
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/semantic-daemon.js +7337 -0
- package/dist/hooks/semantic-daemon.js.map +7 -0
- package/dist/hooks/session-end.js +69 -16
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +154 -24
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +99 -18
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +289 -102
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +69 -16
- package/dist/server/api/index.js.map +2 -2
- package/dist/server/index.js +69 -16
- package/dist/server/index.js.map +2 -2
- package/dist/services/memory-service.js +69 -16
- package/dist/services/memory-service.js.map +2 -2
- package/dist/ui/app.js +48 -1
- package/dist/ui/index.html +11 -3
- package/memory/_index.md +1 -0
- package/memory/agent_response/uncategorized/2026-03-04.md +1098 -1
- package/memory/session_summary/uncategorized/2026-03-04.md +31 -0
- package/memory/tool_observation/uncategorized/2026-03-04.md +733 -1
- package/memory/user_prompt/uncategorized/2026-03-04.md +371 -1
- package/package.json +1 -1
- package/scripts/build.ts +2 -1
- package/specs/selective-tool-observation/context.md +100 -0
- package/specs/selective-tool-observation/plan.md +158 -0
- package/specs/selective-tool-observation/spec.md +127 -0
- package/src/cli/index.ts +1 -0
- package/src/core/embedder.ts +13 -4
- package/src/core/sqlite-event-store.ts +16 -0
- package/src/core/turn-state.ts +48 -0
- package/src/core/types.ts +1 -0
- package/src/hooks/post-tool-use.ts +47 -2
- package/src/hooks/semantic-daemon-client.ts +208 -0
- package/src/hooks/semantic-daemon.ts +276 -0
- package/src/hooks/session-start.ts +7 -0
- package/src/hooks/stop.ts +19 -4
- package/src/hooks/user-prompt-submit.ts +48 -40
- package/src/services/memory-service.ts +59 -16
- package/src/services/session-history-importer.ts +18 -0
- package/src/ui/app.js +48 -1
- package/src/ui/index.html +11 -3
package/dist/hooks/stop.js
CHANGED
|
@@ -1838,6 +1838,21 @@ var SQLiteEventStore = class {
|
|
|
1838
1838
|
[id, eventId, sessionId, score, query.slice(0, 100)]
|
|
1839
1839
|
);
|
|
1840
1840
|
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Get session IDs that have unevaluated retrievals (measured_at IS NULL).
|
|
1843
|
+
* Excludes the current session. Used to backfill sessions that ended without Stop hook.
|
|
1844
|
+
*/
|
|
1845
|
+
async getUnevaluatedSessions(currentSessionId, limit = 5) {
|
|
1846
|
+
await this.initialize();
|
|
1847
|
+
const rows = sqliteAll(
|
|
1848
|
+
this.db,
|
|
1849
|
+
`SELECT DISTINCT session_id FROM memory_helpfulness
|
|
1850
|
+
WHERE measured_at IS NULL AND session_id != ?
|
|
1851
|
+
ORDER BY created_at DESC LIMIT ?`,
|
|
1852
|
+
[currentSessionId, limit]
|
|
1853
|
+
);
|
|
1854
|
+
return rows.map((r) => r.session_id);
|
|
1855
|
+
}
|
|
1841
1856
|
/**
|
|
1842
1857
|
* Evaluate helpfulness for all retrievals in a session
|
|
1843
1858
|
* Called at session end - uses behavioral signals to compute score
|
|
@@ -2625,7 +2640,7 @@ var VectorStore = class {
|
|
|
2625
2640
|
|
|
2626
2641
|
// src/core/embedder.ts
|
|
2627
2642
|
import { pipeline } from "@huggingface/transformers";
|
|
2628
|
-
var Embedder = class {
|
|
2643
|
+
var Embedder = class _Embedder {
|
|
2629
2644
|
pipeline = null;
|
|
2630
2645
|
modelName;
|
|
2631
2646
|
activeModelName;
|
|
@@ -2656,6 +2671,11 @@ var Embedder = class {
|
|
|
2656
2671
|
this.initialized = true;
|
|
2657
2672
|
}
|
|
2658
2673
|
}
|
|
2674
|
+
// ~4 chars per token; 512 tokens * 4 = 2048, use 2000 to be safe
|
|
2675
|
+
static MAX_CHARS = 2e3;
|
|
2676
|
+
truncate(text) {
|
|
2677
|
+
return text.length > _Embedder.MAX_CHARS ? text.slice(0, _Embedder.MAX_CHARS) : text;
|
|
2678
|
+
}
|
|
2659
2679
|
/**
|
|
2660
2680
|
* Generate embedding for a single text
|
|
2661
2681
|
*/
|
|
@@ -2664,10 +2684,11 @@ var Embedder = class {
|
|
|
2664
2684
|
if (!this.pipeline) {
|
|
2665
2685
|
throw new Error("Embedding pipeline not initialized");
|
|
2666
2686
|
}
|
|
2667
|
-
const output = await this.pipeline(text, {
|
|
2687
|
+
const output = await this.pipeline(this.truncate(text), {
|
|
2668
2688
|
pooling: "mean",
|
|
2669
2689
|
normalize: true,
|
|
2670
|
-
truncation: true
|
|
2690
|
+
truncation: true,
|
|
2691
|
+
max_length: 512
|
|
2671
2692
|
});
|
|
2672
2693
|
const vector = Array.from(output.data);
|
|
2673
2694
|
return {
|
|
@@ -2689,10 +2710,11 @@ var Embedder = class {
|
|
|
2689
2710
|
for (let i = 0; i < texts.length; i += batchSize) {
|
|
2690
2711
|
const batch = texts.slice(i, i + batchSize);
|
|
2691
2712
|
for (const text of batch) {
|
|
2692
|
-
const output = await this.pipeline(text, {
|
|
2713
|
+
const output = await this.pipeline(this.truncate(text), {
|
|
2693
2714
|
pooling: "mean",
|
|
2694
2715
|
normalize: true,
|
|
2695
|
-
truncation: true
|
|
2716
|
+
truncation: true,
|
|
2717
|
+
max_length: 512
|
|
2696
2718
|
});
|
|
2697
2719
|
const vector = Array.from(output.data);
|
|
2698
2720
|
results.push({
|
|
@@ -5962,6 +5984,7 @@ var MemoryService = class {
|
|
|
5962
5984
|
projectPath = null;
|
|
5963
5985
|
readOnly;
|
|
5964
5986
|
lightweightMode;
|
|
5987
|
+
embeddingOnly;
|
|
5965
5988
|
mdMirror;
|
|
5966
5989
|
storagePath;
|
|
5967
5990
|
constructor(config) {
|
|
@@ -5969,6 +5992,7 @@ var MemoryService = class {
|
|
|
5969
5992
|
this.storagePath = storagePath;
|
|
5970
5993
|
this.readOnly = config.readOnly ?? false;
|
|
5971
5994
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
5995
|
+
this.embeddingOnly = config.embeddingOnly ?? false;
|
|
5972
5996
|
this.mdMirror = new MarkdownMirror2(process.cwd());
|
|
5973
5997
|
if (!this.readOnly && !fs4.existsSync(storagePath)) {
|
|
5974
5998
|
fs4.mkdirSync(storagePath, { recursive: true });
|
|
@@ -6042,19 +6066,21 @@ var MemoryService = class {
|
|
|
6042
6066
|
this.embedder
|
|
6043
6067
|
);
|
|
6044
6068
|
this.vectorWorker.start();
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
this.
|
|
6048
|
-
this.graduation
|
|
6049
|
-
);
|
|
6050
|
-
this.graduationWorker.start();
|
|
6051
|
-
if (this.analyticsStore) {
|
|
6052
|
-
this.syncWorker = new SyncWorker(
|
|
6069
|
+
if (!this.embeddingOnly) {
|
|
6070
|
+
this.retriever.setGraduationPipeline(this.graduation);
|
|
6071
|
+
this.graduationWorker = createGraduationWorker(
|
|
6053
6072
|
this.sqliteStore,
|
|
6054
|
-
this.
|
|
6055
|
-
{ intervalMs: 3e4, batchSize: 500 }
|
|
6073
|
+
this.graduation
|
|
6056
6074
|
);
|
|
6057
|
-
this.
|
|
6075
|
+
this.graduationWorker.start();
|
|
6076
|
+
if (this.analyticsStore) {
|
|
6077
|
+
this.syncWorker = new SyncWorker(
|
|
6078
|
+
this.sqliteStore,
|
|
6079
|
+
this.analyticsStore,
|
|
6080
|
+
{ intervalMs: 3e4, batchSize: 500 }
|
|
6081
|
+
);
|
|
6082
|
+
this.syncWorker.start();
|
|
6083
|
+
}
|
|
6058
6084
|
}
|
|
6059
6085
|
const savedMode = await this.sqliteStore.getEndlessConfig("mode");
|
|
6060
6086
|
if (savedMode === "endless") {
|
|
@@ -6778,6 +6804,19 @@ var MemoryService = class {
|
|
|
6778
6804
|
await this.initialize();
|
|
6779
6805
|
await this.sqliteStore.recordRetrieval(eventId, sessionId, score, query);
|
|
6780
6806
|
}
|
|
6807
|
+
/**
|
|
6808
|
+
* Record a query-level retrieval trace (used by user-prompt-submit hook).
|
|
6809
|
+
* Feeds the retrieval_traces table that powers dashboard stats.
|
|
6810
|
+
*/
|
|
6811
|
+
async recordQueryTrace(input) {
|
|
6812
|
+
await this.initialize();
|
|
6813
|
+
await this.sqliteStore.recordRetrievalTrace({
|
|
6814
|
+
...input,
|
|
6815
|
+
candidateDetails: [],
|
|
6816
|
+
selectedDetails: [],
|
|
6817
|
+
fallbackTrace: []
|
|
6818
|
+
});
|
|
6819
|
+
}
|
|
6781
6820
|
/**
|
|
6782
6821
|
* Evaluate helpfulness of retrievals in a session (called at session end)
|
|
6783
6822
|
*/
|
|
@@ -6785,6 +6824,20 @@ var MemoryService = class {
|
|
|
6785
6824
|
await this.initialize();
|
|
6786
6825
|
await this.sqliteStore.evaluateSessionHelpfulness(sessionId);
|
|
6787
6826
|
}
|
|
6827
|
+
/**
|
|
6828
|
+
* Backfill helpfulness evaluation for sessions that ended without Stop hook.
|
|
6829
|
+
* Call on first turn of a new session to catch missed evaluations.
|
|
6830
|
+
*/
|
|
6831
|
+
async evaluatePendingSessions(currentSessionId) {
|
|
6832
|
+
await this.initialize();
|
|
6833
|
+
const sessions = await this.sqliteStore.getUnevaluatedSessions(currentSessionId, 5);
|
|
6834
|
+
for (const sid of sessions) {
|
|
6835
|
+
try {
|
|
6836
|
+
await this.sqliteStore.evaluateSessionHelpfulness(sid);
|
|
6837
|
+
} catch {
|
|
6838
|
+
}
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6788
6841
|
/**
|
|
6789
6842
|
* Get most helpful memories ranked by helpfulness score
|
|
6790
6843
|
*/
|
|
@@ -7279,6 +7332,24 @@ function clearTurnState(sessionId) {
|
|
|
7279
7332
|
}
|
|
7280
7333
|
}
|
|
7281
7334
|
}
|
|
7335
|
+
var LAST_RESPONSE_SNIPPET_CHARS = 500;
|
|
7336
|
+
function getLastResponsePath(sessionId) {
|
|
7337
|
+
return path4.join(TURN_STATE_DIR, `.last-response-${sessionId}.json`);
|
|
7338
|
+
}
|
|
7339
|
+
function writeLastAssistantSnippet(sessionId, text) {
|
|
7340
|
+
try {
|
|
7341
|
+
if (!fs5.existsSync(TURN_STATE_DIR)) {
|
|
7342
|
+
fs5.mkdirSync(TURN_STATE_DIR, { recursive: true });
|
|
7343
|
+
}
|
|
7344
|
+
const snippet = text.slice(0, LAST_RESPONSE_SNIPPET_CHARS);
|
|
7345
|
+
const state = { sessionId, snippet, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
7346
|
+
const filePath = getLastResponsePath(sessionId);
|
|
7347
|
+
const tempPath = filePath + ".tmp";
|
|
7348
|
+
fs5.writeFileSync(tempPath, JSON.stringify(state));
|
|
7349
|
+
fs5.renameSync(tempPath, filePath);
|
|
7350
|
+
} catch {
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7282
7353
|
|
|
7283
7354
|
// src/hooks/stop.ts
|
|
7284
7355
|
var DEFAULT_PRIVACY_CONFIG = {
|
|
@@ -7326,13 +7397,19 @@ async function main() {
|
|
|
7326
7397
|
try {
|
|
7327
7398
|
const turnId = readTurnState(input.session_id);
|
|
7328
7399
|
const assistantMessages = await extractAssistantMessages(input.transcript_path);
|
|
7329
|
-
|
|
7400
|
+
const MIN_AGENT_RESPONSE_LEN = parseInt(
|
|
7401
|
+
process.env.CLAUDE_MEMORY_AGENT_RESPONSE_MIN_LEN || "150"
|
|
7402
|
+
);
|
|
7403
|
+
const lastIdx = assistantMessages.length - 1;
|
|
7404
|
+
for (let i = 0; i < assistantMessages.length; i++) {
|
|
7405
|
+
const text = assistantMessages[i];
|
|
7406
|
+
const isLast = i === lastIdx;
|
|
7330
7407
|
const filterResult = applyPrivacyFilter(text, DEFAULT_PRIVACY_CONFIG);
|
|
7331
7408
|
let content = filterResult.content;
|
|
7332
7409
|
if (content.length > 5e3) {
|
|
7333
7410
|
content = content.slice(0, 5e3) + "...[truncated]";
|
|
7334
7411
|
}
|
|
7335
|
-
if (content.trim().length <
|
|
7412
|
+
if (!isLast && content.trim().length < MIN_AGENT_RESPONSE_LEN)
|
|
7336
7413
|
continue;
|
|
7337
7414
|
await memoryService.storeAgentResponse(
|
|
7338
7415
|
input.session_id,
|
|
@@ -7343,6 +7420,10 @@ async function main() {
|
|
|
7343
7420
|
}
|
|
7344
7421
|
);
|
|
7345
7422
|
}
|
|
7423
|
+
if (assistantMessages.length > 0) {
|
|
7424
|
+
const lastMessage = assistantMessages[assistantMessages.length - 1];
|
|
7425
|
+
writeLastAssistantSnippet(input.session_id, lastMessage);
|
|
7426
|
+
}
|
|
7346
7427
|
clearTurnState(input.session_id);
|
|
7347
7428
|
await memoryService.processPendingEmbeddings();
|
|
7348
7429
|
console.log(JSON.stringify({}));
|