engrm 0.4.25 → 0.4.26

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 CHANGED
@@ -212,8 +212,8 @@ The MCP server exposes tools that supported agents can call directly:
212
212
  | `memory_stats` | View high-level capture and sync health |
213
213
  | `capture_status` | Check whether local hooks are registered and raw prompt/tool chronology is actually being captured |
214
214
  | `activity_feed` | Inspect one chronological local feed across prompts, tools, chat, handoffs, observations, and summaries |
215
- | `memory_console` | Show a high-signal local memory console for the current project |
216
- | `project_memory_index` | Show typed local memory by project, including hot files and recent sessions |
215
+ | `memory_console` | Show a high-signal local memory console for the current project, including continuity state |
216
+ | `project_memory_index` | Show typed local memory by project, including hot files, recent sessions, and continuity state |
217
217
  | `workspace_memory_index` | Show cross-project local memory coverage across the whole workspace |
218
218
  | `tool_memory_index` | Show which source tools and plugins are creating durable memory |
219
219
  | `session_tool_memory` | Show which tools in one session produced reusable memory and which produced none |
@@ -357,14 +357,16 @@ Recommended flow:
357
357
  What each tool is good for:
358
358
 
359
359
  - `capture_status` tells you whether prompt/tool hooks are live on this machine
360
- - `memory_console` gives the quickest project snapshot
360
+ - `capture_quality` shows whether chat recall is transcript-backed or still hook-only across the workspace
361
+ - `memory_console` gives the quickest project snapshot, including whether continuity is `fresh`, `thin`, or `cold`
361
362
  - `activity_feed` shows the merged chronology across prompts, tools, chat, handoffs, observations, and summaries
362
363
  - `recent_sessions` helps you pick a session worth opening
363
364
  - `session_story` reconstructs one session in detail, including handoffs and chat recall
364
365
  - `tool_memory_index` shows which tools and plugins are actually producing durable memory
365
366
  - `session_tool_memory` shows which tool calls in one session turned into reusable memory and which did not
366
- - `project_memory_index` shows typed memory by repo
367
+ - `project_memory_index` shows typed memory by repo, including continuity state and hot files
367
368
  - `workspace_memory_index` shows coverage across all repos on the machine
369
+ - `recent_chat` / `search_chat` now report transcript-vs-hook coverage too, so weak OpenClaw recall is easier to diagnose and refresh
368
370
 
369
371
  ### Thin Tool Workflow
370
372
 
@@ -3225,6 +3225,7 @@ function compactLine(value) {
3225
3225
  }
3226
3226
 
3227
3227
  // src/context/inject.ts
3228
+ var FRESH_CONTINUITY_WINDOW_DAYS = 3;
3228
3229
  function tokenizeProjectHint(text) {
3229
3230
  return Array.from(new Set((text.toLowerCase().match(/[a-z0-9_+-]{4,}/g) ?? []).filter(Boolean)));
3230
3231
  }
@@ -3364,20 +3365,21 @@ function buildSessionContext(db, cwd, options = {}) {
3364
3365
  const canonicalId = project?.canonical_id ?? detected.canonical_id;
3365
3366
  if (maxCount !== undefined) {
3366
3367
  const remaining = Math.max(0, maxCount - pinned.length - dedupedRecent.length);
3367
- const all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
3368
- const recentPrompts2 = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
3369
- const recentToolEvents2 = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
3368
+ let all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
3369
+ const recentPrompts2 = isNewProject ? [] : db.getRecentUserPrompts(projectId, 6, opts.userId);
3370
+ const recentToolEvents2 = isNewProject ? [] : db.getRecentToolEvents(projectId, 6, opts.userId);
3370
3371
  const recentSessions2 = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
3371
3372
  const projectTypeCounts2 = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
3372
3373
  const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions2);
3373
- const recentHandoffs2 = getRecentHandoffs(db, {
3374
+ const recentHandoffs2 = isNewProject ? [] : getRecentHandoffs(db, {
3374
3375
  cwd,
3375
- project_scoped: !isNewProject,
3376
+ project_scoped: true,
3376
3377
  user_id: opts.userId,
3377
3378
  current_device_id: opts.currentDeviceId,
3378
3379
  limit: 3
3379
3380
  }).handoffs;
3380
3381
  const recentChatMessages2 = !isNewProject && project ? db.getRecentChatMessages(project.id, 4, opts.userId) : [];
3382
+ all = filterAutoLoadedObservationsForContinuity(all, pinned, isNewProject, recentPrompts2, recentToolEvents2, recentSessions2, recentHandoffs2, recentChatMessages2, summariesFromRecentSessions(db, projectId, recentSessions2));
3381
3383
  return {
3382
3384
  project_name: projectName,
3383
3385
  canonical_id: canonicalId,
@@ -3413,19 +3415,20 @@ function buildSessionContext(db, cwd, options = {}) {
3413
3415
  selected.push(obs);
3414
3416
  }
3415
3417
  const summaries = isNewProject ? [] : db.getRecentSummaries(projectId, 5);
3416
- const recentPrompts = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
3417
- const recentToolEvents = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
3418
+ const recentPrompts = isNewProject ? [] : db.getRecentUserPrompts(projectId, 6, opts.userId);
3419
+ const recentToolEvents = isNewProject ? [] : db.getRecentToolEvents(projectId, 6, opts.userId);
3418
3420
  const recentSessions = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
3419
3421
  const projectTypeCounts = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
3420
3422
  const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions);
3421
- const recentHandoffs = getRecentHandoffs(db, {
3423
+ const recentHandoffs = isNewProject ? [] : getRecentHandoffs(db, {
3422
3424
  cwd,
3423
- project_scoped: !isNewProject,
3425
+ project_scoped: true,
3424
3426
  user_id: opts.userId,
3425
3427
  current_device_id: opts.currentDeviceId,
3426
3428
  limit: 3
3427
3429
  }).handoffs;
3428
3430
  const recentChatMessages = !isNewProject ? db.getRecentChatMessages(projectId, 4, opts.userId) : [];
3431
+ const filteredSelected = filterAutoLoadedObservationsForContinuity(selected, pinned, isNewProject, recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries);
3429
3432
  let securityFindings = [];
3430
3433
  if (!isNewProject) {
3431
3434
  try {
@@ -3473,8 +3476,8 @@ function buildSessionContext(db, cwd, options = {}) {
3473
3476
  return {
3474
3477
  project_name: projectName,
3475
3478
  canonical_id: canonicalId,
3476
- observations: selected.map(toContextObservation),
3477
- session_count: selected.length,
3479
+ observations: filteredSelected.map(toContextObservation),
3480
+ session_count: filteredSelected.length,
3478
3481
  total_active: totalActive,
3479
3482
  summaries: summaries.length > 0 ? summaries : undefined,
3480
3483
  securityFindings: securityFindings.length > 0 ? securityFindings : undefined,
@@ -3489,6 +3492,39 @@ function buildSessionContext(db, cwd, options = {}) {
3489
3492
  recentChatMessages: recentChatMessages.length > 0 ? recentChatMessages : undefined
3490
3493
  };
3491
3494
  }
3495
+ function filterAutoLoadedObservationsForContinuity(observations, pinned, isNewProject, recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries) {
3496
+ if (isNewProject)
3497
+ return observations;
3498
+ if (hasFreshProjectContinuity(recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries)) {
3499
+ return observations;
3500
+ }
3501
+ const pinnedIds = new Set(pinned.map((obs) => obs.id));
3502
+ return observations.filter((obs) => {
3503
+ if (pinnedIds.has(obs.id))
3504
+ return true;
3505
+ return observationAgeDays(obs.created_at_epoch) <= FRESH_CONTINUITY_WINDOW_DAYS;
3506
+ });
3507
+ }
3508
+ function hasFreshProjectContinuity(recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries) {
3509
+ const freshEnough = (epoch) => typeof epoch === "number" && observationAgeDays(epoch) <= FRESH_CONTINUITY_WINDOW_DAYS;
3510
+ 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));
3511
+ }
3512
+ function summariesFromRecentSessions(db, projectId, recentSessions) {
3513
+ const seen = new Set;
3514
+ const rows = [];
3515
+ for (const session of recentSessions) {
3516
+ if (seen.has(session.session_id))
3517
+ continue;
3518
+ seen.add(session.session_id);
3519
+ const summary = db.getSessionSummary(session.session_id);
3520
+ if (summary && summary.project_id === projectId)
3521
+ rows.push(summary);
3522
+ }
3523
+ return rows;
3524
+ }
3525
+ function observationAgeDays(createdAtEpoch) {
3526
+ return Math.max(0, (Math.floor(Date.now() / 1000) - createdAtEpoch) / 86400);
3527
+ }
3492
3528
  function estimateObservationTokens(obs, index) {
3493
3529
  const DETAILED_THRESHOLD = 5;
3494
3530
  const titleCost = estimateTokens(`- **[${obs.type}]** ${obs.title} (2026-01-01, q=0.5)`);