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.
- package/dist/cli/index.js +776 -126
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +377 -23
- package/dist/core/index.js.map +3 -3
- package/dist/hooks/post-tool-use.js +470 -75
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +428 -68
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +428 -68
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +428 -68
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +469 -74
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +428 -68
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +389 -29
- package/dist/index.js.map +3 -3
- package/dist/mcp/index.js +474 -79
- package/dist/mcp/index.js.map +4 -4
- package/dist/server/api/index.js +632 -112
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +632 -112
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +428 -68
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/assets/js/chat.js +21 -3
- package/package.json +1 -1
package/dist/hooks/stop.js
CHANGED
|
@@ -10,8 +10,8 @@ const __dirname = dirname(__filename);
|
|
|
10
10
|
import * as os5 from "os";
|
|
11
11
|
|
|
12
12
|
// src/core/engine/memory-service-composition.ts
|
|
13
|
-
import * as
|
|
14
|
-
import * as
|
|
13
|
+
import * as os2 from "os";
|
|
14
|
+
import * as path7 from "path";
|
|
15
15
|
|
|
16
16
|
// src/core/metadata-extractor.ts
|
|
17
17
|
function createToolObservationEmbedding(toolName, metadata, success) {
|
|
@@ -1522,8 +1522,8 @@ function createEndlessMemoryServices(options) {
|
|
|
1522
1522
|
}
|
|
1523
1523
|
|
|
1524
1524
|
// src/core/engine/memory-engine-services.ts
|
|
1525
|
-
import * as
|
|
1526
|
-
import * as
|
|
1525
|
+
import * as fs6 from "fs";
|
|
1526
|
+
import * as path5 from "path";
|
|
1527
1527
|
|
|
1528
1528
|
// src/extensions/vector/embedder.ts
|
|
1529
1529
|
var DEFAULT_EMBEDDING_MODEL = "Xenova/multilingual-e5-small";
|
|
@@ -2201,14 +2201,39 @@ function makeDedupeKey(content, sessionId) {
|
|
|
2201
2201
|
return `${sessionId}:${contentHash}`;
|
|
2202
2202
|
}
|
|
2203
2203
|
|
|
2204
|
+
// src/core/sqlite-event-store.ts
|
|
2205
|
+
import * as nodePath2 from "path";
|
|
2206
|
+
|
|
2207
|
+
// src/core/registry/project-path.ts
|
|
2208
|
+
import * as crypto2 from "crypto";
|
|
2209
|
+
import * as fs3 from "fs";
|
|
2210
|
+
import * as os from "os";
|
|
2211
|
+
import * as path3 from "path";
|
|
2212
|
+
function normalizeProjectPath(projectPath) {
|
|
2213
|
+
const expanded = projectPath.startsWith("~") ? path3.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
2214
|
+
try {
|
|
2215
|
+
return fs3.realpathSync(expanded);
|
|
2216
|
+
} catch {
|
|
2217
|
+
return path3.resolve(expanded);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
function hashProjectPath(projectPath) {
|
|
2221
|
+
const normalizedPath = normalizeProjectPath(projectPath);
|
|
2222
|
+
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
2223
|
+
}
|
|
2224
|
+
function getProjectStoragePath(projectPath) {
|
|
2225
|
+
const hash = hashProjectPath(projectPath);
|
|
2226
|
+
return path3.join(os.homedir(), ".claude-code", "memory", "projects", hash);
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2204
2229
|
// src/core/sqlite-wrapper.ts
|
|
2205
2230
|
import Database from "better-sqlite3";
|
|
2206
|
-
import * as
|
|
2231
|
+
import * as fs4 from "fs";
|
|
2207
2232
|
import * as nodePath from "path";
|
|
2208
2233
|
function createSQLiteDatabase(path12, options) {
|
|
2209
2234
|
const dir = nodePath.dirname(path12);
|
|
2210
|
-
if (!
|
|
2211
|
-
|
|
2235
|
+
if (!fs4.existsSync(dir)) {
|
|
2236
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
2212
2237
|
}
|
|
2213
2238
|
const db = new Database(path12, {
|
|
2214
2239
|
readonly: options?.readonly ?? false
|
|
@@ -2257,8 +2282,8 @@ function toSQLiteTimestamp(date) {
|
|
|
2257
2282
|
}
|
|
2258
2283
|
|
|
2259
2284
|
// src/core/markdown-mirror.ts
|
|
2260
|
-
import * as
|
|
2261
|
-
import * as
|
|
2285
|
+
import * as fs5 from "fs/promises";
|
|
2286
|
+
import * as path4 from "path";
|
|
2262
2287
|
var DEFAULT_NAMESPACE = "default";
|
|
2263
2288
|
var DEFAULT_CATEGORY = "uncategorized";
|
|
2264
2289
|
function sanitizeSegment2(input, fallback) {
|
|
@@ -2287,7 +2312,7 @@ function buildMirrorPath2(rootDir, event) {
|
|
|
2287
2312
|
const yyyy = d.getFullYear();
|
|
2288
2313
|
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
2289
2314
|
const dd = String(d.getDate()).padStart(2, "0");
|
|
2290
|
-
return
|
|
2315
|
+
return path4.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
|
|
2291
2316
|
}
|
|
2292
2317
|
function formatMirrorEntry(event) {
|
|
2293
2318
|
const category = Array.isArray(event.metadata?.categoryPath) ? event.metadata.categoryPath.join("/") : String(event.metadata?.category ?? event.eventType);
|
|
@@ -2308,8 +2333,8 @@ var MarkdownMirror2 = class {
|
|
|
2308
2333
|
}
|
|
2309
2334
|
async append(event) {
|
|
2310
2335
|
const outPath = buildMirrorPath2(this.rootDir, event);
|
|
2311
|
-
await
|
|
2312
|
-
await
|
|
2336
|
+
await fs5.mkdir(path4.dirname(outPath), { recursive: true });
|
|
2337
|
+
await fs5.appendFile(outPath, formatMirrorEntry(event), "utf8");
|
|
2313
2338
|
return outPath;
|
|
2314
2339
|
}
|
|
2315
2340
|
};
|
|
@@ -2322,6 +2347,143 @@ function normalizeQueryRewriteKind(value) {
|
|
|
2322
2347
|
return "none";
|
|
2323
2348
|
}
|
|
2324
2349
|
var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
|
|
2350
|
+
var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
2351
|
+
var DEFAULT_OUTBOX_MAX_RETRIES = 3;
|
|
2352
|
+
function emptyOutboxRecoveryResult() {
|
|
2353
|
+
return {
|
|
2354
|
+
embedding: { recoveredProcessing: 0, retriedFailed: 0 },
|
|
2355
|
+
vector: { recoveredProcessing: 0, retriedFailed: 0 }
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
function isRecord(value) {
|
|
2359
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2360
|
+
}
|
|
2361
|
+
function getNestedRecord(root, path12) {
|
|
2362
|
+
let cursor = root;
|
|
2363
|
+
for (const key of path12) {
|
|
2364
|
+
if (!isRecord(cursor))
|
|
2365
|
+
return void 0;
|
|
2366
|
+
cursor = cursor[key];
|
|
2367
|
+
}
|
|
2368
|
+
return isRecord(cursor) ? cursor : void 0;
|
|
2369
|
+
}
|
|
2370
|
+
function getNestedString(root, path12) {
|
|
2371
|
+
let cursor = root;
|
|
2372
|
+
for (const key of path12) {
|
|
2373
|
+
if (!isRecord(cursor))
|
|
2374
|
+
return void 0;
|
|
2375
|
+
cursor = cursor[key];
|
|
2376
|
+
}
|
|
2377
|
+
return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
|
|
2378
|
+
}
|
|
2379
|
+
function metadataProjectHash(metadata) {
|
|
2380
|
+
return getNestedString(metadata, ["scope", "project", "hash"]);
|
|
2381
|
+
}
|
|
2382
|
+
function metadataProjectPaths(metadata) {
|
|
2383
|
+
const candidates = [
|
|
2384
|
+
getNestedString(metadata, ["projectPath"]),
|
|
2385
|
+
getNestedString(metadata, ["sourceProjectPath"]),
|
|
2386
|
+
getNestedString(metadata, ["scope", "project", "path"])
|
|
2387
|
+
];
|
|
2388
|
+
const paths = [];
|
|
2389
|
+
for (const value of candidates) {
|
|
2390
|
+
if (value && !paths.includes(value))
|
|
2391
|
+
paths.push(value);
|
|
2392
|
+
}
|
|
2393
|
+
return paths;
|
|
2394
|
+
}
|
|
2395
|
+
function metadataProjectPath(metadata) {
|
|
2396
|
+
return metadataProjectPaths(metadata)[0];
|
|
2397
|
+
}
|
|
2398
|
+
function isActiveQuarantinedMetadata(metadata) {
|
|
2399
|
+
const quarantine = getNestedRecord(metadata, ["quarantine"]);
|
|
2400
|
+
return quarantine?.status === "active";
|
|
2401
|
+
}
|
|
2402
|
+
function activeQuarantineStatusExpression(column = "metadata") {
|
|
2403
|
+
return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
|
|
2404
|
+
}
|
|
2405
|
+
function notActiveQuarantinedSql(column = "metadata") {
|
|
2406
|
+
return `${activeQuarantineStatusExpression(column)} != 'active'`;
|
|
2407
|
+
}
|
|
2408
|
+
function maybeQuarantinePredicate(options, column = "metadata") {
|
|
2409
|
+
return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
|
|
2410
|
+
}
|
|
2411
|
+
function safeParseMetadataValue(value) {
|
|
2412
|
+
if (!value)
|
|
2413
|
+
return void 0;
|
|
2414
|
+
if (typeof value === "object")
|
|
2415
|
+
return isRecord(value) ? value : void 0;
|
|
2416
|
+
if (typeof value !== "string")
|
|
2417
|
+
return void 0;
|
|
2418
|
+
try {
|
|
2419
|
+
const parsed = JSON.parse(value);
|
|
2420
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
2421
|
+
} catch {
|
|
2422
|
+
return void 0;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
function isImportedOrLegacyScopedMetadata(metadata) {
|
|
2426
|
+
if (!metadata)
|
|
2427
|
+
return false;
|
|
2428
|
+
return Boolean(
|
|
2429
|
+
metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
function addMetadataTag(metadata, tag) {
|
|
2433
|
+
const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
|
|
2434
|
+
if (!current.includes(tag))
|
|
2435
|
+
metadata.tags = [...current, tag];
|
|
2436
|
+
}
|
|
2437
|
+
function buildRepairResult(projectHash, dryRun) {
|
|
2438
|
+
return {
|
|
2439
|
+
dryRun,
|
|
2440
|
+
projectHash,
|
|
2441
|
+
scanned: 0,
|
|
2442
|
+
repaired: 0,
|
|
2443
|
+
quarantined: 0,
|
|
2444
|
+
alreadyScoped: 0,
|
|
2445
|
+
skipped: 0,
|
|
2446
|
+
samples: []
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
function normalizeRepoName(value) {
|
|
2450
|
+
return value.replace(/\.git$/i, "").trim().toLowerCase();
|
|
2451
|
+
}
|
|
2452
|
+
function projectBasename(projectPath) {
|
|
2453
|
+
if (!projectPath)
|
|
2454
|
+
return void 0;
|
|
2455
|
+
const trimmed = projectPath.replace(/[\\/]+$/, "");
|
|
2456
|
+
const basename2 = nodePath2.basename(trimmed);
|
|
2457
|
+
return basename2 ? normalizeRepoName(basename2) : void 0;
|
|
2458
|
+
}
|
|
2459
|
+
function isProjectScopeRepairExplanation(content) {
|
|
2460
|
+
const normalized = content.toLowerCase();
|
|
2461
|
+
const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
|
|
2462
|
+
const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
|
|
2463
|
+
return hasRepairContext && hasExplanationContext;
|
|
2464
|
+
}
|
|
2465
|
+
function hasConflictingContentProjectHint(content, projectPath) {
|
|
2466
|
+
const currentName = projectBasename(projectPath);
|
|
2467
|
+
if (!currentName)
|
|
2468
|
+
return false;
|
|
2469
|
+
if (isProjectScopeRepairExplanation(content))
|
|
2470
|
+
return false;
|
|
2471
|
+
const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
|
|
2472
|
+
let githubMatch;
|
|
2473
|
+
while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
|
|
2474
|
+
const repo = normalizeRepoName(githubMatch[2] || "");
|
|
2475
|
+
if (repo && repo !== currentName)
|
|
2476
|
+
return true;
|
|
2477
|
+
}
|
|
2478
|
+
const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
|
|
2479
|
+
let workspaceMatch;
|
|
2480
|
+
while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
|
|
2481
|
+
const repo = normalizeRepoName(workspaceMatch[1] || "");
|
|
2482
|
+
if (repo && repo !== currentName)
|
|
2483
|
+
return true;
|
|
2484
|
+
}
|
|
2485
|
+
return false;
|
|
2486
|
+
}
|
|
2325
2487
|
var SQLiteEventStore = class {
|
|
2326
2488
|
db;
|
|
2327
2489
|
initialized = false;
|
|
@@ -2820,11 +2982,11 @@ var SQLiteEventStore = class {
|
|
|
2820
2982
|
/**
|
|
2821
2983
|
* Get events by session ID
|
|
2822
2984
|
*/
|
|
2823
|
-
async getSessionEvents(sessionId) {
|
|
2985
|
+
async getSessionEvents(sessionId, options) {
|
|
2824
2986
|
await this.initialize();
|
|
2825
2987
|
const rows = sqliteAll(
|
|
2826
2988
|
this.db,
|
|
2827
|
-
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
2989
|
+
`SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
2828
2990
|
[sessionId]
|
|
2829
2991
|
);
|
|
2830
2992
|
return rows.map(this.rowToEvent);
|
|
@@ -2832,11 +2994,11 @@ var SQLiteEventStore = class {
|
|
|
2832
2994
|
/**
|
|
2833
2995
|
* Get recent events
|
|
2834
2996
|
*/
|
|
2835
|
-
async getRecentEvents(limit = 100) {
|
|
2997
|
+
async getRecentEvents(limit = 100, options) {
|
|
2836
2998
|
await this.initialize();
|
|
2837
2999
|
const rows = sqliteAll(
|
|
2838
3000
|
this.db,
|
|
2839
|
-
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
3001
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
|
|
2840
3002
|
[limit]
|
|
2841
3003
|
);
|
|
2842
3004
|
return rows.map(this.rowToEvent);
|
|
@@ -2844,11 +3006,11 @@ var SQLiteEventStore = class {
|
|
|
2844
3006
|
/**
|
|
2845
3007
|
* Get event by ID
|
|
2846
3008
|
*/
|
|
2847
|
-
async getEvent(id) {
|
|
3009
|
+
async getEvent(id, options) {
|
|
2848
3010
|
await this.initialize();
|
|
2849
3011
|
const row = sqliteGet(
|
|
2850
3012
|
this.db,
|
|
2851
|
-
`SELECT * FROM events WHERE id =
|
|
3013
|
+
`SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
|
|
2852
3014
|
[id]
|
|
2853
3015
|
);
|
|
2854
3016
|
if (!row)
|
|
@@ -2858,11 +3020,11 @@ var SQLiteEventStore = class {
|
|
|
2858
3020
|
/**
|
|
2859
3021
|
* Get events since a timestamp (for sync)
|
|
2860
3022
|
*/
|
|
2861
|
-
async getEventsSince(timestamp, limit = 1e3) {
|
|
3023
|
+
async getEventsSince(timestamp, limit = 1e3, options) {
|
|
2862
3024
|
await this.initialize();
|
|
2863
3025
|
const rows = sqliteAll(
|
|
2864
3026
|
this.db,
|
|
2865
|
-
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
3027
|
+
`SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
|
|
2866
3028
|
[timestamp, limit]
|
|
2867
3029
|
);
|
|
2868
3030
|
return rows.map(this.rowToEvent);
|
|
@@ -2871,11 +3033,11 @@ var SQLiteEventStore = class {
|
|
|
2871
3033
|
* Get events since a SQLite rowid (for robust incremental replication).
|
|
2872
3034
|
* Rowid is monotonic for append-only tables, independent of client timestamps.
|
|
2873
3035
|
*/
|
|
2874
|
-
async getEventsSinceRowid(lastRowid, limit = 1e3) {
|
|
3036
|
+
async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
|
|
2875
3037
|
await this.initialize();
|
|
2876
3038
|
const rows = sqliteAll(
|
|
2877
3039
|
this.db,
|
|
2878
|
-
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
|
|
3040
|
+
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
|
|
2879
3041
|
[lastRowid, limit]
|
|
2880
3042
|
);
|
|
2881
3043
|
return rows.map((row) => ({
|
|
@@ -3072,7 +3234,9 @@ var SQLiteEventStore = class {
|
|
|
3072
3234
|
const placeholders = ids.map(() => "?").join(",");
|
|
3073
3235
|
sqliteRun(
|
|
3074
3236
|
this.db,
|
|
3075
|
-
`UPDATE embedding_outbox
|
|
3237
|
+
`UPDATE embedding_outbox
|
|
3238
|
+
SET status = 'processing', processed_at = datetime('now'), error_message = NULL
|
|
3239
|
+
WHERE id IN (${placeholders})`,
|
|
3076
3240
|
ids
|
|
3077
3241
|
);
|
|
3078
3242
|
return pending.map((row) => ({
|
|
@@ -3108,19 +3272,19 @@ var SQLiteEventStore = class {
|
|
|
3108
3272
|
/**
|
|
3109
3273
|
* Count total events
|
|
3110
3274
|
*/
|
|
3111
|
-
async countEvents() {
|
|
3275
|
+
async countEvents(options) {
|
|
3112
3276
|
await this.initialize();
|
|
3113
|
-
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
3277
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
|
|
3114
3278
|
return row?.count || 0;
|
|
3115
3279
|
}
|
|
3116
3280
|
/**
|
|
3117
3281
|
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
3118
3282
|
*/
|
|
3119
|
-
async getEventsPage(limit = 1e3, offset = 0) {
|
|
3283
|
+
async getEventsPage(limit = 1e3, offset = 0, options) {
|
|
3120
3284
|
await this.initialize();
|
|
3121
3285
|
const rows = sqliteAll(
|
|
3122
3286
|
this.db,
|
|
3123
|
-
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3287
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3124
3288
|
[limit, offset]
|
|
3125
3289
|
);
|
|
3126
3290
|
return rows.map(this.rowToEvent);
|
|
@@ -3142,6 +3306,197 @@ var SQLiteEventStore = class {
|
|
|
3142
3306
|
[error, ...ids]
|
|
3143
3307
|
);
|
|
3144
3308
|
}
|
|
3309
|
+
/**
|
|
3310
|
+
* Recover abandoned outbox work after a worker/process crash.
|
|
3311
|
+
*
|
|
3312
|
+
* Rows in `processing` are claimed work. If the process exits before marking
|
|
3313
|
+
* them done/failed, they otherwise remain invisible to future processing.
|
|
3314
|
+
* Recovery is deliberately age-gated so an active worker is not disturbed.
|
|
3315
|
+
*/
|
|
3316
|
+
async recoverStuckOutboxItems(options = {}) {
|
|
3317
|
+
await this.initialize();
|
|
3318
|
+
const thresholdMs = Number.isFinite(options.stuckThresholdMs) && (options.stuckThresholdMs ?? 0) >= 0 ? options.stuckThresholdMs : DEFAULT_OUTBOX_STUCK_THRESHOLD_MS;
|
|
3319
|
+
const maxRetries = Number.isFinite(options.maxRetries) && (options.maxRetries ?? 0) > 0 ? options.maxRetries : DEFAULT_OUTBOX_MAX_RETRIES;
|
|
3320
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
3321
|
+
const threshold = new Date(now.getTime() - thresholdMs).toISOString();
|
|
3322
|
+
const result = emptyOutboxRecoveryResult();
|
|
3323
|
+
const embeddingRecovered = sqliteRun(
|
|
3324
|
+
this.db,
|
|
3325
|
+
`UPDATE embedding_outbox
|
|
3326
|
+
SET status = 'pending', processed_at = NULL, error_message = NULL
|
|
3327
|
+
WHERE status = 'processing'
|
|
3328
|
+
AND datetime(COALESCE(processed_at, created_at)) < datetime(?)`,
|
|
3329
|
+
[threshold]
|
|
3330
|
+
);
|
|
3331
|
+
result.embedding.recoveredProcessing = Number(embeddingRecovered.changes ?? 0);
|
|
3332
|
+
const embeddingRetried = sqliteRun(
|
|
3333
|
+
this.db,
|
|
3334
|
+
`UPDATE embedding_outbox
|
|
3335
|
+
SET status = 'pending', error_message = NULL
|
|
3336
|
+
WHERE status = 'failed'
|
|
3337
|
+
AND retry_count < ?`,
|
|
3338
|
+
[maxRetries]
|
|
3339
|
+
);
|
|
3340
|
+
result.embedding.retriedFailed = Number(embeddingRetried.changes ?? 0);
|
|
3341
|
+
const vectorRecovered = sqliteRun(
|
|
3342
|
+
this.db,
|
|
3343
|
+
`UPDATE vector_outbox
|
|
3344
|
+
SET status = 'pending', updated_at = ?, error = NULL
|
|
3345
|
+
WHERE status = 'processing'
|
|
3346
|
+
AND datetime(updated_at) < datetime(?)`,
|
|
3347
|
+
[now.toISOString(), threshold]
|
|
3348
|
+
);
|
|
3349
|
+
result.vector.recoveredProcessing = Number(vectorRecovered.changes ?? 0);
|
|
3350
|
+
const vectorRetried = sqliteRun(
|
|
3351
|
+
this.db,
|
|
3352
|
+
`UPDATE vector_outbox
|
|
3353
|
+
SET status = 'pending', updated_at = ?, error = NULL
|
|
3354
|
+
WHERE status = 'failed'
|
|
3355
|
+
AND retry_count < ?`,
|
|
3356
|
+
[now.toISOString(), maxRetries]
|
|
3357
|
+
);
|
|
3358
|
+
result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
|
|
3359
|
+
return result;
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Repair legacy imported events that predate canonical project scope metadata.
|
|
3363
|
+
*
|
|
3364
|
+
* Same-project legacy rows are tagged with scope.project.hash. Rows that look
|
|
3365
|
+
* imported but cannot be proven to belong to this project are quarantined so
|
|
3366
|
+
* dashboard default reads/search do not surface cross-project contamination.
|
|
3367
|
+
*/
|
|
3368
|
+
async repairLegacyProjectScope(options = {}) {
|
|
3369
|
+
await this.initialize();
|
|
3370
|
+
const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
|
|
3371
|
+
if (!projectHash) {
|
|
3372
|
+
throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
|
|
3373
|
+
}
|
|
3374
|
+
if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
|
|
3375
|
+
throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
|
|
3376
|
+
}
|
|
3377
|
+
const dryRun = options.dryRun === true;
|
|
3378
|
+
const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
|
|
3379
|
+
const result = buildRepairResult(projectHash, dryRun);
|
|
3380
|
+
const rows = sqliteAll(
|
|
3381
|
+
this.db,
|
|
3382
|
+
`SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
|
|
3383
|
+
FROM events e
|
|
3384
|
+
LEFT JOIN sessions s ON s.id = e.session_id
|
|
3385
|
+
ORDER BY e.timestamp ASC`,
|
|
3386
|
+
[]
|
|
3387
|
+
);
|
|
3388
|
+
const sample = (entry) => {
|
|
3389
|
+
if (result.samples.length < 20)
|
|
3390
|
+
result.samples.push(entry);
|
|
3391
|
+
};
|
|
3392
|
+
for (const row of rows) {
|
|
3393
|
+
result.scanned++;
|
|
3394
|
+
let metadata = {};
|
|
3395
|
+
let metadataParseInvalid = false;
|
|
3396
|
+
if (row.metadata) {
|
|
3397
|
+
const parsed = safeParseMetadataValue(row.metadata);
|
|
3398
|
+
if (parsed) {
|
|
3399
|
+
metadata = parsed;
|
|
3400
|
+
} else {
|
|
3401
|
+
metadataParseInvalid = true;
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
if (isActiveQuarantinedMetadata(metadata)) {
|
|
3405
|
+
result.skipped++;
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
const currentHash = metadataProjectHash(metadata);
|
|
3409
|
+
const explicitPath = metadataProjectPath(metadata);
|
|
3410
|
+
const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
|
|
3411
|
+
const candidatePaths = metadataProjectPaths(metadata);
|
|
3412
|
+
if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
|
|
3413
|
+
candidatePaths.push(sessionProjectPath);
|
|
3414
|
+
}
|
|
3415
|
+
const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
|
|
3416
|
+
const pathHashes = candidatePaths.map((candidate) => {
|
|
3417
|
+
try {
|
|
3418
|
+
return { path: candidate, hash: hashProjectPath(candidate) };
|
|
3419
|
+
} catch {
|
|
3420
|
+
return { path: candidate, hash: void 0 };
|
|
3421
|
+
}
|
|
3422
|
+
});
|
|
3423
|
+
const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
|
|
3424
|
+
const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
|
|
3425
|
+
let action = "skipped";
|
|
3426
|
+
let reason;
|
|
3427
|
+
let observedProjectHash;
|
|
3428
|
+
if (foreignPath) {
|
|
3429
|
+
action = "quarantined";
|
|
3430
|
+
reason = "project-path-mismatch";
|
|
3431
|
+
observedProjectHash = foreignPath.hash;
|
|
3432
|
+
} else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
|
|
3433
|
+
action = "quarantined";
|
|
3434
|
+
reason = "content-project-mismatch";
|
|
3435
|
+
} else if (currentHash === projectHash) {
|
|
3436
|
+
result.alreadyScoped++;
|
|
3437
|
+
continue;
|
|
3438
|
+
} else if (currentHash && currentHash !== projectHash) {
|
|
3439
|
+
action = "quarantined";
|
|
3440
|
+
reason = "scope-hash-mismatch";
|
|
3441
|
+
observedProjectHash = currentHash;
|
|
3442
|
+
} else if (matchingPath) {
|
|
3443
|
+
action = "repaired";
|
|
3444
|
+
reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
|
|
3445
|
+
} else if (candidatePaths.length > 0) {
|
|
3446
|
+
action = "quarantined";
|
|
3447
|
+
reason = "project-path-mismatch";
|
|
3448
|
+
} else if (importedOrLegacy) {
|
|
3449
|
+
action = "quarantined";
|
|
3450
|
+
reason = "missing-project-scope";
|
|
3451
|
+
}
|
|
3452
|
+
if (action === "skipped" || !reason) {
|
|
3453
|
+
result.skipped++;
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
if (action === "repaired") {
|
|
3457
|
+
const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
|
|
3458
|
+
const project = isRecord(scope.project) ? { ...scope.project } : {};
|
|
3459
|
+
project.hash = projectHash;
|
|
3460
|
+
scope.project = project;
|
|
3461
|
+
metadata.scope = scope;
|
|
3462
|
+
metadata.repair = {
|
|
3463
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3464
|
+
legacyProjectScope: {
|
|
3465
|
+
action,
|
|
3466
|
+
reason,
|
|
3467
|
+
repairedAt: nowIso
|
|
3468
|
+
}
|
|
3469
|
+
};
|
|
3470
|
+
addMetadataTag(metadata, `proj:${projectHash}`);
|
|
3471
|
+
result.repaired++;
|
|
3472
|
+
} else {
|
|
3473
|
+
metadata.quarantine = {
|
|
3474
|
+
...isRecord(metadata.quarantine) ? metadata.quarantine : {},
|
|
3475
|
+
status: "active",
|
|
3476
|
+
category: "project-scope",
|
|
3477
|
+
reason,
|
|
3478
|
+
detectedAt: nowIso,
|
|
3479
|
+
expectedProjectHash: projectHash,
|
|
3480
|
+
...observedProjectHash ? { observedProjectHash } : {}
|
|
3481
|
+
};
|
|
3482
|
+
metadata.repair = {
|
|
3483
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3484
|
+
legacyProjectScope: {
|
|
3485
|
+
action,
|
|
3486
|
+
reason,
|
|
3487
|
+
repairedAt: nowIso
|
|
3488
|
+
}
|
|
3489
|
+
};
|
|
3490
|
+
addMetadataTag(metadata, "quarantine:project-scope");
|
|
3491
|
+
result.quarantined++;
|
|
3492
|
+
}
|
|
3493
|
+
sample({ eventId: row.id, action, reason });
|
|
3494
|
+
if (!dryRun) {
|
|
3495
|
+
sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
return result;
|
|
3499
|
+
}
|
|
3145
3500
|
/**
|
|
3146
3501
|
* Get embedding/vector outbox health statistics
|
|
3147
3502
|
*/
|
|
@@ -3189,7 +3544,11 @@ var SQLiteEventStore = class {
|
|
|
3189
3544
|
await this.initialize();
|
|
3190
3545
|
const rows = sqliteAll(
|
|
3191
3546
|
this.db,
|
|
3192
|
-
`SELECT level, COUNT(*) as count
|
|
3547
|
+
`SELECT ml.level, COUNT(*) as count
|
|
3548
|
+
FROM memory_levels ml
|
|
3549
|
+
INNER JOIN events e ON e.id = ml.event_id
|
|
3550
|
+
WHERE ${notActiveQuarantinedSql("e.metadata")}
|
|
3551
|
+
GROUP BY ml.level`
|
|
3193
3552
|
);
|
|
3194
3553
|
return rows;
|
|
3195
3554
|
}
|
|
@@ -3205,6 +3564,7 @@ var SQLiteEventStore = class {
|
|
|
3205
3564
|
`SELECT e.* FROM events e
|
|
3206
3565
|
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
3207
3566
|
WHERE ml.level = ?
|
|
3567
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3208
3568
|
ORDER BY e.timestamp DESC
|
|
3209
3569
|
LIMIT ? OFFSET ?`,
|
|
3210
3570
|
[level, limit, offset]
|
|
@@ -3297,12 +3657,13 @@ var SQLiteEventStore = class {
|
|
|
3297
3657
|
/**
|
|
3298
3658
|
* Get most accessed memories (falls back to recent events if none accessed)
|
|
3299
3659
|
*/
|
|
3300
|
-
async getMostAccessed(limit = 10) {
|
|
3660
|
+
async getMostAccessed(limit = 10, options) {
|
|
3301
3661
|
await this.initialize();
|
|
3302
3662
|
let rows = sqliteAll(
|
|
3303
3663
|
this.db,
|
|
3304
3664
|
`SELECT * FROM events
|
|
3305
3665
|
WHERE access_count > 0
|
|
3666
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3306
3667
|
ORDER BY access_count DESC, last_accessed_at DESC
|
|
3307
3668
|
LIMIT ?`,
|
|
3308
3669
|
[limit]
|
|
@@ -3311,6 +3672,7 @@ var SQLiteEventStore = class {
|
|
|
3311
3672
|
rows = sqliteAll(
|
|
3312
3673
|
this.db,
|
|
3313
3674
|
`SELECT * FROM events
|
|
3675
|
+
WHERE ${maybeQuarantinePredicate(options)}
|
|
3314
3676
|
ORDER BY timestamp DESC
|
|
3315
3677
|
LIMIT ?`,
|
|
3316
3678
|
[limit]
|
|
@@ -3441,6 +3803,7 @@ var SQLiteEventStore = class {
|
|
|
3441
3803
|
FROM memory_helpfulness mh
|
|
3442
3804
|
JOIN events e ON e.id = mh.event_id
|
|
3443
3805
|
WHERE mh.measured_at IS NOT NULL
|
|
3806
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3444
3807
|
GROUP BY mh.event_id
|
|
3445
3808
|
ORDER BY avg_score DESC
|
|
3446
3809
|
LIMIT ?`,
|
|
@@ -3505,6 +3868,7 @@ var SQLiteEventStore = class {
|
|
|
3505
3868
|
FROM events_fts fts
|
|
3506
3869
|
JOIN events e ON e.id = fts.event_id
|
|
3507
3870
|
WHERE events_fts MATCH ?
|
|
3871
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3508
3872
|
ORDER BY fts.rank
|
|
3509
3873
|
LIMIT ?`,
|
|
3510
3874
|
[searchTerms, limit]
|
|
@@ -3519,6 +3883,7 @@ var SQLiteEventStore = class {
|
|
|
3519
3883
|
this.db,
|
|
3520
3884
|
`SELECT *, 0 as rank FROM events
|
|
3521
3885
|
WHERE content LIKE ?
|
|
3886
|
+
AND ${notActiveQuarantinedSql()}
|
|
3522
3887
|
ORDER BY timestamp DESC
|
|
3523
3888
|
LIMIT ?`,
|
|
3524
3889
|
[likePattern, limit]
|
|
@@ -3725,6 +4090,7 @@ var SQLiteEventStore = class {
|
|
|
3725
4090
|
`SELECT turn_id, MIN(timestamp) as min_ts
|
|
3726
4091
|
FROM events
|
|
3727
4092
|
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4093
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3728
4094
|
GROUP BY turn_id
|
|
3729
4095
|
ORDER BY min_ts DESC
|
|
3730
4096
|
LIMIT ? OFFSET ?`,
|
|
@@ -3732,7 +4098,7 @@ var SQLiteEventStore = class {
|
|
|
3732
4098
|
);
|
|
3733
4099
|
const turns = [];
|
|
3734
4100
|
for (const turnRow of turnRows) {
|
|
3735
|
-
const events = await this.getEventsByTurn(turnRow.turn_id);
|
|
4101
|
+
const events = await this.getEventsByTurn(turnRow.turn_id, options);
|
|
3736
4102
|
const promptEvent = events.find((e) => e.eventType === "user_prompt");
|
|
3737
4103
|
const toolEvents = events.filter((e) => e.eventType === "tool_observation");
|
|
3738
4104
|
const hasResponse = events.some((e) => e.eventType === "agent_response");
|
|
@@ -3751,11 +4117,11 @@ var SQLiteEventStore = class {
|
|
|
3751
4117
|
/**
|
|
3752
4118
|
* Get all events for a specific turn_id
|
|
3753
4119
|
*/
|
|
3754
|
-
async getEventsByTurn(turnId) {
|
|
4120
|
+
async getEventsByTurn(turnId, options) {
|
|
3755
4121
|
await this.initialize();
|
|
3756
4122
|
const rows = sqliteAll(
|
|
3757
4123
|
this.db,
|
|
3758
|
-
`SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
|
|
4124
|
+
`SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
3759
4125
|
[turnId]
|
|
3760
4126
|
);
|
|
3761
4127
|
return rows.map(this.rowToEvent);
|
|
@@ -3763,13 +4129,14 @@ var SQLiteEventStore = class {
|
|
|
3763
4129
|
/**
|
|
3764
4130
|
* Count total turns for a session
|
|
3765
4131
|
*/
|
|
3766
|
-
async countSessionTurns(sessionId) {
|
|
4132
|
+
async countSessionTurns(sessionId, options) {
|
|
3767
4133
|
await this.initialize();
|
|
3768
4134
|
const row = sqliteGet(
|
|
3769
4135
|
this.db,
|
|
3770
4136
|
`SELECT COUNT(DISTINCT turn_id) as count
|
|
3771
4137
|
FROM events
|
|
3772
|
-
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4138
|
+
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4139
|
+
AND ${maybeQuarantinePredicate(options)}`,
|
|
3773
4140
|
[sessionId]
|
|
3774
4141
|
);
|
|
3775
4142
|
return row?.count || 0;
|
|
@@ -3861,7 +4228,7 @@ var SQLiteEventStore = class {
|
|
|
3861
4228
|
content: row.content,
|
|
3862
4229
|
canonicalKey: row.canonical_key,
|
|
3863
4230
|
dedupeKey: row.dedupe_key,
|
|
3864
|
-
metadata:
|
|
4231
|
+
metadata: safeParseMetadataValue(row.metadata)
|
|
3865
4232
|
};
|
|
3866
4233
|
if (row.access_count !== void 0) {
|
|
3867
4234
|
event.access_count = row.access_count;
|
|
@@ -4013,6 +4380,7 @@ var VectorStore = class {
|
|
|
4013
4380
|
* Get total count of vectors
|
|
4014
4381
|
*/
|
|
4015
4382
|
async count() {
|
|
4383
|
+
await this.initialize();
|
|
4016
4384
|
if (!this.table)
|
|
4017
4385
|
return 0;
|
|
4018
4386
|
const result = await this.table.countRows();
|
|
@@ -4451,6 +4819,14 @@ var MemoryQueryService = class {
|
|
|
4451
4819
|
await this.initialize();
|
|
4452
4820
|
return this.getMaintenanceStore("getOutboxStats").getOutboxStats();
|
|
4453
4821
|
}
|
|
4822
|
+
async recoverStuckOutboxItems(options) {
|
|
4823
|
+
await this.initialize();
|
|
4824
|
+
return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
|
|
4825
|
+
}
|
|
4826
|
+
async repairLegacyProjectScope(options) {
|
|
4827
|
+
await this.initialize();
|
|
4828
|
+
return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
|
|
4829
|
+
}
|
|
4454
4830
|
async getStats() {
|
|
4455
4831
|
await this.initialize();
|
|
4456
4832
|
const deps = this.getStatsDeps();
|
|
@@ -6073,18 +6449,18 @@ function assertDefaultRetrieverStore(eventStore) {
|
|
|
6073
6449
|
function createMemoryEngineServices(options) {
|
|
6074
6450
|
const factories = options.factories ?? {};
|
|
6075
6451
|
const storagePath = options.storagePath;
|
|
6076
|
-
if (!options.readOnly && !
|
|
6077
|
-
|
|
6452
|
+
if (!options.readOnly && !fs6.existsSync(storagePath)) {
|
|
6453
|
+
fs6.mkdirSync(storagePath, { recursive: true });
|
|
6078
6454
|
}
|
|
6079
6455
|
const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
|
|
6080
|
-
|
|
6456
|
+
path5.join(storagePath, "events.sqlite"),
|
|
6081
6457
|
{
|
|
6082
6458
|
readonly: options.readOnly,
|
|
6083
6459
|
markdownMirrorRoot: storagePath
|
|
6084
6460
|
}
|
|
6085
6461
|
);
|
|
6086
6462
|
const vectorStore = (factories.createVectorStore ?? defaultCreateVectorStore)(
|
|
6087
|
-
|
|
6463
|
+
path5.join(storagePath, "vectors")
|
|
6088
6464
|
);
|
|
6089
6465
|
const embeddingModel = options.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6090
6466
|
const embedder = embeddingModel ? (factories.createEmbedder ?? defaultCreateEmbedder)(embeddingModel) : (factories.getDefaultEmbedder ?? getDefaultEmbedder)();
|
|
@@ -6495,8 +6871,8 @@ function createMemoryRuntimeService(deps) {
|
|
|
6495
6871
|
}
|
|
6496
6872
|
|
|
6497
6873
|
// src/extensions/shared-memory/shared-memory-services.ts
|
|
6498
|
-
import * as
|
|
6499
|
-
import * as
|
|
6874
|
+
import * as fs7 from "fs";
|
|
6875
|
+
import * as path6 from "path";
|
|
6500
6876
|
|
|
6501
6877
|
// src/core/shared-event-store.ts
|
|
6502
6878
|
var SharedEventStore = class {
|
|
@@ -7184,7 +7560,7 @@ var SharedMemoryServices = class {
|
|
|
7184
7560
|
this.ensureDirectory(sharedPath, { allowCreate: true });
|
|
7185
7561
|
const store = await this.openStore(sharedPath);
|
|
7186
7562
|
this.sharedVectorStore = this.factories.createSharedVectorStore(
|
|
7187
|
-
|
|
7563
|
+
path6.join(sharedPath, "vectors")
|
|
7188
7564
|
);
|
|
7189
7565
|
await this.sharedVectorStore.initialize();
|
|
7190
7566
|
this.sharedPromoter = this.factories.createSharedPromoter(
|
|
@@ -7257,7 +7633,7 @@ var SharedMemoryServices = class {
|
|
|
7257
7633
|
async createOpenStorePromise(sharedPath) {
|
|
7258
7634
|
if (!this.sharedEventStore) {
|
|
7259
7635
|
const sharedEventStore = this.factories.createSharedEventStore(
|
|
7260
|
-
|
|
7636
|
+
path6.join(sharedPath, "shared.duckdb")
|
|
7261
7637
|
);
|
|
7262
7638
|
await sharedEventStore.initialize();
|
|
7263
7639
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -7277,9 +7653,9 @@ var SharedMemoryServices = class {
|
|
|
7277
7653
|
}
|
|
7278
7654
|
get factories() {
|
|
7279
7655
|
return {
|
|
7280
|
-
existsSync: this.options.factories?.existsSync ??
|
|
7656
|
+
existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
|
|
7281
7657
|
mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
|
|
7282
|
-
|
|
7658
|
+
fs7.mkdirSync(targetPath, { recursive: true });
|
|
7283
7659
|
}),
|
|
7284
7660
|
createSharedEventStore: this.options.factories?.createSharedEventStore ?? createSharedEventStore,
|
|
7285
7661
|
createSharedStore: this.options.factories?.createSharedStore ?? createSharedStore,
|
|
@@ -7394,33 +7770,11 @@ function createMemoryServiceComposition(options) {
|
|
|
7394
7770
|
}
|
|
7395
7771
|
function defaultExpandPath(targetPath) {
|
|
7396
7772
|
if (targetPath.startsWith("~")) {
|
|
7397
|
-
return
|
|
7773
|
+
return path7.join(os2.homedir(), targetPath.slice(1));
|
|
7398
7774
|
}
|
|
7399
7775
|
return targetPath;
|
|
7400
7776
|
}
|
|
7401
7777
|
|
|
7402
|
-
// src/core/registry/project-path.ts
|
|
7403
|
-
import * as crypto2 from "crypto";
|
|
7404
|
-
import * as fs7 from "fs";
|
|
7405
|
-
import * as os2 from "os";
|
|
7406
|
-
import * as path7 from "path";
|
|
7407
|
-
function normalizeProjectPath(projectPath) {
|
|
7408
|
-
const expanded = projectPath.startsWith("~") ? path7.join(os2.homedir(), projectPath.slice(1)) : projectPath;
|
|
7409
|
-
try {
|
|
7410
|
-
return fs7.realpathSync(expanded);
|
|
7411
|
-
} catch {
|
|
7412
|
-
return path7.resolve(expanded);
|
|
7413
|
-
}
|
|
7414
|
-
}
|
|
7415
|
-
function hashProjectPath(projectPath) {
|
|
7416
|
-
const normalizedPath = normalizeProjectPath(projectPath);
|
|
7417
|
-
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
7418
|
-
}
|
|
7419
|
-
function getProjectStoragePath(projectPath) {
|
|
7420
|
-
const hash = hashProjectPath(projectPath);
|
|
7421
|
-
return path7.join(os2.homedir(), ".claude-code", "memory", "projects", hash);
|
|
7422
|
-
}
|
|
7423
|
-
|
|
7424
7778
|
// src/core/registry/session-registry.ts
|
|
7425
7779
|
import * as fs8 from "fs";
|
|
7426
7780
|
import * as os3 from "os";
|
|
@@ -7719,6 +8073,12 @@ var MemoryService = class {
|
|
|
7719
8073
|
async getOutboxStats() {
|
|
7720
8074
|
return this.queryService.getOutboxStats();
|
|
7721
8075
|
}
|
|
8076
|
+
async recoverStuckOutboxItems(options) {
|
|
8077
|
+
return this.queryService.recoverStuckOutboxItems(options);
|
|
8078
|
+
}
|
|
8079
|
+
async repairLegacyProjectScope(options) {
|
|
8080
|
+
return this.queryService.repairLegacyProjectScope(options);
|
|
8081
|
+
}
|
|
7722
8082
|
async getRetrievalTraceStats() {
|
|
7723
8083
|
return this.retrievalAnalyticsService.getRetrievalTraceStats();
|
|
7724
8084
|
}
|
|
@@ -8104,10 +8464,10 @@ var SENSITIVE_PATTERNS = [
|
|
|
8104
8464
|
// Redact the whole URI so usernames, credentials, hosts, paths, and query
|
|
8105
8465
|
// params do not leak either.
|
|
8106
8466
|
/\b[a-z][a-z0-9+.-]*:\/\/[^\s'"`<>/@]+@[^\s'"`<>]+/gi,
|
|
8107
|
-
/password\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8108
|
-
/api[_
|
|
8109
|
-
/secret\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8110
|
-
/token\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8467
|
+
/(?:[\w.-]+[-_])?password\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8468
|
+
/(?:[\w.-]+[-_])?api[-_]?key\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8469
|
+
/(?:[\w.-]+[-_])?secret\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8470
|
+
/(?:[\w.-]+[-_])?token\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8111
8471
|
/bearer\s+[a-zA-Z0-9\-_.]+/gi,
|
|
8112
8472
|
/AWS[_-]?ACCESS[_-]?KEY[_-]?ID\s*[:=]\s*['"]?[A-Z0-9]+/gi,
|
|
8113
8473
|
/AWS[_-]?SECRET[_-]?ACCESS[_-]?KEY\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
@@ -8117,6 +8477,35 @@ var SENSITIVE_PATTERNS = [
|
|
|
8117
8477
|
/sk-[a-zA-Z0-9]{48}/g
|
|
8118
8478
|
// OpenAI API Key
|
|
8119
8479
|
];
|
|
8480
|
+
var CLI_SECRET_OPTION_PATTERNS = [
|
|
8481
|
+
/(--(?:[a-z0-9]+[-_])*(?:password|secret|api[-_]?key|token|bearer)(?:[-_][a-z0-9]+)*(?:\s+|=))(?:"[^"]*"|'[^']*'|[^\s'"`<>]+)/gi
|
|
8482
|
+
];
|
|
8483
|
+
var URL_FOLLOWING_SECRET_PATTERN = /((?:https?:\/\/[^\s'"`<>]+)\s*\r?\n\s*)([A-Za-z0-9!@#$%^&*._+\-~:=/]{6,})(?=\s*(?:\r?\n|$))/gi;
|
|
8484
|
+
function looksLikePastedSecret(value) {
|
|
8485
|
+
return value.length >= 8 && /(?:\d|[^A-Za-z0-9])/.test(value);
|
|
8486
|
+
}
|
|
8487
|
+
function maskUrlFollowingSecret(value) {
|
|
8488
|
+
let count = 0;
|
|
8489
|
+
const content = value.replace(URL_FOLLOWING_SECRET_PATTERN, (_match, prefix, secret) => {
|
|
8490
|
+
if (!looksLikePastedSecret(secret))
|
|
8491
|
+
return `${prefix}${secret}`;
|
|
8492
|
+
count++;
|
|
8493
|
+
return `${prefix}[REDACTED]`;
|
|
8494
|
+
});
|
|
8495
|
+
return { content, count };
|
|
8496
|
+
}
|
|
8497
|
+
function maskCliSecretOptions(value) {
|
|
8498
|
+
let count = 0;
|
|
8499
|
+
let filtered = value;
|
|
8500
|
+
for (const pattern of CLI_SECRET_OPTION_PATTERNS) {
|
|
8501
|
+
pattern.lastIndex = 0;
|
|
8502
|
+
filtered = filtered.replace(pattern, (_match, prefix) => {
|
|
8503
|
+
count++;
|
|
8504
|
+
return `${prefix}[REDACTED]`;
|
|
8505
|
+
});
|
|
8506
|
+
}
|
|
8507
|
+
return { content: filtered, count };
|
|
8508
|
+
}
|
|
8120
8509
|
function applyPrivacyFilter(content, config) {
|
|
8121
8510
|
let filtered = content;
|
|
8122
8511
|
let privateTagCount = 0;
|
|
@@ -8130,6 +8519,12 @@ function applyPrivacyFilter(content, config) {
|
|
|
8130
8519
|
filtered = tagResult.filtered;
|
|
8131
8520
|
privateTagCount = tagResult.stats.count;
|
|
8132
8521
|
}
|
|
8522
|
+
const cliResult = maskCliSecretOptions(filtered);
|
|
8523
|
+
filtered = cliResult.content;
|
|
8524
|
+
patternMatchCount += cliResult.count;
|
|
8525
|
+
const urlSecretResult = maskUrlFollowingSecret(filtered);
|
|
8526
|
+
filtered = urlSecretResult.content;
|
|
8527
|
+
patternMatchCount += urlSecretResult.count;
|
|
8133
8528
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
8134
8529
|
pattern.lastIndex = 0;
|
|
8135
8530
|
const matches = filtered.match(pattern);
|
|
@@ -8141,13 +8536,13 @@ function applyPrivacyFilter(content, config) {
|
|
|
8141
8536
|
for (const patternStr of config.excludePatterns || []) {
|
|
8142
8537
|
try {
|
|
8143
8538
|
const regex = new RegExp(
|
|
8144
|
-
`(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
|
|
8539
|
+
`(^|[^\\w-])(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
|
|
8145
8540
|
"gi"
|
|
8146
8541
|
);
|
|
8147
8542
|
const matches = filtered.match(regex);
|
|
8148
8543
|
if (matches) {
|
|
8149
8544
|
patternMatchCount += matches.length;
|
|
8150
|
-
filtered = filtered.replace(regex, "[REDACTED]");
|
|
8545
|
+
filtered = filtered.replace(regex, "$1[REDACTED]");
|
|
8151
8546
|
}
|
|
8152
8547
|
} catch {
|
|
8153
8548
|
}
|