claude-memory-layer 1.0.52 → 1.0.54
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/README.md +5 -5
- package/dist/cli/index.js +960 -534
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +437 -164
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +502 -159
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +432 -134
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +496 -152
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +503 -152
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +494 -154
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +503 -159
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +484 -193
- package/dist/index.js.map +4 -4
- package/dist/mcp/index.js +580 -234
- package/dist/mcp/index.js.map +4 -4
- package/dist/server/api/index.js +693 -354
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +744 -368
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +419 -133
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/assets/js/overview.js +34 -2
- package/package.json +5 -2
|
@@ -164,7 +164,13 @@ function toDate(value) {
|
|
|
164
164
|
return new Date(String(value));
|
|
165
165
|
}
|
|
166
166
|
function createDatabase(dbPath, options) {
|
|
167
|
-
|
|
167
|
+
const db = new BetterSqlite3(dbPath, { readonly: options?.readOnly });
|
|
168
|
+
db.pragma("busy_timeout = 5000");
|
|
169
|
+
if (!options?.readOnly) {
|
|
170
|
+
db.pragma("journal_mode = WAL");
|
|
171
|
+
db.pragma("synchronous = NORMAL");
|
|
172
|
+
}
|
|
173
|
+
return db;
|
|
168
174
|
}
|
|
169
175
|
function dbRun(db, sql, params = []) {
|
|
170
176
|
db.prepare(sql).run(...params);
|
|
@@ -397,19 +403,29 @@ var ConsolidatedStore = class {
|
|
|
397
403
|
*/
|
|
398
404
|
async create(input) {
|
|
399
405
|
const memoryId = randomUUID2();
|
|
400
|
-
|
|
401
|
-
|
|
406
|
+
const sourceEvents = Array.isArray(input.sourceEvents) ? input.sourceEvents : [];
|
|
407
|
+
const insertMemory = this.db.prepare(
|
|
402
408
|
`INSERT INTO consolidated_memories
|
|
403
409
|
(memory_id, summary, topics, source_events, confidence, created_at)
|
|
404
|
-
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
405
|
-
|
|
410
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`
|
|
411
|
+
);
|
|
412
|
+
const insertEvent = this.db.prepare(
|
|
413
|
+
`INSERT OR IGNORE INTO consolidated_memory_events (memory_id, event_id) VALUES (?, ?)`
|
|
414
|
+
);
|
|
415
|
+
this.db.transaction(() => {
|
|
416
|
+
insertMemory.run(
|
|
406
417
|
memoryId,
|
|
407
418
|
input.summary,
|
|
408
419
|
JSON.stringify(input.topics),
|
|
409
|
-
JSON.stringify(
|
|
420
|
+
JSON.stringify(sourceEvents),
|
|
410
421
|
input.confidence
|
|
411
|
-
|
|
412
|
-
|
|
422
|
+
);
|
|
423
|
+
for (const eventId of sourceEvents) {
|
|
424
|
+
if (typeof eventId === "string" && eventId.length > 0) {
|
|
425
|
+
insertEvent.run(memoryId, eventId);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
})();
|
|
413
429
|
return memoryId;
|
|
414
430
|
}
|
|
415
431
|
/**
|
|
@@ -515,11 +531,12 @@ var ConsolidatedStore = class {
|
|
|
515
531
|
* Delete a consolidated memory
|
|
516
532
|
*/
|
|
517
533
|
async delete(memoryId) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
534
|
+
const deleteEvents = this.db.prepare(`DELETE FROM consolidated_memory_events WHERE memory_id = ?`);
|
|
535
|
+
const deleteMemory = this.db.prepare(`DELETE FROM consolidated_memories WHERE memory_id = ?`);
|
|
536
|
+
this.db.transaction(() => {
|
|
537
|
+
deleteEvents.run(memoryId);
|
|
538
|
+
deleteMemory.run(memoryId);
|
|
539
|
+
})();
|
|
523
540
|
}
|
|
524
541
|
/**
|
|
525
542
|
* Create a long-term rule promoted from stable summaries
|
|
@@ -632,16 +649,16 @@ var ConsolidatedStore = class {
|
|
|
632
649
|
* Check if source events are already consolidated
|
|
633
650
|
*/
|
|
634
651
|
async isAlreadyConsolidated(eventIds) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
return
|
|
652
|
+
if (eventIds.length === 0) return false;
|
|
653
|
+
const placeholders = eventIds.map(() => "?").join(",");
|
|
654
|
+
const rows = await dbAll(
|
|
655
|
+
this.db,
|
|
656
|
+
`SELECT memory_id FROM consolidated_memory_events
|
|
657
|
+
WHERE event_id IN (${placeholders})
|
|
658
|
+
LIMIT 1`,
|
|
659
|
+
eventIds
|
|
660
|
+
);
|
|
661
|
+
return rows.length > 0;
|
|
645
662
|
}
|
|
646
663
|
/**
|
|
647
664
|
* Get the last consolidation time
|
|
@@ -2177,6 +2194,19 @@ function makeDedupeKey(content, sessionId) {
|
|
|
2177
2194
|
return `${sessionId}:${contentHash}`;
|
|
2178
2195
|
}
|
|
2179
2196
|
|
|
2197
|
+
// src/core/citation-generator.ts
|
|
2198
|
+
import { createHash as createHash2 } from "crypto";
|
|
2199
|
+
var ID_LENGTH = 6;
|
|
2200
|
+
var CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
2201
|
+
function generateCitationId(eventId) {
|
|
2202
|
+
const hash = createHash2("sha256").update(eventId).digest();
|
|
2203
|
+
let id = "";
|
|
2204
|
+
for (let i = 0; i < ID_LENGTH; i++) {
|
|
2205
|
+
id += CHARSET[hash[i] % CHARSET.length];
|
|
2206
|
+
}
|
|
2207
|
+
return id;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2180
2210
|
// src/core/sqlite-event-store.ts
|
|
2181
2211
|
import * as nodePath2 from "path";
|
|
2182
2212
|
|
|
@@ -2221,17 +2251,28 @@ function createSQLiteDatabase(path12, options) {
|
|
|
2221
2251
|
}
|
|
2222
2252
|
return db;
|
|
2223
2253
|
}
|
|
2254
|
+
var statementCache = /* @__PURE__ */ new WeakMap();
|
|
2255
|
+
function prepareCached(db, sql) {
|
|
2256
|
+
let cache = statementCache.get(db);
|
|
2257
|
+
if (!cache) {
|
|
2258
|
+
cache = /* @__PURE__ */ new Map();
|
|
2259
|
+
statementCache.set(db, cache);
|
|
2260
|
+
}
|
|
2261
|
+
let stmt = cache.get(sql);
|
|
2262
|
+
if (!stmt) {
|
|
2263
|
+
stmt = db.prepare(sql);
|
|
2264
|
+
cache.set(sql, stmt);
|
|
2265
|
+
}
|
|
2266
|
+
return stmt;
|
|
2267
|
+
}
|
|
2224
2268
|
function sqliteRun(db, sql, params = []) {
|
|
2225
|
-
|
|
2226
|
-
return stmt.run(...params);
|
|
2269
|
+
return prepareCached(db, sql).run(...params);
|
|
2227
2270
|
}
|
|
2228
2271
|
function sqliteAll(db, sql, params = []) {
|
|
2229
|
-
|
|
2230
|
-
return stmt.all(...params);
|
|
2272
|
+
return prepareCached(db, sql).all(...params);
|
|
2231
2273
|
}
|
|
2232
2274
|
function sqliteGet(db, sql, params = []) {
|
|
2233
|
-
|
|
2234
|
-
return stmt.get(...params);
|
|
2275
|
+
return prepareCached(db, sql).get(...params);
|
|
2235
2276
|
}
|
|
2236
2277
|
function sqliteExec(db, sql) {
|
|
2237
2278
|
db.exec(sql);
|
|
@@ -2653,6 +2694,7 @@ function normalizeQueryRewriteKind(value) {
|
|
|
2653
2694
|
var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
|
|
2654
2695
|
var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
2655
2696
|
var DEFAULT_OUTBOX_MAX_RETRIES = 3;
|
|
2697
|
+
var SQLITE_SCHEMA_VERSION = 1;
|
|
2656
2698
|
function emptyOutboxRecoveryResult() {
|
|
2657
2699
|
return {
|
|
2658
2700
|
embedding: { recoveredProcessing: 0, retriedFailed: 0 },
|
|
@@ -2997,6 +3039,15 @@ var SQLiteEventStore = class {
|
|
|
2997
3039
|
access_count INTEGER DEFAULT 0
|
|
2998
3040
|
);
|
|
2999
3041
|
|
|
3042
|
+
-- Junction: consolidated memory -> source event id (indexed lookup that
|
|
3043
|
+
-- replaces per-event source_events LIKE scans during consolidation).
|
|
3044
|
+
CREATE TABLE IF NOT EXISTS consolidated_memory_events (
|
|
3045
|
+
memory_id TEXT NOT NULL,
|
|
3046
|
+
event_id TEXT NOT NULL,
|
|
3047
|
+
PRIMARY KEY (memory_id, event_id)
|
|
3048
|
+
);
|
|
3049
|
+
CREATE INDEX IF NOT EXISTS idx_cme_event ON consolidated_memory_events(event_id);
|
|
3050
|
+
|
|
3000
3051
|
-- Continuity Log table (tracks context transitions)
|
|
3001
3052
|
CREATE TABLE IF NOT EXISTS continuity_log (
|
|
3002
3053
|
log_id TEXT PRIMARY KEY,
|
|
@@ -3356,11 +3407,17 @@ var SQLiteEventStore = class {
|
|
|
3356
3407
|
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
3357
3408
|
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
3358
3409
|
END;
|
|
3410
|
+
|
|
3411
|
+
-- Reverse index for citation ids (a non-reversible hash of event id), so
|
|
3412
|
+
-- citation lookups are O(1) instead of scanning recent events and hashing
|
|
3413
|
+
-- each one. Populated lazily/self-healing by getEventByCitationId.
|
|
3414
|
+
CREATE TABLE IF NOT EXISTS event_citations (
|
|
3415
|
+
event_id TEXT PRIMARY KEY,
|
|
3416
|
+
citation_id TEXT NOT NULL
|
|
3417
|
+
);
|
|
3418
|
+
CREATE INDEX IF NOT EXISTS idx_event_citations_citation ON event_citations(citation_id);
|
|
3359
3419
|
`);
|
|
3360
|
-
|
|
3361
|
-
sqliteExec(this.db, `ALTER TABLE memory_action_edges ADD COLUMN source TEXT NOT NULL DEFAULT 'manual';`);
|
|
3362
|
-
} catch {
|
|
3363
|
-
}
|
|
3420
|
+
this.addColumnIfMissing("memory_action_edges", "source", `TEXT NOT NULL DEFAULT 'manual'`);
|
|
3364
3421
|
try {
|
|
3365
3422
|
const edgeIndexes = sqliteAll(this.db, `PRAGMA index_list(memory_action_edges)`, []);
|
|
3366
3423
|
const hasSourceAwareUnique = edgeIndexes.some((index) => {
|
|
@@ -3397,55 +3454,17 @@ var SQLiteEventStore = class {
|
|
|
3397
3454
|
}
|
|
3398
3455
|
} catch {
|
|
3399
3456
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
try {
|
|
3405
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN candidate_details_json TEXT;`);
|
|
3406
|
-
} catch {
|
|
3407
|
-
}
|
|
3408
|
-
try {
|
|
3409
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN raw_query_text TEXT;`);
|
|
3410
|
-
} catch {
|
|
3411
|
-
}
|
|
3412
|
-
try {
|
|
3413
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN query_rewrite_kind TEXT;`);
|
|
3414
|
-
} catch {
|
|
3415
|
-
}
|
|
3457
|
+
this.addColumnIfMissing("retrieval_traces", "selected_details_json", "TEXT");
|
|
3458
|
+
this.addColumnIfMissing("retrieval_traces", "candidate_details_json", "TEXT");
|
|
3459
|
+
this.addColumnIfMissing("retrieval_traces", "raw_query_text", "TEXT");
|
|
3460
|
+
this.addColumnIfMissing("retrieval_traces", "query_rewrite_kind", "TEXT");
|
|
3416
3461
|
try {
|
|
3417
3462
|
sqliteExec(this.db, `CREATE INDEX IF NOT EXISTS idx_retrieval_traces_query_rewrite_kind ON retrieval_traces(query_rewrite_kind);`);
|
|
3418
3463
|
} catch {
|
|
3419
3464
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
try {
|
|
3424
|
-
sqliteExec(this.db, `
|
|
3425
|
-
ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
|
|
3426
|
-
`);
|
|
3427
|
-
} catch (err) {
|
|
3428
|
-
console.error("Error adding access_count column:", err);
|
|
3429
|
-
}
|
|
3430
|
-
}
|
|
3431
|
-
if (!columnNames.includes("last_accessed_at")) {
|
|
3432
|
-
try {
|
|
3433
|
-
sqliteExec(this.db, `
|
|
3434
|
-
ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
|
|
3435
|
-
`);
|
|
3436
|
-
} catch (err) {
|
|
3437
|
-
console.error("Error adding last_accessed_at column:", err);
|
|
3438
|
-
}
|
|
3439
|
-
}
|
|
3440
|
-
if (!columnNames.includes("turn_id")) {
|
|
3441
|
-
try {
|
|
3442
|
-
sqliteExec(this.db, `
|
|
3443
|
-
ALTER TABLE events ADD COLUMN turn_id TEXT;
|
|
3444
|
-
`);
|
|
3445
|
-
} catch (err) {
|
|
3446
|
-
console.error("Error adding turn_id column:", err);
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3465
|
+
this.addColumnIfMissing("events", "access_count", "INTEGER DEFAULT 0");
|
|
3466
|
+
this.addColumnIfMissing("events", "last_accessed_at", "TEXT");
|
|
3467
|
+
this.addColumnIfMissing("events", "turn_id", "TEXT");
|
|
3449
3468
|
try {
|
|
3450
3469
|
sqliteExec(this.db, `
|
|
3451
3470
|
CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
|
|
@@ -3462,8 +3481,18 @@ var SQLiteEventStore = class {
|
|
|
3462
3481
|
sqliteExec(this.db, `
|
|
3463
3482
|
CREATE INDEX IF NOT EXISTS idx_events_turn_id ON events(turn_id);
|
|
3464
3483
|
`);
|
|
3465
|
-
} catch
|
|
3484
|
+
} catch {
|
|
3466
3485
|
}
|
|
3486
|
+
const cmeCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM consolidated_memory_events`, []);
|
|
3487
|
+
if ((cmeCount?.c ?? 0) === 0) {
|
|
3488
|
+
sqliteExec(this.db, `
|
|
3489
|
+
INSERT OR IGNORE INTO consolidated_memory_events (memory_id, event_id)
|
|
3490
|
+
SELECT cm.memory_id, je.value
|
|
3491
|
+
FROM consolidated_memories cm, json_each(cm.source_events) je
|
|
3492
|
+
WHERE json_valid(cm.source_events);
|
|
3493
|
+
`);
|
|
3494
|
+
}
|
|
3495
|
+
sqliteExec(this.db, `PRAGMA user_version = ${SQLITE_SCHEMA_VERSION}`);
|
|
3467
3496
|
this.initialized = true;
|
|
3468
3497
|
}
|
|
3469
3498
|
/**
|
|
@@ -3595,6 +3624,67 @@ var SQLiteEventStore = class {
|
|
|
3595
3624
|
);
|
|
3596
3625
|
return rows.map(this.rowToEvent);
|
|
3597
3626
|
}
|
|
3627
|
+
/**
|
|
3628
|
+
* Aggregate event counts by type via SQL GROUP BY, instead of loading rows
|
|
3629
|
+
* into memory and counting in JS. Counts all events (the old recent-window
|
|
3630
|
+
* scan also under-counted stores larger than its cap).
|
|
3631
|
+
*/
|
|
3632
|
+
async getEventTypeCounts(options) {
|
|
3633
|
+
await this.initialize();
|
|
3634
|
+
const rows = sqliteAll(
|
|
3635
|
+
this.db,
|
|
3636
|
+
`SELECT event_type, COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)} GROUP BY event_type`,
|
|
3637
|
+
[]
|
|
3638
|
+
);
|
|
3639
|
+
return rows.map((row) => ({ eventType: row.event_type, count: row.count }));
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Fetch every event at/after an ISO timestamp (ascending), with no row cap.
|
|
3643
|
+
* For window-scoped analytics (KPI/usefulness) this fetches exactly the events
|
|
3644
|
+
* the window needs instead of an arbitrary "most recent N" slice that both
|
|
3645
|
+
* over-fetches sparse windows and truncates very active ones.
|
|
3646
|
+
*/
|
|
3647
|
+
async getEventsAfter(sinceIso, options) {
|
|
3648
|
+
await this.initialize();
|
|
3649
|
+
const rows = sqliteAll(
|
|
3650
|
+
this.db,
|
|
3651
|
+
`SELECT * FROM events WHERE timestamp >= ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
3652
|
+
[sinceIso]
|
|
3653
|
+
);
|
|
3654
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
3655
|
+
}
|
|
3656
|
+
/** Count distinct sessions via SQL instead of materializing a Set in JS. */
|
|
3657
|
+
async getDistinctSessionCount(options) {
|
|
3658
|
+
await this.initialize();
|
|
3659
|
+
const row = sqliteGet(
|
|
3660
|
+
this.db,
|
|
3661
|
+
`SELECT COUNT(DISTINCT session_id) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`,
|
|
3662
|
+
[]
|
|
3663
|
+
);
|
|
3664
|
+
return row?.count ?? 0;
|
|
3665
|
+
}
|
|
3666
|
+
/**
|
|
3667
|
+
* Per-day event counts (with type breakdown) since an ISO timestamp, computed
|
|
3668
|
+
* by SQL GROUP BY on the date prefix. Timestamps are stored as ISO-8601
|
|
3669
|
+
* strings, so substr(...,1,10) is the UTC day and `timestamp >= ?` compares
|
|
3670
|
+
* lexicographically.
|
|
3671
|
+
*/
|
|
3672
|
+
async getDailyEventCounts(sinceIso, options) {
|
|
3673
|
+
await this.initialize();
|
|
3674
|
+
return sqliteAll(
|
|
3675
|
+
this.db,
|
|
3676
|
+
`SELECT substr(timestamp, 1, 10) as day,
|
|
3677
|
+
COUNT(*) as total,
|
|
3678
|
+
SUM(CASE WHEN event_type = 'user_prompt' THEN 1 ELSE 0 END) as prompts,
|
|
3679
|
+
SUM(CASE WHEN event_type = 'agent_response' THEN 1 ELSE 0 END) as responses,
|
|
3680
|
+
SUM(CASE WHEN event_type = 'tool_observation' THEN 1 ELSE 0 END) as tools
|
|
3681
|
+
FROM events
|
|
3682
|
+
WHERE timestamp >= ? AND ${maybeQuarantinePredicate(options)}
|
|
3683
|
+
GROUP BY day
|
|
3684
|
+
ORDER BY day ASC`,
|
|
3685
|
+
[sinceIso]
|
|
3686
|
+
);
|
|
3687
|
+
}
|
|
3598
3688
|
/**
|
|
3599
3689
|
* Get event by ID
|
|
3600
3690
|
*/
|
|
@@ -3608,6 +3698,71 @@ var SQLiteEventStore = class {
|
|
|
3608
3698
|
if (!row) return null;
|
|
3609
3699
|
return this.rowToEvent(row);
|
|
3610
3700
|
}
|
|
3701
|
+
/**
|
|
3702
|
+
* Batch-fetch events by id in a single query, applying the same quarantine
|
|
3703
|
+
* predicate as getEvent. Lets hot paths replace N sequential round-trips with
|
|
3704
|
+
* one. Missing ids are simply absent from the result (order not guaranteed).
|
|
3705
|
+
*/
|
|
3706
|
+
async getEvents(ids, options) {
|
|
3707
|
+
await this.initialize();
|
|
3708
|
+
if (ids.length === 0) return [];
|
|
3709
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
3710
|
+
const rows = sqliteAll(
|
|
3711
|
+
this.db,
|
|
3712
|
+
`SELECT * FROM events WHERE id IN (${placeholders}) AND ${maybeQuarantinePredicate(options)}`,
|
|
3713
|
+
ids
|
|
3714
|
+
);
|
|
3715
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
3716
|
+
}
|
|
3717
|
+
/**
|
|
3718
|
+
* Resolve an event from its citation id via the reverse index, instead of
|
|
3719
|
+
* scanning recent events and hashing each one. The index is self-healing: a
|
|
3720
|
+
* miss triggers a one-time backfill of any events not yet indexed (covering
|
|
3721
|
+
* freshly appended events) before retrying.
|
|
3722
|
+
*/
|
|
3723
|
+
async getEventByCitationId(citationId, options) {
|
|
3724
|
+
await this.initialize();
|
|
3725
|
+
if (!citationId) return null;
|
|
3726
|
+
let row = sqliteGet(
|
|
3727
|
+
this.db,
|
|
3728
|
+
`SELECT event_id FROM event_citations WHERE citation_id = ? LIMIT 1`,
|
|
3729
|
+
[citationId]
|
|
3730
|
+
);
|
|
3731
|
+
if (!row) {
|
|
3732
|
+
this.indexMissingCitations();
|
|
3733
|
+
row = sqliteGet(
|
|
3734
|
+
this.db,
|
|
3735
|
+
`SELECT event_id FROM event_citations WHERE citation_id = ? LIMIT 1`,
|
|
3736
|
+
[citationId]
|
|
3737
|
+
);
|
|
3738
|
+
}
|
|
3739
|
+
if (!row) return null;
|
|
3740
|
+
return this.getEvent(row.event_id, options);
|
|
3741
|
+
}
|
|
3742
|
+
/**
|
|
3743
|
+
* Backfill citation ids for any events not yet present in event_citations.
|
|
3744
|
+
* Gated on a cheap count comparison so the common (fully-indexed) path does no
|
|
3745
|
+
* work, and the scan only runs when there are genuinely new events to index.
|
|
3746
|
+
*/
|
|
3747
|
+
indexMissingCitations() {
|
|
3748
|
+
const eventCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM events`, [])?.c ?? 0;
|
|
3749
|
+
const indexedCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM event_citations`, [])?.c ?? 0;
|
|
3750
|
+
if (indexedCount >= eventCount) return;
|
|
3751
|
+
const unindexed = sqliteAll(
|
|
3752
|
+
this.db,
|
|
3753
|
+
`SELECT e.id FROM events e
|
|
3754
|
+
LEFT JOIN event_citations ec ON ec.event_id = e.id
|
|
3755
|
+
WHERE ec.event_id IS NULL`,
|
|
3756
|
+
[]
|
|
3757
|
+
);
|
|
3758
|
+
if (unindexed.length === 0) return;
|
|
3759
|
+
const insert = this.db.prepare(`INSERT OR IGNORE INTO event_citations (event_id, citation_id) VALUES (?, ?)`);
|
|
3760
|
+
this.db.transaction(() => {
|
|
3761
|
+
for (const { id } of unindexed) {
|
|
3762
|
+
insert.run(id, generateCitationId(id));
|
|
3763
|
+
}
|
|
3764
|
+
})();
|
|
3765
|
+
}
|
|
3611
3766
|
/**
|
|
3612
3767
|
* Get events since a timestamp (for sync)
|
|
3613
3768
|
*/
|
|
@@ -3809,25 +3964,22 @@ var SQLiteEventStore = class {
|
|
|
3809
3964
|
*/
|
|
3810
3965
|
async getPendingOutboxItems(limit = 32) {
|
|
3811
3966
|
await this.initialize();
|
|
3812
|
-
const
|
|
3813
|
-
this.db,
|
|
3814
|
-
`SELECT * FROM embedding_outbox
|
|
3815
|
-
WHERE status = 'pending'
|
|
3816
|
-
ORDER BY created_at
|
|
3817
|
-
LIMIT ?`,
|
|
3818
|
-
[limit]
|
|
3819
|
-
);
|
|
3820
|
-
if (pending.length === 0) return [];
|
|
3821
|
-
const ids = pending.map((r) => r.id);
|
|
3822
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
3823
|
-
sqliteRun(
|
|
3967
|
+
const claimed = sqliteAll(
|
|
3824
3968
|
this.db,
|
|
3825
3969
|
`UPDATE embedding_outbox
|
|
3826
3970
|
SET status = 'processing', processed_at = datetime('now'), error_message = NULL
|
|
3827
|
-
WHERE id IN (
|
|
3828
|
-
|
|
3971
|
+
WHERE id IN (
|
|
3972
|
+
SELECT id FROM embedding_outbox
|
|
3973
|
+
WHERE status = 'pending'
|
|
3974
|
+
ORDER BY created_at
|
|
3975
|
+
LIMIT ?
|
|
3976
|
+
)
|
|
3977
|
+
RETURNING *`,
|
|
3978
|
+
[limit]
|
|
3829
3979
|
);
|
|
3830
|
-
|
|
3980
|
+
if (claimed.length === 0) return [];
|
|
3981
|
+
claimed.sort((a, b) => String(a.created_at).localeCompare(String(b.created_at)));
|
|
3982
|
+
return claimed.map((row) => ({
|
|
3831
3983
|
id: row.id,
|
|
3832
3984
|
eventId: row.event_id,
|
|
3833
3985
|
content: row.content,
|
|
@@ -4431,7 +4583,7 @@ var SQLiteEventStore = class {
|
|
|
4431
4583
|
const promptEvents = sessionEvents.filter((e) => e.event_type === "user_prompt");
|
|
4432
4584
|
const toolEvents = sessionEvents.filter((e) => e.event_type === "tool_observation");
|
|
4433
4585
|
let toolSuccessCount = 0;
|
|
4434
|
-
|
|
4586
|
+
const toolTotalCount = toolEvents.length;
|
|
4435
4587
|
for (const t of toolEvents) {
|
|
4436
4588
|
try {
|
|
4437
4589
|
const content = JSON.parse(t.content);
|
|
@@ -4594,10 +4746,14 @@ var SQLiteEventStore = class {
|
|
|
4594
4746
|
* Rebuild FTS index from existing events
|
|
4595
4747
|
* Call this once after upgrading to FTS5
|
|
4596
4748
|
*/
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4749
|
+
/**
|
|
4750
|
+
* Drop and recreate the events_fts virtual table + sync triggers, repopulating
|
|
4751
|
+
* from the current events table. Recreating the table (instead of issuing
|
|
4752
|
+
* DELETEs against it) sidesteps a quirk where older migrated FTS5 tables fail
|
|
4753
|
+
* with `no such column: T.event_id` on synthetic deletes. Safe to call inside
|
|
4754
|
+
* a transaction (pure DDL + INSERT...SELECT, no transaction control).
|
|
4755
|
+
*/
|
|
4756
|
+
recreateEventsFtsTable() {
|
|
4601
4757
|
sqliteExec(this.db, `
|
|
4602
4758
|
DROP TRIGGER IF EXISTS events_fts_insert;
|
|
4603
4759
|
DROP TRIGGER IF EXISTS events_fts_delete;
|
|
@@ -4626,6 +4782,12 @@ var SQLiteEventStore = class {
|
|
|
4626
4782
|
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
4627
4783
|
END;
|
|
4628
4784
|
`);
|
|
4785
|
+
}
|
|
4786
|
+
async rebuildFtsIndex() {
|
|
4787
|
+
await this.initialize();
|
|
4788
|
+
const countRow = sqliteGet(this.db, "SELECT COUNT(*) as count FROM events", []);
|
|
4789
|
+
const totalEvents = countRow?.count ?? 0;
|
|
4790
|
+
this.recreateEventsFtsTable();
|
|
4629
4791
|
return totalEvents;
|
|
4630
4792
|
}
|
|
4631
4793
|
/**
|
|
@@ -4643,6 +4805,35 @@ var SQLiteEventStore = class {
|
|
|
4643
4805
|
return false;
|
|
4644
4806
|
}
|
|
4645
4807
|
}
|
|
4808
|
+
hasTable(tableName) {
|
|
4809
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(tableName)) return false;
|
|
4810
|
+
try {
|
|
4811
|
+
const rows = sqliteAll(
|
|
4812
|
+
this.db,
|
|
4813
|
+
`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`,
|
|
4814
|
+
[tableName]
|
|
4815
|
+
);
|
|
4816
|
+
return rows.length > 0;
|
|
4817
|
+
} catch {
|
|
4818
|
+
return false;
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
/**
|
|
4822
|
+
* Add a column only when its table exists and the column is missing.
|
|
4823
|
+
*
|
|
4824
|
+
* Unlike a blind `try { ALTER } catch {}`, this distinguishes "already
|
|
4825
|
+
* migrated" / "table not present yet" (both safely skipped) from a genuine
|
|
4826
|
+
* failure (disk full, corruption), which is allowed to surface instead of
|
|
4827
|
+
* being silently swallowed as if the migration had succeeded.
|
|
4828
|
+
*/
|
|
4829
|
+
addColumnIfMissing(table, column, definition) {
|
|
4830
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(column)) {
|
|
4831
|
+
throw new Error(`Invalid identifier in migration: ${table}.${column}`);
|
|
4832
|
+
}
|
|
4833
|
+
if (!this.hasTable(table)) return;
|
|
4834
|
+
if (this.hasTableColumn(table, column)) return;
|
|
4835
|
+
sqliteExec(this.db, `ALTER TABLE ${table} ADD COLUMN ${column} ${definition};`);
|
|
4836
|
+
}
|
|
4646
4837
|
async recordRetrievalTrace(input) {
|
|
4647
4838
|
await this.initialize();
|
|
4648
4839
|
const traceId = randomUUID6();
|
|
@@ -4728,6 +4919,45 @@ var SQLiteEventStore = class {
|
|
|
4728
4919
|
FROM retrieval_traces`,
|
|
4729
4920
|
[]
|
|
4730
4921
|
);
|
|
4922
|
+
const strategyColumnSql = this.hasTableColumn("retrieval_traces", "strategy") ? "COALESCE(NULLIF(TRIM(strategy), ''), 'unknown')" : "'unknown'";
|
|
4923
|
+
const strategyRows = sqliteAll(
|
|
4924
|
+
this.db,
|
|
4925
|
+
`SELECT
|
|
4926
|
+
${strategyColumnSql} as strategy,
|
|
4927
|
+
COUNT(*) as total_queries,
|
|
4928
|
+
SUM(CASE WHEN selected_count > 0 THEN 1 ELSE 0 END) as queries_with_selection,
|
|
4929
|
+
SUM(CASE WHEN ${rewrittenQueryRewriteKindSql} THEN 1 ELSE 0 END) as rewritten_queries,
|
|
4930
|
+
SUM(candidate_count) as total_candidate_count,
|
|
4931
|
+
SUM(selected_count) as total_selected_count,
|
|
4932
|
+
AVG(candidate_count) as avg_candidate_count,
|
|
4933
|
+
AVG(selected_count) as avg_selected_count,
|
|
4934
|
+
CASE
|
|
4935
|
+
WHEN SUM(candidate_count) > 0 THEN (SUM(selected_count) * 1.0 / SUM(candidate_count))
|
|
4936
|
+
ELSE 0
|
|
4937
|
+
END as selection_rate
|
|
4938
|
+
FROM retrieval_traces
|
|
4939
|
+
GROUP BY ${strategyColumnSql}
|
|
4940
|
+
ORDER BY total_queries DESC, strategy ASC`,
|
|
4941
|
+
[]
|
|
4942
|
+
);
|
|
4943
|
+
const strategyBreakdown = strategyRows.map((strategyRow) => {
|
|
4944
|
+
const strategyTotalQueries = Number(strategyRow.total_queries || 0);
|
|
4945
|
+
const strategyQueriesWithSelection = Number(strategyRow.queries_with_selection || 0);
|
|
4946
|
+
const strategyRewrittenQueries = Number(strategyRow.rewritten_queries || 0);
|
|
4947
|
+
return {
|
|
4948
|
+
strategy: String(strategyRow.strategy || "unknown"),
|
|
4949
|
+
totalQueries: strategyTotalQueries,
|
|
4950
|
+
queriesWithSelection: strategyQueriesWithSelection,
|
|
4951
|
+
rewrittenQueries: strategyRewrittenQueries,
|
|
4952
|
+
rewriteRate: strategyTotalQueries > 0 ? strategyRewrittenQueries / strategyTotalQueries : 0,
|
|
4953
|
+
totalCandidateCount: Number(strategyRow.total_candidate_count || 0),
|
|
4954
|
+
totalSelectedCount: Number(strategyRow.total_selected_count || 0),
|
|
4955
|
+
avgCandidateCount: Number(strategyRow.avg_candidate_count || 0),
|
|
4956
|
+
avgSelectedCount: Number(strategyRow.avg_selected_count || 0),
|
|
4957
|
+
selectionRate: Number(strategyRow.selection_rate || 0),
|
|
4958
|
+
queryYieldRate: strategyTotalQueries > 0 ? strategyQueriesWithSelection / strategyTotalQueries : 0
|
|
4959
|
+
};
|
|
4960
|
+
});
|
|
4731
4961
|
const totalQueries = Number(row?.total_queries || 0);
|
|
4732
4962
|
const rewrittenQueries = Number(row?.rewritten_queries || 0);
|
|
4733
4963
|
const rawQueries = Math.max(0, totalQueries - rewrittenQueries);
|
|
@@ -4745,7 +4975,8 @@ var SQLiteEventStore = class {
|
|
|
4745
4975
|
rewrittenSelectionRate: rewrittenQueries > 0 ? rewrittenQueriesWithSelection / rewrittenQueries : 0,
|
|
4746
4976
|
rawSelectionRate: rawQueries > 0 ? rawQueriesWithSelection / rawQueries : 0,
|
|
4747
4977
|
avgSelectedCountForRewrittenQueries: Number(row?.avg_selected_count_for_rewritten_queries || 0),
|
|
4748
|
-
avgSelectedCountForRawQueries: Number(row?.avg_selected_count_for_raw_queries || 0)
|
|
4978
|
+
avgSelectedCountForRawQueries: Number(row?.avg_selected_count_for_raw_queries || 0),
|
|
4979
|
+
strategyBreakdown
|
|
4749
4980
|
};
|
|
4750
4981
|
} catch (err) {
|
|
4751
4982
|
if (err?.message?.includes("no such table")) {
|
|
@@ -4761,7 +4992,8 @@ var SQLiteEventStore = class {
|
|
|
4761
4992
|
rewrittenSelectionRate: 0,
|
|
4762
4993
|
rawSelectionRate: 0,
|
|
4763
4994
|
avgSelectedCountForRewrittenQueries: 0,
|
|
4764
|
-
avgSelectedCountForRawQueries: 0
|
|
4995
|
+
avgSelectedCountForRawQueries: 0,
|
|
4996
|
+
strategyBreakdown: []
|
|
4765
4997
|
};
|
|
4766
4998
|
}
|
|
4767
4999
|
throw err;
|
|
@@ -4878,38 +5110,19 @@ var SQLiteEventStore = class {
|
|
|
4878
5110
|
if (events.length === 0) return 0;
|
|
4879
5111
|
const eventIds = events.map((e) => e.id);
|
|
4880
5112
|
const placeholders = eventIds.map(() => "?").join(",");
|
|
4881
|
-
const
|
|
4882
|
-
|
|
4883
|
-
|
|
5113
|
+
const relatedTables = ["event_dedup", "memory_levels", "embedding_queue", "embedding_outbox", "vector_outbox", "event_citations"].filter((table) => this.hasTable(table) && this.hasTableColumn(table, "event_id"));
|
|
5114
|
+
const runDelete = this.db.transaction(() => {
|
|
5115
|
+
for (const triggerName of ["events_fts_delete", "events_fts_update", "events_fts_insert"]) {
|
|
4884
5116
|
sqliteRun(this.db, `DROP TRIGGER IF EXISTS ${triggerName}`);
|
|
4885
|
-
ftsTriggersDropped.push(triggerName);
|
|
4886
|
-
} catch {
|
|
4887
5117
|
}
|
|
4888
|
-
|
|
4889
|
-
for (const table of ["event_dedup", "memory_levels", "embedding_queue", "embedding_outbox", "vector_outbox"]) {
|
|
4890
|
-
try {
|
|
5118
|
+
for (const table of relatedTables) {
|
|
4891
5119
|
sqliteRun(this.db, `DELETE FROM ${table} WHERE event_id IN (${placeholders})`, eventIds);
|
|
4892
|
-
} catch {
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
const result = sqliteRun(this.db, `DELETE FROM events WHERE session_id = ?`, [sessionId]);
|
|
4896
|
-
if (ftsTriggersDropped.length > 0) {
|
|
4897
|
-
try {
|
|
4898
|
-
sqliteRun(this.db, `INSERT INTO events_fts(events_fts) VALUES('rebuild')`);
|
|
4899
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_insert AFTER INSERT ON events BEGIN
|
|
4900
|
-
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
4901
|
-
END`);
|
|
4902
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_delete AFTER DELETE ON events BEGIN
|
|
4903
|
-
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
4904
|
-
END`);
|
|
4905
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_update AFTER UPDATE ON events BEGIN
|
|
4906
|
-
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
4907
|
-
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
4908
|
-
END`);
|
|
4909
|
-
} catch {
|
|
4910
5120
|
}
|
|
4911
|
-
|
|
4912
|
-
|
|
5121
|
+
const result = sqliteRun(this.db, `DELETE FROM events WHERE session_id = ?`, [sessionId]);
|
|
5122
|
+
this.recreateEventsFtsTable();
|
|
5123
|
+
return result.changes || 0;
|
|
5124
|
+
});
|
|
5125
|
+
return runDelete();
|
|
4913
5126
|
}
|
|
4914
5127
|
/**
|
|
4915
5128
|
* Convert database row to MemoryEvent
|
|
@@ -7242,7 +7455,7 @@ var SessionActorRepository = class {
|
|
|
7242
7455
|
};
|
|
7243
7456
|
|
|
7244
7457
|
// src/core/operations/perspective-observation-repository.ts
|
|
7245
|
-
import { createHash as
|
|
7458
|
+
import { createHash as createHash4, randomUUID as randomUUID9 } from "crypto";
|
|
7246
7459
|
function projectHashToStorage4(projectHash) {
|
|
7247
7460
|
return projectHash ?? "";
|
|
7248
7461
|
}
|
|
@@ -7279,7 +7492,7 @@ function sanitizeStoredRecord(value) {
|
|
|
7279
7492
|
return sanitizeGovernanceAuditValue(value);
|
|
7280
7493
|
}
|
|
7281
7494
|
function stableHash(value) {
|
|
7282
|
-
return
|
|
7495
|
+
return createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
7283
7496
|
}
|
|
7284
7497
|
function evidenceHash(sourceEventIds, sourceObservationIds) {
|
|
7285
7498
|
return stableHash({
|
|
@@ -8394,6 +8607,30 @@ var MemoryQueryService = class {
|
|
|
8394
8607
|
score: 1 - (r.rank - minRank) / rankRange
|
|
8395
8608
|
})).filter((r) => !options?.minScore || r.score >= options.minScore);
|
|
8396
8609
|
}
|
|
8610
|
+
async getEvent(id) {
|
|
8611
|
+
await this.initialize();
|
|
8612
|
+
return this.queryStore.getEvent(id);
|
|
8613
|
+
}
|
|
8614
|
+
async getEventByCitationId(citationId) {
|
|
8615
|
+
await this.initialize();
|
|
8616
|
+
return this.getMaintenanceStore("getEventByCitationId").getEventByCitationId(citationId);
|
|
8617
|
+
}
|
|
8618
|
+
async getEventsAfter(sinceIso) {
|
|
8619
|
+
await this.initialize();
|
|
8620
|
+
return this.getMaintenanceStore("getEventsAfter").getEventsAfter(sinceIso);
|
|
8621
|
+
}
|
|
8622
|
+
async getEventTypeCounts() {
|
|
8623
|
+
await this.initialize();
|
|
8624
|
+
return this.getMaintenanceStore("getEventTypeCounts").getEventTypeCounts();
|
|
8625
|
+
}
|
|
8626
|
+
async getDistinctSessionCount() {
|
|
8627
|
+
await this.initialize();
|
|
8628
|
+
return this.getMaintenanceStore("getDistinctSessionCount").getDistinctSessionCount();
|
|
8629
|
+
}
|
|
8630
|
+
async getDailyEventCounts(sinceIso) {
|
|
8631
|
+
await this.initialize();
|
|
8632
|
+
return this.getMaintenanceStore("getDailyEventCounts").getDailyEventCounts(sinceIso);
|
|
8633
|
+
}
|
|
8397
8634
|
async getSessionHistory(sessionId) {
|
|
8398
8635
|
await this.initialize();
|
|
8399
8636
|
return this.queryStore.getSessionEvents(sessionId);
|
|
@@ -9362,11 +9599,12 @@ var Retriever = class {
|
|
|
9362
9599
|
if (!scope && projectScopeMode === "global" && facetFilters === null) return results;
|
|
9363
9600
|
const normalizedIncludes = (scope?.contentIncludes || []).map((s) => s.toLowerCase());
|
|
9364
9601
|
const filtered = [];
|
|
9602
|
+
const eventMap = await this.fetchEventsByIds(results.map((r) => r.eventId));
|
|
9365
9603
|
for (const result of results) {
|
|
9366
9604
|
if (scope?.sessionId && result.sessionId !== scope.sessionId) continue;
|
|
9367
9605
|
if (scope?.sessionIdPrefix && !result.sessionId.startsWith(scope.sessionIdPrefix)) continue;
|
|
9368
9606
|
if (scope?.eventTypes && scope.eventTypes.length > 0 && !scope.eventTypes.includes(result.eventType)) continue;
|
|
9369
|
-
const event =
|
|
9607
|
+
const event = eventMap.get(result.eventId);
|
|
9370
9608
|
if (!event) continue;
|
|
9371
9609
|
if (scope?.canonicalKeyPrefix && !event.canonicalKey.startsWith(scope.canonicalKeyPrefix)) continue;
|
|
9372
9610
|
if (normalizedIncludes.length > 0) {
|
|
@@ -9475,10 +9713,30 @@ var Retriever = class {
|
|
|
9475
9713
|
async retrieveRecent(limit = 100) {
|
|
9476
9714
|
return this.eventStore.getRecentEvents(limit);
|
|
9477
9715
|
}
|
|
9716
|
+
/**
|
|
9717
|
+
* Batch-load events by id into a Map in a single query, de-duplicating ids.
|
|
9718
|
+
* Replaces per-result getEvent round-trips in the scope/enrich hot paths.
|
|
9719
|
+
*/
|
|
9720
|
+
async fetchEventsByIds(ids) {
|
|
9721
|
+
const map = /* @__PURE__ */ new Map();
|
|
9722
|
+
if (ids.length === 0) return map;
|
|
9723
|
+
const unique = [...new Set(ids)];
|
|
9724
|
+
if (typeof this.eventStore.getEvents === "function") {
|
|
9725
|
+
const events = await this.eventStore.getEvents(unique);
|
|
9726
|
+
for (const event of events) map.set(event.id, event);
|
|
9727
|
+
} else {
|
|
9728
|
+
const events = await Promise.all(unique.map((id) => this.eventStore.getEvent(id)));
|
|
9729
|
+
for (const event of events) {
|
|
9730
|
+
if (event) map.set(event.id, event);
|
|
9731
|
+
}
|
|
9732
|
+
}
|
|
9733
|
+
return map;
|
|
9734
|
+
}
|
|
9478
9735
|
async enrichResults(results, options, query) {
|
|
9479
9736
|
const memories = [];
|
|
9737
|
+
const eventMap = await this.fetchEventsByIds(results.map((r) => r.eventId));
|
|
9480
9738
|
for (const result of results) {
|
|
9481
|
-
const event =
|
|
9739
|
+
const event = eventMap.get(result.eventId);
|
|
9482
9740
|
if (!event) continue;
|
|
9483
9741
|
if (this.graduation) {
|
|
9484
9742
|
this.graduation.recordAccess(event.id, options.sessionId || "unknown", result.score);
|
|
@@ -12206,6 +12464,34 @@ var MemoryService = class {
|
|
|
12206
12464
|
async rebuildFtsIndex() {
|
|
12207
12465
|
return this.queryService.rebuildFtsIndex();
|
|
12208
12466
|
}
|
|
12467
|
+
/**
|
|
12468
|
+
* Get a single event by id (indexed lookup).
|
|
12469
|
+
*/
|
|
12470
|
+
async getEvent(id) {
|
|
12471
|
+
return this.queryService.getEvent(id);
|
|
12472
|
+
}
|
|
12473
|
+
/**
|
|
12474
|
+
* Resolve an event from its citation id via the reverse index.
|
|
12475
|
+
*/
|
|
12476
|
+
async getEventByCitationId(citationId) {
|
|
12477
|
+
return this.queryService.getEventByCitationId(citationId);
|
|
12478
|
+
}
|
|
12479
|
+
/** All events at/after an ISO timestamp (ascending, uncapped). */
|
|
12480
|
+
async getEventsAfter(sinceIso) {
|
|
12481
|
+
return this.queryService.getEventsAfter(sinceIso);
|
|
12482
|
+
}
|
|
12483
|
+
/** Aggregate event counts by type (SQL GROUP BY). */
|
|
12484
|
+
async getEventTypeCounts() {
|
|
12485
|
+
return this.queryService.getEventTypeCounts();
|
|
12486
|
+
}
|
|
12487
|
+
/** Count distinct sessions (SQL). */
|
|
12488
|
+
async getDistinctSessionCount() {
|
|
12489
|
+
return this.queryService.getDistinctSessionCount();
|
|
12490
|
+
}
|
|
12491
|
+
/** Per-day event counts with type breakdown since an ISO timestamp (SQL). */
|
|
12492
|
+
async getDailyEventCounts(sinceIso) {
|
|
12493
|
+
return this.queryService.getDailyEventCounts(sinceIso);
|
|
12494
|
+
}
|
|
12209
12495
|
/**
|
|
12210
12496
|
* Get session history
|
|
12211
12497
|
*/
|
|
@@ -12529,6 +12815,18 @@ var getLightweightMemoryService = defaultRegistry.getLightweightMemoryService;
|
|
|
12529
12815
|
var getLightweightMemoryServiceForProject = defaultRegistry.getLightweightMemoryServiceForProject;
|
|
12530
12816
|
var createMemoryService = defaultRegistry.createMemoryService;
|
|
12531
12817
|
|
|
12818
|
+
// src/adapters/claude/hooks/hook-runtime.ts
|
|
12819
|
+
function readNumberEnv(name, fallback, options = {}) {
|
|
12820
|
+
const raw = process.env[name];
|
|
12821
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
12822
|
+
const parsed = options.integer ? Number.parseInt(raw, 10) : Number.parseFloat(raw);
|
|
12823
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
12824
|
+
let value = parsed;
|
|
12825
|
+
if (options.min !== void 0) value = Math.max(options.min, value);
|
|
12826
|
+
if (options.max !== void 0) value = Math.min(options.max, value);
|
|
12827
|
+
return value;
|
|
12828
|
+
}
|
|
12829
|
+
|
|
12532
12830
|
// src/adapters/claude/hooks/semantic-daemon.ts
|
|
12533
12831
|
var SOCKET_PATH = process.env.CLAUDE_MEMORY_SEMANTIC_SOCKET || path11.join(
|
|
12534
12832
|
os6.homedir(),
|
|
@@ -12536,7 +12834,7 @@ var SOCKET_PATH = process.env.CLAUDE_MEMORY_SEMANTIC_SOCKET || path11.join(
|
|
|
12536
12834
|
"memory",
|
|
12537
12835
|
"semantic-daemon.sock"
|
|
12538
12836
|
);
|
|
12539
|
-
var IDLE_TIMEOUT_MS =
|
|
12837
|
+
var IDLE_TIMEOUT_MS = readNumberEnv("CLAUDE_MEMORY_SEMANTIC_DAEMON_IDLE_MS", 6e5, { integer: true, min: 1e3 });
|
|
12540
12838
|
var serviceCache = /* @__PURE__ */ new Map();
|
|
12541
12839
|
var server = null;
|
|
12542
12840
|
var idleTimer = null;
|