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
|
@@ -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 getFileType(filePath) {
|
|
@@ -1641,8 +1641,8 @@ function createEndlessMemoryServices(options) {
|
|
|
1641
1641
|
}
|
|
1642
1642
|
|
|
1643
1643
|
// src/core/engine/memory-engine-services.ts
|
|
1644
|
-
import * as
|
|
1645
|
-
import * as
|
|
1644
|
+
import * as fs6 from "fs";
|
|
1645
|
+
import * as path5 from "path";
|
|
1646
1646
|
|
|
1647
1647
|
// src/extensions/vector/embedder.ts
|
|
1648
1648
|
var DEFAULT_EMBEDDING_MODEL = "Xenova/multilingual-e5-small";
|
|
@@ -2320,14 +2320,39 @@ function makeDedupeKey(content, sessionId) {
|
|
|
2320
2320
|
return `${sessionId}:${contentHash}`;
|
|
2321
2321
|
}
|
|
2322
2322
|
|
|
2323
|
+
// src/core/sqlite-event-store.ts
|
|
2324
|
+
import * as nodePath2 from "path";
|
|
2325
|
+
|
|
2326
|
+
// src/core/registry/project-path.ts
|
|
2327
|
+
import * as crypto2 from "crypto";
|
|
2328
|
+
import * as fs3 from "fs";
|
|
2329
|
+
import * as os from "os";
|
|
2330
|
+
import * as path3 from "path";
|
|
2331
|
+
function normalizeProjectPath(projectPath) {
|
|
2332
|
+
const expanded = projectPath.startsWith("~") ? path3.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
2333
|
+
try {
|
|
2334
|
+
return fs3.realpathSync(expanded);
|
|
2335
|
+
} catch {
|
|
2336
|
+
return path3.resolve(expanded);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
function hashProjectPath(projectPath) {
|
|
2340
|
+
const normalizedPath = normalizeProjectPath(projectPath);
|
|
2341
|
+
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
2342
|
+
}
|
|
2343
|
+
function getProjectStoragePath(projectPath) {
|
|
2344
|
+
const hash = hashProjectPath(projectPath);
|
|
2345
|
+
return path3.join(os.homedir(), ".claude-code", "memory", "projects", hash);
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2323
2348
|
// src/core/sqlite-wrapper.ts
|
|
2324
2349
|
import Database from "better-sqlite3";
|
|
2325
|
-
import * as
|
|
2350
|
+
import * as fs4 from "fs";
|
|
2326
2351
|
import * as nodePath from "path";
|
|
2327
2352
|
function createSQLiteDatabase(path12, options) {
|
|
2328
2353
|
const dir = nodePath.dirname(path12);
|
|
2329
|
-
if (!
|
|
2330
|
-
|
|
2354
|
+
if (!fs4.existsSync(dir)) {
|
|
2355
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
2331
2356
|
}
|
|
2332
2357
|
const db = new Database(path12, {
|
|
2333
2358
|
readonly: options?.readonly ?? false
|
|
@@ -2376,8 +2401,8 @@ function toSQLiteTimestamp(date) {
|
|
|
2376
2401
|
}
|
|
2377
2402
|
|
|
2378
2403
|
// src/core/markdown-mirror.ts
|
|
2379
|
-
import * as
|
|
2380
|
-
import * as
|
|
2404
|
+
import * as fs5 from "fs/promises";
|
|
2405
|
+
import * as path4 from "path";
|
|
2381
2406
|
var DEFAULT_NAMESPACE = "default";
|
|
2382
2407
|
var DEFAULT_CATEGORY = "uncategorized";
|
|
2383
2408
|
function sanitizeSegment2(input, fallback) {
|
|
@@ -2406,7 +2431,7 @@ function buildMirrorPath2(rootDir, event) {
|
|
|
2406
2431
|
const yyyy = d.getFullYear();
|
|
2407
2432
|
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
2408
2433
|
const dd = String(d.getDate()).padStart(2, "0");
|
|
2409
|
-
return
|
|
2434
|
+
return path4.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
|
|
2410
2435
|
}
|
|
2411
2436
|
function formatMirrorEntry(event) {
|
|
2412
2437
|
const category = Array.isArray(event.metadata?.categoryPath) ? event.metadata.categoryPath.join("/") : String(event.metadata?.category ?? event.eventType);
|
|
@@ -2427,8 +2452,8 @@ var MarkdownMirror2 = class {
|
|
|
2427
2452
|
}
|
|
2428
2453
|
async append(event) {
|
|
2429
2454
|
const outPath = buildMirrorPath2(this.rootDir, event);
|
|
2430
|
-
await
|
|
2431
|
-
await
|
|
2455
|
+
await fs5.mkdir(path4.dirname(outPath), { recursive: true });
|
|
2456
|
+
await fs5.appendFile(outPath, formatMirrorEntry(event), "utf8");
|
|
2432
2457
|
return outPath;
|
|
2433
2458
|
}
|
|
2434
2459
|
};
|
|
@@ -2441,6 +2466,143 @@ function normalizeQueryRewriteKind(value) {
|
|
|
2441
2466
|
return "none";
|
|
2442
2467
|
}
|
|
2443
2468
|
var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
|
|
2469
|
+
var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
2470
|
+
var DEFAULT_OUTBOX_MAX_RETRIES = 3;
|
|
2471
|
+
function emptyOutboxRecoveryResult() {
|
|
2472
|
+
return {
|
|
2473
|
+
embedding: { recoveredProcessing: 0, retriedFailed: 0 },
|
|
2474
|
+
vector: { recoveredProcessing: 0, retriedFailed: 0 }
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
function isRecord(value) {
|
|
2478
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2479
|
+
}
|
|
2480
|
+
function getNestedRecord(root, path12) {
|
|
2481
|
+
let cursor = root;
|
|
2482
|
+
for (const key of path12) {
|
|
2483
|
+
if (!isRecord(cursor))
|
|
2484
|
+
return void 0;
|
|
2485
|
+
cursor = cursor[key];
|
|
2486
|
+
}
|
|
2487
|
+
return isRecord(cursor) ? cursor : void 0;
|
|
2488
|
+
}
|
|
2489
|
+
function getNestedString(root, path12) {
|
|
2490
|
+
let cursor = root;
|
|
2491
|
+
for (const key of path12) {
|
|
2492
|
+
if (!isRecord(cursor))
|
|
2493
|
+
return void 0;
|
|
2494
|
+
cursor = cursor[key];
|
|
2495
|
+
}
|
|
2496
|
+
return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
|
|
2497
|
+
}
|
|
2498
|
+
function metadataProjectHash(metadata) {
|
|
2499
|
+
return getNestedString(metadata, ["scope", "project", "hash"]);
|
|
2500
|
+
}
|
|
2501
|
+
function metadataProjectPaths(metadata) {
|
|
2502
|
+
const candidates = [
|
|
2503
|
+
getNestedString(metadata, ["projectPath"]),
|
|
2504
|
+
getNestedString(metadata, ["sourceProjectPath"]),
|
|
2505
|
+
getNestedString(metadata, ["scope", "project", "path"])
|
|
2506
|
+
];
|
|
2507
|
+
const paths = [];
|
|
2508
|
+
for (const value of candidates) {
|
|
2509
|
+
if (value && !paths.includes(value))
|
|
2510
|
+
paths.push(value);
|
|
2511
|
+
}
|
|
2512
|
+
return paths;
|
|
2513
|
+
}
|
|
2514
|
+
function metadataProjectPath(metadata) {
|
|
2515
|
+
return metadataProjectPaths(metadata)[0];
|
|
2516
|
+
}
|
|
2517
|
+
function isActiveQuarantinedMetadata(metadata) {
|
|
2518
|
+
const quarantine = getNestedRecord(metadata, ["quarantine"]);
|
|
2519
|
+
return quarantine?.status === "active";
|
|
2520
|
+
}
|
|
2521
|
+
function activeQuarantineStatusExpression(column = "metadata") {
|
|
2522
|
+
return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
|
|
2523
|
+
}
|
|
2524
|
+
function notActiveQuarantinedSql(column = "metadata") {
|
|
2525
|
+
return `${activeQuarantineStatusExpression(column)} != 'active'`;
|
|
2526
|
+
}
|
|
2527
|
+
function maybeQuarantinePredicate(options, column = "metadata") {
|
|
2528
|
+
return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
|
|
2529
|
+
}
|
|
2530
|
+
function safeParseMetadataValue(value) {
|
|
2531
|
+
if (!value)
|
|
2532
|
+
return void 0;
|
|
2533
|
+
if (typeof value === "object")
|
|
2534
|
+
return isRecord(value) ? value : void 0;
|
|
2535
|
+
if (typeof value !== "string")
|
|
2536
|
+
return void 0;
|
|
2537
|
+
try {
|
|
2538
|
+
const parsed = JSON.parse(value);
|
|
2539
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
2540
|
+
} catch {
|
|
2541
|
+
return void 0;
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
function isImportedOrLegacyScopedMetadata(metadata) {
|
|
2545
|
+
if (!metadata)
|
|
2546
|
+
return false;
|
|
2547
|
+
return Boolean(
|
|
2548
|
+
metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
function addMetadataTag(metadata, tag) {
|
|
2552
|
+
const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
|
|
2553
|
+
if (!current.includes(tag))
|
|
2554
|
+
metadata.tags = [...current, tag];
|
|
2555
|
+
}
|
|
2556
|
+
function buildRepairResult(projectHash, dryRun) {
|
|
2557
|
+
return {
|
|
2558
|
+
dryRun,
|
|
2559
|
+
projectHash,
|
|
2560
|
+
scanned: 0,
|
|
2561
|
+
repaired: 0,
|
|
2562
|
+
quarantined: 0,
|
|
2563
|
+
alreadyScoped: 0,
|
|
2564
|
+
skipped: 0,
|
|
2565
|
+
samples: []
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
function normalizeRepoName(value) {
|
|
2569
|
+
return value.replace(/\.git$/i, "").trim().toLowerCase();
|
|
2570
|
+
}
|
|
2571
|
+
function projectBasename(projectPath) {
|
|
2572
|
+
if (!projectPath)
|
|
2573
|
+
return void 0;
|
|
2574
|
+
const trimmed = projectPath.replace(/[\\/]+$/, "");
|
|
2575
|
+
const basename2 = nodePath2.basename(trimmed);
|
|
2576
|
+
return basename2 ? normalizeRepoName(basename2) : void 0;
|
|
2577
|
+
}
|
|
2578
|
+
function isProjectScopeRepairExplanation(content) {
|
|
2579
|
+
const normalized = content.toLowerCase();
|
|
2580
|
+
const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
|
|
2581
|
+
const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
|
|
2582
|
+
return hasRepairContext && hasExplanationContext;
|
|
2583
|
+
}
|
|
2584
|
+
function hasConflictingContentProjectHint(content, projectPath) {
|
|
2585
|
+
const currentName = projectBasename(projectPath);
|
|
2586
|
+
if (!currentName)
|
|
2587
|
+
return false;
|
|
2588
|
+
if (isProjectScopeRepairExplanation(content))
|
|
2589
|
+
return false;
|
|
2590
|
+
const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
|
|
2591
|
+
let githubMatch;
|
|
2592
|
+
while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
|
|
2593
|
+
const repo = normalizeRepoName(githubMatch[2] || "");
|
|
2594
|
+
if (repo && repo !== currentName)
|
|
2595
|
+
return true;
|
|
2596
|
+
}
|
|
2597
|
+
const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
|
|
2598
|
+
let workspaceMatch;
|
|
2599
|
+
while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
|
|
2600
|
+
const repo = normalizeRepoName(workspaceMatch[1] || "");
|
|
2601
|
+
if (repo && repo !== currentName)
|
|
2602
|
+
return true;
|
|
2603
|
+
}
|
|
2604
|
+
return false;
|
|
2605
|
+
}
|
|
2444
2606
|
var SQLiteEventStore = class {
|
|
2445
2607
|
db;
|
|
2446
2608
|
initialized = false;
|
|
@@ -2939,11 +3101,11 @@ var SQLiteEventStore = class {
|
|
|
2939
3101
|
/**
|
|
2940
3102
|
* Get events by session ID
|
|
2941
3103
|
*/
|
|
2942
|
-
async getSessionEvents(sessionId) {
|
|
3104
|
+
async getSessionEvents(sessionId, options) {
|
|
2943
3105
|
await this.initialize();
|
|
2944
3106
|
const rows = sqliteAll(
|
|
2945
3107
|
this.db,
|
|
2946
|
-
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
3108
|
+
`SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
2947
3109
|
[sessionId]
|
|
2948
3110
|
);
|
|
2949
3111
|
return rows.map(this.rowToEvent);
|
|
@@ -2951,11 +3113,11 @@ var SQLiteEventStore = class {
|
|
|
2951
3113
|
/**
|
|
2952
3114
|
* Get recent events
|
|
2953
3115
|
*/
|
|
2954
|
-
async getRecentEvents(limit = 100) {
|
|
3116
|
+
async getRecentEvents(limit = 100, options) {
|
|
2955
3117
|
await this.initialize();
|
|
2956
3118
|
const rows = sqliteAll(
|
|
2957
3119
|
this.db,
|
|
2958
|
-
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
3120
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
|
|
2959
3121
|
[limit]
|
|
2960
3122
|
);
|
|
2961
3123
|
return rows.map(this.rowToEvent);
|
|
@@ -2963,11 +3125,11 @@ var SQLiteEventStore = class {
|
|
|
2963
3125
|
/**
|
|
2964
3126
|
* Get event by ID
|
|
2965
3127
|
*/
|
|
2966
|
-
async getEvent(id) {
|
|
3128
|
+
async getEvent(id, options) {
|
|
2967
3129
|
await this.initialize();
|
|
2968
3130
|
const row = sqliteGet(
|
|
2969
3131
|
this.db,
|
|
2970
|
-
`SELECT * FROM events WHERE id =
|
|
3132
|
+
`SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
|
|
2971
3133
|
[id]
|
|
2972
3134
|
);
|
|
2973
3135
|
if (!row)
|
|
@@ -2977,11 +3139,11 @@ var SQLiteEventStore = class {
|
|
|
2977
3139
|
/**
|
|
2978
3140
|
* Get events since a timestamp (for sync)
|
|
2979
3141
|
*/
|
|
2980
|
-
async getEventsSince(timestamp, limit = 1e3) {
|
|
3142
|
+
async getEventsSince(timestamp, limit = 1e3, options) {
|
|
2981
3143
|
await this.initialize();
|
|
2982
3144
|
const rows = sqliteAll(
|
|
2983
3145
|
this.db,
|
|
2984
|
-
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
3146
|
+
`SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
|
|
2985
3147
|
[timestamp, limit]
|
|
2986
3148
|
);
|
|
2987
3149
|
return rows.map(this.rowToEvent);
|
|
@@ -2990,11 +3152,11 @@ var SQLiteEventStore = class {
|
|
|
2990
3152
|
* Get events since a SQLite rowid (for robust incremental replication).
|
|
2991
3153
|
* Rowid is monotonic for append-only tables, independent of client timestamps.
|
|
2992
3154
|
*/
|
|
2993
|
-
async getEventsSinceRowid(lastRowid, limit = 1e3) {
|
|
3155
|
+
async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
|
|
2994
3156
|
await this.initialize();
|
|
2995
3157
|
const rows = sqliteAll(
|
|
2996
3158
|
this.db,
|
|
2997
|
-
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
|
|
3159
|
+
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
|
|
2998
3160
|
[lastRowid, limit]
|
|
2999
3161
|
);
|
|
3000
3162
|
return rows.map((row) => ({
|
|
@@ -3191,7 +3353,9 @@ var SQLiteEventStore = class {
|
|
|
3191
3353
|
const placeholders = ids.map(() => "?").join(",");
|
|
3192
3354
|
sqliteRun(
|
|
3193
3355
|
this.db,
|
|
3194
|
-
`UPDATE embedding_outbox
|
|
3356
|
+
`UPDATE embedding_outbox
|
|
3357
|
+
SET status = 'processing', processed_at = datetime('now'), error_message = NULL
|
|
3358
|
+
WHERE id IN (${placeholders})`,
|
|
3195
3359
|
ids
|
|
3196
3360
|
);
|
|
3197
3361
|
return pending.map((row) => ({
|
|
@@ -3227,19 +3391,19 @@ var SQLiteEventStore = class {
|
|
|
3227
3391
|
/**
|
|
3228
3392
|
* Count total events
|
|
3229
3393
|
*/
|
|
3230
|
-
async countEvents() {
|
|
3394
|
+
async countEvents(options) {
|
|
3231
3395
|
await this.initialize();
|
|
3232
|
-
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
3396
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
|
|
3233
3397
|
return row?.count || 0;
|
|
3234
3398
|
}
|
|
3235
3399
|
/**
|
|
3236
3400
|
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
3237
3401
|
*/
|
|
3238
|
-
async getEventsPage(limit = 1e3, offset = 0) {
|
|
3402
|
+
async getEventsPage(limit = 1e3, offset = 0, options) {
|
|
3239
3403
|
await this.initialize();
|
|
3240
3404
|
const rows = sqliteAll(
|
|
3241
3405
|
this.db,
|
|
3242
|
-
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3406
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3243
3407
|
[limit, offset]
|
|
3244
3408
|
);
|
|
3245
3409
|
return rows.map(this.rowToEvent);
|
|
@@ -3261,6 +3425,197 @@ var SQLiteEventStore = class {
|
|
|
3261
3425
|
[error, ...ids]
|
|
3262
3426
|
);
|
|
3263
3427
|
}
|
|
3428
|
+
/**
|
|
3429
|
+
* Recover abandoned outbox work after a worker/process crash.
|
|
3430
|
+
*
|
|
3431
|
+
* Rows in `processing` are claimed work. If the process exits before marking
|
|
3432
|
+
* them done/failed, they otherwise remain invisible to future processing.
|
|
3433
|
+
* Recovery is deliberately age-gated so an active worker is not disturbed.
|
|
3434
|
+
*/
|
|
3435
|
+
async recoverStuckOutboxItems(options = {}) {
|
|
3436
|
+
await this.initialize();
|
|
3437
|
+
const thresholdMs = Number.isFinite(options.stuckThresholdMs) && (options.stuckThresholdMs ?? 0) >= 0 ? options.stuckThresholdMs : DEFAULT_OUTBOX_STUCK_THRESHOLD_MS;
|
|
3438
|
+
const maxRetries = Number.isFinite(options.maxRetries) && (options.maxRetries ?? 0) > 0 ? options.maxRetries : DEFAULT_OUTBOX_MAX_RETRIES;
|
|
3439
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
3440
|
+
const threshold = new Date(now.getTime() - thresholdMs).toISOString();
|
|
3441
|
+
const result = emptyOutboxRecoveryResult();
|
|
3442
|
+
const embeddingRecovered = sqliteRun(
|
|
3443
|
+
this.db,
|
|
3444
|
+
`UPDATE embedding_outbox
|
|
3445
|
+
SET status = 'pending', processed_at = NULL, error_message = NULL
|
|
3446
|
+
WHERE status = 'processing'
|
|
3447
|
+
AND datetime(COALESCE(processed_at, created_at)) < datetime(?)`,
|
|
3448
|
+
[threshold]
|
|
3449
|
+
);
|
|
3450
|
+
result.embedding.recoveredProcessing = Number(embeddingRecovered.changes ?? 0);
|
|
3451
|
+
const embeddingRetried = sqliteRun(
|
|
3452
|
+
this.db,
|
|
3453
|
+
`UPDATE embedding_outbox
|
|
3454
|
+
SET status = 'pending', error_message = NULL
|
|
3455
|
+
WHERE status = 'failed'
|
|
3456
|
+
AND retry_count < ?`,
|
|
3457
|
+
[maxRetries]
|
|
3458
|
+
);
|
|
3459
|
+
result.embedding.retriedFailed = Number(embeddingRetried.changes ?? 0);
|
|
3460
|
+
const vectorRecovered = sqliteRun(
|
|
3461
|
+
this.db,
|
|
3462
|
+
`UPDATE vector_outbox
|
|
3463
|
+
SET status = 'pending', updated_at = ?, error = NULL
|
|
3464
|
+
WHERE status = 'processing'
|
|
3465
|
+
AND datetime(updated_at) < datetime(?)`,
|
|
3466
|
+
[now.toISOString(), threshold]
|
|
3467
|
+
);
|
|
3468
|
+
result.vector.recoveredProcessing = Number(vectorRecovered.changes ?? 0);
|
|
3469
|
+
const vectorRetried = sqliteRun(
|
|
3470
|
+
this.db,
|
|
3471
|
+
`UPDATE vector_outbox
|
|
3472
|
+
SET status = 'pending', updated_at = ?, error = NULL
|
|
3473
|
+
WHERE status = 'failed'
|
|
3474
|
+
AND retry_count < ?`,
|
|
3475
|
+
[now.toISOString(), maxRetries]
|
|
3476
|
+
);
|
|
3477
|
+
result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
|
|
3478
|
+
return result;
|
|
3479
|
+
}
|
|
3480
|
+
/**
|
|
3481
|
+
* Repair legacy imported events that predate canonical project scope metadata.
|
|
3482
|
+
*
|
|
3483
|
+
* Same-project legacy rows are tagged with scope.project.hash. Rows that look
|
|
3484
|
+
* imported but cannot be proven to belong to this project are quarantined so
|
|
3485
|
+
* dashboard default reads/search do not surface cross-project contamination.
|
|
3486
|
+
*/
|
|
3487
|
+
async repairLegacyProjectScope(options = {}) {
|
|
3488
|
+
await this.initialize();
|
|
3489
|
+
const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
|
|
3490
|
+
if (!projectHash) {
|
|
3491
|
+
throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
|
|
3492
|
+
}
|
|
3493
|
+
if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
|
|
3494
|
+
throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
|
|
3495
|
+
}
|
|
3496
|
+
const dryRun = options.dryRun === true;
|
|
3497
|
+
const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
|
|
3498
|
+
const result = buildRepairResult(projectHash, dryRun);
|
|
3499
|
+
const rows = sqliteAll(
|
|
3500
|
+
this.db,
|
|
3501
|
+
`SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
|
|
3502
|
+
FROM events e
|
|
3503
|
+
LEFT JOIN sessions s ON s.id = e.session_id
|
|
3504
|
+
ORDER BY e.timestamp ASC`,
|
|
3505
|
+
[]
|
|
3506
|
+
);
|
|
3507
|
+
const sample = (entry) => {
|
|
3508
|
+
if (result.samples.length < 20)
|
|
3509
|
+
result.samples.push(entry);
|
|
3510
|
+
};
|
|
3511
|
+
for (const row of rows) {
|
|
3512
|
+
result.scanned++;
|
|
3513
|
+
let metadata = {};
|
|
3514
|
+
let metadataParseInvalid = false;
|
|
3515
|
+
if (row.metadata) {
|
|
3516
|
+
const parsed = safeParseMetadataValue(row.metadata);
|
|
3517
|
+
if (parsed) {
|
|
3518
|
+
metadata = parsed;
|
|
3519
|
+
} else {
|
|
3520
|
+
metadataParseInvalid = true;
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
if (isActiveQuarantinedMetadata(metadata)) {
|
|
3524
|
+
result.skipped++;
|
|
3525
|
+
continue;
|
|
3526
|
+
}
|
|
3527
|
+
const currentHash = metadataProjectHash(metadata);
|
|
3528
|
+
const explicitPath = metadataProjectPath(metadata);
|
|
3529
|
+
const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
|
|
3530
|
+
const candidatePaths = metadataProjectPaths(metadata);
|
|
3531
|
+
if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
|
|
3532
|
+
candidatePaths.push(sessionProjectPath);
|
|
3533
|
+
}
|
|
3534
|
+
const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
|
|
3535
|
+
const pathHashes = candidatePaths.map((candidate) => {
|
|
3536
|
+
try {
|
|
3537
|
+
return { path: candidate, hash: hashProjectPath(candidate) };
|
|
3538
|
+
} catch {
|
|
3539
|
+
return { path: candidate, hash: void 0 };
|
|
3540
|
+
}
|
|
3541
|
+
});
|
|
3542
|
+
const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
|
|
3543
|
+
const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
|
|
3544
|
+
let action = "skipped";
|
|
3545
|
+
let reason;
|
|
3546
|
+
let observedProjectHash;
|
|
3547
|
+
if (foreignPath) {
|
|
3548
|
+
action = "quarantined";
|
|
3549
|
+
reason = "project-path-mismatch";
|
|
3550
|
+
observedProjectHash = foreignPath.hash;
|
|
3551
|
+
} else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
|
|
3552
|
+
action = "quarantined";
|
|
3553
|
+
reason = "content-project-mismatch";
|
|
3554
|
+
} else if (currentHash === projectHash) {
|
|
3555
|
+
result.alreadyScoped++;
|
|
3556
|
+
continue;
|
|
3557
|
+
} else if (currentHash && currentHash !== projectHash) {
|
|
3558
|
+
action = "quarantined";
|
|
3559
|
+
reason = "scope-hash-mismatch";
|
|
3560
|
+
observedProjectHash = currentHash;
|
|
3561
|
+
} else if (matchingPath) {
|
|
3562
|
+
action = "repaired";
|
|
3563
|
+
reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
|
|
3564
|
+
} else if (candidatePaths.length > 0) {
|
|
3565
|
+
action = "quarantined";
|
|
3566
|
+
reason = "project-path-mismatch";
|
|
3567
|
+
} else if (importedOrLegacy) {
|
|
3568
|
+
action = "quarantined";
|
|
3569
|
+
reason = "missing-project-scope";
|
|
3570
|
+
}
|
|
3571
|
+
if (action === "skipped" || !reason) {
|
|
3572
|
+
result.skipped++;
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
if (action === "repaired") {
|
|
3576
|
+
const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
|
|
3577
|
+
const project = isRecord(scope.project) ? { ...scope.project } : {};
|
|
3578
|
+
project.hash = projectHash;
|
|
3579
|
+
scope.project = project;
|
|
3580
|
+
metadata.scope = scope;
|
|
3581
|
+
metadata.repair = {
|
|
3582
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3583
|
+
legacyProjectScope: {
|
|
3584
|
+
action,
|
|
3585
|
+
reason,
|
|
3586
|
+
repairedAt: nowIso
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
addMetadataTag(metadata, `proj:${projectHash}`);
|
|
3590
|
+
result.repaired++;
|
|
3591
|
+
} else {
|
|
3592
|
+
metadata.quarantine = {
|
|
3593
|
+
...isRecord(metadata.quarantine) ? metadata.quarantine : {},
|
|
3594
|
+
status: "active",
|
|
3595
|
+
category: "project-scope",
|
|
3596
|
+
reason,
|
|
3597
|
+
detectedAt: nowIso,
|
|
3598
|
+
expectedProjectHash: projectHash,
|
|
3599
|
+
...observedProjectHash ? { observedProjectHash } : {}
|
|
3600
|
+
};
|
|
3601
|
+
metadata.repair = {
|
|
3602
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3603
|
+
legacyProjectScope: {
|
|
3604
|
+
action,
|
|
3605
|
+
reason,
|
|
3606
|
+
repairedAt: nowIso
|
|
3607
|
+
}
|
|
3608
|
+
};
|
|
3609
|
+
addMetadataTag(metadata, "quarantine:project-scope");
|
|
3610
|
+
result.quarantined++;
|
|
3611
|
+
}
|
|
3612
|
+
sample({ eventId: row.id, action, reason });
|
|
3613
|
+
if (!dryRun) {
|
|
3614
|
+
sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
return result;
|
|
3618
|
+
}
|
|
3264
3619
|
/**
|
|
3265
3620
|
* Get embedding/vector outbox health statistics
|
|
3266
3621
|
*/
|
|
@@ -3308,7 +3663,11 @@ var SQLiteEventStore = class {
|
|
|
3308
3663
|
await this.initialize();
|
|
3309
3664
|
const rows = sqliteAll(
|
|
3310
3665
|
this.db,
|
|
3311
|
-
`SELECT level, COUNT(*) as count
|
|
3666
|
+
`SELECT ml.level, COUNT(*) as count
|
|
3667
|
+
FROM memory_levels ml
|
|
3668
|
+
INNER JOIN events e ON e.id = ml.event_id
|
|
3669
|
+
WHERE ${notActiveQuarantinedSql("e.metadata")}
|
|
3670
|
+
GROUP BY ml.level`
|
|
3312
3671
|
);
|
|
3313
3672
|
return rows;
|
|
3314
3673
|
}
|
|
@@ -3324,6 +3683,7 @@ var SQLiteEventStore = class {
|
|
|
3324
3683
|
`SELECT e.* FROM events e
|
|
3325
3684
|
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
3326
3685
|
WHERE ml.level = ?
|
|
3686
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3327
3687
|
ORDER BY e.timestamp DESC
|
|
3328
3688
|
LIMIT ? OFFSET ?`,
|
|
3329
3689
|
[level, limit, offset]
|
|
@@ -3416,12 +3776,13 @@ var SQLiteEventStore = class {
|
|
|
3416
3776
|
/**
|
|
3417
3777
|
* Get most accessed memories (falls back to recent events if none accessed)
|
|
3418
3778
|
*/
|
|
3419
|
-
async getMostAccessed(limit = 10) {
|
|
3779
|
+
async getMostAccessed(limit = 10, options) {
|
|
3420
3780
|
await this.initialize();
|
|
3421
3781
|
let rows = sqliteAll(
|
|
3422
3782
|
this.db,
|
|
3423
3783
|
`SELECT * FROM events
|
|
3424
3784
|
WHERE access_count > 0
|
|
3785
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3425
3786
|
ORDER BY access_count DESC, last_accessed_at DESC
|
|
3426
3787
|
LIMIT ?`,
|
|
3427
3788
|
[limit]
|
|
@@ -3430,6 +3791,7 @@ var SQLiteEventStore = class {
|
|
|
3430
3791
|
rows = sqliteAll(
|
|
3431
3792
|
this.db,
|
|
3432
3793
|
`SELECT * FROM events
|
|
3794
|
+
WHERE ${maybeQuarantinePredicate(options)}
|
|
3433
3795
|
ORDER BY timestamp DESC
|
|
3434
3796
|
LIMIT ?`,
|
|
3435
3797
|
[limit]
|
|
@@ -3560,6 +3922,7 @@ var SQLiteEventStore = class {
|
|
|
3560
3922
|
FROM memory_helpfulness mh
|
|
3561
3923
|
JOIN events e ON e.id = mh.event_id
|
|
3562
3924
|
WHERE mh.measured_at IS NOT NULL
|
|
3925
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3563
3926
|
GROUP BY mh.event_id
|
|
3564
3927
|
ORDER BY avg_score DESC
|
|
3565
3928
|
LIMIT ?`,
|
|
@@ -3624,6 +3987,7 @@ var SQLiteEventStore = class {
|
|
|
3624
3987
|
FROM events_fts fts
|
|
3625
3988
|
JOIN events e ON e.id = fts.event_id
|
|
3626
3989
|
WHERE events_fts MATCH ?
|
|
3990
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3627
3991
|
ORDER BY fts.rank
|
|
3628
3992
|
LIMIT ?`,
|
|
3629
3993
|
[searchTerms, limit]
|
|
@@ -3638,6 +4002,7 @@ var SQLiteEventStore = class {
|
|
|
3638
4002
|
this.db,
|
|
3639
4003
|
`SELECT *, 0 as rank FROM events
|
|
3640
4004
|
WHERE content LIKE ?
|
|
4005
|
+
AND ${notActiveQuarantinedSql()}
|
|
3641
4006
|
ORDER BY timestamp DESC
|
|
3642
4007
|
LIMIT ?`,
|
|
3643
4008
|
[likePattern, limit]
|
|
@@ -3844,6 +4209,7 @@ var SQLiteEventStore = class {
|
|
|
3844
4209
|
`SELECT turn_id, MIN(timestamp) as min_ts
|
|
3845
4210
|
FROM events
|
|
3846
4211
|
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4212
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3847
4213
|
GROUP BY turn_id
|
|
3848
4214
|
ORDER BY min_ts DESC
|
|
3849
4215
|
LIMIT ? OFFSET ?`,
|
|
@@ -3851,7 +4217,7 @@ var SQLiteEventStore = class {
|
|
|
3851
4217
|
);
|
|
3852
4218
|
const turns = [];
|
|
3853
4219
|
for (const turnRow of turnRows) {
|
|
3854
|
-
const events = await this.getEventsByTurn(turnRow.turn_id);
|
|
4220
|
+
const events = await this.getEventsByTurn(turnRow.turn_id, options);
|
|
3855
4221
|
const promptEvent = events.find((e) => e.eventType === "user_prompt");
|
|
3856
4222
|
const toolEvents = events.filter((e) => e.eventType === "tool_observation");
|
|
3857
4223
|
const hasResponse = events.some((e) => e.eventType === "agent_response");
|
|
@@ -3870,11 +4236,11 @@ var SQLiteEventStore = class {
|
|
|
3870
4236
|
/**
|
|
3871
4237
|
* Get all events for a specific turn_id
|
|
3872
4238
|
*/
|
|
3873
|
-
async getEventsByTurn(turnId) {
|
|
4239
|
+
async getEventsByTurn(turnId, options) {
|
|
3874
4240
|
await this.initialize();
|
|
3875
4241
|
const rows = sqliteAll(
|
|
3876
4242
|
this.db,
|
|
3877
|
-
`SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
|
|
4243
|
+
`SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
3878
4244
|
[turnId]
|
|
3879
4245
|
);
|
|
3880
4246
|
return rows.map(this.rowToEvent);
|
|
@@ -3882,13 +4248,14 @@ var SQLiteEventStore = class {
|
|
|
3882
4248
|
/**
|
|
3883
4249
|
* Count total turns for a session
|
|
3884
4250
|
*/
|
|
3885
|
-
async countSessionTurns(sessionId) {
|
|
4251
|
+
async countSessionTurns(sessionId, options) {
|
|
3886
4252
|
await this.initialize();
|
|
3887
4253
|
const row = sqliteGet(
|
|
3888
4254
|
this.db,
|
|
3889
4255
|
`SELECT COUNT(DISTINCT turn_id) as count
|
|
3890
4256
|
FROM events
|
|
3891
|
-
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4257
|
+
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4258
|
+
AND ${maybeQuarantinePredicate(options)}`,
|
|
3892
4259
|
[sessionId]
|
|
3893
4260
|
);
|
|
3894
4261
|
return row?.count || 0;
|
|
@@ -3980,7 +4347,7 @@ var SQLiteEventStore = class {
|
|
|
3980
4347
|
content: row.content,
|
|
3981
4348
|
canonicalKey: row.canonical_key,
|
|
3982
4349
|
dedupeKey: row.dedupe_key,
|
|
3983
|
-
metadata:
|
|
4350
|
+
metadata: safeParseMetadataValue(row.metadata)
|
|
3984
4351
|
};
|
|
3985
4352
|
if (row.access_count !== void 0) {
|
|
3986
4353
|
event.access_count = row.access_count;
|
|
@@ -4132,6 +4499,7 @@ var VectorStore = class {
|
|
|
4132
4499
|
* Get total count of vectors
|
|
4133
4500
|
*/
|
|
4134
4501
|
async count() {
|
|
4502
|
+
await this.initialize();
|
|
4135
4503
|
if (!this.table)
|
|
4136
4504
|
return 0;
|
|
4137
4505
|
const result = await this.table.countRows();
|
|
@@ -4570,6 +4938,14 @@ var MemoryQueryService = class {
|
|
|
4570
4938
|
await this.initialize();
|
|
4571
4939
|
return this.getMaintenanceStore("getOutboxStats").getOutboxStats();
|
|
4572
4940
|
}
|
|
4941
|
+
async recoverStuckOutboxItems(options) {
|
|
4942
|
+
await this.initialize();
|
|
4943
|
+
return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
|
|
4944
|
+
}
|
|
4945
|
+
async repairLegacyProjectScope(options) {
|
|
4946
|
+
await this.initialize();
|
|
4947
|
+
return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
|
|
4948
|
+
}
|
|
4573
4949
|
async getStats() {
|
|
4574
4950
|
await this.initialize();
|
|
4575
4951
|
const deps = this.getStatsDeps();
|
|
@@ -6192,18 +6568,18 @@ function assertDefaultRetrieverStore(eventStore) {
|
|
|
6192
6568
|
function createMemoryEngineServices(options) {
|
|
6193
6569
|
const factories = options.factories ?? {};
|
|
6194
6570
|
const storagePath = options.storagePath;
|
|
6195
|
-
if (!options.readOnly && !
|
|
6196
|
-
|
|
6571
|
+
if (!options.readOnly && !fs6.existsSync(storagePath)) {
|
|
6572
|
+
fs6.mkdirSync(storagePath, { recursive: true });
|
|
6197
6573
|
}
|
|
6198
6574
|
const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
|
|
6199
|
-
|
|
6575
|
+
path5.join(storagePath, "events.sqlite"),
|
|
6200
6576
|
{
|
|
6201
6577
|
readonly: options.readOnly,
|
|
6202
6578
|
markdownMirrorRoot: storagePath
|
|
6203
6579
|
}
|
|
6204
6580
|
);
|
|
6205
6581
|
const vectorStore = (factories.createVectorStore ?? defaultCreateVectorStore)(
|
|
6206
|
-
|
|
6582
|
+
path5.join(storagePath, "vectors")
|
|
6207
6583
|
);
|
|
6208
6584
|
const embeddingModel = options.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6209
6585
|
const embedder = embeddingModel ? (factories.createEmbedder ?? defaultCreateEmbedder)(embeddingModel) : (factories.getDefaultEmbedder ?? getDefaultEmbedder)();
|
|
@@ -6614,8 +6990,8 @@ function createMemoryRuntimeService(deps) {
|
|
|
6614
6990
|
}
|
|
6615
6991
|
|
|
6616
6992
|
// src/extensions/shared-memory/shared-memory-services.ts
|
|
6617
|
-
import * as
|
|
6618
|
-
import * as
|
|
6993
|
+
import * as fs7 from "fs";
|
|
6994
|
+
import * as path6 from "path";
|
|
6619
6995
|
|
|
6620
6996
|
// src/core/shared-event-store.ts
|
|
6621
6997
|
var SharedEventStore = class {
|
|
@@ -7303,7 +7679,7 @@ var SharedMemoryServices = class {
|
|
|
7303
7679
|
this.ensureDirectory(sharedPath, { allowCreate: true });
|
|
7304
7680
|
const store = await this.openStore(sharedPath);
|
|
7305
7681
|
this.sharedVectorStore = this.factories.createSharedVectorStore(
|
|
7306
|
-
|
|
7682
|
+
path6.join(sharedPath, "vectors")
|
|
7307
7683
|
);
|
|
7308
7684
|
await this.sharedVectorStore.initialize();
|
|
7309
7685
|
this.sharedPromoter = this.factories.createSharedPromoter(
|
|
@@ -7376,7 +7752,7 @@ var SharedMemoryServices = class {
|
|
|
7376
7752
|
async createOpenStorePromise(sharedPath) {
|
|
7377
7753
|
if (!this.sharedEventStore) {
|
|
7378
7754
|
const sharedEventStore = this.factories.createSharedEventStore(
|
|
7379
|
-
|
|
7755
|
+
path6.join(sharedPath, "shared.duckdb")
|
|
7380
7756
|
);
|
|
7381
7757
|
await sharedEventStore.initialize();
|
|
7382
7758
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -7396,9 +7772,9 @@ var SharedMemoryServices = class {
|
|
|
7396
7772
|
}
|
|
7397
7773
|
get factories() {
|
|
7398
7774
|
return {
|
|
7399
|
-
existsSync: this.options.factories?.existsSync ??
|
|
7775
|
+
existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
|
|
7400
7776
|
mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
|
|
7401
|
-
|
|
7777
|
+
fs7.mkdirSync(targetPath, { recursive: true });
|
|
7402
7778
|
}),
|
|
7403
7779
|
createSharedEventStore: this.options.factories?.createSharedEventStore ?? createSharedEventStore,
|
|
7404
7780
|
createSharedStore: this.options.factories?.createSharedStore ?? createSharedStore,
|
|
@@ -7513,33 +7889,11 @@ function createMemoryServiceComposition(options) {
|
|
|
7513
7889
|
}
|
|
7514
7890
|
function defaultExpandPath(targetPath) {
|
|
7515
7891
|
if (targetPath.startsWith("~")) {
|
|
7516
|
-
return
|
|
7892
|
+
return path7.join(os2.homedir(), targetPath.slice(1));
|
|
7517
7893
|
}
|
|
7518
7894
|
return targetPath;
|
|
7519
7895
|
}
|
|
7520
7896
|
|
|
7521
|
-
// src/core/registry/project-path.ts
|
|
7522
|
-
import * as crypto2 from "crypto";
|
|
7523
|
-
import * as fs7 from "fs";
|
|
7524
|
-
import * as os2 from "os";
|
|
7525
|
-
import * as path7 from "path";
|
|
7526
|
-
function normalizeProjectPath(projectPath) {
|
|
7527
|
-
const expanded = projectPath.startsWith("~") ? path7.join(os2.homedir(), projectPath.slice(1)) : projectPath;
|
|
7528
|
-
try {
|
|
7529
|
-
return fs7.realpathSync(expanded);
|
|
7530
|
-
} catch {
|
|
7531
|
-
return path7.resolve(expanded);
|
|
7532
|
-
}
|
|
7533
|
-
}
|
|
7534
|
-
function hashProjectPath(projectPath) {
|
|
7535
|
-
const normalizedPath = normalizeProjectPath(projectPath);
|
|
7536
|
-
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
7537
|
-
}
|
|
7538
|
-
function getProjectStoragePath(projectPath) {
|
|
7539
|
-
const hash = hashProjectPath(projectPath);
|
|
7540
|
-
return path7.join(os2.homedir(), ".claude-code", "memory", "projects", hash);
|
|
7541
|
-
}
|
|
7542
|
-
|
|
7543
7897
|
// src/core/registry/session-registry.ts
|
|
7544
7898
|
import * as fs8 from "fs";
|
|
7545
7899
|
import * as os3 from "os";
|
|
@@ -7838,6 +8192,12 @@ var MemoryService = class {
|
|
|
7838
8192
|
async getOutboxStats() {
|
|
7839
8193
|
return this.queryService.getOutboxStats();
|
|
7840
8194
|
}
|
|
8195
|
+
async recoverStuckOutboxItems(options) {
|
|
8196
|
+
return this.queryService.recoverStuckOutboxItems(options);
|
|
8197
|
+
}
|
|
8198
|
+
async repairLegacyProjectScope(options) {
|
|
8199
|
+
return this.queryService.repairLegacyProjectScope(options);
|
|
8200
|
+
}
|
|
7841
8201
|
async getRetrievalTraceStats() {
|
|
7842
8202
|
return this.retrievalAnalyticsService.getRetrievalTraceStats();
|
|
7843
8203
|
}
|
|
@@ -8223,10 +8583,10 @@ var SENSITIVE_PATTERNS = [
|
|
|
8223
8583
|
// Redact the whole URI so usernames, credentials, hosts, paths, and query
|
|
8224
8584
|
// params do not leak either.
|
|
8225
8585
|
/\b[a-z][a-z0-9+.-]*:\/\/[^\s'"`<>/@]+@[^\s'"`<>]+/gi,
|
|
8226
|
-
/password\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8227
|
-
/api[_
|
|
8228
|
-
/secret\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8229
|
-
/token\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8586
|
+
/(?:[\w.-]+[-_])?password\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8587
|
+
/(?:[\w.-]+[-_])?api[-_]?key\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8588
|
+
/(?:[\w.-]+[-_])?secret\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8589
|
+
/(?:[\w.-]+[-_])?token\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8230
8590
|
/bearer\s+[a-zA-Z0-9\-_.]+/gi,
|
|
8231
8591
|
/AWS[_-]?ACCESS[_-]?KEY[_-]?ID\s*[:=]\s*['"]?[A-Z0-9]+/gi,
|
|
8232
8592
|
/AWS[_-]?SECRET[_-]?ACCESS[_-]?KEY\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
@@ -8236,8 +8596,37 @@ var SENSITIVE_PATTERNS = [
|
|
|
8236
8596
|
/sk-[a-zA-Z0-9]{48}/g
|
|
8237
8597
|
// OpenAI API Key
|
|
8238
8598
|
];
|
|
8239
|
-
|
|
8599
|
+
var CLI_SECRET_OPTION_PATTERNS = [
|
|
8600
|
+
/(--(?:[a-z0-9]+[-_])*(?:password|secret|api[-_]?key|token|bearer)(?:[-_][a-z0-9]+)*(?:\s+|=))(?:"[^"]*"|'[^']*'|[^\s'"`<>]+)/gi
|
|
8601
|
+
];
|
|
8602
|
+
var URL_FOLLOWING_SECRET_PATTERN = /((?:https?:\/\/[^\s'"`<>]+)\s*\r?\n\s*)([A-Za-z0-9!@#$%^&*._+\-~:=/]{6,})(?=\s*(?:\r?\n|$))/gi;
|
|
8603
|
+
function looksLikePastedSecret(value) {
|
|
8604
|
+
return value.length >= 8 && /(?:\d|[^A-Za-z0-9])/.test(value);
|
|
8605
|
+
}
|
|
8606
|
+
function maskUrlFollowingSecret(value) {
|
|
8607
|
+
let count = 0;
|
|
8608
|
+
const content = value.replace(URL_FOLLOWING_SECRET_PATTERN, (_match, prefix, secret) => {
|
|
8609
|
+
if (!looksLikePastedSecret(secret))
|
|
8610
|
+
return `${prefix}${secret}`;
|
|
8611
|
+
count++;
|
|
8612
|
+
return `${prefix}[REDACTED]`;
|
|
8613
|
+
});
|
|
8614
|
+
return { content, count };
|
|
8615
|
+
}
|
|
8616
|
+
function maskCliSecretOptions(value) {
|
|
8617
|
+
let count = 0;
|
|
8240
8618
|
let filtered = value;
|
|
8619
|
+
for (const pattern of CLI_SECRET_OPTION_PATTERNS) {
|
|
8620
|
+
pattern.lastIndex = 0;
|
|
8621
|
+
filtered = filtered.replace(pattern, (_match, prefix) => {
|
|
8622
|
+
count++;
|
|
8623
|
+
return `${prefix}[REDACTED]`;
|
|
8624
|
+
});
|
|
8625
|
+
}
|
|
8626
|
+
return { content: filtered, count };
|
|
8627
|
+
}
|
|
8628
|
+
function maskSensitiveString(value) {
|
|
8629
|
+
let filtered = maskCliSecretOptions(value).content;
|
|
8241
8630
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
8242
8631
|
pattern.lastIndex = 0;
|
|
8243
8632
|
filtered = filtered.replace(pattern, "[REDACTED]");
|
|
@@ -8257,6 +8646,12 @@ function applyPrivacyFilter(content, config) {
|
|
|
8257
8646
|
filtered = tagResult.filtered;
|
|
8258
8647
|
privateTagCount = tagResult.stats.count;
|
|
8259
8648
|
}
|
|
8649
|
+
const cliResult = maskCliSecretOptions(filtered);
|
|
8650
|
+
filtered = cliResult.content;
|
|
8651
|
+
patternMatchCount += cliResult.count;
|
|
8652
|
+
const urlSecretResult = maskUrlFollowingSecret(filtered);
|
|
8653
|
+
filtered = urlSecretResult.content;
|
|
8654
|
+
patternMatchCount += urlSecretResult.count;
|
|
8260
8655
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
8261
8656
|
pattern.lastIndex = 0;
|
|
8262
8657
|
const matches = filtered.match(pattern);
|
|
@@ -8268,13 +8663,13 @@ function applyPrivacyFilter(content, config) {
|
|
|
8268
8663
|
for (const patternStr of config.excludePatterns || []) {
|
|
8269
8664
|
try {
|
|
8270
8665
|
const regex = new RegExp(
|
|
8271
|
-
`(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
|
|
8666
|
+
`(^|[^\\w-])(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
|
|
8272
8667
|
"gi"
|
|
8273
8668
|
);
|
|
8274
8669
|
const matches = filtered.match(regex);
|
|
8275
8670
|
if (matches) {
|
|
8276
8671
|
patternMatchCount += matches.length;
|
|
8277
|
-
filtered = filtered.replace(regex, "[REDACTED]");
|
|
8672
|
+
filtered = filtered.replace(regex, "$1[REDACTED]");
|
|
8278
8673
|
}
|
|
8279
8674
|
} catch {
|
|
8280
8675
|
}
|