claude-memory-layer 1.0.36 → 1.0.38

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.
@@ -1128,6 +1128,14 @@ var MemoryQueryService = class {
1128
1128
  await this.initialize();
1129
1129
  return this.getMaintenanceStore("getOutboxStats").getOutboxStats();
1130
1130
  }
1131
+ async recoverStuckOutboxItems(options) {
1132
+ await this.initialize();
1133
+ return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
1134
+ }
1135
+ async repairLegacyProjectScope(options) {
1136
+ await this.initialize();
1137
+ return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
1138
+ }
1131
1139
  async getStats() {
1132
1140
  await this.initialize();
1133
1141
  const deps = this.getStatsDeps();
@@ -1922,6 +1930,9 @@ function parseEntityCanonicalKey(canonicalKey) {
1922
1930
  return null;
1923
1931
  }
1924
1932
 
1933
+ // src/core/sqlite-event-store.ts
1934
+ import * as nodePath2 from "path";
1935
+
1925
1936
  // src/core/sqlite-wrapper.ts
1926
1937
  import Database from "better-sqlite3";
1927
1938
  import * as fs4 from "fs";
@@ -2046,6 +2057,143 @@ function normalizeQueryRewriteKind(value) {
2046
2057
  return "none";
2047
2058
  }
2048
2059
  var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
2060
+ var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
2061
+ var DEFAULT_OUTBOX_MAX_RETRIES = 3;
2062
+ function emptyOutboxRecoveryResult() {
2063
+ return {
2064
+ embedding: { recoveredProcessing: 0, retriedFailed: 0 },
2065
+ vector: { recoveredProcessing: 0, retriedFailed: 0 }
2066
+ };
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
+ }
2049
2197
  var SQLiteEventStore = class {
2050
2198
  db;
2051
2199
  initialized = false;
@@ -2544,11 +2692,11 @@ var SQLiteEventStore = class {
2544
2692
  /**
2545
2693
  * Get events by session ID
2546
2694
  */
2547
- async getSessionEvents(sessionId) {
2695
+ async getSessionEvents(sessionId, options) {
2548
2696
  await this.initialize();
2549
2697
  const rows = sqliteAll(
2550
2698
  this.db,
2551
- `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
2699
+ `SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
2552
2700
  [sessionId]
2553
2701
  );
2554
2702
  return rows.map(this.rowToEvent);
@@ -2556,11 +2704,11 @@ var SQLiteEventStore = class {
2556
2704
  /**
2557
2705
  * Get recent events
2558
2706
  */
2559
- async getRecentEvents(limit = 100) {
2707
+ async getRecentEvents(limit = 100, options) {
2560
2708
  await this.initialize();
2561
2709
  const rows = sqliteAll(
2562
2710
  this.db,
2563
- `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
2711
+ `SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
2564
2712
  [limit]
2565
2713
  );
2566
2714
  return rows.map(this.rowToEvent);
@@ -2568,11 +2716,11 @@ var SQLiteEventStore = class {
2568
2716
  /**
2569
2717
  * Get event by ID
2570
2718
  */
2571
- async getEvent(id) {
2719
+ async getEvent(id, options) {
2572
2720
  await this.initialize();
2573
2721
  const row = sqliteGet(
2574
2722
  this.db,
2575
- `SELECT * FROM events WHERE id = ?`,
2723
+ `SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
2576
2724
  [id]
2577
2725
  );
2578
2726
  if (!row)
@@ -2582,11 +2730,11 @@ var SQLiteEventStore = class {
2582
2730
  /**
2583
2731
  * Get events since a timestamp (for sync)
2584
2732
  */
2585
- async getEventsSince(timestamp, limit = 1e3) {
2733
+ async getEventsSince(timestamp, limit = 1e3, options) {
2586
2734
  await this.initialize();
2587
2735
  const rows = sqliteAll(
2588
2736
  this.db,
2589
- `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
2737
+ `SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
2590
2738
  [timestamp, limit]
2591
2739
  );
2592
2740
  return rows.map(this.rowToEvent);
@@ -2595,11 +2743,11 @@ var SQLiteEventStore = class {
2595
2743
  * Get events since a SQLite rowid (for robust incremental replication).
2596
2744
  * Rowid is monotonic for append-only tables, independent of client timestamps.
2597
2745
  */
2598
- async getEventsSinceRowid(lastRowid, limit = 1e3) {
2746
+ async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
2599
2747
  await this.initialize();
2600
2748
  const rows = sqliteAll(
2601
2749
  this.db,
2602
- `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 ?`,
2603
2751
  [lastRowid, limit]
2604
2752
  );
2605
2753
  return rows.map((row) => ({
@@ -2796,7 +2944,9 @@ var SQLiteEventStore = class {
2796
2944
  const placeholders = ids.map(() => "?").join(",");
2797
2945
  sqliteRun(
2798
2946
  this.db,
2799
- `UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
2947
+ `UPDATE embedding_outbox
2948
+ SET status = 'processing', processed_at = datetime('now'), error_message = NULL
2949
+ WHERE id IN (${placeholders})`,
2800
2950
  ids
2801
2951
  );
2802
2952
  return pending.map((row) => ({
@@ -2832,19 +2982,19 @@ var SQLiteEventStore = class {
2832
2982
  /**
2833
2983
  * Count total events
2834
2984
  */
2835
- async countEvents() {
2985
+ async countEvents(options) {
2836
2986
  await this.initialize();
2837
- 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)}`);
2838
2988
  return row?.count || 0;
2839
2989
  }
2840
2990
  /**
2841
2991
  * Get events page in timestamp ascending order (stable migration/reindex scans)
2842
2992
  */
2843
- async getEventsPage(limit = 1e3, offset = 0) {
2993
+ async getEventsPage(limit = 1e3, offset = 0, options) {
2844
2994
  await this.initialize();
2845
2995
  const rows = sqliteAll(
2846
2996
  this.db,
2847
- `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
2997
+ `SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
2848
2998
  [limit, offset]
2849
2999
  );
2850
3000
  return rows.map(this.rowToEvent);
@@ -2866,6 +3016,197 @@ var SQLiteEventStore = class {
2866
3016
  [error, ...ids]
2867
3017
  );
2868
3018
  }
3019
+ /**
3020
+ * Recover abandoned outbox work after a worker/process crash.
3021
+ *
3022
+ * Rows in `processing` are claimed work. If the process exits before marking
3023
+ * them done/failed, they otherwise remain invisible to future processing.
3024
+ * Recovery is deliberately age-gated so an active worker is not disturbed.
3025
+ */
3026
+ async recoverStuckOutboxItems(options = {}) {
3027
+ await this.initialize();
3028
+ const thresholdMs = Number.isFinite(options.stuckThresholdMs) && (options.stuckThresholdMs ?? 0) >= 0 ? options.stuckThresholdMs : DEFAULT_OUTBOX_STUCK_THRESHOLD_MS;
3029
+ const maxRetries = Number.isFinite(options.maxRetries) && (options.maxRetries ?? 0) > 0 ? options.maxRetries : DEFAULT_OUTBOX_MAX_RETRIES;
3030
+ const now = options.now ?? /* @__PURE__ */ new Date();
3031
+ const threshold = new Date(now.getTime() - thresholdMs).toISOString();
3032
+ const result = emptyOutboxRecoveryResult();
3033
+ const embeddingRecovered = sqliteRun(
3034
+ this.db,
3035
+ `UPDATE embedding_outbox
3036
+ SET status = 'pending', processed_at = NULL, error_message = NULL
3037
+ WHERE status = 'processing'
3038
+ AND datetime(COALESCE(processed_at, created_at)) < datetime(?)`,
3039
+ [threshold]
3040
+ );
3041
+ result.embedding.recoveredProcessing = Number(embeddingRecovered.changes ?? 0);
3042
+ const embeddingRetried = sqliteRun(
3043
+ this.db,
3044
+ `UPDATE embedding_outbox
3045
+ SET status = 'pending', error_message = NULL
3046
+ WHERE status = 'failed'
3047
+ AND retry_count < ?`,
3048
+ [maxRetries]
3049
+ );
3050
+ result.embedding.retriedFailed = Number(embeddingRetried.changes ?? 0);
3051
+ const vectorRecovered = sqliteRun(
3052
+ this.db,
3053
+ `UPDATE vector_outbox
3054
+ SET status = 'pending', updated_at = ?, error = NULL
3055
+ WHERE status = 'processing'
3056
+ AND datetime(updated_at) < datetime(?)`,
3057
+ [now.toISOString(), threshold]
3058
+ );
3059
+ result.vector.recoveredProcessing = Number(vectorRecovered.changes ?? 0);
3060
+ const vectorRetried = sqliteRun(
3061
+ this.db,
3062
+ `UPDATE vector_outbox
3063
+ SET status = 'pending', updated_at = ?, error = NULL
3064
+ WHERE status = 'failed'
3065
+ AND retry_count < ?`,
3066
+ [now.toISOString(), maxRetries]
3067
+ );
3068
+ result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
3069
+ return result;
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
+ }
2869
3210
  /**
2870
3211
  * Get embedding/vector outbox health statistics
2871
3212
  */
@@ -2913,7 +3254,11 @@ var SQLiteEventStore = class {
2913
3254
  await this.initialize();
2914
3255
  const rows = sqliteAll(
2915
3256
  this.db,
2916
- `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
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`
2917
3262
  );
2918
3263
  return rows;
2919
3264
  }
@@ -2929,6 +3274,7 @@ var SQLiteEventStore = class {
2929
3274
  `SELECT e.* FROM events e
2930
3275
  INNER JOIN memory_levels ml ON e.id = ml.event_id
2931
3276
  WHERE ml.level = ?
3277
+ AND ${notActiveQuarantinedSql("e.metadata")}
2932
3278
  ORDER BY e.timestamp DESC
2933
3279
  LIMIT ? OFFSET ?`,
2934
3280
  [level, limit, offset]
@@ -3021,12 +3367,13 @@ var SQLiteEventStore = class {
3021
3367
  /**
3022
3368
  * Get most accessed memories (falls back to recent events if none accessed)
3023
3369
  */
3024
- async getMostAccessed(limit = 10) {
3370
+ async getMostAccessed(limit = 10, options) {
3025
3371
  await this.initialize();
3026
3372
  let rows = sqliteAll(
3027
3373
  this.db,
3028
3374
  `SELECT * FROM events
3029
3375
  WHERE access_count > 0
3376
+ AND ${maybeQuarantinePredicate(options)}
3030
3377
  ORDER BY access_count DESC, last_accessed_at DESC
3031
3378
  LIMIT ?`,
3032
3379
  [limit]
@@ -3035,6 +3382,7 @@ var SQLiteEventStore = class {
3035
3382
  rows = sqliteAll(
3036
3383
  this.db,
3037
3384
  `SELECT * FROM events
3385
+ WHERE ${maybeQuarantinePredicate(options)}
3038
3386
  ORDER BY timestamp DESC
3039
3387
  LIMIT ?`,
3040
3388
  [limit]
@@ -3165,6 +3513,7 @@ var SQLiteEventStore = class {
3165
3513
  FROM memory_helpfulness mh
3166
3514
  JOIN events e ON e.id = mh.event_id
3167
3515
  WHERE mh.measured_at IS NOT NULL
3516
+ AND ${notActiveQuarantinedSql("e.metadata")}
3168
3517
  GROUP BY mh.event_id
3169
3518
  ORDER BY avg_score DESC
3170
3519
  LIMIT ?`,
@@ -3229,6 +3578,7 @@ var SQLiteEventStore = class {
3229
3578
  FROM events_fts fts
3230
3579
  JOIN events e ON e.id = fts.event_id
3231
3580
  WHERE events_fts MATCH ?
3581
+ AND ${notActiveQuarantinedSql("e.metadata")}
3232
3582
  ORDER BY fts.rank
3233
3583
  LIMIT ?`,
3234
3584
  [searchTerms, limit]
@@ -3243,6 +3593,7 @@ var SQLiteEventStore = class {
3243
3593
  this.db,
3244
3594
  `SELECT *, 0 as rank FROM events
3245
3595
  WHERE content LIKE ?
3596
+ AND ${notActiveQuarantinedSql()}
3246
3597
  ORDER BY timestamp DESC
3247
3598
  LIMIT ?`,
3248
3599
  [likePattern, limit]
@@ -3449,6 +3800,7 @@ var SQLiteEventStore = class {
3449
3800
  `SELECT turn_id, MIN(timestamp) as min_ts
3450
3801
  FROM events
3451
3802
  WHERE session_id = ? AND turn_id IS NOT NULL
3803
+ AND ${maybeQuarantinePredicate(options)}
3452
3804
  GROUP BY turn_id
3453
3805
  ORDER BY min_ts DESC
3454
3806
  LIMIT ? OFFSET ?`,
@@ -3456,7 +3808,7 @@ var SQLiteEventStore = class {
3456
3808
  );
3457
3809
  const turns = [];
3458
3810
  for (const turnRow of turnRows) {
3459
- const events = await this.getEventsByTurn(turnRow.turn_id);
3811
+ const events = await this.getEventsByTurn(turnRow.turn_id, options);
3460
3812
  const promptEvent = events.find((e) => e.eventType === "user_prompt");
3461
3813
  const toolEvents = events.filter((e) => e.eventType === "tool_observation");
3462
3814
  const hasResponse = events.some((e) => e.eventType === "agent_response");
@@ -3475,11 +3827,11 @@ var SQLiteEventStore = class {
3475
3827
  /**
3476
3828
  * Get all events for a specific turn_id
3477
3829
  */
3478
- async getEventsByTurn(turnId) {
3830
+ async getEventsByTurn(turnId, options) {
3479
3831
  await this.initialize();
3480
3832
  const rows = sqliteAll(
3481
3833
  this.db,
3482
- `SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
3834
+ `SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
3483
3835
  [turnId]
3484
3836
  );
3485
3837
  return rows.map(this.rowToEvent);
@@ -3487,13 +3839,14 @@ var SQLiteEventStore = class {
3487
3839
  /**
3488
3840
  * Count total turns for a session
3489
3841
  */
3490
- async countSessionTurns(sessionId) {
3842
+ async countSessionTurns(sessionId, options) {
3491
3843
  await this.initialize();
3492
3844
  const row = sqliteGet(
3493
3845
  this.db,
3494
3846
  `SELECT COUNT(DISTINCT turn_id) as count
3495
3847
  FROM events
3496
- WHERE session_id = ? AND turn_id IS NOT NULL`,
3848
+ WHERE session_id = ? AND turn_id IS NOT NULL
3849
+ AND ${maybeQuarantinePredicate(options)}`,
3497
3850
  [sessionId]
3498
3851
  );
3499
3852
  return row?.count || 0;
@@ -3585,7 +3938,7 @@ var SQLiteEventStore = class {
3585
3938
  content: row.content,
3586
3939
  canonicalKey: row.canonical_key,
3587
3940
  dedupeKey: row.dedupe_key,
3588
- metadata: row.metadata ? JSON.parse(row.metadata) : void 0
3941
+ metadata: safeParseMetadataValue(row.metadata)
3589
3942
  };
3590
3943
  if (row.access_count !== void 0) {
3591
3944
  event.access_count = row.access_count;
@@ -3737,6 +4090,7 @@ var VectorStore = class {
3737
4090
  * Get total count of vectors
3738
4091
  */
3739
4092
  async count() {
4093
+ await this.initialize();
3740
4094
  if (!this.table)
3741
4095
  return 0;
3742
4096
  const result = await this.table.countRows();