engrm 0.4.32 → 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 +5 -1
- package/dist/hooks/session-start.js +96 -3
- package/dist/hooks/stop.js +1 -1
- package/dist/server.js +292 -15
- 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,10 +403,13 @@ 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
|
-
- `resume_thread` is the fastest “get me back into the live thread” path when you want freshness, source, next actions, tool trail, and
|
|
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
|
|
408
411
|
- `load_recall_item` completes that protocol by letting agents open one exact recall key directly after listing
|
|
412
|
+
- `memory_console`, `project_memory_index`, and `session_context` now also surface one best exact `load_recall_item(...)` jump, so the workbench can hand you the right deterministic next step instead of only showing recall counts
|
|
409
413
|
- `memory_console`, `project_memory_index`, and `session_context` now also show whether project chat recall is transcript-backed, history-backed, or only hook-captured
|
|
410
414
|
- `memory_console`, `project_memory_index`, and `session_context` also expose resume-readiness directly, so you can see whether a repo is `live`, `recent`, or `stale` before drilling deeper
|
|
411
415
|
- when chat continuity is only partial, the workbench and startup hints now prefer `repair_recall`, and still suggest `refresh_chat_recall` when a single session likely just needs transcript hydration
|
|
@@ -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))
|
|
@@ -5617,7 +5617,15 @@ function formatSplashScreen(data) {
|
|
|
5617
5617
|
lines.push(` ${line}`);
|
|
5618
5618
|
}
|
|
5619
5619
|
}
|
|
5620
|
-
const
|
|
5620
|
+
const recallItems = buildStartupRecallItems(data.context);
|
|
5621
|
+
const recallPreview = formatStartupRecallPreview(recallItems);
|
|
5622
|
+
if (recallPreview.length > 0) {
|
|
5623
|
+
lines.push("");
|
|
5624
|
+
for (const line of recallPreview) {
|
|
5625
|
+
lines.push(` ${line}`);
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
const inspectHints = formatInspectHints(data.context, contextIndex.observationIds, recallItems);
|
|
5621
5629
|
if (inspectHints.length > 0) {
|
|
5622
5630
|
lines.push("");
|
|
5623
5631
|
for (const line of inspectHints) {
|
|
@@ -5836,14 +5844,18 @@ function formatContextIndex(context, shownItems) {
|
|
|
5836
5844
|
observationIds: selected.map((obs) => obs.id)
|
|
5837
5845
|
};
|
|
5838
5846
|
}
|
|
5839
|
-
function formatInspectHints(context, visibleObservationIds = []) {
|
|
5847
|
+
function formatInspectHints(context, visibleObservationIds = [], recallItems = []) {
|
|
5840
5848
|
const hints = [];
|
|
5841
5849
|
const continuityState = getStartupContinuityState(context);
|
|
5850
|
+
const activeAgents = collectStartupAgents(context);
|
|
5842
5851
|
if ((context.recentSessions?.length ?? 0) > 0) {
|
|
5843
5852
|
hints.push("recent_sessions");
|
|
5844
5853
|
hints.push("session_story");
|
|
5845
5854
|
hints.push("create_handoff");
|
|
5846
5855
|
}
|
|
5856
|
+
if (activeAgents.length > 1) {
|
|
5857
|
+
hints.push("agent_memory_index");
|
|
5858
|
+
}
|
|
5847
5859
|
if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0 || (context.recentChatMessages?.length ?? 0) > 0) {
|
|
5848
5860
|
hints.push("activity_feed");
|
|
5849
5861
|
}
|
|
@@ -5875,12 +5887,93 @@ function formatInspectHints(context, visibleObservationIds = []) {
|
|
|
5875
5887
|
if (unique.length === 0)
|
|
5876
5888
|
return [];
|
|
5877
5889
|
const ids = visibleObservationIds.slice(0, 5);
|
|
5890
|
+
const openNowItem = recallItems.find((item) => item.kind !== "memory") ?? null;
|
|
5878
5891
|
const fetchHint = ids.length > 0 ? `get_observations([${ids.join(", ")}])` : null;
|
|
5879
5892
|
return [
|
|
5880
5893
|
`${c2.dim}Next look:${c2.reset} ${unique.join(" \xB7 ")}`,
|
|
5894
|
+
...openNowItem ? [`${c2.dim}Open now:${c2.reset} load_recall_item("${openNowItem.key}")`] : [],
|
|
5881
5895
|
...fetchHint ? [`${c2.dim}Pull detail:${c2.reset} ${fetchHint}`] : []
|
|
5882
5896
|
];
|
|
5883
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
|
+
}
|
|
5901
|
+
function formatStartupRecallPreview(recallItems) {
|
|
5902
|
+
const items = recallItems.slice(0, 3);
|
|
5903
|
+
if (items.length === 0)
|
|
5904
|
+
return [];
|
|
5905
|
+
return [
|
|
5906
|
+
`${c2.dim}Recall preview:${c2.reset} exact keys you can open now`,
|
|
5907
|
+
...items.map((item) => `${item.key} [${item.kind} \xB7 ${item.freshness}] ${truncateInline(item.title, 110)}`)
|
|
5908
|
+
];
|
|
5909
|
+
}
|
|
5910
|
+
function buildStartupRecallItems(context) {
|
|
5911
|
+
const items = [];
|
|
5912
|
+
for (const handoff of context.recentHandoffs?.slice(0, 2) ?? []) {
|
|
5913
|
+
const title = handoff.title.replace(/^Handoff(?: Draft)?:\s*/i, "").replace(/\s+\u00B7\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}Z$/, "").trim();
|
|
5914
|
+
if (!title)
|
|
5915
|
+
continue;
|
|
5916
|
+
const freshness = classifyResumeFreshness(handoff.created_at_epoch);
|
|
5917
|
+
items.push({
|
|
5918
|
+
key: `handoff:${handoff.id}`,
|
|
5919
|
+
kind: "handoff",
|
|
5920
|
+
freshness,
|
|
5921
|
+
title,
|
|
5922
|
+
score: freshnessScore(freshness) + 40
|
|
5923
|
+
});
|
|
5924
|
+
}
|
|
5925
|
+
for (const session of context.recentSessions?.slice(0, 2) ?? []) {
|
|
5926
|
+
const title = chooseMeaningfulSessionSummary(session.request, session.completed);
|
|
5927
|
+
if (!title)
|
|
5928
|
+
continue;
|
|
5929
|
+
const sourceEpoch = session.completed_at_epoch ?? session.started_at_epoch ?? null;
|
|
5930
|
+
const freshness = classifyResumeFreshness(sourceEpoch);
|
|
5931
|
+
items.push({
|
|
5932
|
+
key: `session:${session.session_id}`,
|
|
5933
|
+
kind: "thread",
|
|
5934
|
+
freshness,
|
|
5935
|
+
title,
|
|
5936
|
+
score: freshnessScore(freshness) + 30
|
|
5937
|
+
});
|
|
5938
|
+
}
|
|
5939
|
+
for (const message of context.recentChatMessages?.slice(-2) ?? []) {
|
|
5940
|
+
if (!message.content.trim())
|
|
5941
|
+
continue;
|
|
5942
|
+
const freshness = classifyResumeFreshness(message.created_at_epoch);
|
|
5943
|
+
items.push({
|
|
5944
|
+
key: `chat:${message.id}`,
|
|
5945
|
+
kind: "chat",
|
|
5946
|
+
freshness,
|
|
5947
|
+
title: `[${message.role}] ${message.content.replace(/\s+/g, " ").trim()}`,
|
|
5948
|
+
score: freshnessScore(freshness) + 20
|
|
5949
|
+
});
|
|
5950
|
+
}
|
|
5951
|
+
for (const obs of pickContextIndexObservations(context).slice(0, 2)) {
|
|
5952
|
+
const createdAtEpoch = Math.floor(new Date(obs.created_at).getTime() / 1000);
|
|
5953
|
+
const freshness = classifyResumeFreshness(Number.isFinite(createdAtEpoch) ? createdAtEpoch : null);
|
|
5954
|
+
items.push({
|
|
5955
|
+
key: `obs:${obs.id}`,
|
|
5956
|
+
kind: "memory",
|
|
5957
|
+
freshness,
|
|
5958
|
+
title: obs.title,
|
|
5959
|
+
score: freshnessScore(freshness) + 10
|
|
5960
|
+
});
|
|
5961
|
+
}
|
|
5962
|
+
const seen = new Set;
|
|
5963
|
+
return items.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key)).filter((item) => {
|
|
5964
|
+
if (seen.has(item.key))
|
|
5965
|
+
return false;
|
|
5966
|
+
seen.add(item.key);
|
|
5967
|
+
return true;
|
|
5968
|
+
}).map(({ score: _score, ...item }) => item);
|
|
5969
|
+
}
|
|
5970
|
+
function freshnessScore(freshness) {
|
|
5971
|
+
if (freshness === "live")
|
|
5972
|
+
return 3;
|
|
5973
|
+
if (freshness === "recent")
|
|
5974
|
+
return 2;
|
|
5975
|
+
return 1;
|
|
5976
|
+
}
|
|
5884
5977
|
function rememberShownItem(shown, value) {
|
|
5885
5978
|
if (!value)
|
|
5886
5979
|
return;
|
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
|
`),
|
|
@@ -18602,9 +18603,12 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18602
18603
|
`));
|
|
18603
18604
|
const continuityState = classifyContinuityState(recentRequestsCount, recentToolsCount, recentHandoffsCount.length, recentChatCount, recentSessions, recentOutcomes.length);
|
|
18604
18605
|
const sourceTimestamp = pickResumeSourceTimestamp(latestSession, recentChat.messages);
|
|
18606
|
+
const bestRecallItem = pickBestRecallItem(recallIndex.items);
|
|
18605
18607
|
return {
|
|
18606
18608
|
project: project.name,
|
|
18607
18609
|
canonical_id: project.canonical_id,
|
|
18610
|
+
active_agents: activeAgents,
|
|
18611
|
+
cross_agent_active: activeAgents.length > 1,
|
|
18608
18612
|
continuity_state: continuityState,
|
|
18609
18613
|
continuity_summary: describeContinuityState(continuityState),
|
|
18610
18614
|
recall_mode: recallIndex.continuity_mode,
|
|
@@ -18615,6 +18619,9 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18615
18619
|
freshness: item.freshness,
|
|
18616
18620
|
title: item.title
|
|
18617
18621
|
})),
|
|
18622
|
+
best_recall_key: bestRecallItem?.key ?? null,
|
|
18623
|
+
best_recall_title: bestRecallItem?.title ?? null,
|
|
18624
|
+
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
18618
18625
|
resume_freshness: classifyResumeFreshness(sourceTimestamp),
|
|
18619
18626
|
resume_source_session_id: latestSession?.session_id ?? null,
|
|
18620
18627
|
resume_source_device_id: latestSession?.device_id ?? null,
|
|
@@ -18643,6 +18650,9 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18643
18650
|
suggested_tools: suggestedTools
|
|
18644
18651
|
};
|
|
18645
18652
|
}
|
|
18653
|
+
function pickBestRecallItem(items) {
|
|
18654
|
+
return items.find((item) => item.kind !== "memory") ?? items[0] ?? null;
|
|
18655
|
+
}
|
|
18646
18656
|
function pickResumeSourceTimestamp(latestSession, messages) {
|
|
18647
18657
|
const latestChatEpoch = messages.length > 0 ? messages[messages.length - 1]?.created_at_epoch ?? null : null;
|
|
18648
18658
|
return latestChatEpoch ?? latestSession?.completed_at_epoch ?? latestSession?.started_at_epoch ?? null;
|
|
@@ -18725,11 +18735,17 @@ function summarizeCaptureState(sessions) {
|
|
|
18725
18735
|
}
|
|
18726
18736
|
return summary;
|
|
18727
18737
|
}
|
|
18728
|
-
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) {
|
|
18729
18742
|
const suggested = [];
|
|
18730
18743
|
if (sessions.length > 0) {
|
|
18731
18744
|
suggested.push("recent_sessions");
|
|
18732
18745
|
}
|
|
18746
|
+
if (activeAgents.length > 1) {
|
|
18747
|
+
suggested.push("agent_memory_index");
|
|
18748
|
+
}
|
|
18733
18749
|
if (requestCount > 0 || toolCount > 0) {
|
|
18734
18750
|
suggested.push("activity_feed");
|
|
18735
18751
|
}
|
|
@@ -18754,7 +18770,7 @@ function buildSuggestedTools(sessions, requestCount, toolCount, observationCount
|
|
|
18754
18770
|
if (recentChatCount > 0) {
|
|
18755
18771
|
suggested.push("recent_chat", "search_chat");
|
|
18756
18772
|
}
|
|
18757
|
-
return Array.from(new Set(suggested)).slice(0,
|
|
18773
|
+
return Array.from(new Set(suggested)).slice(0, 6);
|
|
18758
18774
|
}
|
|
18759
18775
|
|
|
18760
18776
|
// src/tools/memory-console.ts
|
|
@@ -18812,8 +18828,11 @@ function getMemoryConsole(db, input) {
|
|
|
18812
18828
|
user_id: input.user_id
|
|
18813
18829
|
}) : null;
|
|
18814
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);
|
|
18815
18832
|
return {
|
|
18816
18833
|
project: project?.name,
|
|
18834
|
+
active_agents: activeAgents,
|
|
18835
|
+
cross_agent_active: projectIndex?.cross_agent_active ?? activeAgents.length > 1,
|
|
18817
18836
|
capture_mode: requests.length > 0 || tools.length > 0 ? "rich" : "observations-only",
|
|
18818
18837
|
continuity_state: continuityState,
|
|
18819
18838
|
continuity_summary: projectIndex?.continuity_summary ?? describeContinuityState(continuityState),
|
|
@@ -18825,6 +18844,9 @@ function getMemoryConsole(db, input) {
|
|
|
18825
18844
|
freshness: item.freshness,
|
|
18826
18845
|
title: item.title
|
|
18827
18846
|
})),
|
|
18847
|
+
best_recall_key: projectIndex?.best_recall_key ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.key ?? null,
|
|
18848
|
+
best_recall_title: projectIndex?.best_recall_title ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.title ?? null,
|
|
18849
|
+
best_recall_kind: projectIndex?.best_recall_kind ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.kind ?? null,
|
|
18828
18850
|
resume_freshness: projectIndex?.resume_freshness ?? "stale",
|
|
18829
18851
|
resume_source_session_id: projectIndex?.resume_source_session_id ?? sessions[0]?.session_id ?? null,
|
|
18830
18852
|
resume_source_device_id: projectIndex?.resume_source_device_id ?? sessions[0]?.device_id ?? null,
|
|
@@ -18848,13 +18870,15 @@ function getMemoryConsole(db, input) {
|
|
|
18848
18870
|
assistant_checkpoint_types: projectIndex?.assistant_checkpoint_types ?? [],
|
|
18849
18871
|
top_types: projectIndex?.top_types ?? [],
|
|
18850
18872
|
estimated_read_tokens: projectIndex?.estimated_read_tokens,
|
|
18851
|
-
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)
|
|
18852
18874
|
};
|
|
18853
18875
|
}
|
|
18854
|
-
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, chatCoverageState) {
|
|
18876
|
+
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, chatCoverageState, activeAgentCount) {
|
|
18855
18877
|
const suggested = [];
|
|
18856
18878
|
if (sessionCount > 0)
|
|
18857
18879
|
suggested.push("recent_sessions");
|
|
18880
|
+
if (activeAgentCount > 1)
|
|
18881
|
+
suggested.push("agent_memory_index");
|
|
18858
18882
|
if (requestCount > 0 || toolCount > 0)
|
|
18859
18883
|
suggested.push("activity_feed");
|
|
18860
18884
|
if (requestCount > 0 || chatCount > 0 || observationCount > 0)
|
|
@@ -18873,7 +18897,7 @@ function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, obse
|
|
|
18873
18897
|
suggested.push("refresh_chat_recall");
|
|
18874
18898
|
if (chatCount > 0)
|
|
18875
18899
|
suggested.push("recent_chat", "search_chat");
|
|
18876
|
-
return Array.from(new Set(suggested)).slice(0,
|
|
18900
|
+
return Array.from(new Set(suggested)).slice(0, 6);
|
|
18877
18901
|
}
|
|
18878
18902
|
|
|
18879
18903
|
// src/tools/workspace-memory-index.ts
|
|
@@ -19519,6 +19543,192 @@ function getCaptureQuality(db, input = {}) {
|
|
|
19519
19543
|
};
|
|
19520
19544
|
}
|
|
19521
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
|
+
|
|
19522
19732
|
// src/tools/tool-memory-index.ts
|
|
19523
19733
|
function parseConcepts(value) {
|
|
19524
19734
|
if (!value)
|
|
@@ -19721,9 +19931,13 @@ function getSessionContext(db, input) {
|
|
|
19721
19931
|
const continuityState = classifyContinuityState(recentRequests, recentTools, recentHandoffs, recentChatMessages, context.recentSessions ?? [], (context.recentOutcomes ?? []).length);
|
|
19722
19932
|
const latestChatEpoch = recentChat.messages.length > 0 ? recentChat.messages[recentChat.messages.length - 1]?.created_at_epoch ?? null : null;
|
|
19723
19933
|
const resumeTimestamp = latestChatEpoch ?? latestSession?.completed_at_epoch ?? latestSession?.started_at_epoch ?? null;
|
|
19934
|
+
const bestRecallItem = recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null;
|
|
19935
|
+
const activeAgents = collectActiveAgents(context.recentSessions ?? []);
|
|
19724
19936
|
return {
|
|
19725
19937
|
project_name: context.project_name,
|
|
19726
19938
|
canonical_id: context.canonical_id,
|
|
19939
|
+
active_agents: activeAgents,
|
|
19940
|
+
cross_agent_active: activeAgents.length > 1,
|
|
19727
19941
|
continuity_state: continuityState,
|
|
19728
19942
|
continuity_summary: describeContinuityState(continuityState),
|
|
19729
19943
|
recall_mode: recallIndex.continuity_mode,
|
|
@@ -19734,6 +19948,9 @@ function getSessionContext(db, input) {
|
|
|
19734
19948
|
freshness: item.freshness,
|
|
19735
19949
|
title: item.title
|
|
19736
19950
|
})),
|
|
19951
|
+
best_recall_key: bestRecallItem?.key ?? null,
|
|
19952
|
+
best_recall_title: bestRecallItem?.title ?? null,
|
|
19953
|
+
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
19737
19954
|
resume_freshness: classifyResumeFreshness(resumeTimestamp),
|
|
19738
19955
|
resume_source_session_id: latestSession?.session_id ?? null,
|
|
19739
19956
|
resume_source_device_id: latestSession?.device_id ?? null,
|
|
@@ -19756,7 +19973,7 @@ function getSessionContext(db, input) {
|
|
|
19756
19973
|
capture_state: captureState,
|
|
19757
19974
|
raw_capture_active: recentRequests > 0 || recentTools > 0,
|
|
19758
19975
|
estimated_read_tokens: estimateTokens(preview),
|
|
19759
|
-
suggested_tools:
|
|
19976
|
+
suggested_tools: buildSuggestedTools3(context, recentChat.coverage_state, activeAgents.length),
|
|
19760
19977
|
preview
|
|
19761
19978
|
};
|
|
19762
19979
|
}
|
|
@@ -19787,11 +20004,14 @@ function parseJsonArray3(value) {
|
|
|
19787
20004
|
return [];
|
|
19788
20005
|
}
|
|
19789
20006
|
}
|
|
19790
|
-
function
|
|
20007
|
+
function buildSuggestedTools3(context, chatCoverageState, activeAgentCount) {
|
|
19791
20008
|
const tools = [];
|
|
19792
20009
|
if ((context.recentSessions?.length ?? 0) > 0) {
|
|
19793
20010
|
tools.push("recent_sessions");
|
|
19794
20011
|
}
|
|
20012
|
+
if (activeAgentCount > 1) {
|
|
20013
|
+
tools.push("agent_memory_index");
|
|
20014
|
+
}
|
|
19795
20015
|
if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0) {
|
|
19796
20016
|
tools.push("activity_feed");
|
|
19797
20017
|
}
|
|
@@ -19819,7 +20039,7 @@ function buildSuggestedTools2(context, chatCoverageState) {
|
|
|
19819
20039
|
if ((context.recentChatMessages?.length ?? 0) > 0) {
|
|
19820
20040
|
tools.push("recent_chat", "search_chat");
|
|
19821
20041
|
}
|
|
19822
|
-
return Array.from(new Set(tools)).slice(0,
|
|
20042
|
+
return Array.from(new Set(tools)).slice(0, 6);
|
|
19823
20043
|
}
|
|
19824
20044
|
|
|
19825
20045
|
// src/tools/load-recall-item.ts
|
|
@@ -20390,6 +20610,7 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20390
20610
|
}
|
|
20391
20611
|
}
|
|
20392
20612
|
const { context, handoff, recentChat, recentSessions, recall } = snapshot;
|
|
20613
|
+
const bestRecallItem = pickBestRecallItem2(snapshot.recallIndex.items);
|
|
20393
20614
|
const latestSession = recentSessions[0] ?? null;
|
|
20394
20615
|
const latestSummary = latestSession ? db.getSessionSummary(latestSession.session_id) : null;
|
|
20395
20616
|
const inferredRequest = latestSession?.request?.trim() || null;
|
|
@@ -20418,6 +20639,7 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20418
20639
|
currentThread
|
|
20419
20640
|
});
|
|
20420
20641
|
const suggestedTools = Array.from(new Set([
|
|
20642
|
+
...bestRecallItem ? ["load_recall_item"] : [],
|
|
20421
20643
|
"search_recall",
|
|
20422
20644
|
...recentChat.coverage_state !== "transcript-backed" && recentChat.messages.length > 0 ? ["repair_recall", "refresh_chat_recall"] : [],
|
|
20423
20645
|
...handoff ? ["load_handoff"] : [],
|
|
@@ -20432,6 +20654,9 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20432
20654
|
resume_source_device_id: handoff?.device_id ?? latestSession?.device_id ?? null,
|
|
20433
20655
|
resume_confidence: resumeConfidence,
|
|
20434
20656
|
resume_basis: resumeBasis,
|
|
20657
|
+
best_recall_key: bestRecallItem?.key ?? null,
|
|
20658
|
+
best_recall_title: bestRecallItem?.title ?? null,
|
|
20659
|
+
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
20435
20660
|
repair_attempted: shouldRepair,
|
|
20436
20661
|
repair_result: repairResult ? {
|
|
20437
20662
|
imported_chat_messages: repairResult.imported_chat_messages,
|
|
@@ -20490,14 +20715,25 @@ async function buildResumeSnapshot(db, cwd, userId, currentDeviceId, limit) {
|
|
|
20490
20715
|
user_id: userId,
|
|
20491
20716
|
limit
|
|
20492
20717
|
});
|
|
20718
|
+
const recallIndex = listRecallItems(db, {
|
|
20719
|
+
cwd,
|
|
20720
|
+
project_scoped: true,
|
|
20721
|
+
user_id: userId,
|
|
20722
|
+
current_device_id: currentDeviceId,
|
|
20723
|
+
limit
|
|
20724
|
+
});
|
|
20493
20725
|
return {
|
|
20494
20726
|
context,
|
|
20495
20727
|
handoff: handoffResult.handoff,
|
|
20496
20728
|
recentChat,
|
|
20497
20729
|
recentSessions,
|
|
20498
|
-
recall
|
|
20730
|
+
recall,
|
|
20731
|
+
recallIndex
|
|
20499
20732
|
};
|
|
20500
20733
|
}
|
|
20734
|
+
function pickBestRecallItem2(items) {
|
|
20735
|
+
return items.find((item) => item.kind !== "memory") ?? items[0] ?? null;
|
|
20736
|
+
}
|
|
20501
20737
|
function extractCurrentThread(handoff) {
|
|
20502
20738
|
const narrative = handoff?.narrative ?? "";
|
|
20503
20739
|
const match = narrative.match(/Current thread:\s*(.+)/i);
|
|
@@ -22429,7 +22665,7 @@ process.on("SIGTERM", () => {
|
|
|
22429
22665
|
});
|
|
22430
22666
|
var server = new McpServer({
|
|
22431
22667
|
name: "engrm",
|
|
22432
|
-
version: "0.4.
|
|
22668
|
+
version: "0.4.34"
|
|
22433
22669
|
});
|
|
22434
22670
|
server.tool("save_observation", "Save an observation to memory", {
|
|
22435
22671
|
type: exports_external.enum([
|
|
@@ -23023,6 +23259,8 @@ server.tool("resume_thread", "USE FIRST when you want one direct 'where were we?
|
|
|
23023
23259
|
const handoffLine = result.handoff ? `Handoff: #${result.handoff.id} ${result.handoff.title}${result.handoff.source ? ` (${result.handoff.source})` : ""}
|
|
23024
23260
|
` : `Handoff: (none)
|
|
23025
23261
|
`;
|
|
23262
|
+
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23263
|
+
` : "";
|
|
23026
23264
|
const basisLines = result.resume_basis.length > 0 ? result.resume_basis.map((item) => `- ${item}`).join(`
|
|
23027
23265
|
`) : "- (none)";
|
|
23028
23266
|
const toolTrailLines = result.tool_trail.length > 0 ? result.tool_trail.map((item) => `- ${item}`).join(`
|
|
@@ -23057,7 +23295,7 @@ server.tool("resume_thread", "USE FIRST when you want one direct 'where were we?
|
|
|
23057
23295
|
` + `Freshness: ${result.resume_freshness}
|
|
23058
23296
|
` + `Source: ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23059
23297
|
` + `Resume confidence: ${result.resume_confidence}
|
|
23060
|
-
` + repairLine + `Current thread: ${result.current_thread ?? "(unknown)"}
|
|
23298
|
+
` + repairLine + openExactLine + `Current thread: ${result.current_thread ?? "(unknown)"}
|
|
23061
23299
|
` + `Latest request: ${result.latest_request ?? "(none)"}
|
|
23062
23300
|
` + `${handoffLine}` + `Chat recall: ${result.chat_coverage_state}
|
|
23063
23301
|
` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
@@ -23395,6 +23633,8 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23395
23633
|
`) : "- (none)";
|
|
23396
23634
|
const recallPreviewLines = result.recall_index_preview.length > 0 ? result.recall_index_preview.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}] ${item.title}`).join(`
|
|
23397
23635
|
`) : "- (none)";
|
|
23636
|
+
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23637
|
+
` : "";
|
|
23398
23638
|
const projectLine = result.project ? `Project: ${result.project}
|
|
23399
23639
|
|
|
23400
23640
|
` : "";
|
|
@@ -23407,7 +23647,8 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23407
23647
|
content: [
|
|
23408
23648
|
{
|
|
23409
23649
|
type: "text",
|
|
23410
|
-
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}
|
|
23411
23652
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
23412
23653
|
` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23413
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})
|
|
@@ -23416,7 +23657,7 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23416
23657
|
` + `${typeof result.estimated_read_tokens === "number" ? `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
23417
23658
|
` : ""}` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
23418
23659
|
|
|
23419
|
-
` + `Recall preview:
|
|
23660
|
+
` + openExactLine + `Recall preview:
|
|
23420
23661
|
${recallPreviewLines}
|
|
23421
23662
|
|
|
23422
23663
|
` + `Next actions:
|
|
@@ -23530,6 +23771,37 @@ ${projectLines}`
|
|
|
23530
23771
|
]
|
|
23531
23772
|
};
|
|
23532
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
|
+
});
|
|
23533
23805
|
server.tool("tool_memory_index", "Show which tools are actually producing durable memory, which plugins they exercise, and what memory types they create.", {
|
|
23534
23806
|
cwd: exports_external.string().optional().describe("Project path to inspect. Defaults to the current working directory."),
|
|
23535
23807
|
project_scoped: exports_external.boolean().optional().describe("If true, limit results to the current project instead of the whole workspace."),
|
|
@@ -23615,8 +23887,10 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
23615
23887
|
type: "text",
|
|
23616
23888
|
text: `Project: ${result.project_name}
|
|
23617
23889
|
` + `Canonical ID: ${result.canonical_id}
|
|
23890
|
+
` + `Agents active: ${result.active_agents.join(", ") || "(none)"}
|
|
23618
23891
|
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23619
23892
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
23893
|
+
` + `Open exact: ${result.best_recall_key ? `load_recall_item("${result.best_recall_key}")` : "(none)"}
|
|
23620
23894
|
` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23621
23895
|
` + `Loaded observations: ${result.session_count}
|
|
23622
23896
|
` + `Searchable total: ${result.total_active}
|
|
@@ -23697,12 +23971,15 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23697
23971
|
`) : "- (none)";
|
|
23698
23972
|
const recallPreviewLines = result.recall_index_preview.length > 0 ? result.recall_index_preview.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}] ${item.title}`).join(`
|
|
23699
23973
|
`) : "- (none)";
|
|
23974
|
+
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23975
|
+
` : "";
|
|
23700
23976
|
return {
|
|
23701
23977
|
content: [
|
|
23702
23978
|
{
|
|
23703
23979
|
type: "text",
|
|
23704
23980
|
text: `Project: ${result.project}
|
|
23705
23981
|
` + `Canonical ID: ${result.canonical_id}
|
|
23982
|
+
` + `Agents active: ${result.active_agents.join(", ") || "(none)"}
|
|
23706
23983
|
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23707
23984
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
23708
23985
|
` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
@@ -23720,7 +23997,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23720
23997
|
` + `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
23721
23998
|
` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
23722
23999
|
|
|
23723
|
-
` + `Recall preview:
|
|
24000
|
+
` + openExactLine + `Recall preview:
|
|
23724
24001
|
${recallPreviewLines}
|
|
23725
24002
|
|
|
23726
24003
|
` + `Next actions:
|
package/package.json
CHANGED