engrm 0.4.33 → 0.4.34

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
@@ -226,6 +226,7 @@ The MCP server exposes tools that supported agents can call directly:
226
226
  | `recent_handoffs` | List recent saved handoffs for the current project or workspace |
227
227
  | `load_handoff` | Open a saved handoff as a resume point for a new session |
228
228
  | `refresh_chat_recall` | Rehydrate the separate chat lane from a Claude transcript when a long session feels under-captured |
229
+ | `agent_memory_index` | Compare continuity and capture health across Claude Code, Codex, OpenClaw, and other agents |
229
230
  | `repair_recall` | Use when continuity feels thin; rehydrate recent recall from transcript or Claude history fallback |
230
231
  | `list_recall_items` | Use first when continuity feels fuzzy; list the best current handoffs, threads, chat snippets, and memory entries |
231
232
  | `load_recall_item` | Use after `list_recall_items`; load one exact recall item key |
@@ -402,6 +403,8 @@ What each tool is good for:
402
403
 
403
404
  - `capture_status` tells you whether prompt/tool hooks are live on this machine
404
405
  - `capture_quality` shows whether chat recall is transcript-backed, history-backed, or still hook-only across the workspace
406
+ - `agent_memory_index` lets you compare Claude Code, Codex, and other agent sessions on the same repo, so cross-agent validation stops being guesswork
407
+ - when multiple agents are active on the same repo, startup plus the MCP workbench now surface the active agent set and suggest `agent_memory_index` automatically
405
408
  - `memory_console` gives the quickest project snapshot, including whether continuity is `fresh`, `thin`, or `cold`
406
409
  - `resume_thread` is the fastest “get me back into the live thread” path when you want freshness, source, next actions, tool trail, chat, and one exact `load_recall_item(...)` suggestion in one place
407
410
  - `list_recall_items` is the deterministic directory-first path when you want to inspect the best candidate handoffs/threads before opening one exact item
@@ -3144,7 +3144,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
3144
3144
  import { join as join3 } from "node:path";
3145
3145
  import { homedir } from "node:os";
3146
3146
  var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
3147
- var CLIENT_VERSION = "0.4.33";
3147
+ var CLIENT_VERSION = "0.4.34";
3148
3148
  function hashFile(filePath) {
3149
3149
  try {
3150
3150
  if (!existsSync3(filePath))
@@ -5847,11 +5847,15 @@ function formatContextIndex(context, shownItems) {
5847
5847
  function formatInspectHints(context, visibleObservationIds = [], recallItems = []) {
5848
5848
  const hints = [];
5849
5849
  const continuityState = getStartupContinuityState(context);
5850
+ const activeAgents = collectStartupAgents(context);
5850
5851
  if ((context.recentSessions?.length ?? 0) > 0) {
5851
5852
  hints.push("recent_sessions");
5852
5853
  hints.push("session_story");
5853
5854
  hints.push("create_handoff");
5854
5855
  }
5856
+ if (activeAgents.length > 1) {
5857
+ hints.push("agent_memory_index");
5858
+ }
5855
5859
  if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0 || (context.recentChatMessages?.length ?? 0) > 0) {
5856
5860
  hints.push("activity_feed");
5857
5861
  }
@@ -5891,6 +5895,9 @@ function formatInspectHints(context, visibleObservationIds = [], recallItems = [
5891
5895
  ...fetchHint ? [`${c2.dim}Pull detail:${c2.reset} ${fetchHint}`] : []
5892
5896
  ];
5893
5897
  }
5898
+ function collectStartupAgents(context) {
5899
+ return Array.from(new Set((context.recentSessions ?? []).map((session) => session.agent?.trim()).filter((agent) => Boolean(agent) && !agent.startsWith("engrm-")))).sort();
5900
+ }
5894
5901
  function formatStartupRecallPreview(recallItems) {
5895
5902
  const items = recallItems.slice(0, 3);
5896
5903
  if (items.length === 0)
@@ -3082,7 +3082,7 @@ function buildBeacon(db, config, sessionId, metrics) {
3082
3082
  sentinel_used: valueSignals.security_findings_count > 0,
3083
3083
  risk_score: riskScore,
3084
3084
  stacks_detected: stacks,
3085
- client_version: "0.4.33",
3085
+ client_version: "0.4.34",
3086
3086
  context_observations_injected: metrics?.contextObsInjected ?? 0,
3087
3087
  context_total_available: metrics?.contextTotalAvailable ?? 0,
3088
3088
  recall_attempts: metrics?.recallAttempts ?? 0,
package/dist/server.js CHANGED
@@ -18589,8 +18589,9 @@ function getProjectMemoryIndex(db, input) {
18589
18589
  const latestSummary = latestSession ? db.getSessionSummary(latestSession.session_id) : null;
18590
18590
  const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0 && !looksLikeFileOperationTitle4(title)).slice(0, 8);
18591
18591
  const captureSummary = summarizeCaptureState(recentSessions);
18592
+ const activeAgents = collectActiveAgents(recentSessions);
18592
18593
  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);
18593
- const suggestedTools = buildSuggestedTools(recentSessions, recentRequestsCount, recentToolsCount, observations.length, recentChatCount, recentChat.coverage_state);
18594
+ const suggestedTools = buildSuggestedTools(recentSessions, recentRequestsCount, recentToolsCount, observations.length, recentChatCount, recentChat.coverage_state, activeAgents);
18594
18595
  const estimatedReadTokens = estimateTokens([
18595
18596
  recentOutcomes.join(`
18596
18597
  `),
@@ -18606,6 +18607,8 @@ function getProjectMemoryIndex(db, input) {
18606
18607
  return {
18607
18608
  project: project.name,
18608
18609
  canonical_id: project.canonical_id,
18610
+ active_agents: activeAgents,
18611
+ cross_agent_active: activeAgents.length > 1,
18609
18612
  continuity_state: continuityState,
18610
18613
  continuity_summary: describeContinuityState(continuityState),
18611
18614
  recall_mode: recallIndex.continuity_mode,
@@ -18732,11 +18735,17 @@ function summarizeCaptureState(sessions) {
18732
18735
  }
18733
18736
  return summary;
18734
18737
  }
18735
- function buildSuggestedTools(sessions, requestCount, toolCount, observationCount, recentChatCount, chatCoverageState) {
18738
+ function collectActiveAgents(sessions) {
18739
+ return Array.from(new Set(sessions.map((session) => session.agent?.trim()).filter((agent) => Boolean(agent) && !agent.startsWith("engrm-")))).sort();
18740
+ }
18741
+ function buildSuggestedTools(sessions, requestCount, toolCount, observationCount, recentChatCount, chatCoverageState, activeAgents) {
18736
18742
  const suggested = [];
18737
18743
  if (sessions.length > 0) {
18738
18744
  suggested.push("recent_sessions");
18739
18745
  }
18746
+ if (activeAgents.length > 1) {
18747
+ suggested.push("agent_memory_index");
18748
+ }
18740
18749
  if (requestCount > 0 || toolCount > 0) {
18741
18750
  suggested.push("activity_feed");
18742
18751
  }
@@ -18761,7 +18770,7 @@ function buildSuggestedTools(sessions, requestCount, toolCount, observationCount
18761
18770
  if (recentChatCount > 0) {
18762
18771
  suggested.push("recent_chat", "search_chat");
18763
18772
  }
18764
- return Array.from(new Set(suggested)).slice(0, 5);
18773
+ return Array.from(new Set(suggested)).slice(0, 6);
18765
18774
  }
18766
18775
 
18767
18776
  // src/tools/memory-console.ts
@@ -18819,8 +18828,11 @@ function getMemoryConsole(db, input) {
18819
18828
  user_id: input.user_id
18820
18829
  }) : null;
18821
18830
  const continuityState = projectIndex?.continuity_state ?? classifyContinuityState(requests.length, tools.length, recentHandoffs.length, recentChat.messages.length, sessions, (projectIndex?.recent_outcomes ?? []).length);
18831
+ const activeAgents = projectIndex?.active_agents ?? collectActiveAgents(sessions);
18822
18832
  return {
18823
18833
  project: project?.name,
18834
+ active_agents: activeAgents,
18835
+ cross_agent_active: projectIndex?.cross_agent_active ?? activeAgents.length > 1,
18824
18836
  capture_mode: requests.length > 0 || tools.length > 0 ? "rich" : "observations-only",
18825
18837
  continuity_state: continuityState,
18826
18838
  continuity_summary: projectIndex?.continuity_summary ?? describeContinuityState(continuityState),
@@ -18858,13 +18870,15 @@ function getMemoryConsole(db, input) {
18858
18870
  assistant_checkpoint_types: projectIndex?.assistant_checkpoint_types ?? [],
18859
18871
  top_types: projectIndex?.top_types ?? [],
18860
18872
  estimated_read_tokens: projectIndex?.estimated_read_tokens,
18861
- suggested_tools: projectIndex?.suggested_tools ?? buildFallbackSuggestedTools(sessions.length, requests.length, tools.length, observations.length, recentHandoffs.length, recentChat.messages.length, recentChat.coverage_state)
18873
+ suggested_tools: projectIndex?.suggested_tools ?? buildFallbackSuggestedTools(sessions.length, requests.length, tools.length, observations.length, recentHandoffs.length, recentChat.messages.length, recentChat.coverage_state, activeAgents.length)
18862
18874
  };
18863
18875
  }
18864
- function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, chatCoverageState) {
18876
+ function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, chatCoverageState, activeAgentCount) {
18865
18877
  const suggested = [];
18866
18878
  if (sessionCount > 0)
18867
18879
  suggested.push("recent_sessions");
18880
+ if (activeAgentCount > 1)
18881
+ suggested.push("agent_memory_index");
18868
18882
  if (requestCount > 0 || toolCount > 0)
18869
18883
  suggested.push("activity_feed");
18870
18884
  if (requestCount > 0 || chatCount > 0 || observationCount > 0)
@@ -18883,7 +18897,7 @@ function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, obse
18883
18897
  suggested.push("refresh_chat_recall");
18884
18898
  if (chatCount > 0)
18885
18899
  suggested.push("recent_chat", "search_chat");
18886
- return Array.from(new Set(suggested)).slice(0, 5);
18900
+ return Array.from(new Set(suggested)).slice(0, 6);
18887
18901
  }
18888
18902
 
18889
18903
  // src/tools/workspace-memory-index.ts
@@ -19529,6 +19543,192 @@ function getCaptureQuality(db, input = {}) {
19529
19543
  };
19530
19544
  }
19531
19545
 
19546
+ // src/tools/agent-memory-index.ts
19547
+ function getAgentMemoryIndex(db, input) {
19548
+ const cwd = input.cwd ?? process.cwd();
19549
+ const projectScoped = input.project_scoped !== false;
19550
+ let projectId = null;
19551
+ let projectName;
19552
+ if (projectScoped) {
19553
+ const detected = detectProject(cwd);
19554
+ const project = db.getProjectByCanonicalId(detected.canonical_id);
19555
+ if (project) {
19556
+ projectId = project.id;
19557
+ projectName = project.name;
19558
+ }
19559
+ }
19560
+ const userFilter = input.user_id ? " AND s.user_id = ?" : "";
19561
+ const userArgs = input.user_id ? [input.user_id] : [];
19562
+ const projectFilter = projectId !== null ? " AND s.project_id = ?" : "";
19563
+ const projectArgs = projectId !== null ? [projectId] : [];
19564
+ const sessionRows = db.db.query(`SELECT
19565
+ s.agent as agent,
19566
+ COUNT(*) as session_count,
19567
+ SUM(CASE WHEN ss.request IS NOT NULL OR ss.completed IS NOT NULL THEN 1 ELSE 0 END) as summary_session_count,
19568
+ SUM(COALESCE(pc.prompt_count, 0)) as prompt_count,
19569
+ SUM(COALESCE(tc.tool_event_count, 0)) as tool_event_count,
19570
+ MAX(COALESCE(s.completed_at_epoch, s.started_at_epoch)) as last_seen_epoch
19571
+ FROM sessions s
19572
+ LEFT JOIN session_summaries ss ON ss.session_id = s.session_id
19573
+ LEFT JOIN (
19574
+ SELECT session_id, COUNT(*) as prompt_count
19575
+ FROM user_prompts
19576
+ GROUP BY session_id
19577
+ ) pc ON pc.session_id = s.session_id
19578
+ LEFT JOIN (
19579
+ SELECT session_id, COUNT(*) as tool_event_count
19580
+ FROM tool_events
19581
+ GROUP BY session_id
19582
+ ) tc ON tc.session_id = s.session_id
19583
+ WHERE 1 = 1${projectFilter}${userFilter}
19584
+ GROUP BY s.agent
19585
+ ORDER BY last_seen_epoch DESC, s.agent ASC`).all(...projectArgs, ...userArgs).filter((row) => !isInternalAgent(row.agent));
19586
+ const observationCounts = new Map;
19587
+ const observationRows = db.db.query(`SELECT
19588
+ COALESCE(s.agent, o.agent) as agent,
19589
+ COUNT(*) as observation_count,
19590
+ SUM(CASE WHEN o.type = 'message' AND o.concepts LIKE '%session-handoff%' THEN 1 ELSE 0 END) as handoff_count
19591
+ FROM observations o
19592
+ LEFT JOIN sessions s ON s.session_id = o.session_id
19593
+ WHERE o.lifecycle IN ('active', 'aging', 'pinned')
19594
+ AND o.superseded_by IS NULL
19595
+ ${projectId !== null ? "AND o.project_id = ?" : ""}
19596
+ ${input.user_id ? "AND (o.sensitivity != 'personal' OR o.user_id = ?)" : ""}
19597
+ GROUP BY COALESCE(s.agent, o.agent)`).all(...projectId !== null ? [projectId] : [], ...input.user_id ? [input.user_id] : []).filter((row) => !isInternalAgent(row.agent));
19598
+ for (const row of observationRows) {
19599
+ observationCounts.set(row.agent, {
19600
+ observation_count: row.observation_count,
19601
+ handoff_count: row.handoff_count
19602
+ });
19603
+ }
19604
+ const chatCoverage = new Map;
19605
+ const chatRows = db.db.query(`SELECT
19606
+ COALESCE(s.agent, cm.agent) as agent,
19607
+ CASE
19608
+ WHEN cm.source_kind = 'transcript' THEN 'transcript'
19609
+ WHEN cm.remote_source_id LIKE 'history:%' THEN 'history'
19610
+ ELSE 'hook'
19611
+ END as origin_kind,
19612
+ COUNT(*) as count
19613
+ FROM chat_messages cm
19614
+ LEFT JOIN sessions s ON s.session_id = cm.session_id
19615
+ WHERE 1 = 1
19616
+ ${projectId !== null ? "AND cm.project_id = ?" : ""}
19617
+ ${input.user_id ? "AND cm.user_id = ?" : ""}
19618
+ GROUP BY COALESCE(s.agent, cm.agent), origin_kind`).all(...projectId !== null ? [projectId] : [], ...input.user_id ? [input.user_id] : []).filter((row) => !isInternalAgent(row.agent));
19619
+ for (const row of chatRows) {
19620
+ const current = chatCoverage.get(row.agent) ?? {
19621
+ chat_message_count: 0,
19622
+ transcript_count: 0,
19623
+ history_count: 0,
19624
+ hook_count: 0
19625
+ };
19626
+ current.chat_message_count += row.count;
19627
+ if (row.origin_kind === "transcript")
19628
+ current.transcript_count += row.count;
19629
+ else if (row.origin_kind === "history")
19630
+ current.history_count += row.count;
19631
+ else
19632
+ current.hook_count += row.count;
19633
+ chatCoverage.set(row.agent, current);
19634
+ }
19635
+ const recentSessions = db.getRecentSessions(projectId, 200, input.user_id).filter((session) => !isInternalAgent(session.agent));
19636
+ const latestByAgent = new Map;
19637
+ const devicesByAgent = new Map;
19638
+ for (const session of recentSessions) {
19639
+ if (!latestByAgent.has(session.agent))
19640
+ latestByAgent.set(session.agent, session);
19641
+ const devices = devicesByAgent.get(session.agent) ?? new Set;
19642
+ if (session.device_id)
19643
+ devices.add(session.device_id);
19644
+ devicesByAgent.set(session.agent, devices);
19645
+ }
19646
+ const knownAgents = new Set([
19647
+ ...sessionRows.map((row) => row.agent),
19648
+ ...Array.from(observationCounts.keys()),
19649
+ ...Array.from(chatCoverage.keys())
19650
+ ]);
19651
+ const agents = Array.from(knownAgents).map((agent) => {
19652
+ const session = sessionRows.find((row) => row.agent === agent) ?? {
19653
+ agent,
19654
+ session_count: 0,
19655
+ summary_session_count: 0,
19656
+ prompt_count: 0,
19657
+ tool_event_count: 0,
19658
+ last_seen_epoch: null
19659
+ };
19660
+ const obs = observationCounts.get(agent) ?? { observation_count: 0, handoff_count: 0 };
19661
+ const chat = chatCoverage.get(agent) ?? {
19662
+ chat_message_count: 0,
19663
+ transcript_count: 0,
19664
+ history_count: 0,
19665
+ hook_count: 0
19666
+ };
19667
+ const latestSession = latestByAgent.get(agent) ?? null;
19668
+ return {
19669
+ agent,
19670
+ session_count: session.session_count,
19671
+ summary_session_count: session.summary_session_count,
19672
+ prompt_count: session.prompt_count,
19673
+ tool_event_count: session.tool_event_count,
19674
+ observation_count: obs.observation_count,
19675
+ handoff_count: obs.handoff_count,
19676
+ chat_message_count: chat.chat_message_count,
19677
+ chat_coverage_state: chat.transcript_count > 0 ? "transcript-backed" : chat.history_count > 0 ? "history-backed" : chat.hook_count > 0 ? "hook-only" : "none",
19678
+ continuity_state: classifyAgentContinuity(session.last_seen_epoch, session.prompt_count, session.tool_event_count, chat.chat_message_count, obs.handoff_count, obs.observation_count),
19679
+ capture_state: classifyAgentCaptureState(session.prompt_count, session.tool_event_count, session.summary_session_count, obs.observation_count, chat.chat_message_count),
19680
+ last_seen_epoch: session.last_seen_epoch,
19681
+ latest_session_id: latestSession?.session_id ?? null,
19682
+ latest_summary: latestSession?.current_thread ?? latestSession?.request ?? latestSession?.completed ?? null,
19683
+ devices: Array.from(devicesByAgent.get(agent) ?? []).sort()
19684
+ };
19685
+ }).sort((a, b) => {
19686
+ const epochA = a.last_seen_epoch ?? 0;
19687
+ const epochB = b.last_seen_epoch ?? 0;
19688
+ return epochB - epochA || a.agent.localeCompare(b.agent);
19689
+ });
19690
+ return {
19691
+ project: projectName,
19692
+ agents,
19693
+ suggested_tools: buildSuggestedTools2(agents)
19694
+ };
19695
+ }
19696
+ function isInternalAgent(agent) {
19697
+ return !agent || agent.startsWith("engrm-");
19698
+ }
19699
+ function classifyAgentContinuity(lastSeenEpoch, promptCount, toolCount, chatCount, handoffCount, observationCount) {
19700
+ if (!lastSeenEpoch)
19701
+ return "cold";
19702
+ const ageMs = Date.now() - lastSeenEpoch * 1000;
19703
+ const hasStrongContinuity = promptCount > 0 || toolCount > 0 || chatCount > 0 || handoffCount > 0;
19704
+ if (ageMs <= 3 * 24 * 60 * 60 * 1000 && hasStrongContinuity)
19705
+ return "fresh";
19706
+ if (observationCount > 0 || promptCount > 0 || toolCount > 0 || chatCount > 0)
19707
+ return "thin";
19708
+ return "cold";
19709
+ }
19710
+ function classifyAgentCaptureState(promptCount, toolCount, summarySessionCount, observationCount, chatCount) {
19711
+ if (promptCount > 0 && toolCount > 0)
19712
+ return "rich";
19713
+ if (promptCount > 0 || toolCount > 0)
19714
+ return "partial";
19715
+ if (summarySessionCount > 0 || observationCount > 0 || chatCount > 0)
19716
+ return "summary-only";
19717
+ return "legacy";
19718
+ }
19719
+ function buildSuggestedTools2(agents) {
19720
+ if (agents.length === 0)
19721
+ return [];
19722
+ const suggestions = ["recent_sessions", "capture_quality"];
19723
+ if (agents.some((agent) => agent.continuity_state !== "fresh")) {
19724
+ suggestions.push("resume_thread");
19725
+ }
19726
+ if (agents.some((agent) => agent.chat_coverage_state === "hook-only")) {
19727
+ suggestions.push("repair_recall");
19728
+ }
19729
+ return suggestions;
19730
+ }
19731
+
19532
19732
  // src/tools/tool-memory-index.ts
19533
19733
  function parseConcepts(value) {
19534
19734
  if (!value)
@@ -19732,9 +19932,12 @@ function getSessionContext(db, input) {
19732
19932
  const latestChatEpoch = recentChat.messages.length > 0 ? recentChat.messages[recentChat.messages.length - 1]?.created_at_epoch ?? null : null;
19733
19933
  const resumeTimestamp = latestChatEpoch ?? latestSession?.completed_at_epoch ?? latestSession?.started_at_epoch ?? null;
19734
19934
  const bestRecallItem = recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null;
19935
+ const activeAgents = collectActiveAgents(context.recentSessions ?? []);
19735
19936
  return {
19736
19937
  project_name: context.project_name,
19737
19938
  canonical_id: context.canonical_id,
19939
+ active_agents: activeAgents,
19940
+ cross_agent_active: activeAgents.length > 1,
19738
19941
  continuity_state: continuityState,
19739
19942
  continuity_summary: describeContinuityState(continuityState),
19740
19943
  recall_mode: recallIndex.continuity_mode,
@@ -19770,7 +19973,7 @@ function getSessionContext(db, input) {
19770
19973
  capture_state: captureState,
19771
19974
  raw_capture_active: recentRequests > 0 || recentTools > 0,
19772
19975
  estimated_read_tokens: estimateTokens(preview),
19773
- suggested_tools: buildSuggestedTools2(context, recentChat.coverage_state),
19976
+ suggested_tools: buildSuggestedTools3(context, recentChat.coverage_state, activeAgents.length),
19774
19977
  preview
19775
19978
  };
19776
19979
  }
@@ -19801,11 +20004,14 @@ function parseJsonArray3(value) {
19801
20004
  return [];
19802
20005
  }
19803
20006
  }
19804
- function buildSuggestedTools2(context, chatCoverageState) {
20007
+ function buildSuggestedTools3(context, chatCoverageState, activeAgentCount) {
19805
20008
  const tools = [];
19806
20009
  if ((context.recentSessions?.length ?? 0) > 0) {
19807
20010
  tools.push("recent_sessions");
19808
20011
  }
20012
+ if (activeAgentCount > 1) {
20013
+ tools.push("agent_memory_index");
20014
+ }
19809
20015
  if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0) {
19810
20016
  tools.push("activity_feed");
19811
20017
  }
@@ -19833,7 +20039,7 @@ function buildSuggestedTools2(context, chatCoverageState) {
19833
20039
  if ((context.recentChatMessages?.length ?? 0) > 0) {
19834
20040
  tools.push("recent_chat", "search_chat");
19835
20041
  }
19836
- return Array.from(new Set(tools)).slice(0, 5);
20042
+ return Array.from(new Set(tools)).slice(0, 6);
19837
20043
  }
19838
20044
 
19839
20045
  // src/tools/load-recall-item.ts
@@ -22459,7 +22665,7 @@ process.on("SIGTERM", () => {
22459
22665
  });
22460
22666
  var server = new McpServer({
22461
22667
  name: "engrm",
22462
- version: "0.4.33"
22668
+ version: "0.4.34"
22463
22669
  });
22464
22670
  server.tool("save_observation", "Save an observation to memory", {
22465
22671
  type: exports_external.enum([
@@ -23441,7 +23647,8 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
23441
23647
  content: [
23442
23648
  {
23443
23649
  type: "text",
23444
- text: `${projectLine}` + `${captureLine}` + `Continuity: ${result.continuity_state} ${result.continuity_summary}
23650
+ text: `${projectLine}` + `${captureLine}` + `${result.active_agents.length > 0 ? `Agents active: ${result.active_agents.join(", ")}
23651
+ ` : ""}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
23445
23652
  ` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
23446
23653
  ` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
23447
23654
  ` + `Chat recall: ${result.chat_coverage_state} · ${result.recent_chat.length} messages across ${result.recent_chat_sessions} sessions (transcript ${result.chat_source_summary.transcript}, history ${result.chat_source_summary.history}, hook ${result.chat_source_summary.hook})
@@ -23564,6 +23771,37 @@ ${projectLines}`
23564
23771
  ]
23565
23772
  };
23566
23773
  });
23774
+ server.tool("agent_memory_index", "Compare continuity and capture health across Claude Code, Codex, OpenClaw, and other agents for the current project or workspace.", {
23775
+ cwd: exports_external.string().optional().describe("Project path to inspect. Defaults to the current working directory."),
23776
+ project_scoped: exports_external.boolean().optional().describe("If true, limit results to the current project instead of the whole workspace."),
23777
+ user_id: exports_external.string().optional().describe("Optional user override; defaults to the configured user.")
23778
+ }, async (params) => {
23779
+ const result = getAgentMemoryIndex(db, {
23780
+ cwd: params.cwd ?? process.cwd(),
23781
+ project_scoped: params.project_scoped,
23782
+ user_id: params.user_id ?? config2.user_id
23783
+ });
23784
+ const rows = result.agents.length > 0 ? result.agents.map((agent) => {
23785
+ const lastSeen = agent.last_seen_epoch ? new Date(agent.last_seen_epoch * 1000).toISOString().replace("T", " ").slice(0, 16) : "unknown";
23786
+ const latest = agent.latest_summary ? ` latest="${agent.latest_summary.replace(/\s+/g, " ").trim().slice(0, 120)}"` : "";
23787
+ const devices = agent.devices.length > 0 ? ` devices=[${agent.devices.join(", ")}]` : "";
23788
+ return `- ${agent.agent}: continuity=${agent.continuity_state} capture=${agent.capture_state} chat=${agent.chat_coverage_state} sessions=${agent.session_count} prompts=${agent.prompt_count} tools=${agent.tool_event_count} obs=${agent.observation_count} handoffs=${agent.handoff_count} chat_msgs=${agent.chat_message_count} last_seen=${lastSeen}${devices}${latest}`;
23789
+ }).join(`
23790
+ `) : "- (none)";
23791
+ return {
23792
+ content: [
23793
+ {
23794
+ type: "text",
23795
+ text: `${result.project ? `Project: ${result.project}
23796
+
23797
+ ` : ""}` + `Agent memory index:
23798
+ ${rows}
23799
+
23800
+ ` + `Suggested next step: ${result.suggested_tools.join(", ") || "(none)"}`
23801
+ }
23802
+ ]
23803
+ };
23804
+ });
23567
23805
  server.tool("tool_memory_index", "Show which tools are actually producing durable memory, which plugins they exercise, and what memory types they create.", {
23568
23806
  cwd: exports_external.string().optional().describe("Project path to inspect. Defaults to the current working directory."),
23569
23807
  project_scoped: exports_external.boolean().optional().describe("If true, limit results to the current project instead of the whole workspace."),
@@ -23649,6 +23887,7 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
23649
23887
  type: "text",
23650
23888
  text: `Project: ${result.project_name}
23651
23889
  ` + `Canonical ID: ${result.canonical_id}
23890
+ ` + `Agents active: ${result.active_agents.join(", ") || "(none)"}
23652
23891
  ` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
23653
23892
  ` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
23654
23893
  ` + `Open exact: ${result.best_recall_key ? `load_recall_item("${result.best_recall_key}")` : "(none)"}
@@ -23740,6 +23979,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
23740
23979
  type: "text",
23741
23980
  text: `Project: ${result.project}
23742
23981
  ` + `Canonical ID: ${result.canonical_id}
23982
+ ` + `Agents active: ${result.active_agents.join(", ") || "(none)"}
23743
23983
  ` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
23744
23984
  ` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
23745
23985
  ` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engrm",
3
- "version": "0.4.33",
3
+ "version": "0.4.34",
4
4
  "description": "Shared memory across devices, sessions, and agents, with thin MCP tools for durable capture and live continuity",
5
5
  "mcpName": "io.github.dr12hes/engrm",
6
6
  "type": "module",