claude-memory-layer 1.0.37 → 1.0.39
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 +506 -94
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +309 -22
- package/dist/core/index.js.map +3 -3
- package/dist/hooks/post-tool-use.js +399 -74
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +357 -67
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +357 -67
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +357 -67
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +398 -73
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +357 -67
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +318 -28
- package/dist/index.js.map +3 -3
- package/dist/mcp/index.js +403 -78
- package/dist/mcp/index.js.map +4 -4
- package/dist/server/api/index.js +370 -81
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +370 -81
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +357 -67
- package/dist/services/memory-service.js.map +4 -4
- package/package.json +1 -1
package/dist/core/index.js
CHANGED
|
@@ -1132,6 +1132,10 @@ var MemoryQueryService = class {
|
|
|
1132
1132
|
await this.initialize();
|
|
1133
1133
|
return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
|
|
1134
1134
|
}
|
|
1135
|
+
async repairLegacyProjectScope(options) {
|
|
1136
|
+
await this.initialize();
|
|
1137
|
+
return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
|
|
1138
|
+
}
|
|
1135
1139
|
async getStats() {
|
|
1136
1140
|
await this.initialize();
|
|
1137
1141
|
const deps = this.getStatsDeps();
|
|
@@ -1926,6 +1930,9 @@ function parseEntityCanonicalKey(canonicalKey) {
|
|
|
1926
1930
|
return null;
|
|
1927
1931
|
}
|
|
1928
1932
|
|
|
1933
|
+
// src/core/sqlite-event-store.ts
|
|
1934
|
+
import * as nodePath2 from "path";
|
|
1935
|
+
|
|
1929
1936
|
// src/core/sqlite-wrapper.ts
|
|
1930
1937
|
import Database from "better-sqlite3";
|
|
1931
1938
|
import * as fs4 from "fs";
|
|
@@ -2058,6 +2065,135 @@ function emptyOutboxRecoveryResult() {
|
|
|
2058
2065
|
vector: { recoveredProcessing: 0, retriedFailed: 0 }
|
|
2059
2066
|
};
|
|
2060
2067
|
}
|
|
2068
|
+
function isRecord(value) {
|
|
2069
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2070
|
+
}
|
|
2071
|
+
function getNestedRecord(root, path8) {
|
|
2072
|
+
let cursor = root;
|
|
2073
|
+
for (const key of path8) {
|
|
2074
|
+
if (!isRecord(cursor))
|
|
2075
|
+
return void 0;
|
|
2076
|
+
cursor = cursor[key];
|
|
2077
|
+
}
|
|
2078
|
+
return isRecord(cursor) ? cursor : void 0;
|
|
2079
|
+
}
|
|
2080
|
+
function getNestedString(root, path8) {
|
|
2081
|
+
let cursor = root;
|
|
2082
|
+
for (const key of path8) {
|
|
2083
|
+
if (!isRecord(cursor))
|
|
2084
|
+
return void 0;
|
|
2085
|
+
cursor = cursor[key];
|
|
2086
|
+
}
|
|
2087
|
+
return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
|
|
2088
|
+
}
|
|
2089
|
+
function metadataProjectHash(metadata) {
|
|
2090
|
+
return getNestedString(metadata, ["scope", "project", "hash"]);
|
|
2091
|
+
}
|
|
2092
|
+
function metadataProjectPaths(metadata) {
|
|
2093
|
+
const candidates = [
|
|
2094
|
+
getNestedString(metadata, ["projectPath"]),
|
|
2095
|
+
getNestedString(metadata, ["sourceProjectPath"]),
|
|
2096
|
+
getNestedString(metadata, ["scope", "project", "path"])
|
|
2097
|
+
];
|
|
2098
|
+
const paths = [];
|
|
2099
|
+
for (const value of candidates) {
|
|
2100
|
+
if (value && !paths.includes(value))
|
|
2101
|
+
paths.push(value);
|
|
2102
|
+
}
|
|
2103
|
+
return paths;
|
|
2104
|
+
}
|
|
2105
|
+
function metadataProjectPath(metadata) {
|
|
2106
|
+
return metadataProjectPaths(metadata)[0];
|
|
2107
|
+
}
|
|
2108
|
+
function isActiveQuarantinedMetadata(metadata) {
|
|
2109
|
+
const quarantine = getNestedRecord(metadata, ["quarantine"]);
|
|
2110
|
+
return quarantine?.status === "active";
|
|
2111
|
+
}
|
|
2112
|
+
function activeQuarantineStatusExpression(column = "metadata") {
|
|
2113
|
+
return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
|
|
2114
|
+
}
|
|
2115
|
+
function notActiveQuarantinedSql(column = "metadata") {
|
|
2116
|
+
return `${activeQuarantineStatusExpression(column)} != 'active'`;
|
|
2117
|
+
}
|
|
2118
|
+
function maybeQuarantinePredicate(options, column = "metadata") {
|
|
2119
|
+
return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
|
|
2120
|
+
}
|
|
2121
|
+
function safeParseMetadataValue(value) {
|
|
2122
|
+
if (!value)
|
|
2123
|
+
return void 0;
|
|
2124
|
+
if (typeof value === "object")
|
|
2125
|
+
return isRecord(value) ? value : void 0;
|
|
2126
|
+
if (typeof value !== "string")
|
|
2127
|
+
return void 0;
|
|
2128
|
+
try {
|
|
2129
|
+
const parsed = JSON.parse(value);
|
|
2130
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
2131
|
+
} catch {
|
|
2132
|
+
return void 0;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
function isImportedOrLegacyScopedMetadata(metadata) {
|
|
2136
|
+
if (!metadata)
|
|
2137
|
+
return false;
|
|
2138
|
+
return Boolean(
|
|
2139
|
+
metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
function addMetadataTag(metadata, tag) {
|
|
2143
|
+
const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
|
|
2144
|
+
if (!current.includes(tag))
|
|
2145
|
+
metadata.tags = [...current, tag];
|
|
2146
|
+
}
|
|
2147
|
+
function buildRepairResult(projectHash, dryRun) {
|
|
2148
|
+
return {
|
|
2149
|
+
dryRun,
|
|
2150
|
+
projectHash,
|
|
2151
|
+
scanned: 0,
|
|
2152
|
+
repaired: 0,
|
|
2153
|
+
quarantined: 0,
|
|
2154
|
+
alreadyScoped: 0,
|
|
2155
|
+
skipped: 0,
|
|
2156
|
+
samples: []
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
function normalizeRepoName(value) {
|
|
2160
|
+
return value.replace(/\.git$/i, "").trim().toLowerCase();
|
|
2161
|
+
}
|
|
2162
|
+
function projectBasename(projectPath) {
|
|
2163
|
+
if (!projectPath)
|
|
2164
|
+
return void 0;
|
|
2165
|
+
const trimmed = projectPath.replace(/[\\/]+$/, "");
|
|
2166
|
+
const basename2 = nodePath2.basename(trimmed);
|
|
2167
|
+
return basename2 ? normalizeRepoName(basename2) : void 0;
|
|
2168
|
+
}
|
|
2169
|
+
function isProjectScopeRepairExplanation(content) {
|
|
2170
|
+
const normalized = content.toLowerCase();
|
|
2171
|
+
const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
|
|
2172
|
+
const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
|
|
2173
|
+
return hasRepairContext && hasExplanationContext;
|
|
2174
|
+
}
|
|
2175
|
+
function hasConflictingContentProjectHint(content, projectPath) {
|
|
2176
|
+
const currentName = projectBasename(projectPath);
|
|
2177
|
+
if (!currentName)
|
|
2178
|
+
return false;
|
|
2179
|
+
if (isProjectScopeRepairExplanation(content))
|
|
2180
|
+
return false;
|
|
2181
|
+
const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
|
|
2182
|
+
let githubMatch;
|
|
2183
|
+
while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
|
|
2184
|
+
const repo = normalizeRepoName(githubMatch[2] || "");
|
|
2185
|
+
if (repo && repo !== currentName)
|
|
2186
|
+
return true;
|
|
2187
|
+
}
|
|
2188
|
+
const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
|
|
2189
|
+
let workspaceMatch;
|
|
2190
|
+
while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
|
|
2191
|
+
const repo = normalizeRepoName(workspaceMatch[1] || "");
|
|
2192
|
+
if (repo && repo !== currentName)
|
|
2193
|
+
return true;
|
|
2194
|
+
}
|
|
2195
|
+
return false;
|
|
2196
|
+
}
|
|
2061
2197
|
var SQLiteEventStore = class {
|
|
2062
2198
|
db;
|
|
2063
2199
|
initialized = false;
|
|
@@ -2556,11 +2692,11 @@ var SQLiteEventStore = class {
|
|
|
2556
2692
|
/**
|
|
2557
2693
|
* Get events by session ID
|
|
2558
2694
|
*/
|
|
2559
|
-
async getSessionEvents(sessionId) {
|
|
2695
|
+
async getSessionEvents(sessionId, options) {
|
|
2560
2696
|
await this.initialize();
|
|
2561
2697
|
const rows = sqliteAll(
|
|
2562
2698
|
this.db,
|
|
2563
|
-
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
2699
|
+
`SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
2564
2700
|
[sessionId]
|
|
2565
2701
|
);
|
|
2566
2702
|
return rows.map(this.rowToEvent);
|
|
@@ -2568,11 +2704,11 @@ var SQLiteEventStore = class {
|
|
|
2568
2704
|
/**
|
|
2569
2705
|
* Get recent events
|
|
2570
2706
|
*/
|
|
2571
|
-
async getRecentEvents(limit = 100) {
|
|
2707
|
+
async getRecentEvents(limit = 100, options) {
|
|
2572
2708
|
await this.initialize();
|
|
2573
2709
|
const rows = sqliteAll(
|
|
2574
2710
|
this.db,
|
|
2575
|
-
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
2711
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
|
|
2576
2712
|
[limit]
|
|
2577
2713
|
);
|
|
2578
2714
|
return rows.map(this.rowToEvent);
|
|
@@ -2580,11 +2716,11 @@ var SQLiteEventStore = class {
|
|
|
2580
2716
|
/**
|
|
2581
2717
|
* Get event by ID
|
|
2582
2718
|
*/
|
|
2583
|
-
async getEvent(id) {
|
|
2719
|
+
async getEvent(id, options) {
|
|
2584
2720
|
await this.initialize();
|
|
2585
2721
|
const row = sqliteGet(
|
|
2586
2722
|
this.db,
|
|
2587
|
-
`SELECT * FROM events WHERE id =
|
|
2723
|
+
`SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
|
|
2588
2724
|
[id]
|
|
2589
2725
|
);
|
|
2590
2726
|
if (!row)
|
|
@@ -2594,11 +2730,11 @@ var SQLiteEventStore = class {
|
|
|
2594
2730
|
/**
|
|
2595
2731
|
* Get events since a timestamp (for sync)
|
|
2596
2732
|
*/
|
|
2597
|
-
async getEventsSince(timestamp, limit = 1e3) {
|
|
2733
|
+
async getEventsSince(timestamp, limit = 1e3, options) {
|
|
2598
2734
|
await this.initialize();
|
|
2599
2735
|
const rows = sqliteAll(
|
|
2600
2736
|
this.db,
|
|
2601
|
-
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
2737
|
+
`SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
|
|
2602
2738
|
[timestamp, limit]
|
|
2603
2739
|
);
|
|
2604
2740
|
return rows.map(this.rowToEvent);
|
|
@@ -2607,11 +2743,11 @@ var SQLiteEventStore = class {
|
|
|
2607
2743
|
* Get events since a SQLite rowid (for robust incremental replication).
|
|
2608
2744
|
* Rowid is monotonic for append-only tables, independent of client timestamps.
|
|
2609
2745
|
*/
|
|
2610
|
-
async getEventsSinceRowid(lastRowid, limit = 1e3) {
|
|
2746
|
+
async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
|
|
2611
2747
|
await this.initialize();
|
|
2612
2748
|
const rows = sqliteAll(
|
|
2613
2749
|
this.db,
|
|
2614
|
-
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
|
|
2750
|
+
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
|
|
2615
2751
|
[lastRowid, limit]
|
|
2616
2752
|
);
|
|
2617
2753
|
return rows.map((row) => ({
|
|
@@ -2846,19 +2982,19 @@ var SQLiteEventStore = class {
|
|
|
2846
2982
|
/**
|
|
2847
2983
|
* Count total events
|
|
2848
2984
|
*/
|
|
2849
|
-
async countEvents() {
|
|
2985
|
+
async countEvents(options) {
|
|
2850
2986
|
await this.initialize();
|
|
2851
|
-
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
2987
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
|
|
2852
2988
|
return row?.count || 0;
|
|
2853
2989
|
}
|
|
2854
2990
|
/**
|
|
2855
2991
|
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
2856
2992
|
*/
|
|
2857
|
-
async getEventsPage(limit = 1e3, offset = 0) {
|
|
2993
|
+
async getEventsPage(limit = 1e3, offset = 0, options) {
|
|
2858
2994
|
await this.initialize();
|
|
2859
2995
|
const rows = sqliteAll(
|
|
2860
2996
|
this.db,
|
|
2861
|
-
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
2997
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
2862
2998
|
[limit, offset]
|
|
2863
2999
|
);
|
|
2864
3000
|
return rows.map(this.rowToEvent);
|
|
@@ -2932,6 +3068,145 @@ var SQLiteEventStore = class {
|
|
|
2932
3068
|
result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
|
|
2933
3069
|
return result;
|
|
2934
3070
|
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Repair legacy imported events that predate canonical project scope metadata.
|
|
3073
|
+
*
|
|
3074
|
+
* Same-project legacy rows are tagged with scope.project.hash. Rows that look
|
|
3075
|
+
* imported but cannot be proven to belong to this project are quarantined so
|
|
3076
|
+
* dashboard default reads/search do not surface cross-project contamination.
|
|
3077
|
+
*/
|
|
3078
|
+
async repairLegacyProjectScope(options = {}) {
|
|
3079
|
+
await this.initialize();
|
|
3080
|
+
const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
|
|
3081
|
+
if (!projectHash) {
|
|
3082
|
+
throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
|
|
3083
|
+
}
|
|
3084
|
+
if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
|
|
3085
|
+
throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
|
|
3086
|
+
}
|
|
3087
|
+
const dryRun = options.dryRun === true;
|
|
3088
|
+
const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
|
|
3089
|
+
const result = buildRepairResult(projectHash, dryRun);
|
|
3090
|
+
const rows = sqliteAll(
|
|
3091
|
+
this.db,
|
|
3092
|
+
`SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
|
|
3093
|
+
FROM events e
|
|
3094
|
+
LEFT JOIN sessions s ON s.id = e.session_id
|
|
3095
|
+
ORDER BY e.timestamp ASC`,
|
|
3096
|
+
[]
|
|
3097
|
+
);
|
|
3098
|
+
const sample = (entry) => {
|
|
3099
|
+
if (result.samples.length < 20)
|
|
3100
|
+
result.samples.push(entry);
|
|
3101
|
+
};
|
|
3102
|
+
for (const row of rows) {
|
|
3103
|
+
result.scanned++;
|
|
3104
|
+
let metadata = {};
|
|
3105
|
+
let metadataParseInvalid = false;
|
|
3106
|
+
if (row.metadata) {
|
|
3107
|
+
const parsed = safeParseMetadataValue(row.metadata);
|
|
3108
|
+
if (parsed) {
|
|
3109
|
+
metadata = parsed;
|
|
3110
|
+
} else {
|
|
3111
|
+
metadataParseInvalid = true;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
if (isActiveQuarantinedMetadata(metadata)) {
|
|
3115
|
+
result.skipped++;
|
|
3116
|
+
continue;
|
|
3117
|
+
}
|
|
3118
|
+
const currentHash = metadataProjectHash(metadata);
|
|
3119
|
+
const explicitPath = metadataProjectPath(metadata);
|
|
3120
|
+
const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
|
|
3121
|
+
const candidatePaths = metadataProjectPaths(metadata);
|
|
3122
|
+
if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
|
|
3123
|
+
candidatePaths.push(sessionProjectPath);
|
|
3124
|
+
}
|
|
3125
|
+
const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
|
|
3126
|
+
const pathHashes = candidatePaths.map((candidate) => {
|
|
3127
|
+
try {
|
|
3128
|
+
return { path: candidate, hash: hashProjectPath(candidate) };
|
|
3129
|
+
} catch {
|
|
3130
|
+
return { path: candidate, hash: void 0 };
|
|
3131
|
+
}
|
|
3132
|
+
});
|
|
3133
|
+
const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
|
|
3134
|
+
const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
|
|
3135
|
+
let action = "skipped";
|
|
3136
|
+
let reason;
|
|
3137
|
+
let observedProjectHash;
|
|
3138
|
+
if (foreignPath) {
|
|
3139
|
+
action = "quarantined";
|
|
3140
|
+
reason = "project-path-mismatch";
|
|
3141
|
+
observedProjectHash = foreignPath.hash;
|
|
3142
|
+
} else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
|
|
3143
|
+
action = "quarantined";
|
|
3144
|
+
reason = "content-project-mismatch";
|
|
3145
|
+
} else if (currentHash === projectHash) {
|
|
3146
|
+
result.alreadyScoped++;
|
|
3147
|
+
continue;
|
|
3148
|
+
} else if (currentHash && currentHash !== projectHash) {
|
|
3149
|
+
action = "quarantined";
|
|
3150
|
+
reason = "scope-hash-mismatch";
|
|
3151
|
+
observedProjectHash = currentHash;
|
|
3152
|
+
} else if (matchingPath) {
|
|
3153
|
+
action = "repaired";
|
|
3154
|
+
reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
|
|
3155
|
+
} else if (candidatePaths.length > 0) {
|
|
3156
|
+
action = "quarantined";
|
|
3157
|
+
reason = "project-path-mismatch";
|
|
3158
|
+
} else if (importedOrLegacy) {
|
|
3159
|
+
action = "quarantined";
|
|
3160
|
+
reason = "missing-project-scope";
|
|
3161
|
+
}
|
|
3162
|
+
if (action === "skipped" || !reason) {
|
|
3163
|
+
result.skipped++;
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
if (action === "repaired") {
|
|
3167
|
+
const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
|
|
3168
|
+
const project = isRecord(scope.project) ? { ...scope.project } : {};
|
|
3169
|
+
project.hash = projectHash;
|
|
3170
|
+
scope.project = project;
|
|
3171
|
+
metadata.scope = scope;
|
|
3172
|
+
metadata.repair = {
|
|
3173
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3174
|
+
legacyProjectScope: {
|
|
3175
|
+
action,
|
|
3176
|
+
reason,
|
|
3177
|
+
repairedAt: nowIso
|
|
3178
|
+
}
|
|
3179
|
+
};
|
|
3180
|
+
addMetadataTag(metadata, `proj:${projectHash}`);
|
|
3181
|
+
result.repaired++;
|
|
3182
|
+
} else {
|
|
3183
|
+
metadata.quarantine = {
|
|
3184
|
+
...isRecord(metadata.quarantine) ? metadata.quarantine : {},
|
|
3185
|
+
status: "active",
|
|
3186
|
+
category: "project-scope",
|
|
3187
|
+
reason,
|
|
3188
|
+
detectedAt: nowIso,
|
|
3189
|
+
expectedProjectHash: projectHash,
|
|
3190
|
+
...observedProjectHash ? { observedProjectHash } : {}
|
|
3191
|
+
};
|
|
3192
|
+
metadata.repair = {
|
|
3193
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3194
|
+
legacyProjectScope: {
|
|
3195
|
+
action,
|
|
3196
|
+
reason,
|
|
3197
|
+
repairedAt: nowIso
|
|
3198
|
+
}
|
|
3199
|
+
};
|
|
3200
|
+
addMetadataTag(metadata, "quarantine:project-scope");
|
|
3201
|
+
result.quarantined++;
|
|
3202
|
+
}
|
|
3203
|
+
sample({ eventId: row.id, action, reason });
|
|
3204
|
+
if (!dryRun) {
|
|
3205
|
+
sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
return result;
|
|
3209
|
+
}
|
|
2935
3210
|
/**
|
|
2936
3211
|
* Get embedding/vector outbox health statistics
|
|
2937
3212
|
*/
|
|
@@ -2979,7 +3254,11 @@ var SQLiteEventStore = class {
|
|
|
2979
3254
|
await this.initialize();
|
|
2980
3255
|
const rows = sqliteAll(
|
|
2981
3256
|
this.db,
|
|
2982
|
-
`SELECT level, COUNT(*) as count
|
|
3257
|
+
`SELECT ml.level, COUNT(*) as count
|
|
3258
|
+
FROM memory_levels ml
|
|
3259
|
+
INNER JOIN events e ON e.id = ml.event_id
|
|
3260
|
+
WHERE ${notActiveQuarantinedSql("e.metadata")}
|
|
3261
|
+
GROUP BY ml.level`
|
|
2983
3262
|
);
|
|
2984
3263
|
return rows;
|
|
2985
3264
|
}
|
|
@@ -2995,6 +3274,7 @@ var SQLiteEventStore = class {
|
|
|
2995
3274
|
`SELECT e.* FROM events e
|
|
2996
3275
|
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
2997
3276
|
WHERE ml.level = ?
|
|
3277
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
2998
3278
|
ORDER BY e.timestamp DESC
|
|
2999
3279
|
LIMIT ? OFFSET ?`,
|
|
3000
3280
|
[level, limit, offset]
|
|
@@ -3087,12 +3367,13 @@ var SQLiteEventStore = class {
|
|
|
3087
3367
|
/**
|
|
3088
3368
|
* Get most accessed memories (falls back to recent events if none accessed)
|
|
3089
3369
|
*/
|
|
3090
|
-
async getMostAccessed(limit = 10) {
|
|
3370
|
+
async getMostAccessed(limit = 10, options) {
|
|
3091
3371
|
await this.initialize();
|
|
3092
3372
|
let rows = sqliteAll(
|
|
3093
3373
|
this.db,
|
|
3094
3374
|
`SELECT * FROM events
|
|
3095
3375
|
WHERE access_count > 0
|
|
3376
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3096
3377
|
ORDER BY access_count DESC, last_accessed_at DESC
|
|
3097
3378
|
LIMIT ?`,
|
|
3098
3379
|
[limit]
|
|
@@ -3101,6 +3382,7 @@ var SQLiteEventStore = class {
|
|
|
3101
3382
|
rows = sqliteAll(
|
|
3102
3383
|
this.db,
|
|
3103
3384
|
`SELECT * FROM events
|
|
3385
|
+
WHERE ${maybeQuarantinePredicate(options)}
|
|
3104
3386
|
ORDER BY timestamp DESC
|
|
3105
3387
|
LIMIT ?`,
|
|
3106
3388
|
[limit]
|
|
@@ -3231,6 +3513,7 @@ var SQLiteEventStore = class {
|
|
|
3231
3513
|
FROM memory_helpfulness mh
|
|
3232
3514
|
JOIN events e ON e.id = mh.event_id
|
|
3233
3515
|
WHERE mh.measured_at IS NOT NULL
|
|
3516
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3234
3517
|
GROUP BY mh.event_id
|
|
3235
3518
|
ORDER BY avg_score DESC
|
|
3236
3519
|
LIMIT ?`,
|
|
@@ -3295,6 +3578,7 @@ var SQLiteEventStore = class {
|
|
|
3295
3578
|
FROM events_fts fts
|
|
3296
3579
|
JOIN events e ON e.id = fts.event_id
|
|
3297
3580
|
WHERE events_fts MATCH ?
|
|
3581
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3298
3582
|
ORDER BY fts.rank
|
|
3299
3583
|
LIMIT ?`,
|
|
3300
3584
|
[searchTerms, limit]
|
|
@@ -3309,6 +3593,7 @@ var SQLiteEventStore = class {
|
|
|
3309
3593
|
this.db,
|
|
3310
3594
|
`SELECT *, 0 as rank FROM events
|
|
3311
3595
|
WHERE content LIKE ?
|
|
3596
|
+
AND ${notActiveQuarantinedSql()}
|
|
3312
3597
|
ORDER BY timestamp DESC
|
|
3313
3598
|
LIMIT ?`,
|
|
3314
3599
|
[likePattern, limit]
|
|
@@ -3515,6 +3800,7 @@ var SQLiteEventStore = class {
|
|
|
3515
3800
|
`SELECT turn_id, MIN(timestamp) as min_ts
|
|
3516
3801
|
FROM events
|
|
3517
3802
|
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
3803
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3518
3804
|
GROUP BY turn_id
|
|
3519
3805
|
ORDER BY min_ts DESC
|
|
3520
3806
|
LIMIT ? OFFSET ?`,
|
|
@@ -3522,7 +3808,7 @@ var SQLiteEventStore = class {
|
|
|
3522
3808
|
);
|
|
3523
3809
|
const turns = [];
|
|
3524
3810
|
for (const turnRow of turnRows) {
|
|
3525
|
-
const events = await this.getEventsByTurn(turnRow.turn_id);
|
|
3811
|
+
const events = await this.getEventsByTurn(turnRow.turn_id, options);
|
|
3526
3812
|
const promptEvent = events.find((e) => e.eventType === "user_prompt");
|
|
3527
3813
|
const toolEvents = events.filter((e) => e.eventType === "tool_observation");
|
|
3528
3814
|
const hasResponse = events.some((e) => e.eventType === "agent_response");
|
|
@@ -3541,11 +3827,11 @@ var SQLiteEventStore = class {
|
|
|
3541
3827
|
/**
|
|
3542
3828
|
* Get all events for a specific turn_id
|
|
3543
3829
|
*/
|
|
3544
|
-
async getEventsByTurn(turnId) {
|
|
3830
|
+
async getEventsByTurn(turnId, options) {
|
|
3545
3831
|
await this.initialize();
|
|
3546
3832
|
const rows = sqliteAll(
|
|
3547
3833
|
this.db,
|
|
3548
|
-
`SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
|
|
3834
|
+
`SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
3549
3835
|
[turnId]
|
|
3550
3836
|
);
|
|
3551
3837
|
return rows.map(this.rowToEvent);
|
|
@@ -3553,13 +3839,14 @@ var SQLiteEventStore = class {
|
|
|
3553
3839
|
/**
|
|
3554
3840
|
* Count total turns for a session
|
|
3555
3841
|
*/
|
|
3556
|
-
async countSessionTurns(sessionId) {
|
|
3842
|
+
async countSessionTurns(sessionId, options) {
|
|
3557
3843
|
await this.initialize();
|
|
3558
3844
|
const row = sqliteGet(
|
|
3559
3845
|
this.db,
|
|
3560
3846
|
`SELECT COUNT(DISTINCT turn_id) as count
|
|
3561
3847
|
FROM events
|
|
3562
|
-
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
3848
|
+
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
3849
|
+
AND ${maybeQuarantinePredicate(options)}`,
|
|
3563
3850
|
[sessionId]
|
|
3564
3851
|
);
|
|
3565
3852
|
return row?.count || 0;
|
|
@@ -3651,7 +3938,7 @@ var SQLiteEventStore = class {
|
|
|
3651
3938
|
content: row.content,
|
|
3652
3939
|
canonicalKey: row.canonical_key,
|
|
3653
3940
|
dedupeKey: row.dedupe_key,
|
|
3654
|
-
metadata:
|
|
3941
|
+
metadata: safeParseMetadataValue(row.metadata)
|
|
3655
3942
|
};
|
|
3656
3943
|
if (row.access_count !== void 0) {
|
|
3657
3944
|
event.access_count = row.access_count;
|