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/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: db.getSessionChatMessages(input.session_id, limit).slice(-limit).reverse()
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: db.getRecentChatMessages(projectId, limit, input.user_id),
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
- // src/tools/search-chat.ts
16484
- function searchChat(db, input) {
16485
- const limit = Math.max(1, Math.min(input.limit ?? 20, 100));
16486
- const projectScoped = input.project_scoped !== false;
16487
- let projectId = null;
16488
- let projectName;
16489
- if (projectScoped) {
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
- const all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
17373
- const recentPrompts2 = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
17374
- const recentToolEvents2 = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
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: !isNewProject,
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 = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
17422
- const recentToolEvents = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
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: !isNewProject,
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: selected.map(toContextObservation),
17482
- session_count: selected.length,
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 recentChatCount = getRecentChat(db, {
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
- }).messages.length;
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
- suggested.push("recent_chat");
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
- }).messages;
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 === "user" ? "user" : "assistant",
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 topProjects = workspace.projects.slice(0, limit).map((project) => ({
18626
- name: project.name,
18627
- canonical_id: project.canonical_id,
18628
- observation_count: project.observation_count,
18629
- session_count: project.session_count,
18630
- prompt_count: project.prompt_count,
18631
- tool_event_count: project.tool_event_count,
18632
- assistant_checkpoint_count: project.assistant_checkpoint_count,
18633
- 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"
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 recentChatMessages = getRecentChat(db, {
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
- }).messages.length;
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
- tools.push("recent_chat", "search_chat", "refresh_chat_recall");
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.25"
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}` + `${typeof result.assistant_checkpoint_count === "number" ? `Assistant checkpoints: ${result.assistant_checkpoint_count}
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