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 +3 -0
- package/dist/hooks/session-start.js +8 -1
- package/dist/hooks/stop.js +1 -1
- package/dist/server.js +251 -11
- package/package.json +1 -1
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.
|
|
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)
|
package/dist/hooks/stop.js
CHANGED
|
@@ -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.
|
|
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
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
|
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,
|
|
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.
|
|
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}` +
|
|
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