engrm 0.4.26 → 0.4.28
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 +7 -2
- package/dist/cli.js +84 -0
- package/dist/hooks/elicitation-result.js +73 -0
- package/dist/hooks/post-tool-use.js +89 -2
- package/dist/hooks/pre-compact.js +89 -2
- package/dist/hooks/sentinel.js +70 -0
- package/dist/hooks/session-start.js +92 -2
- package/dist/hooks/stop.js +96 -3
- package/dist/hooks/user-prompt-submit.js +154 -61
- package/dist/server.js +357 -59
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -14230,6 +14230,17 @@ var MIGRATIONS = [
|
|
|
14230
14230
|
ON chat_messages(session_id, transcript_index)
|
|
14231
14231
|
WHERE transcript_index IS NOT NULL;
|
|
14232
14232
|
`
|
|
14233
|
+
},
|
|
14234
|
+
{
|
|
14235
|
+
version: 18,
|
|
14236
|
+
description: "Add sqlite-vec semantic search for chat recall",
|
|
14237
|
+
condition: (db) => isVecExtensionLoaded(db),
|
|
14238
|
+
sql: `
|
|
14239
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chat_messages USING vec0(
|
|
14240
|
+
chat_message_id INTEGER PRIMARY KEY,
|
|
14241
|
+
embedding FLOAT[384]
|
|
14242
|
+
);
|
|
14243
|
+
`
|
|
14233
14244
|
}
|
|
14234
14245
|
];
|
|
14235
14246
|
function isVecExtensionLoaded(db) {
|
|
@@ -14303,6 +14314,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
14303
14314
|
if (columnExists(db, "chat_messages", "source_kind") && columnExists(db, "chat_messages", "transcript_index")) {
|
|
14304
14315
|
version2 = Math.max(version2, 17);
|
|
14305
14316
|
}
|
|
14317
|
+
if (tableExists(db, "vec_chat_messages")) {
|
|
14318
|
+
version2 = Math.max(version2, 18);
|
|
14319
|
+
}
|
|
14306
14320
|
return version2;
|
|
14307
14321
|
}
|
|
14308
14322
|
function runMigrations(db) {
|
|
@@ -14419,6 +14433,20 @@ function ensureChatMessageColumns(db) {
|
|
|
14419
14433
|
db.exec("PRAGMA user_version = 17");
|
|
14420
14434
|
}
|
|
14421
14435
|
}
|
|
14436
|
+
function ensureChatVectorTable(db) {
|
|
14437
|
+
if (!isVecExtensionLoaded(db))
|
|
14438
|
+
return;
|
|
14439
|
+
db.exec(`
|
|
14440
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chat_messages USING vec0(
|
|
14441
|
+
chat_message_id INTEGER PRIMARY KEY,
|
|
14442
|
+
embedding FLOAT[384]
|
|
14443
|
+
);
|
|
14444
|
+
`);
|
|
14445
|
+
const current = getSchemaVersion(db);
|
|
14446
|
+
if (current < 18) {
|
|
14447
|
+
db.exec("PRAGMA user_version = 18");
|
|
14448
|
+
}
|
|
14449
|
+
}
|
|
14422
14450
|
function ensureSyncOutboxSupportsChatMessages(db) {
|
|
14423
14451
|
if (syncOutboxSupportsChatMessages(db)) {
|
|
14424
14452
|
const current = getSchemaVersion(db);
|
|
@@ -14632,6 +14660,7 @@ class MemDatabase {
|
|
|
14632
14660
|
ensureObservationTypes(this.db);
|
|
14633
14661
|
ensureSessionSummaryColumns(this.db);
|
|
14634
14662
|
ensureChatMessageColumns(this.db);
|
|
14663
|
+
ensureChatVectorTable(this.db);
|
|
14635
14664
|
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
14636
14665
|
}
|
|
14637
14666
|
loadVecExtension() {
|
|
@@ -14998,6 +15027,14 @@ class MemDatabase {
|
|
|
14998
15027
|
getChatMessageByRemoteSourceId(remoteSourceId) {
|
|
14999
15028
|
return this.db.query("SELECT * FROM chat_messages WHERE remote_source_id = ?").get(remoteSourceId) ?? null;
|
|
15000
15029
|
}
|
|
15030
|
+
getChatMessagesByIds(ids) {
|
|
15031
|
+
if (ids.length === 0)
|
|
15032
|
+
return [];
|
|
15033
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
15034
|
+
const rows = this.db.query(`SELECT * FROM chat_messages WHERE id IN (${placeholders})`).all(...ids);
|
|
15035
|
+
const order = new Map(ids.map((id, index) => [id, index]));
|
|
15036
|
+
return rows.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
|
|
15037
|
+
}
|
|
15001
15038
|
getSessionChatMessages(sessionId, limit = 50) {
|
|
15002
15039
|
return this.db.query(`SELECT * FROM chat_messages
|
|
15003
15040
|
WHERE session_id = ?
|
|
@@ -15074,6 +15111,39 @@ class MemDatabase {
|
|
|
15074
15111
|
ORDER BY created_at_epoch DESC, id DESC
|
|
15075
15112
|
LIMIT ?`).all(needle, ...userId ? [userId] : [], limit);
|
|
15076
15113
|
}
|
|
15114
|
+
vecChatInsert(chatMessageId, embedding) {
|
|
15115
|
+
if (!this.vecAvailable)
|
|
15116
|
+
return;
|
|
15117
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(chatMessageId, new Uint8Array(embedding.buffer));
|
|
15118
|
+
}
|
|
15119
|
+
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
15120
|
+
if (!this.vecAvailable)
|
|
15121
|
+
return [];
|
|
15122
|
+
const embeddingBlob = new Uint8Array(queryEmbedding.buffer);
|
|
15123
|
+
const visibilityClause = userId ? " AND c.user_id = ?" : "";
|
|
15124
|
+
const transcriptPreference = `
|
|
15125
|
+
AND (
|
|
15126
|
+
c.source_kind = 'transcript'
|
|
15127
|
+
OR NOT EXISTS (
|
|
15128
|
+
SELECT 1 FROM chat_messages t2
|
|
15129
|
+
WHERE t2.session_id = c.session_id
|
|
15130
|
+
AND t2.source_kind = 'transcript'
|
|
15131
|
+
)
|
|
15132
|
+
)`;
|
|
15133
|
+
if (projectId !== null) {
|
|
15134
|
+
return this.db.query(`SELECT v.chat_message_id, v.distance
|
|
15135
|
+
FROM vec_chat_messages v
|
|
15136
|
+
JOIN chat_messages c ON c.id = v.chat_message_id
|
|
15137
|
+
WHERE v.embedding MATCH ?
|
|
15138
|
+
AND k = ?
|
|
15139
|
+
AND c.project_id = ?` + visibilityClause + transcriptPreference).all(embeddingBlob, limit, projectId, ...userId ? [userId] : []);
|
|
15140
|
+
}
|
|
15141
|
+
return this.db.query(`SELECT v.chat_message_id, v.distance
|
|
15142
|
+
FROM vec_chat_messages v
|
|
15143
|
+
JOIN chat_messages c ON c.id = v.chat_message_id
|
|
15144
|
+
WHERE v.embedding MATCH ?
|
|
15145
|
+
AND k = ?` + visibilityClause + transcriptPreference).all(embeddingBlob, limit, ...userId ? [userId] : []);
|
|
15146
|
+
}
|
|
15077
15147
|
getTranscriptChatMessage(sessionId, transcriptIndex) {
|
|
15078
15148
|
return this.db.query("SELECT * FROM chat_messages WHERE session_id = ? AND transcript_index = ?").get(sessionId, transcriptIndex) ?? null;
|
|
15079
15149
|
}
|
|
@@ -15815,6 +15885,9 @@ function composeEmbeddingText(obs) {
|
|
|
15815
15885
|
|
|
15816
15886
|
`);
|
|
15817
15887
|
}
|
|
15888
|
+
function composeChatEmbeddingText(text) {
|
|
15889
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 2000);
|
|
15890
|
+
}
|
|
15818
15891
|
async function getPipeline() {
|
|
15819
15892
|
if (_pipeline)
|
|
15820
15893
|
return _pipeline;
|
|
@@ -16321,6 +16394,163 @@ function sanitizeFtsQuery(query) {
|
|
|
16321
16394
|
return safe;
|
|
16322
16395
|
}
|
|
16323
16396
|
|
|
16397
|
+
// src/tools/search-chat.ts
|
|
16398
|
+
async function searchChat(db, input) {
|
|
16399
|
+
const limit = Math.max(1, Math.min(input.limit ?? 20, 100));
|
|
16400
|
+
const projectScoped = input.project_scoped !== false;
|
|
16401
|
+
let projectId = null;
|
|
16402
|
+
let projectName;
|
|
16403
|
+
if (projectScoped) {
|
|
16404
|
+
const cwd = input.cwd ?? process.cwd();
|
|
16405
|
+
const detected = detectProject(cwd);
|
|
16406
|
+
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
16407
|
+
if (project) {
|
|
16408
|
+
projectId = project.id;
|
|
16409
|
+
projectName = project.name;
|
|
16410
|
+
}
|
|
16411
|
+
}
|
|
16412
|
+
const lexical = db.searchChatMessages(input.query, projectId, limit * 2, input.user_id);
|
|
16413
|
+
let semantic = [];
|
|
16414
|
+
const queryEmbedding = db.vecAvailable ? await embedText(composeChatEmbeddingText(queryForEmbedding(input.query))) : null;
|
|
16415
|
+
if (queryEmbedding && db.vecAvailable) {
|
|
16416
|
+
semantic = db.searchChatVec(queryEmbedding, projectId, limit * 2, input.user_id);
|
|
16417
|
+
}
|
|
16418
|
+
const messageIds = mergeChatResults(lexical, semantic, limit);
|
|
16419
|
+
const messages = messageIds.length > 0 ? db.getChatMessagesByIds(messageIds) : [];
|
|
16420
|
+
return {
|
|
16421
|
+
messages,
|
|
16422
|
+
project: projectName,
|
|
16423
|
+
session_count: countDistinctSessions(messages),
|
|
16424
|
+
source_summary: summarizeChatSources(messages),
|
|
16425
|
+
transcript_backed: messages.some((message) => message.source_kind === "transcript"),
|
|
16426
|
+
semantic_backed: semantic.length > 0
|
|
16427
|
+
};
|
|
16428
|
+
}
|
|
16429
|
+
var RRF_K2 = 40;
|
|
16430
|
+
function mergeChatResults(lexical, semantic, limit) {
|
|
16431
|
+
const scores = new Map;
|
|
16432
|
+
for (let rank = 0;rank < lexical.length; rank++) {
|
|
16433
|
+
const message = lexical[rank];
|
|
16434
|
+
scores.set(message.id, (scores.get(message.id) ?? 0) + 1 / (RRF_K2 + rank + 1));
|
|
16435
|
+
}
|
|
16436
|
+
for (let rank = 0;rank < semantic.length; rank++) {
|
|
16437
|
+
const match = semantic[rank];
|
|
16438
|
+
scores.set(match.chat_message_id, (scores.get(match.chat_message_id) ?? 0) + 1 / (RRF_K2 + rank + 1));
|
|
16439
|
+
}
|
|
16440
|
+
return Array.from(scores.entries()).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([id]) => id);
|
|
16441
|
+
}
|
|
16442
|
+
function queryForEmbedding(query) {
|
|
16443
|
+
return query.replace(/\s+/g, " ").trim().slice(0, 400);
|
|
16444
|
+
}
|
|
16445
|
+
function summarizeChatSources(messages) {
|
|
16446
|
+
return messages.reduce((summary, message) => {
|
|
16447
|
+
summary[message.source_kind] += 1;
|
|
16448
|
+
return summary;
|
|
16449
|
+
}, { transcript: 0, hook: 0 });
|
|
16450
|
+
}
|
|
16451
|
+
function countDistinctSessions(messages) {
|
|
16452
|
+
return new Set(messages.map((message) => message.session_id)).size;
|
|
16453
|
+
}
|
|
16454
|
+
|
|
16455
|
+
// src/tools/search-recall.ts
|
|
16456
|
+
async function searchRecall(db, input) {
|
|
16457
|
+
const query = input.query.trim();
|
|
16458
|
+
if (!query) {
|
|
16459
|
+
return {
|
|
16460
|
+
query,
|
|
16461
|
+
results: [],
|
|
16462
|
+
totals: { memory: 0, chat: 0 }
|
|
16463
|
+
};
|
|
16464
|
+
}
|
|
16465
|
+
const limit = Math.max(1, Math.min(input.limit ?? 10, 50));
|
|
16466
|
+
const [memory, chat] = await Promise.all([
|
|
16467
|
+
searchObservations(db, input),
|
|
16468
|
+
searchChat(db, {
|
|
16469
|
+
query,
|
|
16470
|
+
limit: limit * 2,
|
|
16471
|
+
project_scoped: input.project_scoped,
|
|
16472
|
+
cwd: input.cwd,
|
|
16473
|
+
user_id: input.user_id
|
|
16474
|
+
})
|
|
16475
|
+
]);
|
|
16476
|
+
const merged = mergeRecallResults(memory.observations, chat.messages, limit);
|
|
16477
|
+
return {
|
|
16478
|
+
query,
|
|
16479
|
+
project: memory.project ?? chat.project,
|
|
16480
|
+
results: merged,
|
|
16481
|
+
totals: {
|
|
16482
|
+
memory: memory.total,
|
|
16483
|
+
chat: chat.messages.length
|
|
16484
|
+
}
|
|
16485
|
+
};
|
|
16486
|
+
}
|
|
16487
|
+
function mergeRecallResults(memory, chat, limit) {
|
|
16488
|
+
const nowEpoch = Math.floor(Date.now() / 1000);
|
|
16489
|
+
const scored = [];
|
|
16490
|
+
for (let index = 0;index < memory.length; index++) {
|
|
16491
|
+
const item = memory[index];
|
|
16492
|
+
const base = 1 / (60 + index + 1);
|
|
16493
|
+
const score = base + Math.max(0, item.rank) * 0.08;
|
|
16494
|
+
scored.push({
|
|
16495
|
+
kind: "memory",
|
|
16496
|
+
rank: score,
|
|
16497
|
+
created_at: item.created_at,
|
|
16498
|
+
created_at_epoch: Math.floor(new Date(item.created_at).getTime() / 1000) || undefined,
|
|
16499
|
+
project_name: item.project_name,
|
|
16500
|
+
observation_id: item.id,
|
|
16501
|
+
id: item.id,
|
|
16502
|
+
session_id: null,
|
|
16503
|
+
type: item.type,
|
|
16504
|
+
title: item.title,
|
|
16505
|
+
detail: firstNonEmpty(item.narrative, parseFactsPreview(item.facts), item.files_modified ? `Files: ${item.files_modified}` : null, item.type) ?? item.type
|
|
16506
|
+
});
|
|
16507
|
+
}
|
|
16508
|
+
for (let index = 0;index < chat.length; index++) {
|
|
16509
|
+
const item = chat[index];
|
|
16510
|
+
const base = 1 / (60 + index + 1);
|
|
16511
|
+
const ageHours = Math.max(0, (nowEpoch - item.created_at_epoch) / 3600);
|
|
16512
|
+
const immediacyBoost = ageHours < 1 ? 1 : 0;
|
|
16513
|
+
const recencyBoost = ageHours < 24 ? 0.12 : ageHours < 72 ? 0.05 : 0.02;
|
|
16514
|
+
const sourceBoost = item.source_kind === "transcript" ? 0.06 : 0.03;
|
|
16515
|
+
scored.push({
|
|
16516
|
+
kind: "chat",
|
|
16517
|
+
rank: base + immediacyBoost + recencyBoost + sourceBoost,
|
|
16518
|
+
created_at_epoch: item.created_at_epoch,
|
|
16519
|
+
session_id: item.session_id,
|
|
16520
|
+
id: item.id,
|
|
16521
|
+
role: item.role,
|
|
16522
|
+
source_kind: item.source_kind,
|
|
16523
|
+
title: `${item.role} [${item.source_kind}]`,
|
|
16524
|
+
detail: item.content.replace(/\s+/g, " ").trim()
|
|
16525
|
+
});
|
|
16526
|
+
}
|
|
16527
|
+
return scored.sort((a, b) => {
|
|
16528
|
+
if (b.rank !== a.rank)
|
|
16529
|
+
return b.rank - a.rank;
|
|
16530
|
+
return (b.created_at_epoch ?? 0) - (a.created_at_epoch ?? 0);
|
|
16531
|
+
}).slice(0, limit);
|
|
16532
|
+
}
|
|
16533
|
+
function parseFactsPreview(facts) {
|
|
16534
|
+
if (!facts)
|
|
16535
|
+
return null;
|
|
16536
|
+
try {
|
|
16537
|
+
const parsed = JSON.parse(facts);
|
|
16538
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
16539
|
+
return null;
|
|
16540
|
+
const lines = parsed.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
16541
|
+
return lines.length > 0 ? lines.slice(0, 2).join(" | ") : null;
|
|
16542
|
+
} catch {
|
|
16543
|
+
return facts;
|
|
16544
|
+
}
|
|
16545
|
+
}
|
|
16546
|
+
function firstNonEmpty(...values) {
|
|
16547
|
+
for (const value of values) {
|
|
16548
|
+
if (value && value.trim().length > 0)
|
|
16549
|
+
return value.trim();
|
|
16550
|
+
}
|
|
16551
|
+
return null;
|
|
16552
|
+
}
|
|
16553
|
+
|
|
16324
16554
|
// src/tools/get.ts
|
|
16325
16555
|
function getObservations(db, input) {
|
|
16326
16556
|
if (input.ids.length === 0) {
|
|
@@ -16461,8 +16691,8 @@ function getRecentChat(db, input) {
|
|
|
16461
16691
|
const messages2 = db.getSessionChatMessages(input.session_id, limit).slice(-limit).reverse();
|
|
16462
16692
|
return {
|
|
16463
16693
|
messages: messages2,
|
|
16464
|
-
session_count:
|
|
16465
|
-
source_summary:
|
|
16694
|
+
session_count: countDistinctSessions2(messages2),
|
|
16695
|
+
source_summary: summarizeChatSources2(messages2),
|
|
16466
16696
|
transcript_backed: messages2.some((message) => message.source_kind === "transcript")
|
|
16467
16697
|
};
|
|
16468
16698
|
}
|
|
@@ -16479,40 +16709,6 @@ function getRecentChat(db, input) {
|
|
|
16479
16709
|
}
|
|
16480
16710
|
}
|
|
16481
16711
|
const messages = db.getRecentChatMessages(projectId, limit, input.user_id);
|
|
16482
|
-
return {
|
|
16483
|
-
messages,
|
|
16484
|
-
project: projectName,
|
|
16485
|
-
session_count: countDistinctSessions(messages),
|
|
16486
|
-
source_summary: summarizeChatSources(messages),
|
|
16487
|
-
transcript_backed: messages.some((message) => message.source_kind === "transcript")
|
|
16488
|
-
};
|
|
16489
|
-
}
|
|
16490
|
-
function summarizeChatSources(messages) {
|
|
16491
|
-
return messages.reduce((summary, message) => {
|
|
16492
|
-
summary[message.source_kind] += 1;
|
|
16493
|
-
return summary;
|
|
16494
|
-
}, { transcript: 0, hook: 0 });
|
|
16495
|
-
}
|
|
16496
|
-
function countDistinctSessions(messages) {
|
|
16497
|
-
return new Set(messages.map((message) => message.session_id)).size;
|
|
16498
|
-
}
|
|
16499
|
-
|
|
16500
|
-
// src/tools/search-chat.ts
|
|
16501
|
-
function searchChat(db, input) {
|
|
16502
|
-
const limit = Math.max(1, Math.min(input.limit ?? 20, 100));
|
|
16503
|
-
const projectScoped = input.project_scoped !== false;
|
|
16504
|
-
let projectId = null;
|
|
16505
|
-
let projectName;
|
|
16506
|
-
if (projectScoped) {
|
|
16507
|
-
const cwd = input.cwd ?? process.cwd();
|
|
16508
|
-
const detected = detectProject(cwd);
|
|
16509
|
-
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
16510
|
-
if (project) {
|
|
16511
|
-
projectId = project.id;
|
|
16512
|
-
projectName = project.name;
|
|
16513
|
-
}
|
|
16514
|
-
}
|
|
16515
|
-
const messages = db.searchChatMessages(input.query, projectId, limit, input.user_id);
|
|
16516
16712
|
return {
|
|
16517
16713
|
messages,
|
|
16518
16714
|
project: projectName,
|
|
@@ -16552,6 +16748,8 @@ function getSessionStory(db, input) {
|
|
|
16552
16748
|
summary,
|
|
16553
16749
|
prompts,
|
|
16554
16750
|
chat_messages: chatMessages,
|
|
16751
|
+
chat_source_summary: summarizeChatSources3(chatMessages),
|
|
16752
|
+
chat_coverage_state: chatMessages.some((message) => message.source_kind === "transcript") ? "transcript-backed" : chatMessages.length > 0 ? "hook-only" : "none",
|
|
16555
16753
|
tool_events: toolEvents,
|
|
16556
16754
|
observations,
|
|
16557
16755
|
handoffs,
|
|
@@ -16649,6 +16847,12 @@ function collectProvenanceSummary(observations) {
|
|
|
16649
16847
|
}
|
|
16650
16848
|
return Array.from(counts.entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
16651
16849
|
}
|
|
16850
|
+
function summarizeChatSources3(messages) {
|
|
16851
|
+
return messages.reduce((summary, message) => {
|
|
16852
|
+
summary[message.source_kind] += 1;
|
|
16853
|
+
return summary;
|
|
16854
|
+
}, { transcript: 0, hook: 0 });
|
|
16855
|
+
}
|
|
16652
16856
|
|
|
16653
16857
|
// src/tools/handoffs.ts
|
|
16654
16858
|
async function createHandoff(db, config2, input) {
|
|
@@ -17995,16 +18199,17 @@ function getProjectMemoryIndex(db, input) {
|
|
|
17995
18199
|
}).handoffs;
|
|
17996
18200
|
const rollingHandoffDraftsCount = recentHandoffsCount.filter((handoff) => isDraftHandoff(handoff)).length;
|
|
17997
18201
|
const savedHandoffsCount = recentHandoffsCount.length - rollingHandoffDraftsCount;
|
|
17998
|
-
const
|
|
18202
|
+
const recentChat = getRecentChat(db, {
|
|
17999
18203
|
cwd,
|
|
18000
18204
|
project_scoped: true,
|
|
18001
18205
|
user_id: input.user_id,
|
|
18002
18206
|
limit: 20
|
|
18003
|
-
})
|
|
18207
|
+
});
|
|
18208
|
+
const recentChatCount = recentChat.messages.length;
|
|
18004
18209
|
const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0 && !looksLikeFileOperationTitle3(title)).slice(0, 8);
|
|
18005
18210
|
const captureSummary = summarizeCaptureState(recentSessions);
|
|
18006
18211
|
const topTypes = Object.entries(counts).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count || a.type.localeCompare(b.type)).slice(0, 5);
|
|
18007
|
-
const suggestedTools = buildSuggestedTools(recentSessions, recentRequestsCount, recentToolsCount, observations.length);
|
|
18212
|
+
const suggestedTools = buildSuggestedTools(recentSessions, recentRequestsCount, recentToolsCount, observations.length, recentChatCount, recentChat.transcript_backed);
|
|
18008
18213
|
const estimatedReadTokens = estimateTokens([
|
|
18009
18214
|
recentOutcomes.join(`
|
|
18010
18215
|
`),
|
|
@@ -18029,6 +18234,9 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18029
18234
|
rolling_handoff_drafts_count: rollingHandoffDraftsCount,
|
|
18030
18235
|
saved_handoffs_count: savedHandoffsCount,
|
|
18031
18236
|
recent_chat_count: recentChatCount,
|
|
18237
|
+
recent_chat_sessions: recentChat.session_count,
|
|
18238
|
+
chat_source_summary: recentChat.source_summary,
|
|
18239
|
+
chat_coverage_state: recentChat.transcript_backed ? "transcript-backed" : recentChatCount > 0 ? "hook-only" : "none",
|
|
18032
18240
|
raw_capture_active: recentRequestsCount > 0 || recentToolsCount > 0,
|
|
18033
18241
|
capture_summary: captureSummary,
|
|
18034
18242
|
hot_files: hotFiles,
|
|
@@ -18101,7 +18309,7 @@ function summarizeCaptureState(sessions) {
|
|
|
18101
18309
|
}
|
|
18102
18310
|
return summary;
|
|
18103
18311
|
}
|
|
18104
|
-
function buildSuggestedTools(sessions, requestCount, toolCount, observationCount) {
|
|
18312
|
+
function buildSuggestedTools(sessions, requestCount, toolCount, observationCount, recentChatCount, transcriptBackedChat) {
|
|
18105
18313
|
const suggested = [];
|
|
18106
18314
|
if (sessions.length > 0) {
|
|
18107
18315
|
suggested.push("recent_sessions");
|
|
@@ -18109,13 +18317,21 @@ function buildSuggestedTools(sessions, requestCount, toolCount, observationCount
|
|
|
18109
18317
|
if (requestCount > 0 || toolCount > 0) {
|
|
18110
18318
|
suggested.push("activity_feed");
|
|
18111
18319
|
}
|
|
18320
|
+
if (requestCount > 0 || recentChatCount > 0 || observationCount > 0) {
|
|
18321
|
+
suggested.push("search_recall");
|
|
18322
|
+
}
|
|
18112
18323
|
if (observationCount > 0) {
|
|
18113
18324
|
suggested.push("tool_memory_index", "capture_git_worktree");
|
|
18114
18325
|
}
|
|
18115
18326
|
if (sessions.length > 0) {
|
|
18116
18327
|
suggested.push("create_handoff", "recent_handoffs");
|
|
18117
18328
|
}
|
|
18118
|
-
|
|
18329
|
+
if (recentChatCount > 0 && !transcriptBackedChat) {
|
|
18330
|
+
suggested.push("refresh_chat_recall");
|
|
18331
|
+
}
|
|
18332
|
+
if (recentChatCount > 0) {
|
|
18333
|
+
suggested.push("recent_chat", "search_chat");
|
|
18334
|
+
}
|
|
18119
18335
|
return Array.from(new Set(suggested)).slice(0, 4);
|
|
18120
18336
|
}
|
|
18121
18337
|
|
|
@@ -18162,12 +18378,12 @@ function getMemoryConsole(db, input) {
|
|
|
18162
18378
|
project_scoped: projectScoped,
|
|
18163
18379
|
user_id: input.user_id,
|
|
18164
18380
|
limit: 6
|
|
18165
|
-
})
|
|
18381
|
+
});
|
|
18166
18382
|
const projectIndex = projectScoped ? getProjectMemoryIndex(db, {
|
|
18167
18383
|
cwd,
|
|
18168
18384
|
user_id: input.user_id
|
|
18169
18385
|
}) : null;
|
|
18170
|
-
const continuityState = projectIndex?.continuity_state ?? classifyContinuityState(requests.length, tools.length, recentHandoffs.length, recentChat.length, sessions, (projectIndex?.recent_outcomes ?? []).length);
|
|
18386
|
+
const continuityState = projectIndex?.continuity_state ?? classifyContinuityState(requests.length, tools.length, recentHandoffs.length, recentChat.messages.length, sessions, (projectIndex?.recent_outcomes ?? []).length);
|
|
18171
18387
|
return {
|
|
18172
18388
|
project: project?.name,
|
|
18173
18389
|
capture_mode: requests.length > 0 || tools.length > 0 ? "rich" : "observations-only",
|
|
@@ -18179,7 +18395,10 @@ function getMemoryConsole(db, input) {
|
|
|
18179
18395
|
recent_handoffs: recentHandoffs,
|
|
18180
18396
|
rolling_handoff_drafts: rollingHandoffDrafts,
|
|
18181
18397
|
saved_handoffs: savedHandoffs,
|
|
18182
|
-
recent_chat: recentChat,
|
|
18398
|
+
recent_chat: recentChat.messages,
|
|
18399
|
+
recent_chat_sessions: projectIndex?.recent_chat_sessions ?? recentChat.session_count,
|
|
18400
|
+
chat_source_summary: projectIndex?.chat_source_summary ?? recentChat.source_summary,
|
|
18401
|
+
chat_coverage_state: projectIndex?.chat_coverage_state ?? (recentChat.transcript_backed ? "transcript-backed" : recentChat.messages.length > 0 ? "hook-only" : "none"),
|
|
18183
18402
|
observations,
|
|
18184
18403
|
capture_summary: projectIndex?.capture_summary,
|
|
18185
18404
|
recent_outcomes: projectIndex?.recent_outcomes ?? [],
|
|
@@ -18189,23 +18408,27 @@ function getMemoryConsole(db, input) {
|
|
|
18189
18408
|
assistant_checkpoint_types: projectIndex?.assistant_checkpoint_types ?? [],
|
|
18190
18409
|
top_types: projectIndex?.top_types ?? [],
|
|
18191
18410
|
estimated_read_tokens: projectIndex?.estimated_read_tokens,
|
|
18192
|
-
suggested_tools: projectIndex?.suggested_tools ?? buildFallbackSuggestedTools(sessions.length, requests.length, tools.length, observations.length, recentHandoffs.length, recentChat.length)
|
|
18411
|
+
suggested_tools: projectIndex?.suggested_tools ?? buildFallbackSuggestedTools(sessions.length, requests.length, tools.length, observations.length, recentHandoffs.length, recentChat.messages.length, recentChat.transcript_backed)
|
|
18193
18412
|
};
|
|
18194
18413
|
}
|
|
18195
|
-
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount) {
|
|
18414
|
+
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, transcriptBackedChat) {
|
|
18196
18415
|
const suggested = [];
|
|
18197
18416
|
if (sessionCount > 0)
|
|
18198
18417
|
suggested.push("recent_sessions");
|
|
18199
18418
|
if (requestCount > 0 || toolCount > 0)
|
|
18200
18419
|
suggested.push("activity_feed");
|
|
18420
|
+
if (requestCount > 0 || chatCount > 0 || observationCount > 0)
|
|
18421
|
+
suggested.push("search_recall");
|
|
18201
18422
|
if (observationCount > 0)
|
|
18202
18423
|
suggested.push("tool_memory_index", "capture_git_worktree");
|
|
18203
18424
|
if (sessionCount > 0)
|
|
18204
18425
|
suggested.push("create_handoff", "recent_handoffs");
|
|
18205
18426
|
if (handoffCount > 0)
|
|
18206
18427
|
suggested.push("load_handoff");
|
|
18428
|
+
if (chatCount > 0 && !transcriptBackedChat)
|
|
18429
|
+
suggested.push("refresh_chat_recall");
|
|
18207
18430
|
if (chatCount > 0)
|
|
18208
|
-
suggested.push("recent_chat");
|
|
18431
|
+
suggested.push("recent_chat", "search_chat");
|
|
18209
18432
|
return Array.from(new Set(suggested)).slice(0, 4);
|
|
18210
18433
|
}
|
|
18211
18434
|
|
|
@@ -18428,7 +18651,7 @@ function toChatEvent(message) {
|
|
|
18428
18651
|
created_at_epoch: message.created_at_epoch,
|
|
18429
18652
|
session_id: message.session_id,
|
|
18430
18653
|
id: message.id,
|
|
18431
|
-
title: message.role
|
|
18654
|
+
title: `${message.role} [${message.source_kind}]`,
|
|
18432
18655
|
detail: content.slice(0, 220)
|
|
18433
18656
|
};
|
|
18434
18657
|
}
|
|
@@ -19007,12 +19230,13 @@ function getSessionContext(db, input) {
|
|
|
19007
19230
|
const rollingHandoffDrafts = (context.recentHandoffs ?? []).filter((handoff) => handoff.title.startsWith("Handoff Draft:")).length;
|
|
19008
19231
|
const savedHandoffs = recentHandoffs - rollingHandoffDrafts;
|
|
19009
19232
|
const latestHandoffTitle = context.recentHandoffs?.[0]?.title ?? null;
|
|
19010
|
-
const
|
|
19233
|
+
const recentChat = getRecentChat(db, {
|
|
19011
19234
|
cwd,
|
|
19012
19235
|
project_scoped: true,
|
|
19013
19236
|
user_id: input.user_id,
|
|
19014
19237
|
limit: 8
|
|
19015
|
-
})
|
|
19238
|
+
});
|
|
19239
|
+
const recentChatMessages = recentChat.messages.length;
|
|
19016
19240
|
const captureState = recentRequests > 0 && recentTools > 0 ? "rich" : recentRequests > 0 || recentTools > 0 ? "partial" : "summary-only";
|
|
19017
19241
|
const hotFiles = buildHotFiles(context);
|
|
19018
19242
|
const continuityState = classifyContinuityState(recentRequests, recentTools, recentHandoffs, recentChatMessages, context.recentSessions ?? [], (context.recentOutcomes ?? []).length);
|
|
@@ -19031,12 +19255,15 @@ function getSessionContext(db, input) {
|
|
|
19031
19255
|
saved_handoffs: savedHandoffs,
|
|
19032
19256
|
latest_handoff_title: latestHandoffTitle,
|
|
19033
19257
|
recent_chat_messages: recentChatMessages,
|
|
19258
|
+
recent_chat_sessions: recentChat.session_count,
|
|
19259
|
+
chat_source_summary: recentChat.source_summary,
|
|
19260
|
+
chat_coverage_state: recentChat.transcript_backed ? "transcript-backed" : recentChatMessages > 0 ? "hook-only" : "none",
|
|
19034
19261
|
recent_outcomes: context.recentOutcomes ?? [],
|
|
19035
19262
|
hot_files: hotFiles,
|
|
19036
19263
|
capture_state: captureState,
|
|
19037
19264
|
raw_capture_active: recentRequests > 0 || recentTools > 0,
|
|
19038
19265
|
estimated_read_tokens: estimateTokens(preview),
|
|
19039
|
-
suggested_tools: buildSuggestedTools2(context),
|
|
19266
|
+
suggested_tools: buildSuggestedTools2(context, recentChat.transcript_backed),
|
|
19040
19267
|
preview
|
|
19041
19268
|
};
|
|
19042
19269
|
}
|
|
@@ -19059,7 +19286,7 @@ function parseJsonArray3(value) {
|
|
|
19059
19286
|
return [];
|
|
19060
19287
|
}
|
|
19061
19288
|
}
|
|
19062
|
-
function buildSuggestedTools2(context) {
|
|
19289
|
+
function buildSuggestedTools2(context, transcriptBackedChat) {
|
|
19063
19290
|
const tools = [];
|
|
19064
19291
|
if ((context.recentSessions?.length ?? 0) > 0) {
|
|
19065
19292
|
tools.push("recent_sessions");
|
|
@@ -19067,6 +19294,9 @@ function buildSuggestedTools2(context) {
|
|
|
19067
19294
|
if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0) {
|
|
19068
19295
|
tools.push("activity_feed");
|
|
19069
19296
|
}
|
|
19297
|
+
if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentChatMessages?.length ?? 0) > 0 || context.observations.length > 0) {
|
|
19298
|
+
tools.push("search_recall");
|
|
19299
|
+
}
|
|
19070
19300
|
if (context.observations.length > 0) {
|
|
19071
19301
|
tools.push("tool_memory_index", "capture_git_worktree");
|
|
19072
19302
|
}
|
|
@@ -19076,7 +19306,12 @@ function buildSuggestedTools2(context) {
|
|
|
19076
19306
|
if ((context.recentHandoffs?.length ?? 0) > 0) {
|
|
19077
19307
|
tools.push("load_handoff");
|
|
19078
19308
|
}
|
|
19079
|
-
|
|
19309
|
+
if ((context.recentChatMessages?.length ?? 0) > 0 && !transcriptBackedChat) {
|
|
19310
|
+
tools.push("refresh_chat_recall");
|
|
19311
|
+
}
|
|
19312
|
+
if ((context.recentChatMessages?.length ?? 0) > 0) {
|
|
19313
|
+
tools.push("recent_chat", "search_chat");
|
|
19314
|
+
}
|
|
19080
19315
|
return Array.from(new Set(tools)).slice(0, 4);
|
|
19081
19316
|
}
|
|
19082
19317
|
|
|
@@ -21047,7 +21282,7 @@ function readTranscript(sessionId, cwd, transcriptPath) {
|
|
|
21047
21282
|
}
|
|
21048
21283
|
return messages;
|
|
21049
21284
|
}
|
|
21050
|
-
function syncTranscriptChat(db, config2, sessionId, cwd, transcriptPath) {
|
|
21285
|
+
async function syncTranscriptChat(db, config2, sessionId, cwd, transcriptPath) {
|
|
21051
21286
|
const messages = readTranscript(sessionId, cwd, transcriptPath).map((message) => ({
|
|
21052
21287
|
...message,
|
|
21053
21288
|
text: message.text.trim()
|
|
@@ -21077,6 +21312,12 @@ function syncTranscriptChat(db, config2, sessionId, cwd, transcriptPath) {
|
|
|
21077
21312
|
transcript_index: transcriptIndex
|
|
21078
21313
|
});
|
|
21079
21314
|
db.addToOutbox("chat_message", row.id);
|
|
21315
|
+
if (db.vecAvailable) {
|
|
21316
|
+
const embedding = await embedText(composeChatEmbeddingText(message.text));
|
|
21317
|
+
if (embedding) {
|
|
21318
|
+
db.vecChatInsert(row.id, embedding);
|
|
21319
|
+
}
|
|
21320
|
+
}
|
|
21080
21321
|
imported++;
|
|
21081
21322
|
}
|
|
21082
21323
|
return { imported, total: messages.length };
|
|
@@ -21152,7 +21393,7 @@ process.on("SIGTERM", () => {
|
|
|
21152
21393
|
});
|
|
21153
21394
|
var server = new McpServer({
|
|
21154
21395
|
name: "engrm",
|
|
21155
|
-
version: "0.4.
|
|
21396
|
+
version: "0.4.28"
|
|
21156
21397
|
});
|
|
21157
21398
|
server.tool("save_observation", "Save an observation to memory", {
|
|
21158
21399
|
type: exports_external.enum([
|
|
@@ -21545,6 +21786,58 @@ ${previews.join(`
|
|
|
21545
21786
|
]
|
|
21546
21787
|
};
|
|
21547
21788
|
});
|
|
21789
|
+
server.tool("search_recall", "Search live recall across durable memory and chat together. Best for questions like 'what were we just talking about?'", {
|
|
21790
|
+
query: exports_external.string().describe("Recall query"),
|
|
21791
|
+
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)"),
|
|
21792
|
+
limit: exports_external.number().optional().describe("Max results (default: 10)"),
|
|
21793
|
+
cwd: exports_external.string().optional().describe("Optional cwd override for project-scoped recall"),
|
|
21794
|
+
user_id: exports_external.string().optional().describe("Optional user override")
|
|
21795
|
+
}, async (params) => {
|
|
21796
|
+
const result = await searchRecall(db, {
|
|
21797
|
+
query: params.query,
|
|
21798
|
+
project_scoped: params.project_scoped,
|
|
21799
|
+
limit: params.limit,
|
|
21800
|
+
cwd: params.cwd,
|
|
21801
|
+
user_id: params.user_id ?? config2.user_id
|
|
21802
|
+
});
|
|
21803
|
+
if (result.results.length === 0) {
|
|
21804
|
+
return {
|
|
21805
|
+
content: [
|
|
21806
|
+
{
|
|
21807
|
+
type: "text",
|
|
21808
|
+
text: result.project ? `No recall found for "${params.query}" in project ${result.project}` : `No recall found for "${params.query}"`
|
|
21809
|
+
}
|
|
21810
|
+
]
|
|
21811
|
+
};
|
|
21812
|
+
}
|
|
21813
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
21814
|
+
` : "";
|
|
21815
|
+
const summaryLine = `Matches: ${result.results.length} · memory ${result.totals.memory} · chat ${result.totals.chat}
|
|
21816
|
+
`;
|
|
21817
|
+
const rows = result.results.map((item) => {
|
|
21818
|
+
const sourceBits = [item.kind];
|
|
21819
|
+
if (item.type)
|
|
21820
|
+
sourceBits.push(item.type);
|
|
21821
|
+
if (item.role)
|
|
21822
|
+
sourceBits.push(item.role);
|
|
21823
|
+
if (item.source_kind)
|
|
21824
|
+
sourceBits.push(item.source_kind);
|
|
21825
|
+
const idBit = item.observation_id ? `#${item.observation_id}` : item.id ? `chat:${item.id}` : "";
|
|
21826
|
+
const title = `${idBit ? `${idBit} ` : ""}${item.title}${item.project_name ? ` (${item.project_name})` : ""}`;
|
|
21827
|
+
return `- [${sourceBits.join(" · ")}] ${title}
|
|
21828
|
+
${item.detail.slice(0, 220)}`;
|
|
21829
|
+
}).join(`
|
|
21830
|
+
`);
|
|
21831
|
+
return {
|
|
21832
|
+
content: [
|
|
21833
|
+
{
|
|
21834
|
+
type: "text",
|
|
21835
|
+
text: `${projectLine}${summaryLine}Recall search for "${params.query}":
|
|
21836
|
+
${rows}`
|
|
21837
|
+
}
|
|
21838
|
+
]
|
|
21839
|
+
};
|
|
21840
|
+
});
|
|
21548
21841
|
server.tool("get_observations", "Get observations by ID", {
|
|
21549
21842
|
ids: exports_external.array(exports_external.number()).describe("Observation IDs")
|
|
21550
21843
|
}, async (params) => {
|
|
@@ -21865,6 +22158,7 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
21865
22158
|
{
|
|
21866
22159
|
type: "text",
|
|
21867
22160
|
text: `${projectLine}` + `${captureLine}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
22161
|
+
` + `Chat recall: ${result.chat_coverage_state} · ${result.recent_chat.length} messages across ${result.recent_chat_sessions} sessions (transcript ${result.chat_source_summary.transcript}, hook ${result.chat_source_summary.hook})
|
|
21868
22162
|
` + `${typeof result.assistant_checkpoint_count === "number" ? `Assistant checkpoints: ${result.assistant_checkpoint_count}
|
|
21869
22163
|
` : ""}` + `Handoffs: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
|
|
21870
22164
|
` + `${typeof result.estimated_read_tokens === "number" ? `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
@@ -22071,6 +22365,7 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
22071
22365
|
` + `Recent handoffs: ${result.recent_handoffs}
|
|
22072
22366
|
` + `Handoff split: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
|
|
22073
22367
|
` + `Recent chat messages: ${result.recent_chat_messages}
|
|
22368
|
+
` + `Chat recall: ${result.chat_coverage_state} · ${result.recent_chat_sessions} sessions (transcript ${result.chat_source_summary.transcript}, hook ${result.chat_source_summary.hook})
|
|
22074
22369
|
` + `Latest handoff: ${result.latest_handoff_title ?? "(none)"}
|
|
22075
22370
|
` + `Raw chronology active: ${result.raw_capture_active ? "yes" : "no"}
|
|
22076
22371
|
|
|
@@ -22150,6 +22445,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
22150
22445
|
` + `Recent handoffs captured: ${result.recent_handoffs_count}
|
|
22151
22446
|
` + `Handoff split: ${result.saved_handoffs_count} saved, ${result.rolling_handoff_drafts_count} rolling drafts
|
|
22152
22447
|
` + `Recent chat messages captured: ${result.recent_chat_count}
|
|
22448
|
+
` + `Chat recall: ${result.chat_coverage_state} · ${result.recent_chat_sessions} sessions (transcript ${result.chat_source_summary.transcript}, hook ${result.chat_source_summary.hook})
|
|
22153
22449
|
|
|
22154
22450
|
` + `Raw chronology: ${result.raw_capture_active ? "active" : "observations-only so far"}
|
|
22155
22451
|
|
|
@@ -22321,7 +22617,7 @@ server.tool("refresh_chat_recall", "Hydrate the separate chat lane from the curr
|
|
|
22321
22617
|
content: [{ type: "text", text: "No session available to hydrate chat recall from." }]
|
|
22322
22618
|
};
|
|
22323
22619
|
}
|
|
22324
|
-
const result = syncTranscriptChat(db, config2, sessionId, cwd, params.transcript_path);
|
|
22620
|
+
const result = await syncTranscriptChat(db, config2, sessionId, cwd, params.transcript_path);
|
|
22325
22621
|
return {
|
|
22326
22622
|
content: [
|
|
22327
22623
|
{
|
|
@@ -22437,10 +22733,10 @@ server.tool("search_chat", "Search the separate chat lane without mixing it into
|
|
|
22437
22733
|
cwd: exports_external.string().optional(),
|
|
22438
22734
|
user_id: exports_external.string().optional()
|
|
22439
22735
|
}, async (params) => {
|
|
22440
|
-
const result = searchChat(db, params);
|
|
22736
|
+
const result = await searchChat(db, params);
|
|
22441
22737
|
const projectLine = result.project ? `Project: ${result.project}
|
|
22442
22738
|
` : "";
|
|
22443
|
-
const coverageLine = `Coverage: ${result.messages.length} matches across ${result.session_count} session${result.session_count === 1 ? "" : "s"} ` + `· transcript ${result.source_summary.transcript} · hook ${result.source_summary.hook}
|
|
22739
|
+
const coverageLine = `Coverage: ${result.messages.length} matches across ${result.session_count} session${result.session_count === 1 ? "" : "s"} ` + `· transcript ${result.source_summary.transcript} · hook ${result.source_summary.hook}` + `${result.semantic_backed ? " · semantic yes" : ""}
|
|
22444
22740
|
` + `${result.transcript_backed ? "" : `Hint: run refresh_chat_recall if this looks under-captured.
|
|
22445
22741
|
`}`;
|
|
22446
22742
|
const rows = result.messages.length > 0 ? result.messages.map((msg) => {
|
|
@@ -22554,7 +22850,7 @@ server.tool("session_story", "Show the full local memory story for one session",
|
|
|
22554
22850
|
`) : "(none)";
|
|
22555
22851
|
const promptLines = result.prompts.length > 0 ? result.prompts.map((prompt) => `- #${prompt.prompt_number} ${prompt.prompt.replace(/\s+/g, " ").trim()}`).join(`
|
|
22556
22852
|
`) : "- (none)";
|
|
22557
|
-
const chatLines = result.chat_messages.length > 0 ? result.chat_messages.slice(-12).map((msg) => `- [${msg.role}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`).join(`
|
|
22853
|
+
const chatLines = result.chat_messages.length > 0 ? result.chat_messages.slice(-12).map((msg) => `- [${msg.role}] [${msg.source_kind}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`).join(`
|
|
22558
22854
|
`) : "- (none)";
|
|
22559
22855
|
const toolLines = result.tool_events.length > 0 ? result.tool_events.slice(-15).map((tool) => {
|
|
22560
22856
|
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
@@ -22596,6 +22892,8 @@ ${summaryLines}
|
|
|
22596
22892
|
` + `Prompts:
|
|
22597
22893
|
${promptLines}
|
|
22598
22894
|
|
|
22895
|
+
` + `Chat recall: ${result.chat_coverage_state} (transcript ${result.chat_source_summary.transcript}, hook ${result.chat_source_summary.hook})
|
|
22896
|
+
|
|
22599
22897
|
` + `Chat:
|
|
22600
22898
|
${chatLines}
|
|
22601
22899
|
|