engrm 0.4.25 → 0.4.27
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 +9 -4
- package/dist/hooks/post-tool-use.js +8 -0
- package/dist/hooks/pre-compact.js +55 -11
- package/dist/hooks/session-start.js +909 -133
- package/dist/hooks/stop.js +9 -1
- package/dist/hooks/user-prompt-submit.js +8 -0
- package/dist/server.js +415 -68
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -16321,6 +16321,139 @@ function sanitizeFtsQuery(query) {
|
|
|
16321
16321
|
return safe;
|
|
16322
16322
|
}
|
|
16323
16323
|
|
|
16324
|
+
// src/tools/search-chat.ts
|
|
16325
|
+
function searchChat(db, input) {
|
|
16326
|
+
const limit = Math.max(1, Math.min(input.limit ?? 20, 100));
|
|
16327
|
+
const projectScoped = input.project_scoped !== false;
|
|
16328
|
+
let projectId = null;
|
|
16329
|
+
let projectName;
|
|
16330
|
+
if (projectScoped) {
|
|
16331
|
+
const cwd = input.cwd ?? process.cwd();
|
|
16332
|
+
const detected = detectProject(cwd);
|
|
16333
|
+
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
16334
|
+
if (project) {
|
|
16335
|
+
projectId = project.id;
|
|
16336
|
+
projectName = project.name;
|
|
16337
|
+
}
|
|
16338
|
+
}
|
|
16339
|
+
const messages = db.searchChatMessages(input.query, projectId, limit, input.user_id);
|
|
16340
|
+
return {
|
|
16341
|
+
messages,
|
|
16342
|
+
project: projectName,
|
|
16343
|
+
session_count: countDistinctSessions(messages),
|
|
16344
|
+
source_summary: summarizeChatSources(messages),
|
|
16345
|
+
transcript_backed: messages.some((message) => message.source_kind === "transcript")
|
|
16346
|
+
};
|
|
16347
|
+
}
|
|
16348
|
+
function summarizeChatSources(messages) {
|
|
16349
|
+
return messages.reduce((summary, message) => {
|
|
16350
|
+
summary[message.source_kind] += 1;
|
|
16351
|
+
return summary;
|
|
16352
|
+
}, { transcript: 0, hook: 0 });
|
|
16353
|
+
}
|
|
16354
|
+
function countDistinctSessions(messages) {
|
|
16355
|
+
return new Set(messages.map((message) => message.session_id)).size;
|
|
16356
|
+
}
|
|
16357
|
+
|
|
16358
|
+
// src/tools/search-recall.ts
|
|
16359
|
+
async function searchRecall(db, input) {
|
|
16360
|
+
const query = input.query.trim();
|
|
16361
|
+
if (!query) {
|
|
16362
|
+
return {
|
|
16363
|
+
query,
|
|
16364
|
+
results: [],
|
|
16365
|
+
totals: { memory: 0, chat: 0 }
|
|
16366
|
+
};
|
|
16367
|
+
}
|
|
16368
|
+
const limit = Math.max(1, Math.min(input.limit ?? 10, 50));
|
|
16369
|
+
const [memory, chat] = await Promise.all([
|
|
16370
|
+
searchObservations(db, input),
|
|
16371
|
+
Promise.resolve(searchChat(db, {
|
|
16372
|
+
query,
|
|
16373
|
+
limit: limit * 2,
|
|
16374
|
+
project_scoped: input.project_scoped,
|
|
16375
|
+
cwd: input.cwd,
|
|
16376
|
+
user_id: input.user_id
|
|
16377
|
+
}))
|
|
16378
|
+
]);
|
|
16379
|
+
const merged = mergeRecallResults(memory.observations, chat.messages, limit);
|
|
16380
|
+
return {
|
|
16381
|
+
query,
|
|
16382
|
+
project: memory.project ?? chat.project,
|
|
16383
|
+
results: merged,
|
|
16384
|
+
totals: {
|
|
16385
|
+
memory: memory.total,
|
|
16386
|
+
chat: chat.messages.length
|
|
16387
|
+
}
|
|
16388
|
+
};
|
|
16389
|
+
}
|
|
16390
|
+
function mergeRecallResults(memory, chat, limit) {
|
|
16391
|
+
const nowEpoch = Math.floor(Date.now() / 1000);
|
|
16392
|
+
const scored = [];
|
|
16393
|
+
for (let index = 0;index < memory.length; index++) {
|
|
16394
|
+
const item = memory[index];
|
|
16395
|
+
const base = 1 / (60 + index + 1);
|
|
16396
|
+
const score = base + Math.max(0, item.rank) * 0.08;
|
|
16397
|
+
scored.push({
|
|
16398
|
+
kind: "memory",
|
|
16399
|
+
rank: score,
|
|
16400
|
+
created_at: item.created_at,
|
|
16401
|
+
created_at_epoch: Math.floor(new Date(item.created_at).getTime() / 1000) || undefined,
|
|
16402
|
+
project_name: item.project_name,
|
|
16403
|
+
observation_id: item.id,
|
|
16404
|
+
id: item.id,
|
|
16405
|
+
session_id: null,
|
|
16406
|
+
type: item.type,
|
|
16407
|
+
title: item.title,
|
|
16408
|
+
detail: firstNonEmpty(item.narrative, parseFactsPreview(item.facts), item.files_modified ? `Files: ${item.files_modified}` : null, item.type) ?? item.type
|
|
16409
|
+
});
|
|
16410
|
+
}
|
|
16411
|
+
for (let index = 0;index < chat.length; index++) {
|
|
16412
|
+
const item = chat[index];
|
|
16413
|
+
const base = 1 / (60 + index + 1);
|
|
16414
|
+
const ageHours = Math.max(0, (nowEpoch - item.created_at_epoch) / 3600);
|
|
16415
|
+
const immediacyBoost = ageHours < 1 ? 1 : 0;
|
|
16416
|
+
const recencyBoost = ageHours < 24 ? 0.12 : ageHours < 72 ? 0.05 : 0.02;
|
|
16417
|
+
const sourceBoost = item.source_kind === "transcript" ? 0.06 : 0.03;
|
|
16418
|
+
scored.push({
|
|
16419
|
+
kind: "chat",
|
|
16420
|
+
rank: base + immediacyBoost + recencyBoost + sourceBoost,
|
|
16421
|
+
created_at_epoch: item.created_at_epoch,
|
|
16422
|
+
session_id: item.session_id,
|
|
16423
|
+
id: item.id,
|
|
16424
|
+
role: item.role,
|
|
16425
|
+
source_kind: item.source_kind,
|
|
16426
|
+
title: `${item.role} [${item.source_kind}]`,
|
|
16427
|
+
detail: item.content.replace(/\s+/g, " ").trim()
|
|
16428
|
+
});
|
|
16429
|
+
}
|
|
16430
|
+
return scored.sort((a, b) => {
|
|
16431
|
+
if (b.rank !== a.rank)
|
|
16432
|
+
return b.rank - a.rank;
|
|
16433
|
+
return (b.created_at_epoch ?? 0) - (a.created_at_epoch ?? 0);
|
|
16434
|
+
}).slice(0, limit);
|
|
16435
|
+
}
|
|
16436
|
+
function parseFactsPreview(facts) {
|
|
16437
|
+
if (!facts)
|
|
16438
|
+
return null;
|
|
16439
|
+
try {
|
|
16440
|
+
const parsed = JSON.parse(facts);
|
|
16441
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
16442
|
+
return null;
|
|
16443
|
+
const lines = parsed.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
16444
|
+
return lines.length > 0 ? lines.slice(0, 2).join(" | ") : null;
|
|
16445
|
+
} catch {
|
|
16446
|
+
return facts;
|
|
16447
|
+
}
|
|
16448
|
+
}
|
|
16449
|
+
function firstNonEmpty(...values) {
|
|
16450
|
+
for (const value of values) {
|
|
16451
|
+
if (value && value.trim().length > 0)
|
|
16452
|
+
return value.trim();
|
|
16453
|
+
}
|
|
16454
|
+
return null;
|
|
16455
|
+
}
|
|
16456
|
+
|
|
16324
16457
|
// src/tools/get.ts
|
|
16325
16458
|
function getObservations(db, input) {
|
|
16326
16459
|
if (input.ids.length === 0) {
|
|
@@ -16458,8 +16591,12 @@ function getRecentRequests(db, input) {
|
|
|
16458
16591
|
function getRecentChat(db, input) {
|
|
16459
16592
|
const limit = Math.max(1, Math.min(input.limit ?? 20, 100));
|
|
16460
16593
|
if (input.session_id) {
|
|
16594
|
+
const messages2 = db.getSessionChatMessages(input.session_id, limit).slice(-limit).reverse();
|
|
16461
16595
|
return {
|
|
16462
|
-
messages:
|
|
16596
|
+
messages: messages2,
|
|
16597
|
+
session_count: countDistinctSessions2(messages2),
|
|
16598
|
+
source_summary: summarizeChatSources2(messages2),
|
|
16599
|
+
transcript_backed: messages2.some((message) => message.source_kind === "transcript")
|
|
16463
16600
|
};
|
|
16464
16601
|
}
|
|
16465
16602
|
const projectScoped = input.project_scoped !== false;
|
|
@@ -16474,31 +16611,23 @@ function getRecentChat(db, input) {
|
|
|
16474
16611
|
projectName = project.name;
|
|
16475
16612
|
}
|
|
16476
16613
|
}
|
|
16614
|
+
const messages = db.getRecentChatMessages(projectId, limit, input.user_id);
|
|
16477
16615
|
return {
|
|
16478
|
-
messages
|
|
16479
|
-
project: projectName
|
|
16616
|
+
messages,
|
|
16617
|
+
project: projectName,
|
|
16618
|
+
session_count: countDistinctSessions2(messages),
|
|
16619
|
+
source_summary: summarizeChatSources2(messages),
|
|
16620
|
+
transcript_backed: messages.some((message) => message.source_kind === "transcript")
|
|
16480
16621
|
};
|
|
16481
16622
|
}
|
|
16482
|
-
|
|
16483
|
-
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
const cwd = input.cwd ?? process.cwd();
|
|
16491
|
-
const detected = detectProject(cwd);
|
|
16492
|
-
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
16493
|
-
if (project) {
|
|
16494
|
-
projectId = project.id;
|
|
16495
|
-
projectName = project.name;
|
|
16496
|
-
}
|
|
16497
|
-
}
|
|
16498
|
-
return {
|
|
16499
|
-
messages: db.searchChatMessages(input.query, projectId, limit, input.user_id),
|
|
16500
|
-
project: projectName
|
|
16501
|
-
};
|
|
16623
|
+
function summarizeChatSources2(messages) {
|
|
16624
|
+
return messages.reduce((summary, message) => {
|
|
16625
|
+
summary[message.source_kind] += 1;
|
|
16626
|
+
return summary;
|
|
16627
|
+
}, { transcript: 0, hook: 0 });
|
|
16628
|
+
}
|
|
16629
|
+
function countDistinctSessions2(messages) {
|
|
16630
|
+
return new Set(messages.map((message) => message.session_id)).size;
|
|
16502
16631
|
}
|
|
16503
16632
|
|
|
16504
16633
|
// src/tools/session-story.ts
|
|
@@ -16522,6 +16651,8 @@ function getSessionStory(db, input) {
|
|
|
16522
16651
|
summary,
|
|
16523
16652
|
prompts,
|
|
16524
16653
|
chat_messages: chatMessages,
|
|
16654
|
+
chat_source_summary: summarizeChatSources3(chatMessages),
|
|
16655
|
+
chat_coverage_state: chatMessages.some((message) => message.source_kind === "transcript") ? "transcript-backed" : chatMessages.length > 0 ? "hook-only" : "none",
|
|
16525
16656
|
tool_events: toolEvents,
|
|
16526
16657
|
observations,
|
|
16527
16658
|
handoffs,
|
|
@@ -16619,6 +16750,12 @@ function collectProvenanceSummary(observations) {
|
|
|
16619
16750
|
}
|
|
16620
16751
|
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);
|
|
16621
16752
|
}
|
|
16753
|
+
function summarizeChatSources3(messages) {
|
|
16754
|
+
return messages.reduce((summary, message) => {
|
|
16755
|
+
summary[message.source_kind] += 1;
|
|
16756
|
+
return summary;
|
|
16757
|
+
}, { transcript: 0, hook: 0 });
|
|
16758
|
+
}
|
|
16622
16759
|
|
|
16623
16760
|
// src/tools/handoffs.ts
|
|
16624
16761
|
async function createHandoff(db, config2, input) {
|
|
@@ -17230,6 +17367,7 @@ function findStaleDecisionsGlobal(db, options) {
|
|
|
17230
17367
|
}
|
|
17231
17368
|
|
|
17232
17369
|
// src/context/inject.ts
|
|
17370
|
+
var FRESH_CONTINUITY_WINDOW_DAYS = 3;
|
|
17233
17371
|
function tokenizeProjectHint(text) {
|
|
17234
17372
|
return Array.from(new Set((text.toLowerCase().match(/[a-z0-9_+-]{4,}/g) ?? []).filter(Boolean)));
|
|
17235
17373
|
}
|
|
@@ -17369,20 +17507,21 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
17369
17507
|
const canonicalId = project?.canonical_id ?? detected.canonical_id;
|
|
17370
17508
|
if (maxCount !== undefined) {
|
|
17371
17509
|
const remaining = Math.max(0, maxCount - pinned.length - dedupedRecent.length);
|
|
17372
|
-
|
|
17373
|
-
const recentPrompts2 =
|
|
17374
|
-
const recentToolEvents2 =
|
|
17510
|
+
let all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
|
|
17511
|
+
const recentPrompts2 = isNewProject ? [] : db.getRecentUserPrompts(projectId, 6, opts.userId);
|
|
17512
|
+
const recentToolEvents2 = isNewProject ? [] : db.getRecentToolEvents(projectId, 6, opts.userId);
|
|
17375
17513
|
const recentSessions2 = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
17376
17514
|
const projectTypeCounts2 = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
17377
17515
|
const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions2);
|
|
17378
|
-
const recentHandoffs2 = getRecentHandoffs(db, {
|
|
17516
|
+
const recentHandoffs2 = isNewProject ? [] : getRecentHandoffs(db, {
|
|
17379
17517
|
cwd,
|
|
17380
|
-
project_scoped:
|
|
17518
|
+
project_scoped: true,
|
|
17381
17519
|
user_id: opts.userId,
|
|
17382
17520
|
current_device_id: opts.currentDeviceId,
|
|
17383
17521
|
limit: 3
|
|
17384
17522
|
}).handoffs;
|
|
17385
17523
|
const recentChatMessages2 = !isNewProject && project ? db.getRecentChatMessages(project.id, 4, opts.userId) : [];
|
|
17524
|
+
all = filterAutoLoadedObservationsForContinuity(all, pinned, isNewProject, recentPrompts2, recentToolEvents2, recentSessions2, recentHandoffs2, recentChatMessages2, summariesFromRecentSessions(db, projectId, recentSessions2));
|
|
17386
17525
|
return {
|
|
17387
17526
|
project_name: projectName,
|
|
17388
17527
|
canonical_id: canonicalId,
|
|
@@ -17418,19 +17557,20 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
17418
17557
|
selected.push(obs);
|
|
17419
17558
|
}
|
|
17420
17559
|
const summaries = isNewProject ? [] : db.getRecentSummaries(projectId, 5);
|
|
17421
|
-
const recentPrompts =
|
|
17422
|
-
const recentToolEvents =
|
|
17560
|
+
const recentPrompts = isNewProject ? [] : db.getRecentUserPrompts(projectId, 6, opts.userId);
|
|
17561
|
+
const recentToolEvents = isNewProject ? [] : db.getRecentToolEvents(projectId, 6, opts.userId);
|
|
17423
17562
|
const recentSessions = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
17424
17563
|
const projectTypeCounts = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
17425
17564
|
const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions);
|
|
17426
|
-
const recentHandoffs = getRecentHandoffs(db, {
|
|
17565
|
+
const recentHandoffs = isNewProject ? [] : getRecentHandoffs(db, {
|
|
17427
17566
|
cwd,
|
|
17428
|
-
project_scoped:
|
|
17567
|
+
project_scoped: true,
|
|
17429
17568
|
user_id: opts.userId,
|
|
17430
17569
|
current_device_id: opts.currentDeviceId,
|
|
17431
17570
|
limit: 3
|
|
17432
17571
|
}).handoffs;
|
|
17433
17572
|
const recentChatMessages = !isNewProject ? db.getRecentChatMessages(projectId, 4, opts.userId) : [];
|
|
17573
|
+
const filteredSelected = filterAutoLoadedObservationsForContinuity(selected, pinned, isNewProject, recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries);
|
|
17434
17574
|
let securityFindings = [];
|
|
17435
17575
|
if (!isNewProject) {
|
|
17436
17576
|
try {
|
|
@@ -17478,8 +17618,8 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
17478
17618
|
return {
|
|
17479
17619
|
project_name: projectName,
|
|
17480
17620
|
canonical_id: canonicalId,
|
|
17481
|
-
observations:
|
|
17482
|
-
session_count:
|
|
17621
|
+
observations: filteredSelected.map(toContextObservation),
|
|
17622
|
+
session_count: filteredSelected.length,
|
|
17483
17623
|
total_active: totalActive,
|
|
17484
17624
|
summaries: summaries.length > 0 ? summaries : undefined,
|
|
17485
17625
|
securityFindings: securityFindings.length > 0 ? securityFindings : undefined,
|
|
@@ -17494,6 +17634,39 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
17494
17634
|
recentChatMessages: recentChatMessages.length > 0 ? recentChatMessages : undefined
|
|
17495
17635
|
};
|
|
17496
17636
|
}
|
|
17637
|
+
function filterAutoLoadedObservationsForContinuity(observations, pinned, isNewProject, recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries) {
|
|
17638
|
+
if (isNewProject)
|
|
17639
|
+
return observations;
|
|
17640
|
+
if (hasFreshProjectContinuity(recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries)) {
|
|
17641
|
+
return observations;
|
|
17642
|
+
}
|
|
17643
|
+
const pinnedIds = new Set(pinned.map((obs) => obs.id));
|
|
17644
|
+
return observations.filter((obs) => {
|
|
17645
|
+
if (pinnedIds.has(obs.id))
|
|
17646
|
+
return true;
|
|
17647
|
+
return observationAgeDays(obs.created_at_epoch) <= FRESH_CONTINUITY_WINDOW_DAYS;
|
|
17648
|
+
});
|
|
17649
|
+
}
|
|
17650
|
+
function hasFreshProjectContinuity(recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries) {
|
|
17651
|
+
const freshEnough = (epoch) => typeof epoch === "number" && observationAgeDays(epoch) <= FRESH_CONTINUITY_WINDOW_DAYS;
|
|
17652
|
+
return recentPrompts.some((item) => freshEnough(item.created_at_epoch)) || recentToolEvents.some((item) => freshEnough(item.created_at_epoch)) || recentSessions.some((item) => freshEnough(item.completed_at_epoch ?? item.started_at_epoch)) || recentHandoffs.some((item) => freshEnough(item.created_at_epoch)) || recentChatMessages.some((item) => freshEnough(item.created_at_epoch)) || summaries.some((item) => freshEnough(item.created_at_epoch));
|
|
17653
|
+
}
|
|
17654
|
+
function summariesFromRecentSessions(db, projectId, recentSessions) {
|
|
17655
|
+
const seen = new Set;
|
|
17656
|
+
const rows = [];
|
|
17657
|
+
for (const session of recentSessions) {
|
|
17658
|
+
if (seen.has(session.session_id))
|
|
17659
|
+
continue;
|
|
17660
|
+
seen.add(session.session_id);
|
|
17661
|
+
const summary = db.getSessionSummary(session.session_id);
|
|
17662
|
+
if (summary && summary.project_id === projectId)
|
|
17663
|
+
rows.push(summary);
|
|
17664
|
+
}
|
|
17665
|
+
return rows;
|
|
17666
|
+
}
|
|
17667
|
+
function observationAgeDays(createdAtEpoch) {
|
|
17668
|
+
return Math.max(0, (Math.floor(Date.now() / 1000) - createdAtEpoch) / 86400);
|
|
17669
|
+
}
|
|
17497
17670
|
function estimateObservationTokens(obs, index) {
|
|
17498
17671
|
const DETAILED_THRESHOLD = 5;
|
|
17499
17672
|
const titleCost = estimateTokens(`- **[${obs.type}]** ${obs.title} (2026-01-01, q=0.5)`);
|
|
@@ -17929,16 +18102,17 @@ function getProjectMemoryIndex(db, input) {
|
|
|
17929
18102
|
}).handoffs;
|
|
17930
18103
|
const rollingHandoffDraftsCount = recentHandoffsCount.filter((handoff) => isDraftHandoff(handoff)).length;
|
|
17931
18104
|
const savedHandoffsCount = recentHandoffsCount.length - rollingHandoffDraftsCount;
|
|
17932
|
-
const
|
|
18105
|
+
const recentChat = getRecentChat(db, {
|
|
17933
18106
|
cwd,
|
|
17934
18107
|
project_scoped: true,
|
|
17935
18108
|
user_id: input.user_id,
|
|
17936
18109
|
limit: 20
|
|
17937
|
-
})
|
|
18110
|
+
});
|
|
18111
|
+
const recentChatCount = recentChat.messages.length;
|
|
17938
18112
|
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);
|
|
17939
18113
|
const captureSummary = summarizeCaptureState(recentSessions);
|
|
17940
18114
|
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);
|
|
17941
|
-
const suggestedTools = buildSuggestedTools(recentSessions, recentRequestsCount, recentToolsCount, observations.length);
|
|
18115
|
+
const suggestedTools = buildSuggestedTools(recentSessions, recentRequestsCount, recentToolsCount, observations.length, recentChatCount, recentChat.transcript_backed);
|
|
17942
18116
|
const estimatedReadTokens = estimateTokens([
|
|
17943
18117
|
recentOutcomes.join(`
|
|
17944
18118
|
`),
|
|
@@ -17948,9 +18122,12 @@ function getProjectMemoryIndex(db, input) {
|
|
|
17948
18122
|
`)
|
|
17949
18123
|
].filter(Boolean).join(`
|
|
17950
18124
|
`));
|
|
18125
|
+
const continuityState = classifyContinuityState(recentRequestsCount, recentToolsCount, recentHandoffsCount.length, recentChatCount, recentSessions, recentOutcomes.length);
|
|
17951
18126
|
return {
|
|
17952
18127
|
project: project.name,
|
|
17953
18128
|
canonical_id: project.canonical_id,
|
|
18129
|
+
continuity_state: continuityState,
|
|
18130
|
+
continuity_summary: describeContinuityState(continuityState),
|
|
17954
18131
|
observation_counts: counts,
|
|
17955
18132
|
recent_sessions: recentSessions,
|
|
17956
18133
|
recent_outcomes: recentOutcomes,
|
|
@@ -17960,6 +18137,9 @@ function getProjectMemoryIndex(db, input) {
|
|
|
17960
18137
|
rolling_handoff_drafts_count: rollingHandoffDraftsCount,
|
|
17961
18138
|
saved_handoffs_count: savedHandoffsCount,
|
|
17962
18139
|
recent_chat_count: recentChatCount,
|
|
18140
|
+
recent_chat_sessions: recentChat.session_count,
|
|
18141
|
+
chat_source_summary: recentChat.source_summary,
|
|
18142
|
+
chat_coverage_state: recentChat.transcript_backed ? "transcript-backed" : recentChatCount > 0 ? "hook-only" : "none",
|
|
17963
18143
|
raw_capture_active: recentRequestsCount > 0 || recentToolsCount > 0,
|
|
17964
18144
|
capture_summary: captureSummary,
|
|
17965
18145
|
hot_files: hotFiles,
|
|
@@ -17972,6 +18152,26 @@ function getProjectMemoryIndex(db, input) {
|
|
|
17972
18152
|
suggested_tools: suggestedTools
|
|
17973
18153
|
};
|
|
17974
18154
|
}
|
|
18155
|
+
function classifyContinuityState(recentRequestsCount, recentToolsCount, recentHandoffsCount, recentChatCount, recentSessions, recentOutcomesCount) {
|
|
18156
|
+
const hasRaw = recentRequestsCount > 0 || recentToolsCount > 0;
|
|
18157
|
+
const hasResume = recentHandoffsCount > 0 || recentChatCount > 0;
|
|
18158
|
+
const hasSessionThread = recentSessions.length > 0 || recentOutcomesCount > 0;
|
|
18159
|
+
if (hasRaw && (hasResume || hasSessionThread))
|
|
18160
|
+
return "fresh";
|
|
18161
|
+
if (hasRaw || hasResume || hasSessionThread)
|
|
18162
|
+
return "thin";
|
|
18163
|
+
return "cold";
|
|
18164
|
+
}
|
|
18165
|
+
function describeContinuityState(state) {
|
|
18166
|
+
switch (state) {
|
|
18167
|
+
case "fresh":
|
|
18168
|
+
return "Fresh repo-local continuity is available.";
|
|
18169
|
+
case "thin":
|
|
18170
|
+
return "Only partial continuity is available; recent prompts/chat are safer than older memory.";
|
|
18171
|
+
default:
|
|
18172
|
+
return "No fresh repo-local continuity yet; older memory should be treated cautiously.";
|
|
18173
|
+
}
|
|
18174
|
+
}
|
|
17975
18175
|
function extractPaths(value) {
|
|
17976
18176
|
if (!value)
|
|
17977
18177
|
return [];
|
|
@@ -18012,7 +18212,7 @@ function summarizeCaptureState(sessions) {
|
|
|
18012
18212
|
}
|
|
18013
18213
|
return summary;
|
|
18014
18214
|
}
|
|
18015
|
-
function buildSuggestedTools(sessions, requestCount, toolCount, observationCount) {
|
|
18215
|
+
function buildSuggestedTools(sessions, requestCount, toolCount, observationCount, recentChatCount, transcriptBackedChat) {
|
|
18016
18216
|
const suggested = [];
|
|
18017
18217
|
if (sessions.length > 0) {
|
|
18018
18218
|
suggested.push("recent_sessions");
|
|
@@ -18026,7 +18226,12 @@ function buildSuggestedTools(sessions, requestCount, toolCount, observationCount
|
|
|
18026
18226
|
if (sessions.length > 0) {
|
|
18027
18227
|
suggested.push("create_handoff", "recent_handoffs");
|
|
18028
18228
|
}
|
|
18029
|
-
|
|
18229
|
+
if (recentChatCount > 0 && !transcriptBackedChat) {
|
|
18230
|
+
suggested.push("refresh_chat_recall");
|
|
18231
|
+
}
|
|
18232
|
+
if (recentChatCount > 0) {
|
|
18233
|
+
suggested.push("recent_chat", "search_chat");
|
|
18234
|
+
}
|
|
18030
18235
|
return Array.from(new Set(suggested)).slice(0, 4);
|
|
18031
18236
|
}
|
|
18032
18237
|
|
|
@@ -18073,21 +18278,27 @@ function getMemoryConsole(db, input) {
|
|
|
18073
18278
|
project_scoped: projectScoped,
|
|
18074
18279
|
user_id: input.user_id,
|
|
18075
18280
|
limit: 6
|
|
18076
|
-
})
|
|
18281
|
+
});
|
|
18077
18282
|
const projectIndex = projectScoped ? getProjectMemoryIndex(db, {
|
|
18078
18283
|
cwd,
|
|
18079
18284
|
user_id: input.user_id
|
|
18080
18285
|
}) : null;
|
|
18286
|
+
const continuityState = projectIndex?.continuity_state ?? classifyContinuityState(requests.length, tools.length, recentHandoffs.length, recentChat.messages.length, sessions, (projectIndex?.recent_outcomes ?? []).length);
|
|
18081
18287
|
return {
|
|
18082
18288
|
project: project?.name,
|
|
18083
18289
|
capture_mode: requests.length > 0 || tools.length > 0 ? "rich" : "observations-only",
|
|
18290
|
+
continuity_state: continuityState,
|
|
18291
|
+
continuity_summary: projectIndex?.continuity_summary ?? describeContinuityState(continuityState),
|
|
18084
18292
|
sessions,
|
|
18085
18293
|
requests,
|
|
18086
18294
|
tools,
|
|
18087
18295
|
recent_handoffs: recentHandoffs,
|
|
18088
18296
|
rolling_handoff_drafts: rollingHandoffDrafts,
|
|
18089
18297
|
saved_handoffs: savedHandoffs,
|
|
18090
|
-
recent_chat: recentChat,
|
|
18298
|
+
recent_chat: recentChat.messages,
|
|
18299
|
+
recent_chat_sessions: projectIndex?.recent_chat_sessions ?? recentChat.session_count,
|
|
18300
|
+
chat_source_summary: projectIndex?.chat_source_summary ?? recentChat.source_summary,
|
|
18301
|
+
chat_coverage_state: projectIndex?.chat_coverage_state ?? (recentChat.transcript_backed ? "transcript-backed" : recentChat.messages.length > 0 ? "hook-only" : "none"),
|
|
18091
18302
|
observations,
|
|
18092
18303
|
capture_summary: projectIndex?.capture_summary,
|
|
18093
18304
|
recent_outcomes: projectIndex?.recent_outcomes ?? [],
|
|
@@ -18097,10 +18308,10 @@ function getMemoryConsole(db, input) {
|
|
|
18097
18308
|
assistant_checkpoint_types: projectIndex?.assistant_checkpoint_types ?? [],
|
|
18098
18309
|
top_types: projectIndex?.top_types ?? [],
|
|
18099
18310
|
estimated_read_tokens: projectIndex?.estimated_read_tokens,
|
|
18100
|
-
suggested_tools: projectIndex?.suggested_tools ?? buildFallbackSuggestedTools(sessions.length, requests.length, tools.length, observations.length, recentHandoffs.length, recentChat.length)
|
|
18311
|
+
suggested_tools: projectIndex?.suggested_tools ?? buildFallbackSuggestedTools(sessions.length, requests.length, tools.length, observations.length, recentHandoffs.length, recentChat.messages.length, recentChat.transcript_backed)
|
|
18101
18312
|
};
|
|
18102
18313
|
}
|
|
18103
|
-
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount) {
|
|
18314
|
+
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, transcriptBackedChat) {
|
|
18104
18315
|
const suggested = [];
|
|
18105
18316
|
if (sessionCount > 0)
|
|
18106
18317
|
suggested.push("recent_sessions");
|
|
@@ -18112,8 +18323,10 @@ function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, obse
|
|
|
18112
18323
|
suggested.push("create_handoff", "recent_handoffs");
|
|
18113
18324
|
if (handoffCount > 0)
|
|
18114
18325
|
suggested.push("load_handoff");
|
|
18326
|
+
if (chatCount > 0 && !transcriptBackedChat)
|
|
18327
|
+
suggested.push("refresh_chat_recall");
|
|
18115
18328
|
if (chatCount > 0)
|
|
18116
|
-
suggested.push("recent_chat");
|
|
18329
|
+
suggested.push("recent_chat", "search_chat");
|
|
18117
18330
|
return Array.from(new Set(suggested)).slice(0, 4);
|
|
18118
18331
|
}
|
|
18119
18332
|
|
|
@@ -18336,7 +18549,7 @@ function toChatEvent(message) {
|
|
|
18336
18549
|
created_at_epoch: message.created_at_epoch,
|
|
18337
18550
|
session_id: message.session_id,
|
|
18338
18551
|
id: message.id,
|
|
18339
|
-
title: message.role
|
|
18552
|
+
title: `${message.role} [${message.source_kind}]`,
|
|
18340
18553
|
detail: content.slice(0, 220)
|
|
18341
18554
|
};
|
|
18342
18555
|
}
|
|
@@ -18622,16 +18835,65 @@ function getCaptureQuality(db, input = {}) {
|
|
|
18622
18835
|
summary_only: allSessions.filter((s) => s.capture_state === "summary-only").length,
|
|
18623
18836
|
legacy: allSessions.filter((s) => s.capture_state === "legacy").length
|
|
18624
18837
|
};
|
|
18625
|
-
const
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
18629
|
-
|
|
18630
|
-
|
|
18631
|
-
|
|
18632
|
-
|
|
18633
|
-
|
|
18634
|
-
|
|
18838
|
+
const chatCoverageRow = db.db.query(`SELECT
|
|
18839
|
+
COUNT(DISTINCT CASE WHEN source_kind = 'transcript' THEN session_id END) as transcript_backed_sessions,
|
|
18840
|
+
COUNT(DISTINCT CASE
|
|
18841
|
+
WHEN source_kind = 'hook'
|
|
18842
|
+
AND NOT EXISTS (
|
|
18843
|
+
SELECT 1 FROM chat_messages t2
|
|
18844
|
+
WHERE t2.session_id = chat_messages.session_id
|
|
18845
|
+
AND t2.source_kind = 'transcript'
|
|
18846
|
+
)
|
|
18847
|
+
THEN session_id
|
|
18848
|
+
END) as hook_only_sessions,
|
|
18849
|
+
COUNT(*) as chat_messages
|
|
18850
|
+
FROM chat_messages
|
|
18851
|
+
WHERE 1 = 1
|
|
18852
|
+
${input.user_id ? " AND user_id = ?" : ""}`).get(...input.user_id ? [input.user_id] : []) ?? {
|
|
18853
|
+
transcript_backed_sessions: 0,
|
|
18854
|
+
hook_only_sessions: 0,
|
|
18855
|
+
chat_messages: 0
|
|
18856
|
+
};
|
|
18857
|
+
const chatCoverageByProject = new Map;
|
|
18858
|
+
const chatRows = db.db.query(`SELECT p.canonical_id, cm.source_kind, COUNT(*) as count
|
|
18859
|
+
FROM chat_messages cm
|
|
18860
|
+
JOIN projects p ON p.id = cm.project_id
|
|
18861
|
+
WHERE cm.project_id IS NOT NULL
|
|
18862
|
+
${input.user_id ? " AND cm.user_id = ?" : ""}
|
|
18863
|
+
GROUP BY p.canonical_id, cm.source_kind`).all(...input.user_id ? [input.user_id] : []);
|
|
18864
|
+
for (const row of chatRows) {
|
|
18865
|
+
const current = chatCoverageByProject.get(row.canonical_id) ?? {
|
|
18866
|
+
chat_message_count: 0,
|
|
18867
|
+
transcript_count: 0,
|
|
18868
|
+
hook_only_count: 0
|
|
18869
|
+
};
|
|
18870
|
+
current.chat_message_count += row.count;
|
|
18871
|
+
if (row.source_kind === "transcript") {
|
|
18872
|
+
current.transcript_count += row.count;
|
|
18873
|
+
} else {
|
|
18874
|
+
current.hook_only_count += row.count;
|
|
18875
|
+
}
|
|
18876
|
+
chatCoverageByProject.set(row.canonical_id, current);
|
|
18877
|
+
}
|
|
18878
|
+
const topProjects = workspace.projects.slice(0, limit).map((project) => {
|
|
18879
|
+
const chat = chatCoverageByProject.get(project.canonical_id) ?? {
|
|
18880
|
+
chat_message_count: 0,
|
|
18881
|
+
transcript_count: 0,
|
|
18882
|
+
hook_only_count: 0
|
|
18883
|
+
};
|
|
18884
|
+
return {
|
|
18885
|
+
name: project.name,
|
|
18886
|
+
canonical_id: project.canonical_id,
|
|
18887
|
+
observation_count: project.observation_count,
|
|
18888
|
+
session_count: project.session_count,
|
|
18889
|
+
prompt_count: project.prompt_count,
|
|
18890
|
+
tool_event_count: project.tool_event_count,
|
|
18891
|
+
assistant_checkpoint_count: project.assistant_checkpoint_count,
|
|
18892
|
+
chat_message_count: chat.chat_message_count,
|
|
18893
|
+
chat_coverage_state: chat.transcript_count > 0 ? "transcript-backed" : chat.hook_only_count > 0 ? "hook-only" : "none",
|
|
18894
|
+
raw_capture_state: project.prompt_count > 0 && project.tool_event_count > 0 ? "rich" : project.prompt_count > 0 || project.tool_event_count > 0 ? "partial" : "summary-only"
|
|
18895
|
+
};
|
|
18896
|
+
});
|
|
18635
18897
|
const checkpointTypeRows = db.db.query(`SELECT type, COUNT(*) as count
|
|
18636
18898
|
FROM observations
|
|
18637
18899
|
WHERE source_tool = 'assistant-stop'
|
|
@@ -18666,9 +18928,14 @@ function getCaptureQuality(db, input = {}) {
|
|
|
18666
18928
|
sessions: workspace.totals.sessions,
|
|
18667
18929
|
prompts: workspace.totals.prompts,
|
|
18668
18930
|
tool_events: workspace.totals.tool_events,
|
|
18669
|
-
assistant_checkpoints: workspace.totals.assistant_checkpoints
|
|
18931
|
+
assistant_checkpoints: workspace.totals.assistant_checkpoints,
|
|
18932
|
+
chat_messages: chatCoverageRow.chat_messages
|
|
18670
18933
|
},
|
|
18671
18934
|
session_states: sessionStates,
|
|
18935
|
+
chat_coverage: {
|
|
18936
|
+
transcript_backed_sessions: chatCoverageRow.transcript_backed_sessions,
|
|
18937
|
+
hook_only_sessions: chatCoverageRow.hook_only_sessions
|
|
18938
|
+
},
|
|
18672
18939
|
projects_with_raw_capture: workspace.projects_with_raw_capture,
|
|
18673
18940
|
provenance_summary: workspace.provenance_summary,
|
|
18674
18941
|
provenance_type_mix: provenanceTypeMix,
|
|
@@ -18861,17 +19128,21 @@ function getSessionContext(db, input) {
|
|
|
18861
19128
|
const rollingHandoffDrafts = (context.recentHandoffs ?? []).filter((handoff) => handoff.title.startsWith("Handoff Draft:")).length;
|
|
18862
19129
|
const savedHandoffs = recentHandoffs - rollingHandoffDrafts;
|
|
18863
19130
|
const latestHandoffTitle = context.recentHandoffs?.[0]?.title ?? null;
|
|
18864
|
-
const
|
|
19131
|
+
const recentChat = getRecentChat(db, {
|
|
18865
19132
|
cwd,
|
|
18866
19133
|
project_scoped: true,
|
|
18867
19134
|
user_id: input.user_id,
|
|
18868
19135
|
limit: 8
|
|
18869
|
-
})
|
|
19136
|
+
});
|
|
19137
|
+
const recentChatMessages = recentChat.messages.length;
|
|
18870
19138
|
const captureState = recentRequests > 0 && recentTools > 0 ? "rich" : recentRequests > 0 || recentTools > 0 ? "partial" : "summary-only";
|
|
18871
19139
|
const hotFiles = buildHotFiles(context);
|
|
19140
|
+
const continuityState = classifyContinuityState(recentRequests, recentTools, recentHandoffs, recentChatMessages, context.recentSessions ?? [], (context.recentOutcomes ?? []).length);
|
|
18872
19141
|
return {
|
|
18873
19142
|
project_name: context.project_name,
|
|
18874
19143
|
canonical_id: context.canonical_id,
|
|
19144
|
+
continuity_state: continuityState,
|
|
19145
|
+
continuity_summary: describeContinuityState(continuityState),
|
|
18875
19146
|
session_count: context.session_count,
|
|
18876
19147
|
total_active: context.total_active,
|
|
18877
19148
|
recent_requests: recentRequests,
|
|
@@ -18882,12 +19153,15 @@ function getSessionContext(db, input) {
|
|
|
18882
19153
|
saved_handoffs: savedHandoffs,
|
|
18883
19154
|
latest_handoff_title: latestHandoffTitle,
|
|
18884
19155
|
recent_chat_messages: recentChatMessages,
|
|
19156
|
+
recent_chat_sessions: recentChat.session_count,
|
|
19157
|
+
chat_source_summary: recentChat.source_summary,
|
|
19158
|
+
chat_coverage_state: recentChat.transcript_backed ? "transcript-backed" : recentChatMessages > 0 ? "hook-only" : "none",
|
|
18885
19159
|
recent_outcomes: context.recentOutcomes ?? [],
|
|
18886
19160
|
hot_files: hotFiles,
|
|
18887
19161
|
capture_state: captureState,
|
|
18888
19162
|
raw_capture_active: recentRequests > 0 || recentTools > 0,
|
|
18889
19163
|
estimated_read_tokens: estimateTokens(preview),
|
|
18890
|
-
suggested_tools: buildSuggestedTools2(context),
|
|
19164
|
+
suggested_tools: buildSuggestedTools2(context, recentChat.transcript_backed),
|
|
18891
19165
|
preview
|
|
18892
19166
|
};
|
|
18893
19167
|
}
|
|
@@ -18910,7 +19184,7 @@ function parseJsonArray3(value) {
|
|
|
18910
19184
|
return [];
|
|
18911
19185
|
}
|
|
18912
19186
|
}
|
|
18913
|
-
function buildSuggestedTools2(context) {
|
|
19187
|
+
function buildSuggestedTools2(context, transcriptBackedChat) {
|
|
18914
19188
|
const tools = [];
|
|
18915
19189
|
if ((context.recentSessions?.length ?? 0) > 0) {
|
|
18916
19190
|
tools.push("recent_sessions");
|
|
@@ -18927,7 +19201,12 @@ function buildSuggestedTools2(context) {
|
|
|
18927
19201
|
if ((context.recentHandoffs?.length ?? 0) > 0) {
|
|
18928
19202
|
tools.push("load_handoff");
|
|
18929
19203
|
}
|
|
18930
|
-
|
|
19204
|
+
if ((context.recentChatMessages?.length ?? 0) > 0 && !transcriptBackedChat) {
|
|
19205
|
+
tools.push("refresh_chat_recall");
|
|
19206
|
+
}
|
|
19207
|
+
if ((context.recentChatMessages?.length ?? 0) > 0) {
|
|
19208
|
+
tools.push("recent_chat", "search_chat");
|
|
19209
|
+
}
|
|
18931
19210
|
return Array.from(new Set(tools)).slice(0, 4);
|
|
18932
19211
|
}
|
|
18933
19212
|
|
|
@@ -21003,7 +21282,7 @@ process.on("SIGTERM", () => {
|
|
|
21003
21282
|
});
|
|
21004
21283
|
var server = new McpServer({
|
|
21005
21284
|
name: "engrm",
|
|
21006
|
-
version: "0.4.
|
|
21285
|
+
version: "0.4.27"
|
|
21007
21286
|
});
|
|
21008
21287
|
server.tool("save_observation", "Save an observation to memory", {
|
|
21009
21288
|
type: exports_external.enum([
|
|
@@ -21396,6 +21675,58 @@ ${previews.join(`
|
|
|
21396
21675
|
]
|
|
21397
21676
|
};
|
|
21398
21677
|
});
|
|
21678
|
+
server.tool("search_recall", "Search live recall across durable memory and chat together. Best for questions like 'what were we just talking about?'", {
|
|
21679
|
+
query: exports_external.string().describe("Recall query"),
|
|
21680
|
+
project_scoped: exports_external.boolean().optional().describe("Scope to project (default: true)"),
|
|
21681
|
+
limit: exports_external.number().optional().describe("Max results (default: 10)"),
|
|
21682
|
+
cwd: exports_external.string().optional().describe("Optional cwd override for project-scoped recall"),
|
|
21683
|
+
user_id: exports_external.string().optional().describe("Optional user override")
|
|
21684
|
+
}, async (params) => {
|
|
21685
|
+
const result = await searchRecall(db, {
|
|
21686
|
+
query: params.query,
|
|
21687
|
+
project_scoped: params.project_scoped,
|
|
21688
|
+
limit: params.limit,
|
|
21689
|
+
cwd: params.cwd,
|
|
21690
|
+
user_id: params.user_id ?? config2.user_id
|
|
21691
|
+
});
|
|
21692
|
+
if (result.results.length === 0) {
|
|
21693
|
+
return {
|
|
21694
|
+
content: [
|
|
21695
|
+
{
|
|
21696
|
+
type: "text",
|
|
21697
|
+
text: result.project ? `No recall found for "${params.query}" in project ${result.project}` : `No recall found for "${params.query}"`
|
|
21698
|
+
}
|
|
21699
|
+
]
|
|
21700
|
+
};
|
|
21701
|
+
}
|
|
21702
|
+
const projectLine = result.project ? `Project: ${result.project}
|
|
21703
|
+
` : "";
|
|
21704
|
+
const summaryLine = `Matches: ${result.results.length} · memory ${result.totals.memory} · chat ${result.totals.chat}
|
|
21705
|
+
`;
|
|
21706
|
+
const rows = result.results.map((item) => {
|
|
21707
|
+
const sourceBits = [item.kind];
|
|
21708
|
+
if (item.type)
|
|
21709
|
+
sourceBits.push(item.type);
|
|
21710
|
+
if (item.role)
|
|
21711
|
+
sourceBits.push(item.role);
|
|
21712
|
+
if (item.source_kind)
|
|
21713
|
+
sourceBits.push(item.source_kind);
|
|
21714
|
+
const idBit = item.observation_id ? `#${item.observation_id}` : item.id ? `chat:${item.id}` : "";
|
|
21715
|
+
const title = `${idBit ? `${idBit} ` : ""}${item.title}${item.project_name ? ` (${item.project_name})` : ""}`;
|
|
21716
|
+
return `- [${sourceBits.join(" · ")}] ${title}
|
|
21717
|
+
${item.detail.slice(0, 220)}`;
|
|
21718
|
+
}).join(`
|
|
21719
|
+
`);
|
|
21720
|
+
return {
|
|
21721
|
+
content: [
|
|
21722
|
+
{
|
|
21723
|
+
type: "text",
|
|
21724
|
+
text: `${projectLine}${summaryLine}Recall search for "${params.query}":
|
|
21725
|
+
${rows}`
|
|
21726
|
+
}
|
|
21727
|
+
]
|
|
21728
|
+
};
|
|
21729
|
+
});
|
|
21399
21730
|
server.tool("get_observations", "Get observations by ID", {
|
|
21400
21731
|
ids: exports_external.array(exports_external.number()).describe("Observation IDs")
|
|
21401
21732
|
}, async (params) => {
|
|
@@ -21715,7 +22046,9 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
21715
22046
|
content: [
|
|
21716
22047
|
{
|
|
21717
22048
|
type: "text",
|
|
21718
|
-
text: `${projectLine}` + `${captureLine}` +
|
|
22049
|
+
text: `${projectLine}` + `${captureLine}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
22050
|
+
` + `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})
|
|
22051
|
+
` + `${typeof result.assistant_checkpoint_count === "number" ? `Assistant checkpoints: ${result.assistant_checkpoint_count}
|
|
21719
22052
|
` : ""}` + `Handoffs: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
|
|
21720
22053
|
` + `${typeof result.estimated_read_tokens === "number" ? `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
21721
22054
|
` : ""}` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
@@ -21798,16 +22131,18 @@ server.tool("capture_quality", "Show how healthy Engrm capture is across the wor
|
|
|
21798
22131
|
`) : "- (none)";
|
|
21799
22132
|
const checkpointTypeLines = result.assistant_checkpoint_types.length > 0 ? result.assistant_checkpoint_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
21800
22133
|
`) : "- (none)";
|
|
21801
|
-
const projectLines = result.top_projects.length > 0 ? result.top_projects.map((project) => `- ${project.name} [${project.raw_capture_state}] obs=${project.observation_count} sessions=${project.session_count} prompts=${project.prompt_count} tools=${project.tool_event_count} checkpoints=${project.assistant_checkpoint_count}`).join(`
|
|
22134
|
+
const projectLines = result.top_projects.length > 0 ? result.top_projects.map((project) => `- ${project.name} [${project.raw_capture_state}] obs=${project.observation_count} sessions=${project.session_count} prompts=${project.prompt_count} tools=${project.tool_event_count} checkpoints=${project.assistant_checkpoint_count} chat=${project.chat_message_count} (${project.chat_coverage_state})`).join(`
|
|
21802
22135
|
`) : "- (none)";
|
|
21803
22136
|
return {
|
|
21804
22137
|
content: [
|
|
21805
22138
|
{
|
|
21806
22139
|
type: "text",
|
|
21807
|
-
text: `Workspace totals: projects=${result.totals.projects}, observations=${result.totals.observations}, sessions=${result.totals.sessions}, prompts=${result.totals.prompts}, tools=${result.totals.tool_events}, checkpoints=${result.totals.assistant_checkpoints}
|
|
22140
|
+
text: `Workspace totals: projects=${result.totals.projects}, observations=${result.totals.observations}, sessions=${result.totals.sessions}, prompts=${result.totals.prompts}, tools=${result.totals.tool_events}, checkpoints=${result.totals.assistant_checkpoints}, chat=${result.totals.chat_messages}
|
|
21808
22141
|
|
|
21809
22142
|
` + `Session capture states: rich=${result.session_states.rich}, partial=${result.session_states.partial}, summary-only=${result.session_states.summary_only}, legacy=${result.session_states.legacy}
|
|
21810
22143
|
|
|
22144
|
+
` + `Chat recall coverage: transcript-backed sessions=${result.chat_coverage.transcript_backed_sessions}, hook-only sessions=${result.chat_coverage.hook_only_sessions}
|
|
22145
|
+
|
|
21811
22146
|
` + `Projects with raw capture: ${result.projects_with_raw_capture}
|
|
21812
22147
|
|
|
21813
22148
|
` + `Assistant checkpoints by type:
|
|
@@ -21910,6 +22245,7 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
21910
22245
|
type: "text",
|
|
21911
22246
|
text: `Project: ${result.project_name}
|
|
21912
22247
|
` + `Canonical ID: ${result.canonical_id}
|
|
22248
|
+
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
21913
22249
|
` + `Loaded observations: ${result.session_count}
|
|
21914
22250
|
` + `Searchable total: ${result.total_active}
|
|
21915
22251
|
` + `Recent requests: ${result.recent_requests}
|
|
@@ -21918,6 +22254,7 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
21918
22254
|
` + `Recent handoffs: ${result.recent_handoffs}
|
|
21919
22255
|
` + `Handoff split: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
|
|
21920
22256
|
` + `Recent chat messages: ${result.recent_chat_messages}
|
|
22257
|
+
` + `Chat recall: ${result.chat_coverage_state} · ${result.recent_chat_sessions} sessions (transcript ${result.chat_source_summary.transcript}, hook ${result.chat_source_summary.hook})
|
|
21921
22258
|
` + `Latest handoff: ${result.latest_handoff_title ?? "(none)"}
|
|
21922
22259
|
` + `Raw chronology active: ${result.raw_capture_active ? "yes" : "no"}
|
|
21923
22260
|
|
|
@@ -21990,12 +22327,14 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
21990
22327
|
type: "text",
|
|
21991
22328
|
text: `Project: ${result.project}
|
|
21992
22329
|
` + `Canonical ID: ${result.canonical_id}
|
|
22330
|
+
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
21993
22331
|
` + `Recent requests captured: ${result.recent_requests_count}
|
|
21994
22332
|
` + `Recent tools captured: ${result.recent_tools_count}
|
|
21995
22333
|
|
|
21996
22334
|
` + `Recent handoffs captured: ${result.recent_handoffs_count}
|
|
21997
22335
|
` + `Handoff split: ${result.saved_handoffs_count} saved, ${result.rolling_handoff_drafts_count} rolling drafts
|
|
21998
22336
|
` + `Recent chat messages captured: ${result.recent_chat_count}
|
|
22337
|
+
` + `Chat recall: ${result.chat_coverage_state} · ${result.recent_chat_sessions} sessions (transcript ${result.chat_source_summary.transcript}, hook ${result.chat_source_summary.hook})
|
|
21999
22338
|
|
|
22000
22339
|
` + `Raw chronology: ${result.raw_capture_active ? "active" : "observations-only so far"}
|
|
22001
22340
|
|
|
@@ -22258,6 +22597,9 @@ server.tool("recent_chat", "Inspect recently captured chat messages in the separ
|
|
|
22258
22597
|
const result = getRecentChat(db, params);
|
|
22259
22598
|
const projectLine = result.project ? `Project: ${result.project}
|
|
22260
22599
|
` : "";
|
|
22600
|
+
const coverageLine = `Coverage: ${result.messages.length} messages across ${result.session_count} session${result.session_count === 1 ? "" : "s"} ` + `· transcript ${result.source_summary.transcript} · hook ${result.source_summary.hook}
|
|
22601
|
+
` + `${result.transcript_backed ? "" : `Hint: run refresh_chat_recall if this looks under-captured.
|
|
22602
|
+
`}`;
|
|
22261
22603
|
const rows = result.messages.length > 0 ? result.messages.map((msg) => {
|
|
22262
22604
|
const stamp = new Date(msg.created_at_epoch * 1000).toISOString().split("T")[0];
|
|
22263
22605
|
return `- ${stamp} [${msg.role}] [${msg.source_kind}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`;
|
|
@@ -22267,7 +22609,7 @@ server.tool("recent_chat", "Inspect recently captured chat messages in the separ
|
|
|
22267
22609
|
content: [
|
|
22268
22610
|
{
|
|
22269
22611
|
type: "text",
|
|
22270
|
-
text: `${projectLine}Recent chat:
|
|
22612
|
+
text: `${projectLine}${coverageLine}Recent chat:
|
|
22271
22613
|
${rows}`
|
|
22272
22614
|
}
|
|
22273
22615
|
]
|
|
@@ -22283,6 +22625,9 @@ server.tool("search_chat", "Search the separate chat lane without mixing it into
|
|
|
22283
22625
|
const result = searchChat(db, params);
|
|
22284
22626
|
const projectLine = result.project ? `Project: ${result.project}
|
|
22285
22627
|
` : "";
|
|
22628
|
+
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}
|
|
22629
|
+
` + `${result.transcript_backed ? "" : `Hint: run refresh_chat_recall if this looks under-captured.
|
|
22630
|
+
`}`;
|
|
22286
22631
|
const rows = result.messages.length > 0 ? result.messages.map((msg) => {
|
|
22287
22632
|
const stamp = new Date(msg.created_at_epoch * 1000).toISOString().split("T")[0];
|
|
22288
22633
|
return `- ${stamp} [${msg.role}] [${msg.source_kind}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`;
|
|
@@ -22292,7 +22637,7 @@ server.tool("search_chat", "Search the separate chat lane without mixing it into
|
|
|
22292
22637
|
content: [
|
|
22293
22638
|
{
|
|
22294
22639
|
type: "text",
|
|
22295
|
-
text: `${projectLine}Chat search for "${params.query}":
|
|
22640
|
+
text: `${projectLine}${coverageLine}Chat search for "${params.query}":
|
|
22296
22641
|
${rows}`
|
|
22297
22642
|
}
|
|
22298
22643
|
]
|
|
@@ -22394,7 +22739,7 @@ server.tool("session_story", "Show the full local memory story for one session",
|
|
|
22394
22739
|
`) : "(none)";
|
|
22395
22740
|
const promptLines = result.prompts.length > 0 ? result.prompts.map((prompt) => `- #${prompt.prompt_number} ${prompt.prompt.replace(/\s+/g, " ").trim()}`).join(`
|
|
22396
22741
|
`) : "- (none)";
|
|
22397
|
-
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(`
|
|
22742
|
+
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(`
|
|
22398
22743
|
`) : "- (none)";
|
|
22399
22744
|
const toolLines = result.tool_events.length > 0 ? result.tool_events.slice(-15).map((tool) => {
|
|
22400
22745
|
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
@@ -22436,6 +22781,8 @@ ${summaryLines}
|
|
|
22436
22781
|
` + `Prompts:
|
|
22437
22782
|
${promptLines}
|
|
22438
22783
|
|
|
22784
|
+
` + `Chat recall: ${result.chat_coverage_state} (transcript ${result.chat_source_summary.transcript}, hook ${result.chat_source_summary.hook})
|
|
22785
|
+
|
|
22439
22786
|
` + `Chat:
|
|
22440
22787
|
${chatLines}
|
|
22441
22788
|
|