claude-memory-layer 1.0.53 → 1.0.55
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 +1224 -548
- 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 +577 -231
- package/dist/mcp/index.js.map +4 -4
- package/dist/server/api/index.js +896 -354
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +954 -375
- 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 +8 -3
package/dist/mcp/index.js
CHANGED
|
@@ -11261,7 +11261,13 @@ function toDate(value) {
|
|
|
11261
11261
|
return new Date(String(value));
|
|
11262
11262
|
}
|
|
11263
11263
|
function createDatabase(dbPath, options) {
|
|
11264
|
-
|
|
11264
|
+
const db = new BetterSqlite3(dbPath, { readonly: options?.readOnly });
|
|
11265
|
+
db.pragma("busy_timeout = 5000");
|
|
11266
|
+
if (!options?.readOnly) {
|
|
11267
|
+
db.pragma("journal_mode = WAL");
|
|
11268
|
+
db.pragma("synchronous = NORMAL");
|
|
11269
|
+
}
|
|
11270
|
+
return db;
|
|
11265
11271
|
}
|
|
11266
11272
|
function dbRun(db, sql, params = []) {
|
|
11267
11273
|
db.prepare(sql).run(...params);
|
|
@@ -11494,19 +11500,29 @@ var ConsolidatedStore = class {
|
|
|
11494
11500
|
*/
|
|
11495
11501
|
async create(input) {
|
|
11496
11502
|
const memoryId = randomUUID2();
|
|
11497
|
-
|
|
11498
|
-
|
|
11503
|
+
const sourceEvents = Array.isArray(input.sourceEvents) ? input.sourceEvents : [];
|
|
11504
|
+
const insertMemory = this.db.prepare(
|
|
11499
11505
|
`INSERT INTO consolidated_memories
|
|
11500
11506
|
(memory_id, summary, topics, source_events, confidence, created_at)
|
|
11501
|
-
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
11502
|
-
|
|
11507
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`
|
|
11508
|
+
);
|
|
11509
|
+
const insertEvent = this.db.prepare(
|
|
11510
|
+
`INSERT OR IGNORE INTO consolidated_memory_events (memory_id, event_id) VALUES (?, ?)`
|
|
11511
|
+
);
|
|
11512
|
+
this.db.transaction(() => {
|
|
11513
|
+
insertMemory.run(
|
|
11503
11514
|
memoryId,
|
|
11504
11515
|
input.summary,
|
|
11505
11516
|
JSON.stringify(input.topics),
|
|
11506
|
-
JSON.stringify(
|
|
11517
|
+
JSON.stringify(sourceEvents),
|
|
11507
11518
|
input.confidence
|
|
11508
|
-
|
|
11509
|
-
|
|
11519
|
+
);
|
|
11520
|
+
for (const eventId of sourceEvents) {
|
|
11521
|
+
if (typeof eventId === "string" && eventId.length > 0) {
|
|
11522
|
+
insertEvent.run(memoryId, eventId);
|
|
11523
|
+
}
|
|
11524
|
+
}
|
|
11525
|
+
})();
|
|
11510
11526
|
return memoryId;
|
|
11511
11527
|
}
|
|
11512
11528
|
/**
|
|
@@ -11612,11 +11628,12 @@ var ConsolidatedStore = class {
|
|
|
11612
11628
|
* Delete a consolidated memory
|
|
11613
11629
|
*/
|
|
11614
11630
|
async delete(memoryId) {
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11631
|
+
const deleteEvents = this.db.prepare(`DELETE FROM consolidated_memory_events WHERE memory_id = ?`);
|
|
11632
|
+
const deleteMemory = this.db.prepare(`DELETE FROM consolidated_memories WHERE memory_id = ?`);
|
|
11633
|
+
this.db.transaction(() => {
|
|
11634
|
+
deleteEvents.run(memoryId);
|
|
11635
|
+
deleteMemory.run(memoryId);
|
|
11636
|
+
})();
|
|
11620
11637
|
}
|
|
11621
11638
|
/**
|
|
11622
11639
|
* Create a long-term rule promoted from stable summaries
|
|
@@ -11729,16 +11746,16 @@ var ConsolidatedStore = class {
|
|
|
11729
11746
|
* Check if source events are already consolidated
|
|
11730
11747
|
*/
|
|
11731
11748
|
async isAlreadyConsolidated(eventIds) {
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
return
|
|
11749
|
+
if (eventIds.length === 0) return false;
|
|
11750
|
+
const placeholders2 = eventIds.map(() => "?").join(",");
|
|
11751
|
+
const rows = await dbAll(
|
|
11752
|
+
this.db,
|
|
11753
|
+
`SELECT memory_id FROM consolidated_memory_events
|
|
11754
|
+
WHERE event_id IN (${placeholders2})
|
|
11755
|
+
LIMIT 1`,
|
|
11756
|
+
eventIds
|
|
11757
|
+
);
|
|
11758
|
+
return rows.length > 0;
|
|
11742
11759
|
}
|
|
11743
11760
|
/**
|
|
11744
11761
|
* Get the last consolidation time
|
|
@@ -13274,6 +13291,19 @@ function makeDedupeKey(content, sessionId) {
|
|
|
13274
13291
|
return `${sessionId}:${contentHash}`;
|
|
13275
13292
|
}
|
|
13276
13293
|
|
|
13294
|
+
// src/core/citation-generator.ts
|
|
13295
|
+
import { createHash as createHash2 } from "crypto";
|
|
13296
|
+
var ID_LENGTH = 6;
|
|
13297
|
+
var CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
13298
|
+
function generateCitationId(eventId) {
|
|
13299
|
+
const hash = createHash2("sha256").update(eventId).digest();
|
|
13300
|
+
let id = "";
|
|
13301
|
+
for (let i = 0; i < ID_LENGTH; i++) {
|
|
13302
|
+
id += CHARSET[hash[i] % CHARSET.length];
|
|
13303
|
+
}
|
|
13304
|
+
return id;
|
|
13305
|
+
}
|
|
13306
|
+
|
|
13277
13307
|
// src/core/sqlite-event-store.ts
|
|
13278
13308
|
import * as nodePath2 from "path";
|
|
13279
13309
|
|
|
@@ -13318,17 +13348,28 @@ function createSQLiteDatabase(path15, options) {
|
|
|
13318
13348
|
}
|
|
13319
13349
|
return db;
|
|
13320
13350
|
}
|
|
13351
|
+
var statementCache = /* @__PURE__ */ new WeakMap();
|
|
13352
|
+
function prepareCached(db, sql) {
|
|
13353
|
+
let cache = statementCache.get(db);
|
|
13354
|
+
if (!cache) {
|
|
13355
|
+
cache = /* @__PURE__ */ new Map();
|
|
13356
|
+
statementCache.set(db, cache);
|
|
13357
|
+
}
|
|
13358
|
+
let stmt = cache.get(sql);
|
|
13359
|
+
if (!stmt) {
|
|
13360
|
+
stmt = db.prepare(sql);
|
|
13361
|
+
cache.set(sql, stmt);
|
|
13362
|
+
}
|
|
13363
|
+
return stmt;
|
|
13364
|
+
}
|
|
13321
13365
|
function sqliteRun(db, sql, params = []) {
|
|
13322
|
-
|
|
13323
|
-
return stmt.run(...params);
|
|
13366
|
+
return prepareCached(db, sql).run(...params);
|
|
13324
13367
|
}
|
|
13325
13368
|
function sqliteAll(db, sql, params = []) {
|
|
13326
|
-
|
|
13327
|
-
return stmt.all(...params);
|
|
13369
|
+
return prepareCached(db, sql).all(...params);
|
|
13328
13370
|
}
|
|
13329
13371
|
function sqliteGet(db, sql, params = []) {
|
|
13330
|
-
|
|
13331
|
-
return stmt.get(...params);
|
|
13372
|
+
return prepareCached(db, sql).get(...params);
|
|
13332
13373
|
}
|
|
13333
13374
|
function sqliteExec(db, sql) {
|
|
13334
13375
|
db.exec(sql);
|
|
@@ -13750,6 +13791,7 @@ function normalizeQueryRewriteKind(value) {
|
|
|
13750
13791
|
var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
|
|
13751
13792
|
var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
13752
13793
|
var DEFAULT_OUTBOX_MAX_RETRIES = 3;
|
|
13794
|
+
var SQLITE_SCHEMA_VERSION = 1;
|
|
13753
13795
|
function emptyOutboxRecoveryResult() {
|
|
13754
13796
|
return {
|
|
13755
13797
|
embedding: { recoveredProcessing: 0, retriedFailed: 0 },
|
|
@@ -14094,6 +14136,15 @@ var SQLiteEventStore = class {
|
|
|
14094
14136
|
access_count INTEGER DEFAULT 0
|
|
14095
14137
|
);
|
|
14096
14138
|
|
|
14139
|
+
-- Junction: consolidated memory -> source event id (indexed lookup that
|
|
14140
|
+
-- replaces per-event source_events LIKE scans during consolidation).
|
|
14141
|
+
CREATE TABLE IF NOT EXISTS consolidated_memory_events (
|
|
14142
|
+
memory_id TEXT NOT NULL,
|
|
14143
|
+
event_id TEXT NOT NULL,
|
|
14144
|
+
PRIMARY KEY (memory_id, event_id)
|
|
14145
|
+
);
|
|
14146
|
+
CREATE INDEX IF NOT EXISTS idx_cme_event ON consolidated_memory_events(event_id);
|
|
14147
|
+
|
|
14097
14148
|
-- Continuity Log table (tracks context transitions)
|
|
14098
14149
|
CREATE TABLE IF NOT EXISTS continuity_log (
|
|
14099
14150
|
log_id TEXT PRIMARY KEY,
|
|
@@ -14453,11 +14504,17 @@ var SQLiteEventStore = class {
|
|
|
14453
14504
|
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
14454
14505
|
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
14455
14506
|
END;
|
|
14507
|
+
|
|
14508
|
+
-- Reverse index for citation ids (a non-reversible hash of event id), so
|
|
14509
|
+
-- citation lookups are O(1) instead of scanning recent events and hashing
|
|
14510
|
+
-- each one. Populated lazily/self-healing by getEventByCitationId.
|
|
14511
|
+
CREATE TABLE IF NOT EXISTS event_citations (
|
|
14512
|
+
event_id TEXT PRIMARY KEY,
|
|
14513
|
+
citation_id TEXT NOT NULL
|
|
14514
|
+
);
|
|
14515
|
+
CREATE INDEX IF NOT EXISTS idx_event_citations_citation ON event_citations(citation_id);
|
|
14456
14516
|
`);
|
|
14457
|
-
|
|
14458
|
-
sqliteExec(this.db, `ALTER TABLE memory_action_edges ADD COLUMN source TEXT NOT NULL DEFAULT 'manual';`);
|
|
14459
|
-
} catch {
|
|
14460
|
-
}
|
|
14517
|
+
this.addColumnIfMissing("memory_action_edges", "source", `TEXT NOT NULL DEFAULT 'manual'`);
|
|
14461
14518
|
try {
|
|
14462
14519
|
const edgeIndexes = sqliteAll(this.db, `PRAGMA index_list(memory_action_edges)`, []);
|
|
14463
14520
|
const hasSourceAwareUnique = edgeIndexes.some((index) => {
|
|
@@ -14494,55 +14551,17 @@ var SQLiteEventStore = class {
|
|
|
14494
14551
|
}
|
|
14495
14552
|
} catch {
|
|
14496
14553
|
}
|
|
14497
|
-
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
try {
|
|
14502
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN candidate_details_json TEXT;`);
|
|
14503
|
-
} catch {
|
|
14504
|
-
}
|
|
14505
|
-
try {
|
|
14506
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN raw_query_text TEXT;`);
|
|
14507
|
-
} catch {
|
|
14508
|
-
}
|
|
14509
|
-
try {
|
|
14510
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN query_rewrite_kind TEXT;`);
|
|
14511
|
-
} catch {
|
|
14512
|
-
}
|
|
14554
|
+
this.addColumnIfMissing("retrieval_traces", "selected_details_json", "TEXT");
|
|
14555
|
+
this.addColumnIfMissing("retrieval_traces", "candidate_details_json", "TEXT");
|
|
14556
|
+
this.addColumnIfMissing("retrieval_traces", "raw_query_text", "TEXT");
|
|
14557
|
+
this.addColumnIfMissing("retrieval_traces", "query_rewrite_kind", "TEXT");
|
|
14513
14558
|
try {
|
|
14514
14559
|
sqliteExec(this.db, `CREATE INDEX IF NOT EXISTS idx_retrieval_traces_query_rewrite_kind ON retrieval_traces(query_rewrite_kind);`);
|
|
14515
14560
|
} catch {
|
|
14516
14561
|
}
|
|
14517
|
-
|
|
14518
|
-
|
|
14519
|
-
|
|
14520
|
-
try {
|
|
14521
|
-
sqliteExec(this.db, `
|
|
14522
|
-
ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
|
|
14523
|
-
`);
|
|
14524
|
-
} catch (err) {
|
|
14525
|
-
console.error("Error adding access_count column:", err);
|
|
14526
|
-
}
|
|
14527
|
-
}
|
|
14528
|
-
if (!columnNames.includes("last_accessed_at")) {
|
|
14529
|
-
try {
|
|
14530
|
-
sqliteExec(this.db, `
|
|
14531
|
-
ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
|
|
14532
|
-
`);
|
|
14533
|
-
} catch (err) {
|
|
14534
|
-
console.error("Error adding last_accessed_at column:", err);
|
|
14535
|
-
}
|
|
14536
|
-
}
|
|
14537
|
-
if (!columnNames.includes("turn_id")) {
|
|
14538
|
-
try {
|
|
14539
|
-
sqliteExec(this.db, `
|
|
14540
|
-
ALTER TABLE events ADD COLUMN turn_id TEXT;
|
|
14541
|
-
`);
|
|
14542
|
-
} catch (err) {
|
|
14543
|
-
console.error("Error adding turn_id column:", err);
|
|
14544
|
-
}
|
|
14545
|
-
}
|
|
14562
|
+
this.addColumnIfMissing("events", "access_count", "INTEGER DEFAULT 0");
|
|
14563
|
+
this.addColumnIfMissing("events", "last_accessed_at", "TEXT");
|
|
14564
|
+
this.addColumnIfMissing("events", "turn_id", "TEXT");
|
|
14546
14565
|
try {
|
|
14547
14566
|
sqliteExec(this.db, `
|
|
14548
14567
|
CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
|
|
@@ -14559,8 +14578,18 @@ var SQLiteEventStore = class {
|
|
|
14559
14578
|
sqliteExec(this.db, `
|
|
14560
14579
|
CREATE INDEX IF NOT EXISTS idx_events_turn_id ON events(turn_id);
|
|
14561
14580
|
`);
|
|
14562
|
-
} catch
|
|
14581
|
+
} catch {
|
|
14563
14582
|
}
|
|
14583
|
+
const cmeCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM consolidated_memory_events`, []);
|
|
14584
|
+
if ((cmeCount?.c ?? 0) === 0) {
|
|
14585
|
+
sqliteExec(this.db, `
|
|
14586
|
+
INSERT OR IGNORE INTO consolidated_memory_events (memory_id, event_id)
|
|
14587
|
+
SELECT cm.memory_id, je.value
|
|
14588
|
+
FROM consolidated_memories cm, json_each(cm.source_events) je
|
|
14589
|
+
WHERE json_valid(cm.source_events);
|
|
14590
|
+
`);
|
|
14591
|
+
}
|
|
14592
|
+
sqliteExec(this.db, `PRAGMA user_version = ${SQLITE_SCHEMA_VERSION}`);
|
|
14564
14593
|
this.initialized = true;
|
|
14565
14594
|
}
|
|
14566
14595
|
/**
|
|
@@ -14692,6 +14721,67 @@ var SQLiteEventStore = class {
|
|
|
14692
14721
|
);
|
|
14693
14722
|
return rows.map(this.rowToEvent);
|
|
14694
14723
|
}
|
|
14724
|
+
/**
|
|
14725
|
+
* Aggregate event counts by type via SQL GROUP BY, instead of loading rows
|
|
14726
|
+
* into memory and counting in JS. Counts all events (the old recent-window
|
|
14727
|
+
* scan also under-counted stores larger than its cap).
|
|
14728
|
+
*/
|
|
14729
|
+
async getEventTypeCounts(options) {
|
|
14730
|
+
await this.initialize();
|
|
14731
|
+
const rows = sqliteAll(
|
|
14732
|
+
this.db,
|
|
14733
|
+
`SELECT event_type, COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)} GROUP BY event_type`,
|
|
14734
|
+
[]
|
|
14735
|
+
);
|
|
14736
|
+
return rows.map((row) => ({ eventType: row.event_type, count: row.count }));
|
|
14737
|
+
}
|
|
14738
|
+
/**
|
|
14739
|
+
* Fetch every event at/after an ISO timestamp (ascending), with no row cap.
|
|
14740
|
+
* For window-scoped analytics (KPI/usefulness) this fetches exactly the events
|
|
14741
|
+
* the window needs instead of an arbitrary "most recent N" slice that both
|
|
14742
|
+
* over-fetches sparse windows and truncates very active ones.
|
|
14743
|
+
*/
|
|
14744
|
+
async getEventsAfter(sinceIso, options) {
|
|
14745
|
+
await this.initialize();
|
|
14746
|
+
const rows = sqliteAll(
|
|
14747
|
+
this.db,
|
|
14748
|
+
`SELECT * FROM events WHERE timestamp >= ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
14749
|
+
[sinceIso]
|
|
14750
|
+
);
|
|
14751
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
14752
|
+
}
|
|
14753
|
+
/** Count distinct sessions via SQL instead of materializing a Set in JS. */
|
|
14754
|
+
async getDistinctSessionCount(options) {
|
|
14755
|
+
await this.initialize();
|
|
14756
|
+
const row = sqliteGet(
|
|
14757
|
+
this.db,
|
|
14758
|
+
`SELECT COUNT(DISTINCT session_id) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`,
|
|
14759
|
+
[]
|
|
14760
|
+
);
|
|
14761
|
+
return row?.count ?? 0;
|
|
14762
|
+
}
|
|
14763
|
+
/**
|
|
14764
|
+
* Per-day event counts (with type breakdown) since an ISO timestamp, computed
|
|
14765
|
+
* by SQL GROUP BY on the date prefix. Timestamps are stored as ISO-8601
|
|
14766
|
+
* strings, so substr(...,1,10) is the UTC day and `timestamp >= ?` compares
|
|
14767
|
+
* lexicographically.
|
|
14768
|
+
*/
|
|
14769
|
+
async getDailyEventCounts(sinceIso, options) {
|
|
14770
|
+
await this.initialize();
|
|
14771
|
+
return sqliteAll(
|
|
14772
|
+
this.db,
|
|
14773
|
+
`SELECT substr(timestamp, 1, 10) as day,
|
|
14774
|
+
COUNT(*) as total,
|
|
14775
|
+
SUM(CASE WHEN event_type = 'user_prompt' THEN 1 ELSE 0 END) as prompts,
|
|
14776
|
+
SUM(CASE WHEN event_type = 'agent_response' THEN 1 ELSE 0 END) as responses,
|
|
14777
|
+
SUM(CASE WHEN event_type = 'tool_observation' THEN 1 ELSE 0 END) as tools
|
|
14778
|
+
FROM events
|
|
14779
|
+
WHERE timestamp >= ? AND ${maybeQuarantinePredicate(options)}
|
|
14780
|
+
GROUP BY day
|
|
14781
|
+
ORDER BY day ASC`,
|
|
14782
|
+
[sinceIso]
|
|
14783
|
+
);
|
|
14784
|
+
}
|
|
14695
14785
|
/**
|
|
14696
14786
|
* Get event by ID
|
|
14697
14787
|
*/
|
|
@@ -14705,6 +14795,71 @@ var SQLiteEventStore = class {
|
|
|
14705
14795
|
if (!row) return null;
|
|
14706
14796
|
return this.rowToEvent(row);
|
|
14707
14797
|
}
|
|
14798
|
+
/**
|
|
14799
|
+
* Batch-fetch events by id in a single query, applying the same quarantine
|
|
14800
|
+
* predicate as getEvent. Lets hot paths replace N sequential round-trips with
|
|
14801
|
+
* one. Missing ids are simply absent from the result (order not guaranteed).
|
|
14802
|
+
*/
|
|
14803
|
+
async getEvents(ids, options) {
|
|
14804
|
+
await this.initialize();
|
|
14805
|
+
if (ids.length === 0) return [];
|
|
14806
|
+
const placeholders2 = ids.map(() => "?").join(",");
|
|
14807
|
+
const rows = sqliteAll(
|
|
14808
|
+
this.db,
|
|
14809
|
+
`SELECT * FROM events WHERE id IN (${placeholders2}) AND ${maybeQuarantinePredicate(options)}`,
|
|
14810
|
+
ids
|
|
14811
|
+
);
|
|
14812
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
14813
|
+
}
|
|
14814
|
+
/**
|
|
14815
|
+
* Resolve an event from its citation id via the reverse index, instead of
|
|
14816
|
+
* scanning recent events and hashing each one. The index is self-healing: a
|
|
14817
|
+
* miss triggers a one-time backfill of any events not yet indexed (covering
|
|
14818
|
+
* freshly appended events) before retrying.
|
|
14819
|
+
*/
|
|
14820
|
+
async getEventByCitationId(citationId, options) {
|
|
14821
|
+
await this.initialize();
|
|
14822
|
+
if (!citationId) return null;
|
|
14823
|
+
let row = sqliteGet(
|
|
14824
|
+
this.db,
|
|
14825
|
+
`SELECT event_id FROM event_citations WHERE citation_id = ? LIMIT 1`,
|
|
14826
|
+
[citationId]
|
|
14827
|
+
);
|
|
14828
|
+
if (!row) {
|
|
14829
|
+
this.indexMissingCitations();
|
|
14830
|
+
row = sqliteGet(
|
|
14831
|
+
this.db,
|
|
14832
|
+
`SELECT event_id FROM event_citations WHERE citation_id = ? LIMIT 1`,
|
|
14833
|
+
[citationId]
|
|
14834
|
+
);
|
|
14835
|
+
}
|
|
14836
|
+
if (!row) return null;
|
|
14837
|
+
return this.getEvent(row.event_id, options);
|
|
14838
|
+
}
|
|
14839
|
+
/**
|
|
14840
|
+
* Backfill citation ids for any events not yet present in event_citations.
|
|
14841
|
+
* Gated on a cheap count comparison so the common (fully-indexed) path does no
|
|
14842
|
+
* work, and the scan only runs when there are genuinely new events to index.
|
|
14843
|
+
*/
|
|
14844
|
+
indexMissingCitations() {
|
|
14845
|
+
const eventCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM events`, [])?.c ?? 0;
|
|
14846
|
+
const indexedCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM event_citations`, [])?.c ?? 0;
|
|
14847
|
+
if (indexedCount >= eventCount) return;
|
|
14848
|
+
const unindexed = sqliteAll(
|
|
14849
|
+
this.db,
|
|
14850
|
+
`SELECT e.id FROM events e
|
|
14851
|
+
LEFT JOIN event_citations ec ON ec.event_id = e.id
|
|
14852
|
+
WHERE ec.event_id IS NULL`,
|
|
14853
|
+
[]
|
|
14854
|
+
);
|
|
14855
|
+
if (unindexed.length === 0) return;
|
|
14856
|
+
const insert = this.db.prepare(`INSERT OR IGNORE INTO event_citations (event_id, citation_id) VALUES (?, ?)`);
|
|
14857
|
+
this.db.transaction(() => {
|
|
14858
|
+
for (const { id } of unindexed) {
|
|
14859
|
+
insert.run(id, generateCitationId(id));
|
|
14860
|
+
}
|
|
14861
|
+
})();
|
|
14862
|
+
}
|
|
14708
14863
|
/**
|
|
14709
14864
|
* Get events since a timestamp (for sync)
|
|
14710
14865
|
*/
|
|
@@ -14906,25 +15061,22 @@ var SQLiteEventStore = class {
|
|
|
14906
15061
|
*/
|
|
14907
15062
|
async getPendingOutboxItems(limit = 32) {
|
|
14908
15063
|
await this.initialize();
|
|
14909
|
-
const
|
|
14910
|
-
this.db,
|
|
14911
|
-
`SELECT * FROM embedding_outbox
|
|
14912
|
-
WHERE status = 'pending'
|
|
14913
|
-
ORDER BY created_at
|
|
14914
|
-
LIMIT ?`,
|
|
14915
|
-
[limit]
|
|
14916
|
-
);
|
|
14917
|
-
if (pending.length === 0) return [];
|
|
14918
|
-
const ids = pending.map((r) => r.id);
|
|
14919
|
-
const placeholders2 = ids.map(() => "?").join(",");
|
|
14920
|
-
sqliteRun(
|
|
15064
|
+
const claimed = sqliteAll(
|
|
14921
15065
|
this.db,
|
|
14922
15066
|
`UPDATE embedding_outbox
|
|
14923
15067
|
SET status = 'processing', processed_at = datetime('now'), error_message = NULL
|
|
14924
|
-
WHERE id IN (
|
|
14925
|
-
|
|
15068
|
+
WHERE id IN (
|
|
15069
|
+
SELECT id FROM embedding_outbox
|
|
15070
|
+
WHERE status = 'pending'
|
|
15071
|
+
ORDER BY created_at
|
|
15072
|
+
LIMIT ?
|
|
15073
|
+
)
|
|
15074
|
+
RETURNING *`,
|
|
15075
|
+
[limit]
|
|
14926
15076
|
);
|
|
14927
|
-
|
|
15077
|
+
if (claimed.length === 0) return [];
|
|
15078
|
+
claimed.sort((a, b) => String(a.created_at).localeCompare(String(b.created_at)));
|
|
15079
|
+
return claimed.map((row) => ({
|
|
14928
15080
|
id: row.id,
|
|
14929
15081
|
eventId: row.event_id,
|
|
14930
15082
|
content: row.content,
|
|
@@ -15528,7 +15680,7 @@ var SQLiteEventStore = class {
|
|
|
15528
15680
|
const promptEvents = sessionEvents.filter((e) => e.event_type === "user_prompt");
|
|
15529
15681
|
const toolEvents = sessionEvents.filter((e) => e.event_type === "tool_observation");
|
|
15530
15682
|
let toolSuccessCount = 0;
|
|
15531
|
-
|
|
15683
|
+
const toolTotalCount = toolEvents.length;
|
|
15532
15684
|
for (const t of toolEvents) {
|
|
15533
15685
|
try {
|
|
15534
15686
|
const content = JSON.parse(t.content);
|
|
@@ -15691,10 +15843,14 @@ var SQLiteEventStore = class {
|
|
|
15691
15843
|
* Rebuild FTS index from existing events
|
|
15692
15844
|
* Call this once after upgrading to FTS5
|
|
15693
15845
|
*/
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15846
|
+
/**
|
|
15847
|
+
* Drop and recreate the events_fts virtual table + sync triggers, repopulating
|
|
15848
|
+
* from the current events table. Recreating the table (instead of issuing
|
|
15849
|
+
* DELETEs against it) sidesteps a quirk where older migrated FTS5 tables fail
|
|
15850
|
+
* with `no such column: T.event_id` on synthetic deletes. Safe to call inside
|
|
15851
|
+
* a transaction (pure DDL + INSERT...SELECT, no transaction control).
|
|
15852
|
+
*/
|
|
15853
|
+
recreateEventsFtsTable() {
|
|
15698
15854
|
sqliteExec(this.db, `
|
|
15699
15855
|
DROP TRIGGER IF EXISTS events_fts_insert;
|
|
15700
15856
|
DROP TRIGGER IF EXISTS events_fts_delete;
|
|
@@ -15723,6 +15879,12 @@ var SQLiteEventStore = class {
|
|
|
15723
15879
|
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
15724
15880
|
END;
|
|
15725
15881
|
`);
|
|
15882
|
+
}
|
|
15883
|
+
async rebuildFtsIndex() {
|
|
15884
|
+
await this.initialize();
|
|
15885
|
+
const countRow = sqliteGet(this.db, "SELECT COUNT(*) as count FROM events", []);
|
|
15886
|
+
const totalEvents = countRow?.count ?? 0;
|
|
15887
|
+
this.recreateEventsFtsTable();
|
|
15726
15888
|
return totalEvents;
|
|
15727
15889
|
}
|
|
15728
15890
|
/**
|
|
@@ -15740,6 +15902,35 @@ var SQLiteEventStore = class {
|
|
|
15740
15902
|
return false;
|
|
15741
15903
|
}
|
|
15742
15904
|
}
|
|
15905
|
+
hasTable(tableName) {
|
|
15906
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(tableName)) return false;
|
|
15907
|
+
try {
|
|
15908
|
+
const rows = sqliteAll(
|
|
15909
|
+
this.db,
|
|
15910
|
+
`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`,
|
|
15911
|
+
[tableName]
|
|
15912
|
+
);
|
|
15913
|
+
return rows.length > 0;
|
|
15914
|
+
} catch {
|
|
15915
|
+
return false;
|
|
15916
|
+
}
|
|
15917
|
+
}
|
|
15918
|
+
/**
|
|
15919
|
+
* Add a column only when its table exists and the column is missing.
|
|
15920
|
+
*
|
|
15921
|
+
* Unlike a blind `try { ALTER } catch {}`, this distinguishes "already
|
|
15922
|
+
* migrated" / "table not present yet" (both safely skipped) from a genuine
|
|
15923
|
+
* failure (disk full, corruption), which is allowed to surface instead of
|
|
15924
|
+
* being silently swallowed as if the migration had succeeded.
|
|
15925
|
+
*/
|
|
15926
|
+
addColumnIfMissing(table, column, definition) {
|
|
15927
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(column)) {
|
|
15928
|
+
throw new Error(`Invalid identifier in migration: ${table}.${column}`);
|
|
15929
|
+
}
|
|
15930
|
+
if (!this.hasTable(table)) return;
|
|
15931
|
+
if (this.hasTableColumn(table, column)) return;
|
|
15932
|
+
sqliteExec(this.db, `ALTER TABLE ${table} ADD COLUMN ${column} ${definition};`);
|
|
15933
|
+
}
|
|
15743
15934
|
async recordRetrievalTrace(input) {
|
|
15744
15935
|
await this.initialize();
|
|
15745
15936
|
const traceId = randomUUID6();
|
|
@@ -15825,6 +16016,45 @@ var SQLiteEventStore = class {
|
|
|
15825
16016
|
FROM retrieval_traces`,
|
|
15826
16017
|
[]
|
|
15827
16018
|
);
|
|
16019
|
+
const strategyColumnSql = this.hasTableColumn("retrieval_traces", "strategy") ? "COALESCE(NULLIF(TRIM(strategy), ''), 'unknown')" : "'unknown'";
|
|
16020
|
+
const strategyRows = sqliteAll(
|
|
16021
|
+
this.db,
|
|
16022
|
+
`SELECT
|
|
16023
|
+
${strategyColumnSql} as strategy,
|
|
16024
|
+
COUNT(*) as total_queries,
|
|
16025
|
+
SUM(CASE WHEN selected_count > 0 THEN 1 ELSE 0 END) as queries_with_selection,
|
|
16026
|
+
SUM(CASE WHEN ${rewrittenQueryRewriteKindSql} THEN 1 ELSE 0 END) as rewritten_queries,
|
|
16027
|
+
SUM(candidate_count) as total_candidate_count,
|
|
16028
|
+
SUM(selected_count) as total_selected_count,
|
|
16029
|
+
AVG(candidate_count) as avg_candidate_count,
|
|
16030
|
+
AVG(selected_count) as avg_selected_count,
|
|
16031
|
+
CASE
|
|
16032
|
+
WHEN SUM(candidate_count) > 0 THEN (SUM(selected_count) * 1.0 / SUM(candidate_count))
|
|
16033
|
+
ELSE 0
|
|
16034
|
+
END as selection_rate
|
|
16035
|
+
FROM retrieval_traces
|
|
16036
|
+
GROUP BY ${strategyColumnSql}
|
|
16037
|
+
ORDER BY total_queries DESC, strategy ASC`,
|
|
16038
|
+
[]
|
|
16039
|
+
);
|
|
16040
|
+
const strategyBreakdown = strategyRows.map((strategyRow) => {
|
|
16041
|
+
const strategyTotalQueries = Number(strategyRow.total_queries || 0);
|
|
16042
|
+
const strategyQueriesWithSelection = Number(strategyRow.queries_with_selection || 0);
|
|
16043
|
+
const strategyRewrittenQueries = Number(strategyRow.rewritten_queries || 0);
|
|
16044
|
+
return {
|
|
16045
|
+
strategy: String(strategyRow.strategy || "unknown"),
|
|
16046
|
+
totalQueries: strategyTotalQueries,
|
|
16047
|
+
queriesWithSelection: strategyQueriesWithSelection,
|
|
16048
|
+
rewrittenQueries: strategyRewrittenQueries,
|
|
16049
|
+
rewriteRate: strategyTotalQueries > 0 ? strategyRewrittenQueries / strategyTotalQueries : 0,
|
|
16050
|
+
totalCandidateCount: Number(strategyRow.total_candidate_count || 0),
|
|
16051
|
+
totalSelectedCount: Number(strategyRow.total_selected_count || 0),
|
|
16052
|
+
avgCandidateCount: Number(strategyRow.avg_candidate_count || 0),
|
|
16053
|
+
avgSelectedCount: Number(strategyRow.avg_selected_count || 0),
|
|
16054
|
+
selectionRate: Number(strategyRow.selection_rate || 0),
|
|
16055
|
+
queryYieldRate: strategyTotalQueries > 0 ? strategyQueriesWithSelection / strategyTotalQueries : 0
|
|
16056
|
+
};
|
|
16057
|
+
});
|
|
15828
16058
|
const totalQueries = Number(row?.total_queries || 0);
|
|
15829
16059
|
const rewrittenQueries = Number(row?.rewritten_queries || 0);
|
|
15830
16060
|
const rawQueries = Math.max(0, totalQueries - rewrittenQueries);
|
|
@@ -15842,7 +16072,8 @@ var SQLiteEventStore = class {
|
|
|
15842
16072
|
rewrittenSelectionRate: rewrittenQueries > 0 ? rewrittenQueriesWithSelection / rewrittenQueries : 0,
|
|
15843
16073
|
rawSelectionRate: rawQueries > 0 ? rawQueriesWithSelection / rawQueries : 0,
|
|
15844
16074
|
avgSelectedCountForRewrittenQueries: Number(row?.avg_selected_count_for_rewritten_queries || 0),
|
|
15845
|
-
avgSelectedCountForRawQueries: Number(row?.avg_selected_count_for_raw_queries || 0)
|
|
16075
|
+
avgSelectedCountForRawQueries: Number(row?.avg_selected_count_for_raw_queries || 0),
|
|
16076
|
+
strategyBreakdown
|
|
15846
16077
|
};
|
|
15847
16078
|
} catch (err) {
|
|
15848
16079
|
if (err?.message?.includes("no such table")) {
|
|
@@ -15858,7 +16089,8 @@ var SQLiteEventStore = class {
|
|
|
15858
16089
|
rewrittenSelectionRate: 0,
|
|
15859
16090
|
rawSelectionRate: 0,
|
|
15860
16091
|
avgSelectedCountForRewrittenQueries: 0,
|
|
15861
|
-
avgSelectedCountForRawQueries: 0
|
|
16092
|
+
avgSelectedCountForRawQueries: 0,
|
|
16093
|
+
strategyBreakdown: []
|
|
15862
16094
|
};
|
|
15863
16095
|
}
|
|
15864
16096
|
throw err;
|
|
@@ -15975,38 +16207,19 @@ var SQLiteEventStore = class {
|
|
|
15975
16207
|
if (events.length === 0) return 0;
|
|
15976
16208
|
const eventIds = events.map((e) => e.id);
|
|
15977
16209
|
const placeholders2 = eventIds.map(() => "?").join(",");
|
|
15978
|
-
const
|
|
15979
|
-
|
|
15980
|
-
|
|
16210
|
+
const relatedTables = ["event_dedup", "memory_levels", "embedding_queue", "embedding_outbox", "vector_outbox", "event_citations"].filter((table) => this.hasTable(table) && this.hasTableColumn(table, "event_id"));
|
|
16211
|
+
const runDelete = this.db.transaction(() => {
|
|
16212
|
+
for (const triggerName of ["events_fts_delete", "events_fts_update", "events_fts_insert"]) {
|
|
15981
16213
|
sqliteRun(this.db, `DROP TRIGGER IF EXISTS ${triggerName}`);
|
|
15982
|
-
ftsTriggersDropped.push(triggerName);
|
|
15983
|
-
} catch {
|
|
15984
16214
|
}
|
|
15985
|
-
|
|
15986
|
-
for (const table of ["event_dedup", "memory_levels", "embedding_queue", "embedding_outbox", "vector_outbox"]) {
|
|
15987
|
-
try {
|
|
16215
|
+
for (const table of relatedTables) {
|
|
15988
16216
|
sqliteRun(this.db, `DELETE FROM ${table} WHERE event_id IN (${placeholders2})`, eventIds);
|
|
15989
|
-
} catch {
|
|
15990
|
-
}
|
|
15991
|
-
}
|
|
15992
|
-
const result2 = sqliteRun(this.db, `DELETE FROM events WHERE session_id = ?`, [sessionId]);
|
|
15993
|
-
if (ftsTriggersDropped.length > 0) {
|
|
15994
|
-
try {
|
|
15995
|
-
sqliteRun(this.db, `INSERT INTO events_fts(events_fts) VALUES('rebuild')`);
|
|
15996
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_insert AFTER INSERT ON events BEGIN
|
|
15997
|
-
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
15998
|
-
END`);
|
|
15999
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_delete AFTER DELETE ON events BEGIN
|
|
16000
|
-
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
16001
|
-
END`);
|
|
16002
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_update AFTER UPDATE ON events BEGIN
|
|
16003
|
-
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
16004
|
-
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
16005
|
-
END`);
|
|
16006
|
-
} catch {
|
|
16007
16217
|
}
|
|
16008
|
-
|
|
16009
|
-
|
|
16218
|
+
const result2 = sqliteRun(this.db, `DELETE FROM events WHERE session_id = ?`, [sessionId]);
|
|
16219
|
+
this.recreateEventsFtsTable();
|
|
16220
|
+
return result2.changes || 0;
|
|
16221
|
+
});
|
|
16222
|
+
return runDelete();
|
|
16010
16223
|
}
|
|
16011
16224
|
/**
|
|
16012
16225
|
* Convert database row to MemoryEvent
|
|
@@ -19937,7 +20150,7 @@ var ActorCardRepository = class {
|
|
|
19937
20150
|
};
|
|
19938
20151
|
|
|
19939
20152
|
// src/core/operations/perspective-observation-repository.ts
|
|
19940
|
-
import { createHash as
|
|
20153
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
19941
20154
|
function projectHashToStorage6(projectHash) {
|
|
19942
20155
|
return projectHash ?? "";
|
|
19943
20156
|
}
|
|
@@ -19974,7 +20187,7 @@ function sanitizeStoredRecord(value) {
|
|
|
19974
20187
|
return sanitizeGovernanceAuditValue(value);
|
|
19975
20188
|
}
|
|
19976
20189
|
function stableHash(value) {
|
|
19977
|
-
return
|
|
20190
|
+
return createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
19978
20191
|
}
|
|
19979
20192
|
function evidenceHash(sourceEventIds, sourceObservationIds) {
|
|
19980
20193
|
return stableHash({
|
|
@@ -20482,19 +20695,6 @@ function createPerspectiveDeriver(options) {
|
|
|
20482
20695
|
return new PerspectiveDeriver(options);
|
|
20483
20696
|
}
|
|
20484
20697
|
|
|
20485
|
-
// src/core/citation-generator.ts
|
|
20486
|
-
import { createHash as createHash4 } from "crypto";
|
|
20487
|
-
var ID_LENGTH = 6;
|
|
20488
|
-
var CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
20489
|
-
function generateCitationId(eventId) {
|
|
20490
|
-
const hash = createHash4("sha256").update(eventId).digest();
|
|
20491
|
-
let id = "";
|
|
20492
|
-
for (let i = 0; i < ID_LENGTH; i++) {
|
|
20493
|
-
id += CHARSET[hash[i] % CHARSET.length];
|
|
20494
|
-
}
|
|
20495
|
-
return id;
|
|
20496
|
-
}
|
|
20497
|
-
|
|
20498
20698
|
// src/core/vector-store.ts
|
|
20499
20699
|
import * as lancedb from "@lancedb/lancedb";
|
|
20500
20700
|
var VectorStore = class {
|
|
@@ -21102,6 +21302,30 @@ var MemoryQueryService = class {
|
|
|
21102
21302
|
score: 1 - (r.rank - minRank) / rankRange
|
|
21103
21303
|
})).filter((r) => !options?.minScore || r.score >= options.minScore);
|
|
21104
21304
|
}
|
|
21305
|
+
async getEvent(id) {
|
|
21306
|
+
await this.initialize();
|
|
21307
|
+
return this.queryStore.getEvent(id);
|
|
21308
|
+
}
|
|
21309
|
+
async getEventByCitationId(citationId) {
|
|
21310
|
+
await this.initialize();
|
|
21311
|
+
return this.getMaintenanceStore("getEventByCitationId").getEventByCitationId(citationId);
|
|
21312
|
+
}
|
|
21313
|
+
async getEventsAfter(sinceIso) {
|
|
21314
|
+
await this.initialize();
|
|
21315
|
+
return this.getMaintenanceStore("getEventsAfter").getEventsAfter(sinceIso);
|
|
21316
|
+
}
|
|
21317
|
+
async getEventTypeCounts() {
|
|
21318
|
+
await this.initialize();
|
|
21319
|
+
return this.getMaintenanceStore("getEventTypeCounts").getEventTypeCounts();
|
|
21320
|
+
}
|
|
21321
|
+
async getDistinctSessionCount() {
|
|
21322
|
+
await this.initialize();
|
|
21323
|
+
return this.getMaintenanceStore("getDistinctSessionCount").getDistinctSessionCount();
|
|
21324
|
+
}
|
|
21325
|
+
async getDailyEventCounts(sinceIso) {
|
|
21326
|
+
await this.initialize();
|
|
21327
|
+
return this.getMaintenanceStore("getDailyEventCounts").getDailyEventCounts(sinceIso);
|
|
21328
|
+
}
|
|
21105
21329
|
async getSessionHistory(sessionId) {
|
|
21106
21330
|
await this.initialize();
|
|
21107
21331
|
return this.queryStore.getSessionEvents(sessionId);
|
|
@@ -22070,11 +22294,12 @@ var Retriever = class {
|
|
|
22070
22294
|
if (!scope && projectScopeMode === "global" && facetFilters === null) return results;
|
|
22071
22295
|
const normalizedIncludes = (scope?.contentIncludes || []).map((s) => s.toLowerCase());
|
|
22072
22296
|
const filtered = [];
|
|
22297
|
+
const eventMap = await this.fetchEventsByIds(results.map((r) => r.eventId));
|
|
22073
22298
|
for (const result2 of results) {
|
|
22074
22299
|
if (scope?.sessionId && result2.sessionId !== scope.sessionId) continue;
|
|
22075
22300
|
if (scope?.sessionIdPrefix && !result2.sessionId.startsWith(scope.sessionIdPrefix)) continue;
|
|
22076
22301
|
if (scope?.eventTypes && scope.eventTypes.length > 0 && !scope.eventTypes.includes(result2.eventType)) continue;
|
|
22077
|
-
const event =
|
|
22302
|
+
const event = eventMap.get(result2.eventId);
|
|
22078
22303
|
if (!event) continue;
|
|
22079
22304
|
if (scope?.canonicalKeyPrefix && !event.canonicalKey.startsWith(scope.canonicalKeyPrefix)) continue;
|
|
22080
22305
|
if (normalizedIncludes.length > 0) {
|
|
@@ -22183,10 +22408,30 @@ var Retriever = class {
|
|
|
22183
22408
|
async retrieveRecent(limit = 100) {
|
|
22184
22409
|
return this.eventStore.getRecentEvents(limit);
|
|
22185
22410
|
}
|
|
22411
|
+
/**
|
|
22412
|
+
* Batch-load events by id into a Map in a single query, de-duplicating ids.
|
|
22413
|
+
* Replaces per-result getEvent round-trips in the scope/enrich hot paths.
|
|
22414
|
+
*/
|
|
22415
|
+
async fetchEventsByIds(ids) {
|
|
22416
|
+
const map = /* @__PURE__ */ new Map();
|
|
22417
|
+
if (ids.length === 0) return map;
|
|
22418
|
+
const unique2 = [...new Set(ids)];
|
|
22419
|
+
if (typeof this.eventStore.getEvents === "function") {
|
|
22420
|
+
const events = await this.eventStore.getEvents(unique2);
|
|
22421
|
+
for (const event of events) map.set(event.id, event);
|
|
22422
|
+
} else {
|
|
22423
|
+
const events = await Promise.all(unique2.map((id) => this.eventStore.getEvent(id)));
|
|
22424
|
+
for (const event of events) {
|
|
22425
|
+
if (event) map.set(event.id, event);
|
|
22426
|
+
}
|
|
22427
|
+
}
|
|
22428
|
+
return map;
|
|
22429
|
+
}
|
|
22186
22430
|
async enrichResults(results, options, query) {
|
|
22187
22431
|
const memories = [];
|
|
22432
|
+
const eventMap = await this.fetchEventsByIds(results.map((r) => r.eventId));
|
|
22188
22433
|
for (const result2 of results) {
|
|
22189
|
-
const event =
|
|
22434
|
+
const event = eventMap.get(result2.eventId);
|
|
22190
22435
|
if (!event) continue;
|
|
22191
22436
|
if (this.graduation) {
|
|
22192
22437
|
this.graduation.recordAccess(event.id, options.sessionId || "unknown", result2.score);
|
|
@@ -24939,6 +25184,34 @@ var MemoryService = class {
|
|
|
24939
25184
|
async rebuildFtsIndex() {
|
|
24940
25185
|
return this.queryService.rebuildFtsIndex();
|
|
24941
25186
|
}
|
|
25187
|
+
/**
|
|
25188
|
+
* Get a single event by id (indexed lookup).
|
|
25189
|
+
*/
|
|
25190
|
+
async getEvent(id) {
|
|
25191
|
+
return this.queryService.getEvent(id);
|
|
25192
|
+
}
|
|
25193
|
+
/**
|
|
25194
|
+
* Resolve an event from its citation id via the reverse index.
|
|
25195
|
+
*/
|
|
25196
|
+
async getEventByCitationId(citationId) {
|
|
25197
|
+
return this.queryService.getEventByCitationId(citationId);
|
|
25198
|
+
}
|
|
25199
|
+
/** All events at/after an ISO timestamp (ascending, uncapped). */
|
|
25200
|
+
async getEventsAfter(sinceIso) {
|
|
25201
|
+
return this.queryService.getEventsAfter(sinceIso);
|
|
25202
|
+
}
|
|
25203
|
+
/** Aggregate event counts by type (SQL GROUP BY). */
|
|
25204
|
+
async getEventTypeCounts() {
|
|
25205
|
+
return this.queryService.getEventTypeCounts();
|
|
25206
|
+
}
|
|
25207
|
+
/** Count distinct sessions (SQL). */
|
|
25208
|
+
async getDistinctSessionCount() {
|
|
25209
|
+
return this.queryService.getDistinctSessionCount();
|
|
25210
|
+
}
|
|
25211
|
+
/** Per-day event counts with type breakdown since an ISO timestamp (SQL). */
|
|
25212
|
+
async getDailyEventCounts(sinceIso) {
|
|
25213
|
+
return this.queryService.getDailyEventCounts(sinceIso);
|
|
25214
|
+
}
|
|
24942
25215
|
/**
|
|
24943
25216
|
* Get session history
|
|
24944
25217
|
*/
|
|
@@ -25268,6 +25541,20 @@ import * as path11 from "path";
|
|
|
25268
25541
|
import * as os6 from "os";
|
|
25269
25542
|
import * as readline from "readline";
|
|
25270
25543
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
25544
|
+
|
|
25545
|
+
// src/services/turn-buffering.ts
|
|
25546
|
+
var MIN_SUBSTANTIVE_RESPONSE_LEN = 100;
|
|
25547
|
+
var MAX_AGENT_RESPONSE_LEN = 1e4;
|
|
25548
|
+
function mergeAgentResponseBlocks(blocks) {
|
|
25549
|
+
const substantive = blocks.filter((block) => block.length >= MIN_SUBSTANTIVE_RESPONSE_LEN);
|
|
25550
|
+
if (substantive.length > 0) return substantive.join("\n\n");
|
|
25551
|
+
return blocks.reduce((longest, block) => longest.length >= block.length ? longest : block, "");
|
|
25552
|
+
}
|
|
25553
|
+
function truncateAgentResponse(text) {
|
|
25554
|
+
return text.length > MAX_AGENT_RESPONSE_LEN ? `${text.slice(0, MAX_AGENT_RESPONSE_LEN)}...[truncated]` : text;
|
|
25555
|
+
}
|
|
25556
|
+
|
|
25557
|
+
// src/services/session-history-importer.ts
|
|
25271
25558
|
function isClaudeLocalCommandArtifact(content) {
|
|
25272
25559
|
const trimmed = content.trim();
|
|
25273
25560
|
return /^<local-command-(stdout|stderr)>/.test(trimmed) || /^<command-(name|message)>/.test(trimmed) || trimmed.includes("<command-name>") && trimmed.includes("<local-command-stdout>");
|
|
@@ -25399,6 +25686,34 @@ var SessionHistoryImporter = class {
|
|
|
25399
25686
|
/**
|
|
25400
25687
|
* Import a specific session file
|
|
25401
25688
|
*/
|
|
25689
|
+
/**
|
|
25690
|
+
* Pre-flight check used before a destructive force-reimport: returns true only
|
|
25691
|
+
* if the file can be opened and contains at least one line that parses as JSON.
|
|
25692
|
+
* Streams the file and short-circuits on the first valid record.
|
|
25693
|
+
*/
|
|
25694
|
+
async hasParseableContent(filePath) {
|
|
25695
|
+
let stream;
|
|
25696
|
+
let rl;
|
|
25697
|
+
try {
|
|
25698
|
+
stream = fs9.createReadStream(filePath);
|
|
25699
|
+
rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
25700
|
+
for await (const line of rl) {
|
|
25701
|
+
const trimmed = line.trim();
|
|
25702
|
+
if (!trimmed) continue;
|
|
25703
|
+
try {
|
|
25704
|
+
JSON.parse(trimmed);
|
|
25705
|
+
return true;
|
|
25706
|
+
} catch {
|
|
25707
|
+
}
|
|
25708
|
+
}
|
|
25709
|
+
return false;
|
|
25710
|
+
} catch {
|
|
25711
|
+
return false;
|
|
25712
|
+
} finally {
|
|
25713
|
+
rl?.close();
|
|
25714
|
+
stream?.close();
|
|
25715
|
+
}
|
|
25716
|
+
}
|
|
25402
25717
|
async importSessionFile(filePath, options = {}) {
|
|
25403
25718
|
const result2 = {
|
|
25404
25719
|
totalSessions: 1,
|
|
@@ -25414,6 +25729,10 @@ var SessionHistoryImporter = class {
|
|
|
25414
25729
|
}
|
|
25415
25730
|
const sessionId = path11.basename(filePath, ".jsonl");
|
|
25416
25731
|
if (options.force) {
|
|
25732
|
+
if (!await this.hasParseableContent(filePath)) {
|
|
25733
|
+
result2.errors.push(`Skipped force reimport (no parseable records): ${filePath}`);
|
|
25734
|
+
return result2;
|
|
25735
|
+
}
|
|
25417
25736
|
const deleted = await this.memoryService.deleteSessionEvents(sessionId);
|
|
25418
25737
|
if (options.verbose && deleted > 0) {
|
|
25419
25738
|
console.log(` Deleted ${deleted} existing events for session ${sessionId}`);
|
|
@@ -25435,13 +25754,12 @@ var SessionHistoryImporter = class {
|
|
|
25435
25754
|
let lastTimestamp;
|
|
25436
25755
|
const flushTextBuffer = async () => {
|
|
25437
25756
|
if (textBuffer.length === 0 || !currentTurnId) return;
|
|
25438
|
-
const
|
|
25439
|
-
const merged = substantive.length > 0 ? substantive.join("\n\n") : textBuffer.reduce((a, b) => a.length >= b.length ? a : b, "");
|
|
25757
|
+
const merged = mergeAgentResponseBlocks(textBuffer);
|
|
25440
25758
|
if (!merged) {
|
|
25441
25759
|
textBuffer = [];
|
|
25442
25760
|
return;
|
|
25443
25761
|
}
|
|
25444
|
-
const truncated = merged
|
|
25762
|
+
const truncated = truncateAgentResponse(merged);
|
|
25445
25763
|
const appendResult = await this.memoryService.storeAgentResponse(
|
|
25446
25764
|
sessionId,
|
|
25447
25765
|
truncated,
|
|
@@ -25455,55 +25773,60 @@ var SessionHistoryImporter = class {
|
|
|
25455
25773
|
lineCount++;
|
|
25456
25774
|
textBuffer = [];
|
|
25457
25775
|
};
|
|
25458
|
-
|
|
25459
|
-
|
|
25460
|
-
|
|
25461
|
-
|
|
25462
|
-
|
|
25463
|
-
|
|
25464
|
-
|
|
25465
|
-
|
|
25466
|
-
|
|
25467
|
-
|
|
25468
|
-
|
|
25469
|
-
|
|
25470
|
-
|
|
25471
|
-
|
|
25472
|
-
|
|
25473
|
-
|
|
25474
|
-
|
|
25475
|
-
|
|
25476
|
-
|
|
25477
|
-
|
|
25478
|
-
|
|
25479
|
-
|
|
25480
|
-
|
|
25481
|
-
|
|
25776
|
+
try {
|
|
25777
|
+
for await (const line of rl) {
|
|
25778
|
+
if (lineCount >= limit) break;
|
|
25779
|
+
try {
|
|
25780
|
+
const entry = JSON.parse(line);
|
|
25781
|
+
result2.totalMessages++;
|
|
25782
|
+
const msgClass = classifyEntry(entry);
|
|
25783
|
+
if (msgClass === "user_prompt") {
|
|
25784
|
+
await flushTextBuffer();
|
|
25785
|
+
const content = this.extractContent(entry);
|
|
25786
|
+
if (!content) continue;
|
|
25787
|
+
if (!isWorthStoringPrompt(content)) {
|
|
25788
|
+
result2.skippedDuplicates++;
|
|
25789
|
+
continue;
|
|
25790
|
+
}
|
|
25791
|
+
currentTurnId = randomUUID16();
|
|
25792
|
+
const appendResult = await this.memoryService.storeUserPrompt(
|
|
25793
|
+
sessionId,
|
|
25794
|
+
content,
|
|
25795
|
+
{ importedFrom: filePath, originalTimestamp: entry.timestamp, turnId: currentTurnId }
|
|
25796
|
+
);
|
|
25797
|
+
if (appendResult.success && appendResult.isDuplicate) {
|
|
25798
|
+
result2.skippedDuplicates++;
|
|
25799
|
+
} else {
|
|
25800
|
+
result2.importedPrompts++;
|
|
25801
|
+
}
|
|
25802
|
+
lineCount++;
|
|
25803
|
+
} else if (msgClass === "agent_text") {
|
|
25804
|
+
const content = this.extractContent(entry);
|
|
25805
|
+
if (content) {
|
|
25806
|
+
textBuffer.push(content);
|
|
25807
|
+
lastTimestamp = entry.timestamp;
|
|
25808
|
+
}
|
|
25482
25809
|
}
|
|
25483
|
-
|
|
25484
|
-
|
|
25485
|
-
|
|
25486
|
-
|
|
25487
|
-
|
|
25488
|
-
|
|
25810
|
+
const now = Date.now();
|
|
25811
|
+
if (now - lastProgressAt > 200) {
|
|
25812
|
+
lastProgressAt = now;
|
|
25813
|
+
onProgress?.({
|
|
25814
|
+
phase: "session-progress",
|
|
25815
|
+
sessionIndex,
|
|
25816
|
+
messagesProcessed: result2.totalMessages,
|
|
25817
|
+
imported: result2.importedPrompts + result2.importedResponses,
|
|
25818
|
+
skipped: result2.skippedDuplicates
|
|
25819
|
+
});
|
|
25489
25820
|
}
|
|
25821
|
+
} catch (parseError) {
|
|
25822
|
+
result2.errors.push(`Parse error on line: ${parseError}`);
|
|
25490
25823
|
}
|
|
25491
|
-
const now = Date.now();
|
|
25492
|
-
if (now - lastProgressAt > 200) {
|
|
25493
|
-
lastProgressAt = now;
|
|
25494
|
-
onProgress?.({
|
|
25495
|
-
phase: "session-progress",
|
|
25496
|
-
sessionIndex,
|
|
25497
|
-
messagesProcessed: result2.totalMessages,
|
|
25498
|
-
imported: result2.importedPrompts + result2.importedResponses,
|
|
25499
|
-
skipped: result2.skippedDuplicates
|
|
25500
|
-
});
|
|
25501
|
-
}
|
|
25502
|
-
} catch (parseError) {
|
|
25503
|
-
result2.errors.push(`Parse error on line: ${parseError}`);
|
|
25504
25824
|
}
|
|
25825
|
+
await flushTextBuffer();
|
|
25826
|
+
} finally {
|
|
25827
|
+
rl.close();
|
|
25828
|
+
fileStream.close();
|
|
25505
25829
|
}
|
|
25506
|
-
await flushTextBuffer();
|
|
25507
25830
|
await this.memoryService.endSession(sessionId);
|
|
25508
25831
|
if (options.projectPath) {
|
|
25509
25832
|
registerSession(sessionId, options.projectPath);
|
|
@@ -25711,6 +26034,29 @@ function extractCodexContentText(content, maxContentChars = CODEX_VALIDATION_DEF
|
|
|
25711
26034
|
function extractTextFromContent(content) {
|
|
25712
26035
|
return extractCodexContentText(content).text;
|
|
25713
26036
|
}
|
|
26037
|
+
function listCodexSessionFilesRecursive(rootDir) {
|
|
26038
|
+
if (!fs10.existsSync(rootDir)) return [];
|
|
26039
|
+
const out = [];
|
|
26040
|
+
const stack = [rootDir];
|
|
26041
|
+
while (stack.length > 0) {
|
|
26042
|
+
const dir = stack.pop();
|
|
26043
|
+
let entries;
|
|
26044
|
+
try {
|
|
26045
|
+
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
26046
|
+
} catch {
|
|
26047
|
+
continue;
|
|
26048
|
+
}
|
|
26049
|
+
for (const ent of entries) {
|
|
26050
|
+
const fullPath = path12.join(dir, ent.name);
|
|
26051
|
+
if (ent.isDirectory()) {
|
|
26052
|
+
stack.push(fullPath);
|
|
26053
|
+
} else if (ent.isFile() && ent.name.endsWith(".jsonl")) {
|
|
26054
|
+
out.push(fullPath);
|
|
26055
|
+
}
|
|
26056
|
+
}
|
|
26057
|
+
}
|
|
26058
|
+
return out.sort();
|
|
26059
|
+
}
|
|
25714
26060
|
function getFileMtimeMs2(filePath) {
|
|
25715
26061
|
try {
|
|
25716
26062
|
return fs10.statSync(filePath).mtimeMs;
|
|
@@ -25741,29 +26087,6 @@ var CodexSessionHistoryImporter = class {
|
|
|
25741
26087
|
getSessionsRoot() {
|
|
25742
26088
|
return this.sessionsRoot;
|
|
25743
26089
|
}
|
|
25744
|
-
listSessionFilesRecursive(rootDir) {
|
|
25745
|
-
if (!fs10.existsSync(rootDir)) return [];
|
|
25746
|
-
const out = [];
|
|
25747
|
-
const stack = [rootDir];
|
|
25748
|
-
while (stack.length > 0) {
|
|
25749
|
-
const dir = stack.pop();
|
|
25750
|
-
let entries;
|
|
25751
|
-
try {
|
|
25752
|
-
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
25753
|
-
} catch {
|
|
25754
|
-
continue;
|
|
25755
|
-
}
|
|
25756
|
-
for (const ent of entries) {
|
|
25757
|
-
const fullPath = path12.join(dir, ent.name);
|
|
25758
|
-
if (ent.isDirectory()) {
|
|
25759
|
-
stack.push(fullPath);
|
|
25760
|
-
} else if (ent.isFile() && ent.name.endsWith(".jsonl")) {
|
|
25761
|
-
out.push(fullPath);
|
|
25762
|
-
}
|
|
25763
|
-
}
|
|
25764
|
-
}
|
|
25765
|
-
return out;
|
|
25766
|
-
}
|
|
25767
26090
|
async readSessionMeta(filePath) {
|
|
25768
26091
|
const fileStream = fs10.createReadStream(filePath, { encoding: "utf-8" });
|
|
25769
26092
|
const rl = readline2.createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
@@ -25813,7 +26136,7 @@ var CodexSessionHistoryImporter = class {
|
|
|
25813
26136
|
}
|
|
25814
26137
|
const normalizedTarget = normalizeMaybeRealpath(projectPath);
|
|
25815
26138
|
onProgress?.({ phase: "scan", message: "Scanning Codex session files..." });
|
|
25816
|
-
const sessionFiles =
|
|
26139
|
+
const sessionFiles = listCodexSessionFilesRecursive(sessionsRoot);
|
|
25817
26140
|
const matchingFiles = [];
|
|
25818
26141
|
for (const filePath of sessionFiles) {
|
|
25819
26142
|
try {
|
|
@@ -25878,7 +26201,7 @@ var CodexSessionHistoryImporter = class {
|
|
|
25878
26201
|
return result2;
|
|
25879
26202
|
}
|
|
25880
26203
|
onProgress?.({ phase: "scan", message: "Scanning all Codex sessions..." });
|
|
25881
|
-
const sessionFiles =
|
|
26204
|
+
const sessionFiles = listCodexSessionFilesRecursive(sessionsRoot);
|
|
25882
26205
|
const selectedFiles = selectRecentCodexSessionFiles(sessionFiles, options.sessionLimit);
|
|
25883
26206
|
onProgress?.({ phase: "scan", message: `Found ${sessionFiles.length} Codex session file(s)` });
|
|
25884
26207
|
const totalLimit = normalizePositiveImportLimit(options.limit);
|
|
@@ -25957,13 +26280,12 @@ var CodexSessionHistoryImporter = class {
|
|
|
25957
26280
|
return;
|
|
25958
26281
|
}
|
|
25959
26282
|
if (textBuffer.length === 0 || !currentTurnId) return;
|
|
25960
|
-
const
|
|
25961
|
-
const merged = substantive.length > 0 ? substantive.join("\n\n") : textBuffer.reduce((a, b) => a.length >= b.length ? a : b, "");
|
|
26283
|
+
const merged = mergeAgentResponseBlocks(textBuffer);
|
|
25962
26284
|
if (!merged) {
|
|
25963
26285
|
textBuffer = [];
|
|
25964
26286
|
return;
|
|
25965
26287
|
}
|
|
25966
|
-
const truncated = merged
|
|
26288
|
+
const truncated = truncateAgentResponse(merged);
|
|
25967
26289
|
const appendResult = await this.memoryService.storeAgentResponse(
|
|
25968
26290
|
sessionId,
|
|
25969
26291
|
truncated,
|
|
@@ -26047,7 +26369,7 @@ var CodexSessionHistoryImporter = class {
|
|
|
26047
26369
|
async listAvailableSessions(projectPath) {
|
|
26048
26370
|
const sessionsRoot = this.getSessionsRoot();
|
|
26049
26371
|
if (!fs10.existsSync(sessionsRoot)) return [];
|
|
26050
|
-
const files =
|
|
26372
|
+
const files = listCodexSessionFilesRecursive(sessionsRoot);
|
|
26051
26373
|
const sessions = [];
|
|
26052
26374
|
const normalizedTarget = projectPath ? normalizeMaybeRealpath(projectPath) : null;
|
|
26053
26375
|
for (const filePath of files) {
|
|
@@ -26345,9 +26667,7 @@ var HermesSessionHistoryImporter = class {
|
|
|
26345
26667
|
return;
|
|
26346
26668
|
}
|
|
26347
26669
|
if (textBuffer.length === 0 || !currentTurnId) return;
|
|
26348
|
-
const
|
|
26349
|
-
const substantive = contentBlocks.filter((content) => content.length >= 100);
|
|
26350
|
-
const merged = substantive.length > 0 ? substantive.join("\n\n") : contentBlocks.reduce((a, b) => a.length >= b.length ? a : b, "");
|
|
26670
|
+
const merged = mergeAgentResponseBlocks(textBuffer.map((item) => item.content));
|
|
26351
26671
|
if (!merged) {
|
|
26352
26672
|
textBuffer = [];
|
|
26353
26673
|
return;
|
|
@@ -27959,10 +28279,10 @@ async function handleExternalMarketContext(args) {
|
|
|
27959
28279
|
return textResult(renderExternalMarketContextReport(report));
|
|
27960
28280
|
}
|
|
27961
28281
|
async function handleMemSearch(memoryService, args) {
|
|
27962
|
-
const query = args.query;
|
|
28282
|
+
const query = requiredOperationString(args.query, "query");
|
|
27963
28283
|
const topK = numberArg(args.topK, 5, 1, 20);
|
|
27964
28284
|
const fetchTopK = Math.min(topK * 3, 20);
|
|
27965
|
-
const sessionId = args.sessionId;
|
|
28285
|
+
const sessionId = optionalString(args.sessionId);
|
|
27966
28286
|
const eventType = eventTypeArg(args.eventType);
|
|
27967
28287
|
const search = await retrieveMcpMemories(memoryService, query, { topK, fetchTopK, sessionId, eventType });
|
|
27968
28288
|
const lines = [
|
|
@@ -28050,8 +28370,8 @@ function scoreKeywordMatch(content, queryTokens) {
|
|
|
28050
28370
|
return hits / queryTokens.length;
|
|
28051
28371
|
}
|
|
28052
28372
|
async function handleMemTimeline(memoryService, args) {
|
|
28053
|
-
const ids = args.ids;
|
|
28054
|
-
const windowSize = args.windowSize
|
|
28373
|
+
const ids = stringArrayOperationArg(args.ids, 100);
|
|
28374
|
+
const windowSize = numberArg(args.windowSize, 3, 0, 50);
|
|
28055
28375
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
28056
28376
|
const lines = [
|
|
28057
28377
|
"## Timeline Context",
|
|
@@ -28088,7 +28408,10 @@ async function handleMemTimeline(memoryService, args) {
|
|
|
28088
28408
|
};
|
|
28089
28409
|
}
|
|
28090
28410
|
async function handleMemDetails(memoryService, args) {
|
|
28091
|
-
const ids = args.ids;
|
|
28411
|
+
const ids = stringArrayOperationArg(args.ids, 200);
|
|
28412
|
+
if (ids.length === 0) {
|
|
28413
|
+
return { content: [{ type: "text", text: "## No memory ids provided." }] };
|
|
28414
|
+
}
|
|
28092
28415
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
28093
28416
|
const lines = [];
|
|
28094
28417
|
for (const targetId of ids) {
|
|
@@ -28110,7 +28433,7 @@ async function handleMemDetails(memoryService, args) {
|
|
|
28110
28433
|
lines.push("");
|
|
28111
28434
|
lines.push("**Content**:");
|
|
28112
28435
|
lines.push("```");
|
|
28113
|
-
lines.push(event.content);
|
|
28436
|
+
lines.push(applyPrivacyFilter(event.content, MCP_PRIVACY_CONFIG).content);
|
|
28114
28437
|
lines.push("```");
|
|
28115
28438
|
lines.push("");
|
|
28116
28439
|
lines.push("---");
|
|
@@ -28165,6 +28488,12 @@ async function handleMemContextPack(memoryService, args) {
|
|
|
28165
28488
|
recentSessionIds,
|
|
28166
28489
|
projectPath
|
|
28167
28490
|
});
|
|
28491
|
+
await recordMcpContextPackTrace(memoryService, {
|
|
28492
|
+
query,
|
|
28493
|
+
sessionId,
|
|
28494
|
+
candidates: search.memories,
|
|
28495
|
+
selected: relevantMemories
|
|
28496
|
+
});
|
|
28168
28497
|
const hasPerspectiveContext = optionalString(args.observerActorId) !== void 0 || optionalString(args.targetActorId) !== void 0 || optionalString(args.observedActorId) !== void 0;
|
|
28169
28498
|
let perspectiveBundle;
|
|
28170
28499
|
let perspectiveWarning;
|
|
@@ -28251,6 +28580,23 @@ async function handleMemContextPack(memoryService, args) {
|
|
|
28251
28580
|
}
|
|
28252
28581
|
return textResult(applyContextPackBudget(lines.join("\n"), maxContextChars));
|
|
28253
28582
|
}
|
|
28583
|
+
async function recordMcpContextPackTrace(memoryService, input) {
|
|
28584
|
+
try {
|
|
28585
|
+
await memoryService.recordQueryTrace({
|
|
28586
|
+
sessionId: input.sessionId,
|
|
28587
|
+
queryText: sanitizeOperationString(input.query, 1e3),
|
|
28588
|
+
queryRewriteKind: "none",
|
|
28589
|
+
strategy: "mcp-context-pack",
|
|
28590
|
+
candidateEventIds: uniqueContextPackEventIds(input.candidates),
|
|
28591
|
+
selectedEventIds: uniqueContextPackEventIds(input.selected),
|
|
28592
|
+
confidence: input.selected.length > 0 ? "suggested" : "none"
|
|
28593
|
+
});
|
|
28594
|
+
} catch {
|
|
28595
|
+
}
|
|
28596
|
+
}
|
|
28597
|
+
function uniqueContextPackEventIds(memories) {
|
|
28598
|
+
return Array.from(new Set(memories.map((memory) => memory.event.id).filter(Boolean)));
|
|
28599
|
+
}
|
|
28254
28600
|
async function runLatestImport(memoryService, options) {
|
|
28255
28601
|
const summaries = [];
|
|
28256
28602
|
for (const source of options.sources) {
|