engrm 0.4.33 → 0.4.35
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 +7 -0
- package/dist/hooks/session-start.js +108 -8
- package/dist/hooks/stop.js +1 -1
- package/dist/server.js +482 -44
- 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,8 +403,14 @@ 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
|
|
408
|
+
- `agent_memory_index` now also gives the best exact recall jump per agent, so you can compare agents and open the right handoff/thread immediately
|
|
409
|
+
- recall previews and `load_recall_item` now show source-agent provenance too, so exact recall stays readable when Claude, Codex, and OpenClaw all touch the same project
|
|
405
410
|
- `memory_console` gives the quickest project snapshot, including whether continuity is `fresh`, `thin`, or `cold`
|
|
406
411
|
- `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
|
|
412
|
+
- `resume_thread(agent="claude-code")` lets you deliberately recover one agent's thread on a shared repo instead of only taking the blended repo-level default
|
|
413
|
+
- startup and the MCP workbench now surface a direct `resume_thread(agent="...")` hint when multiple agents are active on one repo
|
|
407
414
|
- `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
415
|
- `load_recall_item` completes that protocol by letting agents open one exact recall key directly after listing
|
|
409
416
|
- `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
|
|
@@ -2297,6 +2297,61 @@ function formatHandoffSource2(handoff) {
|
|
|
2297
2297
|
return `from ${handoff.device_id} · ${ageLabel}`;
|
|
2298
2298
|
}
|
|
2299
2299
|
|
|
2300
|
+
// src/tools/inbox-messages.ts
|
|
2301
|
+
function buildHandoffMessageFilterSql(lifecycle) {
|
|
2302
|
+
return `
|
|
2303
|
+
type = 'message'
|
|
2304
|
+
AND lifecycle IN (${lifecycle})
|
|
2305
|
+
AND (
|
|
2306
|
+
COALESCE(source_tool, '') IN ('create_handoff', 'rolling_handoff')
|
|
2307
|
+
OR title LIKE 'Handoff:%'
|
|
2308
|
+
OR title LIKE 'Handoff Draft:%'
|
|
2309
|
+
OR (
|
|
2310
|
+
concepts IS NOT NULL AND (
|
|
2311
|
+
concepts LIKE '%"handoff"%'
|
|
2312
|
+
OR concepts LIKE '%"session-handoff"%'
|
|
2313
|
+
OR concepts LIKE '%"draft-handoff"%'
|
|
2314
|
+
)
|
|
2315
|
+
)
|
|
2316
|
+
)
|
|
2317
|
+
`;
|
|
2318
|
+
}
|
|
2319
|
+
var HANDOFF_MESSAGE_FILTER_SQL = buildHandoffMessageFilterSql("'active', 'pinned'");
|
|
2320
|
+
var INBOX_MESSAGE_FILTER_SQL = `
|
|
2321
|
+
type = 'message'
|
|
2322
|
+
AND lifecycle IN ('active', 'pinned')
|
|
2323
|
+
AND NOT (${HANDOFF_MESSAGE_FILTER_SQL})
|
|
2324
|
+
`;
|
|
2325
|
+
function classifyMessageObservation(observation) {
|
|
2326
|
+
if (observation.type !== "message")
|
|
2327
|
+
return null;
|
|
2328
|
+
const concepts = parseConcepts(observation.concepts);
|
|
2329
|
+
const isDraft = observation.title.startsWith("Handoff Draft:") || observation.source_tool === "rolling_handoff" || concepts.includes("draft-handoff") || concepts.includes("auto-handoff");
|
|
2330
|
+
if (isDraft)
|
|
2331
|
+
return "draft-handoff";
|
|
2332
|
+
const isHandoff = observation.title.startsWith("Handoff:") || observation.source_tool === "create_handoff" || concepts.includes("handoff") || concepts.includes("session-handoff");
|
|
2333
|
+
if (isHandoff)
|
|
2334
|
+
return "handoff";
|
|
2335
|
+
return "inbox-note";
|
|
2336
|
+
}
|
|
2337
|
+
function parseConcepts(value) {
|
|
2338
|
+
if (!value)
|
|
2339
|
+
return [];
|
|
2340
|
+
try {
|
|
2341
|
+
const parsed = JSON.parse(value);
|
|
2342
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
2343
|
+
} catch {
|
|
2344
|
+
return [];
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
function getUnreadInboxMessageCount(db, currentDeviceId, userId, lastReadId) {
|
|
2348
|
+
return db.db.query(`SELECT COUNT(*) as c FROM observations
|
|
2349
|
+
WHERE ${INBOX_MESSAGE_FILTER_SQL}
|
|
2350
|
+
AND id > ?
|
|
2351
|
+
AND device_id != ?
|
|
2352
|
+
AND (sensitivity != 'personal' OR user_id = ?)`).get(lastReadId, currentDeviceId, userId)?.c ?? 0;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2300
2355
|
// src/context/inject.ts
|
|
2301
2356
|
var FRESH_CONTINUITY_WINDOW_DAYS2 = 3;
|
|
2302
2357
|
function tokenizeProjectHint2(text) {
|
|
@@ -2992,6 +3047,32 @@ function getRecentOutcomes2(db, projectId, userId, recentSessions) {
|
|
|
2992
3047
|
return picked;
|
|
2993
3048
|
}
|
|
2994
3049
|
|
|
3050
|
+
// src/tools/inbox-messages.ts
|
|
3051
|
+
function buildHandoffMessageFilterSql2(lifecycle) {
|
|
3052
|
+
return `
|
|
3053
|
+
type = 'message'
|
|
3054
|
+
AND lifecycle IN (${lifecycle})
|
|
3055
|
+
AND (
|
|
3056
|
+
COALESCE(source_tool, '') IN ('create_handoff', 'rolling_handoff')
|
|
3057
|
+
OR title LIKE 'Handoff:%'
|
|
3058
|
+
OR title LIKE 'Handoff Draft:%'
|
|
3059
|
+
OR (
|
|
3060
|
+
concepts IS NOT NULL AND (
|
|
3061
|
+
concepts LIKE '%"handoff"%'
|
|
3062
|
+
OR concepts LIKE '%"session-handoff"%'
|
|
3063
|
+
OR concepts LIKE '%"draft-handoff"%'
|
|
3064
|
+
)
|
|
3065
|
+
)
|
|
3066
|
+
)
|
|
3067
|
+
`;
|
|
3068
|
+
}
|
|
3069
|
+
var HANDOFF_MESSAGE_FILTER_SQL2 = buildHandoffMessageFilterSql2("'active', 'pinned'");
|
|
3070
|
+
var INBOX_MESSAGE_FILTER_SQL2 = `
|
|
3071
|
+
type = 'message'
|
|
3072
|
+
AND lifecycle IN ('active', 'pinned')
|
|
3073
|
+
AND NOT (${HANDOFF_MESSAGE_FILTER_SQL2})
|
|
3074
|
+
`;
|
|
3075
|
+
|
|
2995
3076
|
// src/tools/project-memory-index.ts
|
|
2996
3077
|
function classifyResumeFreshness(sourceTimestamp) {
|
|
2997
3078
|
if (!sourceTimestamp)
|
|
@@ -3144,7 +3225,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
|
|
|
3144
3225
|
import { join as join3 } from "node:path";
|
|
3145
3226
|
import { homedir } from "node:os";
|
|
3146
3227
|
var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
|
|
3147
|
-
var CLIENT_VERSION = "0.4.
|
|
3228
|
+
var CLIENT_VERSION = "0.4.35";
|
|
3148
3229
|
function hashFile(filePath) {
|
|
3149
3230
|
try {
|
|
3150
3231
|
if (!existsSync3(filePath))
|
|
@@ -5494,12 +5575,7 @@ async function main() {
|
|
|
5494
5575
|
try {
|
|
5495
5576
|
const readKey = `messages_read_${config.device_id}`;
|
|
5496
5577
|
const lastReadId = parseInt(db.getSyncState(readKey) ?? "0", 10);
|
|
5497
|
-
msgCount = db.
|
|
5498
|
-
WHERE type = 'message'
|
|
5499
|
-
AND id > ?
|
|
5500
|
-
AND lifecycle IN ('active', 'pinned')
|
|
5501
|
-
AND device_id != ?
|
|
5502
|
-
AND (sensitivity != 'personal' OR user_id = ?)`).get(lastReadId, config.device_id, config.user_id)?.c ?? 0;
|
|
5578
|
+
msgCount = getUnreadInboxMessageCount(db, config.device_id, config.user_id, lastReadId);
|
|
5503
5579
|
} catch {}
|
|
5504
5580
|
const splash = formatSplashScreen({
|
|
5505
5581
|
projectName: context.project_name,
|
|
@@ -5653,6 +5729,7 @@ function formatVisibleStartupBrief(context) {
|
|
|
5653
5729
|
const projectSignals = buildProjectSignalLine(context);
|
|
5654
5730
|
const shownItems = new Set;
|
|
5655
5731
|
const latestHandoffLines = buildLatestHandoffLines(context);
|
|
5732
|
+
const inboxNoteLines = buildRecentInboxNoteLines(context);
|
|
5656
5733
|
const freshContinuity = hasFreshContinuitySignal(context);
|
|
5657
5734
|
if (latestHandoffLines.length > 0) {
|
|
5658
5735
|
lines.push(`${c2.cyan}Latest handoff:${c2.reset}`);
|
|
@@ -5661,6 +5738,13 @@ function formatVisibleStartupBrief(context) {
|
|
|
5661
5738
|
rememberShownItem(shownItems, item);
|
|
5662
5739
|
}
|
|
5663
5740
|
}
|
|
5741
|
+
if (inboxNoteLines.length > 0) {
|
|
5742
|
+
lines.push(`${c2.cyan}Inbox notes:${c2.reset}`);
|
|
5743
|
+
for (const item of inboxNoteLines) {
|
|
5744
|
+
lines.push(` - ${truncateInline(item, 160)}`);
|
|
5745
|
+
rememberShownItem(shownItems, item);
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5664
5748
|
lines.push(`${c2.cyan}Continuity:${c2.reset} ${continuityState} \u2014 ${truncateInline(describeContinuityState(continuityState), 160)}`);
|
|
5665
5749
|
const resumeReadiness = buildResumeReadinessLine(context);
|
|
5666
5750
|
if (resumeReadiness) {
|
|
@@ -5786,6 +5870,10 @@ function buildLatestHandoffLines(context) {
|
|
|
5786
5870
|
}
|
|
5787
5871
|
return Array.from(new Set(lines.filter(Boolean))).slice(0, 2);
|
|
5788
5872
|
}
|
|
5873
|
+
function buildRecentInboxNoteLines(context) {
|
|
5874
|
+
const notes = context.observations.filter((obs) => classifyMessageObservation(obs) === "inbox-note").slice(0, 2);
|
|
5875
|
+
return Array.from(new Set(notes.map((obs) => obs.title.trim()).filter((title) => title.length > 0)));
|
|
5876
|
+
}
|
|
5789
5877
|
function buildResumeReadinessLine(context) {
|
|
5790
5878
|
const latestSession = context.recentSessions?.[0] ?? null;
|
|
5791
5879
|
const latestHandoff = context.recentHandoffs?.[0] ?? null;
|
|
@@ -5847,11 +5935,15 @@ function formatContextIndex(context, shownItems) {
|
|
|
5847
5935
|
function formatInspectHints(context, visibleObservationIds = [], recallItems = []) {
|
|
5848
5936
|
const hints = [];
|
|
5849
5937
|
const continuityState = getStartupContinuityState(context);
|
|
5938
|
+
const activeAgents = collectStartupAgents(context);
|
|
5850
5939
|
if ((context.recentSessions?.length ?? 0) > 0) {
|
|
5851
5940
|
hints.push("recent_sessions");
|
|
5852
5941
|
hints.push("session_story");
|
|
5853
5942
|
hints.push("create_handoff");
|
|
5854
5943
|
}
|
|
5944
|
+
if (activeAgents.length > 1) {
|
|
5945
|
+
hints.push("agent_memory_index");
|
|
5946
|
+
}
|
|
5855
5947
|
if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0 || (context.recentChatMessages?.length ?? 0) > 0) {
|
|
5856
5948
|
hints.push("activity_feed");
|
|
5857
5949
|
}
|
|
@@ -5884,20 +5976,25 @@ function formatInspectHints(context, visibleObservationIds = [], recallItems = [
|
|
|
5884
5976
|
return [];
|
|
5885
5977
|
const ids = visibleObservationIds.slice(0, 5);
|
|
5886
5978
|
const openNowItem = recallItems.find((item) => item.kind !== "memory") ?? null;
|
|
5979
|
+
const resumeAgent = activeAgents.length > 1 ? context.recentSessions?.[0]?.agent ?? null : null;
|
|
5887
5980
|
const fetchHint = ids.length > 0 ? `get_observations([${ids.join(", ")}])` : null;
|
|
5888
5981
|
return [
|
|
5889
5982
|
`${c2.dim}Next look:${c2.reset} ${unique.join(" \xB7 ")}`,
|
|
5890
5983
|
...openNowItem ? [`${c2.dim}Open now:${c2.reset} load_recall_item("${openNowItem.key}")`] : [],
|
|
5984
|
+
...resumeAgent ? [`${c2.dim}Resume agent:${c2.reset} resume_thread(agent="${resumeAgent}")`] : [],
|
|
5891
5985
|
...fetchHint ? [`${c2.dim}Pull detail:${c2.reset} ${fetchHint}`] : []
|
|
5892
5986
|
];
|
|
5893
5987
|
}
|
|
5988
|
+
function collectStartupAgents(context) {
|
|
5989
|
+
return Array.from(new Set((context.recentSessions ?? []).map((session) => session.agent?.trim()).filter((agent) => Boolean(agent) && !agent.startsWith("engrm-")))).sort();
|
|
5990
|
+
}
|
|
5894
5991
|
function formatStartupRecallPreview(recallItems) {
|
|
5895
5992
|
const items = recallItems.slice(0, 3);
|
|
5896
5993
|
if (items.length === 0)
|
|
5897
5994
|
return [];
|
|
5898
5995
|
return [
|
|
5899
5996
|
`${c2.dim}Recall preview:${c2.reset} exact keys you can open now`,
|
|
5900
|
-
...items.map((item) => `${item.key} [${item.kind} \xB7 ${item.freshness}] ${truncateInline(item.title, 110)}`)
|
|
5997
|
+
...items.map((item) => `${item.key} [${item.kind} \xB7 ${item.freshness}${item.source_agent ? ` \xB7 ${item.source_agent}` : ""}] ${truncateInline(item.title, 110)}`)
|
|
5901
5998
|
];
|
|
5902
5999
|
}
|
|
5903
6000
|
function buildStartupRecallItems(context) {
|
|
@@ -5912,6 +6009,7 @@ function buildStartupRecallItems(context) {
|
|
|
5912
6009
|
kind: "handoff",
|
|
5913
6010
|
freshness,
|
|
5914
6011
|
title,
|
|
6012
|
+
source_agent: (context.recentSessions ?? []).find((session) => session.session_id === handoff.session_id)?.agent ?? null,
|
|
5915
6013
|
score: freshnessScore(freshness) + 40
|
|
5916
6014
|
});
|
|
5917
6015
|
}
|
|
@@ -5926,6 +6024,7 @@ function buildStartupRecallItems(context) {
|
|
|
5926
6024
|
kind: "thread",
|
|
5927
6025
|
freshness,
|
|
5928
6026
|
title,
|
|
6027
|
+
source_agent: session.agent ?? null,
|
|
5929
6028
|
score: freshnessScore(freshness) + 30
|
|
5930
6029
|
});
|
|
5931
6030
|
}
|
|
@@ -5938,6 +6037,7 @@ function buildStartupRecallItems(context) {
|
|
|
5938
6037
|
kind: "chat",
|
|
5939
6038
|
freshness,
|
|
5940
6039
|
title: `[${message.role}] ${message.content.replace(/\s+/g, " ").trim()}`,
|
|
6040
|
+
source_agent: message.agent ?? null,
|
|
5941
6041
|
score: freshnessScore(freshness) + 20
|
|
5942
6042
|
});
|
|
5943
6043
|
}
|
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.35",
|
|
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
|
@@ -16827,6 +16827,69 @@ function pinObservation(db, input) {
|
|
|
16827
16827
|
return { success: true };
|
|
16828
16828
|
}
|
|
16829
16829
|
|
|
16830
|
+
// src/tools/inbox-messages.ts
|
|
16831
|
+
function buildHandoffMessageFilterSql(lifecycle) {
|
|
16832
|
+
return `
|
|
16833
|
+
type = 'message'
|
|
16834
|
+
AND lifecycle IN (${lifecycle})
|
|
16835
|
+
AND (
|
|
16836
|
+
COALESCE(source_tool, '') IN ('create_handoff', 'rolling_handoff')
|
|
16837
|
+
OR title LIKE 'Handoff:%'
|
|
16838
|
+
OR title LIKE 'Handoff Draft:%'
|
|
16839
|
+
OR (
|
|
16840
|
+
concepts IS NOT NULL AND (
|
|
16841
|
+
concepts LIKE '%"handoff"%'
|
|
16842
|
+
OR concepts LIKE '%"session-handoff"%'
|
|
16843
|
+
OR concepts LIKE '%"draft-handoff"%'
|
|
16844
|
+
)
|
|
16845
|
+
)
|
|
16846
|
+
)
|
|
16847
|
+
`;
|
|
16848
|
+
}
|
|
16849
|
+
var HANDOFF_MESSAGE_FILTER_SQL = buildHandoffMessageFilterSql("'active', 'pinned'");
|
|
16850
|
+
var INBOX_MESSAGE_FILTER_SQL = `
|
|
16851
|
+
type = 'message'
|
|
16852
|
+
AND lifecycle IN ('active', 'pinned')
|
|
16853
|
+
AND NOT (${HANDOFF_MESSAGE_FILTER_SQL})
|
|
16854
|
+
`;
|
|
16855
|
+
function getHandoffMessageFilterSql(options) {
|
|
16856
|
+
return buildHandoffMessageFilterSql(options?.include_aging ? "'active', 'aging', 'pinned'" : "'active', 'pinned'");
|
|
16857
|
+
}
|
|
16858
|
+
function classifyMessageObservation(observation) {
|
|
16859
|
+
if (observation.type !== "message")
|
|
16860
|
+
return null;
|
|
16861
|
+
const concepts = parseConcepts(observation.concepts);
|
|
16862
|
+
const isDraft = observation.title.startsWith("Handoff Draft:") || observation.source_tool === "rolling_handoff" || concepts.includes("draft-handoff") || concepts.includes("auto-handoff");
|
|
16863
|
+
if (isDraft)
|
|
16864
|
+
return "draft-handoff";
|
|
16865
|
+
const isHandoff = observation.title.startsWith("Handoff:") || observation.source_tool === "create_handoff" || concepts.includes("handoff") || concepts.includes("session-handoff");
|
|
16866
|
+
if (isHandoff)
|
|
16867
|
+
return "handoff";
|
|
16868
|
+
return "inbox-note";
|
|
16869
|
+
}
|
|
16870
|
+
function parseConcepts(value) {
|
|
16871
|
+
if (!value)
|
|
16872
|
+
return [];
|
|
16873
|
+
try {
|
|
16874
|
+
const parsed = JSON.parse(value);
|
|
16875
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
16876
|
+
} catch {
|
|
16877
|
+
return [];
|
|
16878
|
+
}
|
|
16879
|
+
}
|
|
16880
|
+
function getInboxMessageCount(db) {
|
|
16881
|
+
return db.db.query(`SELECT COUNT(*) as c FROM observations
|
|
16882
|
+
WHERE ${INBOX_MESSAGE_FILTER_SQL}`).get()?.c ?? 0;
|
|
16883
|
+
}
|
|
16884
|
+
function getUnreadInboxMessages(db, currentDeviceId, userId, lastReadId, limit = 20) {
|
|
16885
|
+
return db.db.query(`SELECT id, title, narrative, user_id, device_id, created_at FROM observations
|
|
16886
|
+
WHERE ${INBOX_MESSAGE_FILTER_SQL}
|
|
16887
|
+
AND id > ?
|
|
16888
|
+
AND device_id != ?
|
|
16889
|
+
AND (sensitivity != 'personal' OR user_id = ?)
|
|
16890
|
+
ORDER BY created_at_epoch DESC LIMIT ?`).all(lastReadId, currentDeviceId, userId, limit);
|
|
16891
|
+
}
|
|
16892
|
+
|
|
16830
16893
|
// src/tools/recent.ts
|
|
16831
16894
|
function getRecentActivity(db, input) {
|
|
16832
16895
|
const limit = Math.max(1, Math.min(input.limit ?? 10, 50));
|
|
@@ -16865,7 +16928,10 @@ function getRecentActivity(db, input) {
|
|
|
16865
16928
|
LEFT JOIN projects ON projects.id = observations.project_id
|
|
16866
16929
|
WHERE ${conditions.join(" AND ")}
|
|
16867
16930
|
ORDER BY observations.created_at_epoch DESC
|
|
16868
|
-
LIMIT ?`).all(...params)
|
|
16931
|
+
LIMIT ?`).all(...params).map((observation) => ({
|
|
16932
|
+
...observation,
|
|
16933
|
+
message_kind: classifyMessageObservation(observation)
|
|
16934
|
+
}));
|
|
16869
16935
|
return {
|
|
16870
16936
|
observations,
|
|
16871
16937
|
project: projectName
|
|
@@ -18359,6 +18425,7 @@ function listRecallItems(db, input) {
|
|
|
18359
18425
|
user_id: input.user_id,
|
|
18360
18426
|
limit: Math.min(limit * 2, 24)
|
|
18361
18427
|
}).observations;
|
|
18428
|
+
const sessionAgentById = new Map(sessions.filter((session) => Boolean(session.session_id)).map((session) => [session.session_id, session.agent ?? null]));
|
|
18362
18429
|
const items = [
|
|
18363
18430
|
...handoffs.map((handoff) => ({
|
|
18364
18431
|
key: `handoff:${handoff.id}`,
|
|
@@ -18368,7 +18435,8 @@ function listRecallItems(db, input) {
|
|
|
18368
18435
|
created_at_epoch: handoff.created_at_epoch,
|
|
18369
18436
|
freshness: classifyFreshness(handoff.created_at_epoch),
|
|
18370
18437
|
session_id: handoff.session_id,
|
|
18371
|
-
source_device_id: handoff.device_id ?? null
|
|
18438
|
+
source_device_id: handoff.device_id ?? null,
|
|
18439
|
+
source_agent: handoff.session_id ? sessionAgentById.get(handoff.session_id) ?? null : null
|
|
18372
18440
|
})),
|
|
18373
18441
|
...sessions.filter((session) => Boolean(session.request || session.completed || session.current_thread)).map((session) => ({
|
|
18374
18442
|
key: `session:${session.session_id}`,
|
|
@@ -18378,7 +18446,8 @@ function listRecallItems(db, input) {
|
|
|
18378
18446
|
created_at_epoch: session.completed_at_epoch ?? session.started_at_epoch ?? 0,
|
|
18379
18447
|
freshness: classifyFreshness(session.completed_at_epoch ?? session.started_at_epoch ?? null),
|
|
18380
18448
|
session_id: session.session_id,
|
|
18381
|
-
source_device_id: session.device_id ?? null
|
|
18449
|
+
source_device_id: session.device_id ?? null,
|
|
18450
|
+
source_agent: session.agent ?? null
|
|
18382
18451
|
})),
|
|
18383
18452
|
...dedupeChatIndex(chat).map((message) => {
|
|
18384
18453
|
const origin = getChatCaptureOrigin(message);
|
|
@@ -18390,7 +18459,8 @@ function listRecallItems(db, input) {
|
|
|
18390
18459
|
created_at_epoch: message.created_at_epoch,
|
|
18391
18460
|
freshness: classifyFreshness(message.created_at_epoch),
|
|
18392
18461
|
session_id: message.session_id,
|
|
18393
|
-
source_device_id: message.device_id ?? null
|
|
18462
|
+
source_device_id: message.device_id ?? null,
|
|
18463
|
+
source_agent: message.agent ?? null
|
|
18394
18464
|
};
|
|
18395
18465
|
}),
|
|
18396
18466
|
...observations.filter((obs) => obs.type !== "message").filter((obs) => !looksLikeFileOperationTitle3(obs.title)).map((obs) => ({
|
|
@@ -18401,7 +18471,8 @@ function listRecallItems(db, input) {
|
|
|
18401
18471
|
created_at_epoch: obs.created_at_epoch,
|
|
18402
18472
|
freshness: classifyFreshness(obs.created_at_epoch),
|
|
18403
18473
|
session_id: obs.session_id ?? null,
|
|
18404
|
-
source_device_id: obs.device_id ?? null
|
|
18474
|
+
source_device_id: obs.device_id ?? null,
|
|
18475
|
+
source_agent: null
|
|
18405
18476
|
}))
|
|
18406
18477
|
];
|
|
18407
18478
|
const deduped = dedupeRecallItems(items).sort((a, b) => compareRecallItems(a, b, input.current_device_id)).slice(0, limit);
|
|
@@ -18572,6 +18643,7 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18572
18643
|
}).handoffs;
|
|
18573
18644
|
const rollingHandoffDraftsCount = recentHandoffsCount.filter((handoff) => isDraftHandoff(handoff)).length;
|
|
18574
18645
|
const savedHandoffsCount = recentHandoffsCount.length - rollingHandoffDraftsCount;
|
|
18646
|
+
const recentInboxNotes = observations.filter((obs) => classifyMessageObservation(obs) === "inbox-note").slice(0, 5);
|
|
18575
18647
|
const recentChat = getRecentChat(db, {
|
|
18576
18648
|
cwd,
|
|
18577
18649
|
project_scoped: true,
|
|
@@ -18589,8 +18661,9 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18589
18661
|
const latestSummary = latestSession ? db.getSessionSummary(latestSession.session_id) : null;
|
|
18590
18662
|
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
18663
|
const captureSummary = summarizeCaptureState(recentSessions);
|
|
18664
|
+
const activeAgents = collectActiveAgents(recentSessions);
|
|
18592
18665
|
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);
|
|
18666
|
+
const suggestedTools = buildSuggestedTools(recentSessions, recentRequestsCount, recentToolsCount, observations.length, recentChatCount, recentChat.coverage_state, activeAgents);
|
|
18594
18667
|
const estimatedReadTokens = estimateTokens([
|
|
18595
18668
|
recentOutcomes.join(`
|
|
18596
18669
|
`),
|
|
@@ -18606,6 +18679,8 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18606
18679
|
return {
|
|
18607
18680
|
project: project.name,
|
|
18608
18681
|
canonical_id: project.canonical_id,
|
|
18682
|
+
active_agents: activeAgents,
|
|
18683
|
+
cross_agent_active: activeAgents.length > 1,
|
|
18609
18684
|
continuity_state: continuityState,
|
|
18610
18685
|
continuity_summary: describeContinuityState(continuityState),
|
|
18611
18686
|
recall_mode: recallIndex.continuity_mode,
|
|
@@ -18614,11 +18689,13 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18614
18689
|
key: item.key,
|
|
18615
18690
|
kind: item.kind,
|
|
18616
18691
|
freshness: item.freshness,
|
|
18617
|
-
title: item.title
|
|
18692
|
+
title: item.title,
|
|
18693
|
+
source_agent: item.source_agent
|
|
18618
18694
|
})),
|
|
18619
18695
|
best_recall_key: bestRecallItem?.key ?? null,
|
|
18620
18696
|
best_recall_title: bestRecallItem?.title ?? null,
|
|
18621
18697
|
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
18698
|
+
best_agent_resume_agent: activeAgents.length > 1 ? latestSession?.agent ?? null : null,
|
|
18622
18699
|
resume_freshness: classifyResumeFreshness(sourceTimestamp),
|
|
18623
18700
|
resume_source_session_id: latestSession?.session_id ?? null,
|
|
18624
18701
|
resume_source_device_id: latestSession?.device_id ?? null,
|
|
@@ -18631,6 +18708,8 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18631
18708
|
recent_handoffs_count: recentHandoffsCount.length,
|
|
18632
18709
|
rolling_handoff_drafts_count: rollingHandoffDraftsCount,
|
|
18633
18710
|
saved_handoffs_count: savedHandoffsCount,
|
|
18711
|
+
recent_inbox_notes_count: recentInboxNotes.length,
|
|
18712
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
18634
18713
|
recent_chat_count: recentChatCount,
|
|
18635
18714
|
recent_chat_sessions: recentChat.session_count,
|
|
18636
18715
|
chat_source_summary: recentChat.source_summary,
|
|
@@ -18732,11 +18811,17 @@ function summarizeCaptureState(sessions) {
|
|
|
18732
18811
|
}
|
|
18733
18812
|
return summary;
|
|
18734
18813
|
}
|
|
18735
|
-
function
|
|
18814
|
+
function collectActiveAgents(sessions) {
|
|
18815
|
+
return Array.from(new Set(sessions.map((session) => session.agent?.trim()).filter((agent) => Boolean(agent) && !agent.startsWith("engrm-")))).sort();
|
|
18816
|
+
}
|
|
18817
|
+
function buildSuggestedTools(sessions, requestCount, toolCount, observationCount, recentChatCount, chatCoverageState, activeAgents) {
|
|
18736
18818
|
const suggested = [];
|
|
18737
18819
|
if (sessions.length > 0) {
|
|
18738
18820
|
suggested.push("recent_sessions");
|
|
18739
18821
|
}
|
|
18822
|
+
if (activeAgents.length > 1) {
|
|
18823
|
+
suggested.push("agent_memory_index");
|
|
18824
|
+
}
|
|
18740
18825
|
if (requestCount > 0 || toolCount > 0) {
|
|
18741
18826
|
suggested.push("activity_feed");
|
|
18742
18827
|
}
|
|
@@ -18761,7 +18846,7 @@ function buildSuggestedTools(sessions, requestCount, toolCount, observationCount
|
|
|
18761
18846
|
if (recentChatCount > 0) {
|
|
18762
18847
|
suggested.push("recent_chat", "search_chat");
|
|
18763
18848
|
}
|
|
18764
|
-
return Array.from(new Set(suggested)).slice(0,
|
|
18849
|
+
return Array.from(new Set(suggested)).slice(0, 6);
|
|
18765
18850
|
}
|
|
18766
18851
|
|
|
18767
18852
|
// src/tools/memory-console.ts
|
|
@@ -18802,6 +18887,7 @@ function getMemoryConsole(db, input) {
|
|
|
18802
18887
|
}).handoffs;
|
|
18803
18888
|
const rollingHandoffDrafts = recentHandoffs.filter((handoff) => isDraftHandoff(handoff)).length;
|
|
18804
18889
|
const savedHandoffs = recentHandoffs.length - rollingHandoffDrafts;
|
|
18890
|
+
const recentInboxNotes = observations.filter((obs) => obs.message_kind === "inbox-note").slice(0, 3).map((obs) => ({ id: obs.id, title: obs.title, created_at_epoch: obs.created_at_epoch }));
|
|
18805
18891
|
const recentChat = getRecentChat(db, {
|
|
18806
18892
|
cwd,
|
|
18807
18893
|
project_scoped: projectScoped,
|
|
@@ -18819,8 +18905,11 @@ function getMemoryConsole(db, input) {
|
|
|
18819
18905
|
user_id: input.user_id
|
|
18820
18906
|
}) : null;
|
|
18821
18907
|
const continuityState = projectIndex?.continuity_state ?? classifyContinuityState(requests.length, tools.length, recentHandoffs.length, recentChat.messages.length, sessions, (projectIndex?.recent_outcomes ?? []).length);
|
|
18908
|
+
const activeAgents = projectIndex?.active_agents ?? collectActiveAgents(sessions);
|
|
18822
18909
|
return {
|
|
18823
18910
|
project: project?.name,
|
|
18911
|
+
active_agents: activeAgents,
|
|
18912
|
+
cross_agent_active: projectIndex?.cross_agent_active ?? activeAgents.length > 1,
|
|
18824
18913
|
capture_mode: requests.length > 0 || tools.length > 0 ? "rich" : "observations-only",
|
|
18825
18914
|
continuity_state: continuityState,
|
|
18826
18915
|
continuity_summary: projectIndex?.continuity_summary ?? describeContinuityState(continuityState),
|
|
@@ -18830,11 +18919,13 @@ function getMemoryConsole(db, input) {
|
|
|
18830
18919
|
key: item.key,
|
|
18831
18920
|
kind: item.kind,
|
|
18832
18921
|
freshness: item.freshness,
|
|
18833
|
-
title: item.title
|
|
18922
|
+
title: item.title,
|
|
18923
|
+
source_agent: item.source_agent
|
|
18834
18924
|
})),
|
|
18835
18925
|
best_recall_key: projectIndex?.best_recall_key ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.key ?? null,
|
|
18836
18926
|
best_recall_title: projectIndex?.best_recall_title ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.title ?? null,
|
|
18837
18927
|
best_recall_kind: projectIndex?.best_recall_kind ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.kind ?? null,
|
|
18928
|
+
best_agent_resume_agent: projectIndex?.best_agent_resume_agent ?? (activeAgents.length > 1 ? sessions[0]?.agent ?? null : null),
|
|
18838
18929
|
resume_freshness: projectIndex?.resume_freshness ?? "stale",
|
|
18839
18930
|
resume_source_session_id: projectIndex?.resume_source_session_id ?? sessions[0]?.session_id ?? null,
|
|
18840
18931
|
resume_source_device_id: projectIndex?.resume_source_device_id ?? sessions[0]?.device_id ?? null,
|
|
@@ -18845,6 +18936,8 @@ function getMemoryConsole(db, input) {
|
|
|
18845
18936
|
recent_handoffs: recentHandoffs,
|
|
18846
18937
|
rolling_handoff_drafts: rollingHandoffDrafts,
|
|
18847
18938
|
saved_handoffs: savedHandoffs,
|
|
18939
|
+
recent_inbox_notes: recentInboxNotes,
|
|
18940
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
18848
18941
|
recent_chat: recentChat.messages,
|
|
18849
18942
|
recent_chat_sessions: projectIndex?.recent_chat_sessions ?? recentChat.session_count,
|
|
18850
18943
|
chat_source_summary: projectIndex?.chat_source_summary ?? recentChat.source_summary,
|
|
@@ -18858,13 +18951,15 @@ function getMemoryConsole(db, input) {
|
|
|
18858
18951
|
assistant_checkpoint_types: projectIndex?.assistant_checkpoint_types ?? [],
|
|
18859
18952
|
top_types: projectIndex?.top_types ?? [],
|
|
18860
18953
|
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)
|
|
18954
|
+
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
18955
|
};
|
|
18863
18956
|
}
|
|
18864
|
-
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, chatCoverageState) {
|
|
18957
|
+
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, chatCoverageState, activeAgentCount) {
|
|
18865
18958
|
const suggested = [];
|
|
18866
18959
|
if (sessionCount > 0)
|
|
18867
18960
|
suggested.push("recent_sessions");
|
|
18961
|
+
if (activeAgentCount > 1)
|
|
18962
|
+
suggested.push("agent_memory_index");
|
|
18868
18963
|
if (requestCount > 0 || toolCount > 0)
|
|
18869
18964
|
suggested.push("activity_feed");
|
|
18870
18965
|
if (requestCount > 0 || chatCount > 0 || observationCount > 0)
|
|
@@ -18883,7 +18978,7 @@ function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, obse
|
|
|
18883
18978
|
suggested.push("refresh_chat_recall");
|
|
18884
18979
|
if (chatCount > 0)
|
|
18885
18980
|
suggested.push("recent_chat", "search_chat");
|
|
18886
|
-
return Array.from(new Set(suggested)).slice(0,
|
|
18981
|
+
return Array.from(new Set(suggested)).slice(0, 6);
|
|
18887
18982
|
}
|
|
18888
18983
|
|
|
18889
18984
|
// src/tools/workspace-memory-index.ts
|
|
@@ -19111,6 +19206,7 @@ function toChatEvent(message) {
|
|
|
19111
19206
|
};
|
|
19112
19207
|
}
|
|
19113
19208
|
function toObservationEvent(obs) {
|
|
19209
|
+
const messageKind = classifyMessageObservation(obs);
|
|
19114
19210
|
if (looksLikeHandoff(obs)) {
|
|
19115
19211
|
const handoffKind = isDraftHandoff(obs) ? "draft" : "saved";
|
|
19116
19212
|
return {
|
|
@@ -19125,6 +19221,8 @@ function toObservationEvent(obs) {
|
|
|
19125
19221
|
};
|
|
19126
19222
|
}
|
|
19127
19223
|
const detailBits = [];
|
|
19224
|
+
if (messageKind === "inbox-note")
|
|
19225
|
+
detailBits.push("inbox note");
|
|
19128
19226
|
if (obs.source_tool)
|
|
19129
19227
|
detailBits.push(`via ${obs.source_tool}`);
|
|
19130
19228
|
if (typeof obs.source_prompt_number === "number") {
|
|
@@ -19529,8 +19627,214 @@ function getCaptureQuality(db, input = {}) {
|
|
|
19529
19627
|
};
|
|
19530
19628
|
}
|
|
19531
19629
|
|
|
19630
|
+
// src/tools/agent-memory-index.ts
|
|
19631
|
+
function getAgentMemoryIndex(db, input) {
|
|
19632
|
+
const cwd = input.cwd ?? process.cwd();
|
|
19633
|
+
const projectScoped = input.project_scoped !== false;
|
|
19634
|
+
let projectId = null;
|
|
19635
|
+
let projectName;
|
|
19636
|
+
if (projectScoped) {
|
|
19637
|
+
const detected = detectProject(cwd);
|
|
19638
|
+
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
19639
|
+
if (project) {
|
|
19640
|
+
projectId = project.id;
|
|
19641
|
+
projectName = project.name;
|
|
19642
|
+
}
|
|
19643
|
+
}
|
|
19644
|
+
const userFilter = input.user_id ? " AND s.user_id = ?" : "";
|
|
19645
|
+
const userArgs = input.user_id ? [input.user_id] : [];
|
|
19646
|
+
const projectFilter = projectId !== null ? " AND s.project_id = ?" : "";
|
|
19647
|
+
const projectArgs = projectId !== null ? [projectId] : [];
|
|
19648
|
+
const sessionRows = db.db.query(`SELECT
|
|
19649
|
+
s.agent as agent,
|
|
19650
|
+
COUNT(*) as session_count,
|
|
19651
|
+
SUM(CASE WHEN ss.request IS NOT NULL OR ss.completed IS NOT NULL THEN 1 ELSE 0 END) as summary_session_count,
|
|
19652
|
+
SUM(COALESCE(pc.prompt_count, 0)) as prompt_count,
|
|
19653
|
+
SUM(COALESCE(tc.tool_event_count, 0)) as tool_event_count,
|
|
19654
|
+
MAX(COALESCE(s.completed_at_epoch, s.started_at_epoch)) as last_seen_epoch
|
|
19655
|
+
FROM sessions s
|
|
19656
|
+
LEFT JOIN session_summaries ss ON ss.session_id = s.session_id
|
|
19657
|
+
LEFT JOIN (
|
|
19658
|
+
SELECT session_id, COUNT(*) as prompt_count
|
|
19659
|
+
FROM user_prompts
|
|
19660
|
+
GROUP BY session_id
|
|
19661
|
+
) pc ON pc.session_id = s.session_id
|
|
19662
|
+
LEFT JOIN (
|
|
19663
|
+
SELECT session_id, COUNT(*) as tool_event_count
|
|
19664
|
+
FROM tool_events
|
|
19665
|
+
GROUP BY session_id
|
|
19666
|
+
) tc ON tc.session_id = s.session_id
|
|
19667
|
+
WHERE 1 = 1${projectFilter}${userFilter}
|
|
19668
|
+
GROUP BY s.agent
|
|
19669
|
+
ORDER BY last_seen_epoch DESC, s.agent ASC`).all(...projectArgs, ...userArgs).filter((row) => !isInternalAgent(row.agent));
|
|
19670
|
+
const observationCounts = new Map;
|
|
19671
|
+
const observationRows = db.db.query(`SELECT
|
|
19672
|
+
COALESCE(s.agent, o.agent) as agent,
|
|
19673
|
+
COUNT(*) as observation_count,
|
|
19674
|
+
SUM(CASE WHEN o.type = 'message' AND o.concepts LIKE '%session-handoff%' THEN 1 ELSE 0 END) as handoff_count
|
|
19675
|
+
FROM observations o
|
|
19676
|
+
LEFT JOIN sessions s ON s.session_id = o.session_id
|
|
19677
|
+
WHERE o.lifecycle IN ('active', 'aging', 'pinned')
|
|
19678
|
+
AND o.superseded_by IS NULL
|
|
19679
|
+
${projectId !== null ? "AND o.project_id = ?" : ""}
|
|
19680
|
+
${input.user_id ? "AND (o.sensitivity != 'personal' OR o.user_id = ?)" : ""}
|
|
19681
|
+
GROUP BY COALESCE(s.agent, o.agent)`).all(...projectId !== null ? [projectId] : [], ...input.user_id ? [input.user_id] : []).filter((row) => !isInternalAgent(row.agent));
|
|
19682
|
+
for (const row of observationRows) {
|
|
19683
|
+
observationCounts.set(row.agent, {
|
|
19684
|
+
observation_count: row.observation_count,
|
|
19685
|
+
handoff_count: row.handoff_count
|
|
19686
|
+
});
|
|
19687
|
+
}
|
|
19688
|
+
const chatCoverage = new Map;
|
|
19689
|
+
const chatRows = db.db.query(`SELECT
|
|
19690
|
+
COALESCE(s.agent, cm.agent) as agent,
|
|
19691
|
+
CASE
|
|
19692
|
+
WHEN cm.source_kind = 'transcript' THEN 'transcript'
|
|
19693
|
+
WHEN cm.remote_source_id LIKE 'history:%' THEN 'history'
|
|
19694
|
+
ELSE 'hook'
|
|
19695
|
+
END as origin_kind,
|
|
19696
|
+
COUNT(*) as count
|
|
19697
|
+
FROM chat_messages cm
|
|
19698
|
+
LEFT JOIN sessions s ON s.session_id = cm.session_id
|
|
19699
|
+
WHERE 1 = 1
|
|
19700
|
+
${projectId !== null ? "AND cm.project_id = ?" : ""}
|
|
19701
|
+
${input.user_id ? "AND cm.user_id = ?" : ""}
|
|
19702
|
+
GROUP BY COALESCE(s.agent, cm.agent), origin_kind`).all(...projectId !== null ? [projectId] : [], ...input.user_id ? [input.user_id] : []).filter((row) => !isInternalAgent(row.agent));
|
|
19703
|
+
for (const row of chatRows) {
|
|
19704
|
+
const current = chatCoverage.get(row.agent) ?? {
|
|
19705
|
+
chat_message_count: 0,
|
|
19706
|
+
transcript_count: 0,
|
|
19707
|
+
history_count: 0,
|
|
19708
|
+
hook_count: 0
|
|
19709
|
+
};
|
|
19710
|
+
current.chat_message_count += row.count;
|
|
19711
|
+
if (row.origin_kind === "transcript")
|
|
19712
|
+
current.transcript_count += row.count;
|
|
19713
|
+
else if (row.origin_kind === "history")
|
|
19714
|
+
current.history_count += row.count;
|
|
19715
|
+
else
|
|
19716
|
+
current.hook_count += row.count;
|
|
19717
|
+
chatCoverage.set(row.agent, current);
|
|
19718
|
+
}
|
|
19719
|
+
const recentSessions = db.getRecentSessions(projectId, 200, input.user_id).filter((session) => !isInternalAgent(session.agent));
|
|
19720
|
+
const recallItems = listRecallItems(db, {
|
|
19721
|
+
cwd,
|
|
19722
|
+
project_scoped: projectScoped,
|
|
19723
|
+
user_id: input.user_id,
|
|
19724
|
+
limit: 30
|
|
19725
|
+
}).items;
|
|
19726
|
+
const latestByAgent = new Map;
|
|
19727
|
+
const devicesByAgent = new Map;
|
|
19728
|
+
for (const session of recentSessions) {
|
|
19729
|
+
if (!latestByAgent.has(session.agent))
|
|
19730
|
+
latestByAgent.set(session.agent, session);
|
|
19731
|
+
const devices = devicesByAgent.get(session.agent) ?? new Set;
|
|
19732
|
+
if (session.device_id)
|
|
19733
|
+
devices.add(session.device_id);
|
|
19734
|
+
devicesByAgent.set(session.agent, devices);
|
|
19735
|
+
}
|
|
19736
|
+
const knownAgents = new Set([
|
|
19737
|
+
...sessionRows.map((row) => row.agent),
|
|
19738
|
+
...Array.from(observationCounts.keys()),
|
|
19739
|
+
...Array.from(chatCoverage.keys())
|
|
19740
|
+
]);
|
|
19741
|
+
const agents = Array.from(knownAgents).map((agent) => {
|
|
19742
|
+
const session = sessionRows.find((row) => row.agent === agent) ?? {
|
|
19743
|
+
agent,
|
|
19744
|
+
session_count: 0,
|
|
19745
|
+
summary_session_count: 0,
|
|
19746
|
+
prompt_count: 0,
|
|
19747
|
+
tool_event_count: 0,
|
|
19748
|
+
last_seen_epoch: null
|
|
19749
|
+
};
|
|
19750
|
+
const obs = observationCounts.get(agent) ?? { observation_count: 0, handoff_count: 0 };
|
|
19751
|
+
const chat = chatCoverage.get(agent) ?? {
|
|
19752
|
+
chat_message_count: 0,
|
|
19753
|
+
transcript_count: 0,
|
|
19754
|
+
history_count: 0,
|
|
19755
|
+
hook_count: 0
|
|
19756
|
+
};
|
|
19757
|
+
const latestSession = latestByAgent.get(agent) ?? null;
|
|
19758
|
+
const bestRecall = pickBestRecallForAgent(recallItems, agent);
|
|
19759
|
+
return {
|
|
19760
|
+
agent,
|
|
19761
|
+
session_count: session.session_count,
|
|
19762
|
+
summary_session_count: session.summary_session_count,
|
|
19763
|
+
prompt_count: session.prompt_count,
|
|
19764
|
+
tool_event_count: session.tool_event_count,
|
|
19765
|
+
observation_count: obs.observation_count,
|
|
19766
|
+
handoff_count: obs.handoff_count,
|
|
19767
|
+
chat_message_count: chat.chat_message_count,
|
|
19768
|
+
chat_coverage_state: chat.transcript_count > 0 ? "transcript-backed" : chat.history_count > 0 ? "history-backed" : chat.hook_count > 0 ? "hook-only" : "none",
|
|
19769
|
+
continuity_state: classifyAgentContinuity(session.last_seen_epoch, session.prompt_count, session.tool_event_count, chat.chat_message_count, obs.handoff_count, obs.observation_count),
|
|
19770
|
+
capture_state: classifyAgentCaptureState(session.prompt_count, session.tool_event_count, session.summary_session_count, obs.observation_count, chat.chat_message_count),
|
|
19771
|
+
last_seen_epoch: session.last_seen_epoch,
|
|
19772
|
+
latest_session_id: latestSession?.session_id ?? null,
|
|
19773
|
+
latest_summary: latestSession?.current_thread ?? latestSession?.request ?? latestSession?.completed ?? null,
|
|
19774
|
+
devices: Array.from(devicesByAgent.get(agent) ?? []).sort(),
|
|
19775
|
+
best_recall_key: bestRecall?.key ?? null,
|
|
19776
|
+
best_recall_title: bestRecall?.title ?? null,
|
|
19777
|
+
best_recall_kind: bestRecall?.kind ?? null,
|
|
19778
|
+
resume_freshness: bestRecall?.freshness ?? "stale"
|
|
19779
|
+
};
|
|
19780
|
+
}).sort((a, b) => {
|
|
19781
|
+
const epochA = a.last_seen_epoch ?? 0;
|
|
19782
|
+
const epochB = b.last_seen_epoch ?? 0;
|
|
19783
|
+
return epochB - epochA || a.agent.localeCompare(b.agent);
|
|
19784
|
+
});
|
|
19785
|
+
return {
|
|
19786
|
+
project: projectName,
|
|
19787
|
+
agents,
|
|
19788
|
+
suggested_tools: buildSuggestedTools2(agents)
|
|
19789
|
+
};
|
|
19790
|
+
}
|
|
19791
|
+
function isInternalAgent(agent) {
|
|
19792
|
+
return !agent || agent.startsWith("engrm-");
|
|
19793
|
+
}
|
|
19794
|
+
function classifyAgentContinuity(lastSeenEpoch, promptCount, toolCount, chatCount, handoffCount, observationCount) {
|
|
19795
|
+
if (!lastSeenEpoch)
|
|
19796
|
+
return "cold";
|
|
19797
|
+
const ageMs = Date.now() - lastSeenEpoch * 1000;
|
|
19798
|
+
const hasStrongContinuity = promptCount > 0 || toolCount > 0 || chatCount > 0 || handoffCount > 0;
|
|
19799
|
+
if (ageMs <= 3 * 24 * 60 * 60 * 1000 && hasStrongContinuity)
|
|
19800
|
+
return "fresh";
|
|
19801
|
+
if (observationCount > 0 || promptCount > 0 || toolCount > 0 || chatCount > 0)
|
|
19802
|
+
return "thin";
|
|
19803
|
+
return "cold";
|
|
19804
|
+
}
|
|
19805
|
+
function classifyAgentCaptureState(promptCount, toolCount, summarySessionCount, observationCount, chatCount) {
|
|
19806
|
+
if (promptCount > 0 && toolCount > 0)
|
|
19807
|
+
return "rich";
|
|
19808
|
+
if (promptCount > 0 || toolCount > 0)
|
|
19809
|
+
return "partial";
|
|
19810
|
+
if (summarySessionCount > 0 || observationCount > 0 || chatCount > 0)
|
|
19811
|
+
return "summary-only";
|
|
19812
|
+
return "legacy";
|
|
19813
|
+
}
|
|
19814
|
+
function buildSuggestedTools2(agents) {
|
|
19815
|
+
if (agents.length === 0)
|
|
19816
|
+
return [];
|
|
19817
|
+
const suggestions = ["recent_sessions", "capture_quality"];
|
|
19818
|
+
if (agents.length > 1) {
|
|
19819
|
+
suggestions.push("list_recall_items");
|
|
19820
|
+
}
|
|
19821
|
+
if (agents.some((agent) => agent.best_recall_key)) {
|
|
19822
|
+
suggestions.push("load_recall_item");
|
|
19823
|
+
}
|
|
19824
|
+
if (agents.some((agent) => agent.continuity_state !== "fresh")) {
|
|
19825
|
+
suggestions.push("resume_thread");
|
|
19826
|
+
}
|
|
19827
|
+
if (agents.some((agent) => agent.chat_coverage_state === "hook-only")) {
|
|
19828
|
+
suggestions.push("repair_recall");
|
|
19829
|
+
}
|
|
19830
|
+
return suggestions;
|
|
19831
|
+
}
|
|
19832
|
+
function pickBestRecallForAgent(items, agent) {
|
|
19833
|
+
return items.find((item) => item.source_agent === agent) ?? null;
|
|
19834
|
+
}
|
|
19835
|
+
|
|
19532
19836
|
// src/tools/tool-memory-index.ts
|
|
19533
|
-
function
|
|
19837
|
+
function parseConcepts2(value) {
|
|
19534
19838
|
if (!value)
|
|
19535
19839
|
return [];
|
|
19536
19840
|
try {
|
|
@@ -19599,7 +19903,7 @@ function getToolMemoryIndex(db, input = {}) {
|
|
|
19599
19903
|
ORDER BY o.created_at_epoch DESC, o.id DESC
|
|
19600
19904
|
LIMIT 50`).all(...rowParams);
|
|
19601
19905
|
const topPlugins = Array.from(observationRows.reduce((acc, obs) => {
|
|
19602
|
-
for (const concept of
|
|
19906
|
+
for (const concept of parseConcepts2(obs.concepts)) {
|
|
19603
19907
|
if (!concept.startsWith("plugin:"))
|
|
19604
19908
|
continue;
|
|
19605
19909
|
const plugin = concept.slice("plugin:".length);
|
|
@@ -19628,7 +19932,7 @@ function getToolMemoryIndex(db, input = {}) {
|
|
|
19628
19932
|
}
|
|
19629
19933
|
|
|
19630
19934
|
// src/tools/session-tool-memory.ts
|
|
19631
|
-
function
|
|
19935
|
+
function parseConcepts3(value) {
|
|
19632
19936
|
if (!value)
|
|
19633
19937
|
return [];
|
|
19634
19938
|
try {
|
|
@@ -19660,7 +19964,7 @@ function getSessionToolMemory(db, input) {
|
|
|
19660
19964
|
}, new Map).entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count || a.type.localeCompare(b.type)).slice(0, 5);
|
|
19661
19965
|
const sampleTitles = groupedObservations.map((obs) => obs.title).filter((title, index, all) => all.indexOf(title) === index).slice(0, 4);
|
|
19662
19966
|
const topPlugins = Array.from(groupedObservations.reduce((acc, obs) => {
|
|
19663
|
-
for (const concept of
|
|
19967
|
+
for (const concept of parseConcepts3(obs.concepts)) {
|
|
19664
19968
|
if (!concept.startsWith("plugin:"))
|
|
19665
19969
|
continue;
|
|
19666
19970
|
const plugin = concept.slice("plugin:".length);
|
|
@@ -19716,6 +20020,13 @@ function getSessionContext(db, input) {
|
|
|
19716
20020
|
user_id: input.user_id,
|
|
19717
20021
|
limit: 8
|
|
19718
20022
|
});
|
|
20023
|
+
const recentActivity = getRecentActivity(db, {
|
|
20024
|
+
cwd,
|
|
20025
|
+
project_scoped: true,
|
|
20026
|
+
user_id: input.user_id,
|
|
20027
|
+
limit: 12
|
|
20028
|
+
});
|
|
20029
|
+
const recentInboxNotes = recentActivity.observations.filter((obs) => obs.message_kind === "inbox-note").slice(0, 5);
|
|
19719
20030
|
const recentChatMessages = recentChat.messages.length;
|
|
19720
20031
|
const recallIndex = listRecallItems(db, {
|
|
19721
20032
|
cwd,
|
|
@@ -19732,9 +20043,12 @@ function getSessionContext(db, input) {
|
|
|
19732
20043
|
const latestChatEpoch = recentChat.messages.length > 0 ? recentChat.messages[recentChat.messages.length - 1]?.created_at_epoch ?? null : null;
|
|
19733
20044
|
const resumeTimestamp = latestChatEpoch ?? latestSession?.completed_at_epoch ?? latestSession?.started_at_epoch ?? null;
|
|
19734
20045
|
const bestRecallItem = recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null;
|
|
20046
|
+
const activeAgents = collectActiveAgents(context.recentSessions ?? []);
|
|
19735
20047
|
return {
|
|
19736
20048
|
project_name: context.project_name,
|
|
19737
20049
|
canonical_id: context.canonical_id,
|
|
20050
|
+
active_agents: activeAgents,
|
|
20051
|
+
cross_agent_active: activeAgents.length > 1,
|
|
19738
20052
|
continuity_state: continuityState,
|
|
19739
20053
|
continuity_summary: describeContinuityState(continuityState),
|
|
19740
20054
|
recall_mode: recallIndex.continuity_mode,
|
|
@@ -19743,11 +20057,13 @@ function getSessionContext(db, input) {
|
|
|
19743
20057
|
key: item.key,
|
|
19744
20058
|
kind: item.kind,
|
|
19745
20059
|
freshness: item.freshness,
|
|
19746
|
-
title: item.title
|
|
20060
|
+
title: item.title,
|
|
20061
|
+
source_agent: item.source_agent
|
|
19747
20062
|
})),
|
|
19748
20063
|
best_recall_key: bestRecallItem?.key ?? null,
|
|
19749
20064
|
best_recall_title: bestRecallItem?.title ?? null,
|
|
19750
20065
|
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
20066
|
+
best_agent_resume_agent: activeAgents.length > 1 ? latestSession?.agent ?? null : null,
|
|
19751
20067
|
resume_freshness: classifyResumeFreshness(resumeTimestamp),
|
|
19752
20068
|
resume_source_session_id: latestSession?.session_id ?? null,
|
|
19753
20069
|
resume_source_device_id: latestSession?.device_id ?? null,
|
|
@@ -19761,6 +20077,8 @@ function getSessionContext(db, input) {
|
|
|
19761
20077
|
rolling_handoff_drafts: rollingHandoffDrafts,
|
|
19762
20078
|
saved_handoffs: savedHandoffs,
|
|
19763
20079
|
latest_handoff_title: latestHandoffTitle,
|
|
20080
|
+
recent_inbox_notes: recentInboxNotes.length,
|
|
20081
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
19764
20082
|
recent_chat_messages: recentChatMessages,
|
|
19765
20083
|
recent_chat_sessions: recentChat.session_count,
|
|
19766
20084
|
chat_source_summary: recentChat.source_summary,
|
|
@@ -19770,7 +20088,7 @@ function getSessionContext(db, input) {
|
|
|
19770
20088
|
capture_state: captureState,
|
|
19771
20089
|
raw_capture_active: recentRequests > 0 || recentTools > 0,
|
|
19772
20090
|
estimated_read_tokens: estimateTokens(preview),
|
|
19773
|
-
suggested_tools:
|
|
20091
|
+
suggested_tools: buildSuggestedTools3(context, recentChat.coverage_state, activeAgents.length),
|
|
19774
20092
|
preview
|
|
19775
20093
|
};
|
|
19776
20094
|
}
|
|
@@ -19801,11 +20119,14 @@ function parseJsonArray3(value) {
|
|
|
19801
20119
|
return [];
|
|
19802
20120
|
}
|
|
19803
20121
|
}
|
|
19804
|
-
function
|
|
20122
|
+
function buildSuggestedTools3(context, chatCoverageState, activeAgentCount) {
|
|
19805
20123
|
const tools = [];
|
|
19806
20124
|
if ((context.recentSessions?.length ?? 0) > 0) {
|
|
19807
20125
|
tools.push("recent_sessions");
|
|
19808
20126
|
}
|
|
20127
|
+
if (activeAgentCount > 1) {
|
|
20128
|
+
tools.push("agent_memory_index");
|
|
20129
|
+
}
|
|
19809
20130
|
if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0) {
|
|
19810
20131
|
tools.push("activity_feed");
|
|
19811
20132
|
}
|
|
@@ -19833,7 +20154,7 @@ function buildSuggestedTools2(context, chatCoverageState) {
|
|
|
19833
20154
|
if ((context.recentChatMessages?.length ?? 0) > 0) {
|
|
19834
20155
|
tools.push("recent_chat", "search_chat");
|
|
19835
20156
|
}
|
|
19836
|
-
return Array.from(new Set(tools)).slice(0,
|
|
20157
|
+
return Array.from(new Set(tools)).slice(0, 6);
|
|
19837
20158
|
}
|
|
19838
20159
|
|
|
19839
20160
|
// src/tools/load-recall-item.ts
|
|
@@ -19847,6 +20168,7 @@ function loadRecallItem(db, input) {
|
|
|
19847
20168
|
detail: "Malformed recall key",
|
|
19848
20169
|
session_id: null,
|
|
19849
20170
|
source_device_id: null,
|
|
20171
|
+
source_agent: null,
|
|
19850
20172
|
payload: null
|
|
19851
20173
|
};
|
|
19852
20174
|
}
|
|
@@ -19868,6 +20190,7 @@ function loadRecallItem(db, input) {
|
|
|
19868
20190
|
detail: summarizeNarrative(result.handoff.narrative),
|
|
19869
20191
|
session_id: result.handoff.session_id ?? null,
|
|
19870
20192
|
source_device_id: result.handoff.device_id ?? null,
|
|
20193
|
+
source_agent: result.handoff.session_id ? lookupSessionAgent(db, result.handoff.session_id) : null,
|
|
19871
20194
|
payload: {
|
|
19872
20195
|
type: "handoff",
|
|
19873
20196
|
handoff_id: result.handoff.id,
|
|
@@ -19887,6 +20210,7 @@ function loadRecallItem(db, input) {
|
|
|
19887
20210
|
detail: story.summary?.next_steps ?? story.summary?.completed ?? null,
|
|
19888
20211
|
session_id: story.session.session_id,
|
|
19889
20212
|
source_device_id: story.session.device_id ?? null,
|
|
20213
|
+
source_agent: story.session.agent ?? null,
|
|
19890
20214
|
payload: {
|
|
19891
20215
|
type: "thread",
|
|
19892
20216
|
latest_request: story.latest_request,
|
|
@@ -19916,6 +20240,7 @@ function loadRecallItem(db, input) {
|
|
|
19916
20240
|
detail: message.content,
|
|
19917
20241
|
session_id: message.session_id,
|
|
19918
20242
|
source_device_id: message.device_id ?? null,
|
|
20243
|
+
source_agent: message.agent ?? null,
|
|
19919
20244
|
payload: {
|
|
19920
20245
|
type: "chat",
|
|
19921
20246
|
role: message.role,
|
|
@@ -19941,6 +20266,7 @@ function loadRecallItem(db, input) {
|
|
|
19941
20266
|
detail: obs.narrative ?? obs.facts ?? null,
|
|
19942
20267
|
session_id: obs.session_id ?? null,
|
|
19943
20268
|
source_device_id: obs.device_id ?? null,
|
|
20269
|
+
source_agent: obs.session_id ? lookupSessionAgent(db, obs.session_id) : obs.agent?.startsWith("engrm-") ? null : obs.agent ?? null,
|
|
19944
20270
|
payload: {
|
|
19945
20271
|
type: "memory",
|
|
19946
20272
|
observation_id: obs.id,
|
|
@@ -19957,6 +20283,10 @@ function summarizeNarrative(value) {
|
|
|
19957
20283
|
return null;
|
|
19958
20284
|
return value.split(/\n+/).map((line) => line.trim()).find(Boolean) ?? null;
|
|
19959
20285
|
}
|
|
20286
|
+
function lookupSessionAgent(db, sessionId) {
|
|
20287
|
+
const row = db.db.query("SELECT agent FROM sessions WHERE session_id = ? LIMIT 1").get(sessionId);
|
|
20288
|
+
return row?.agent ?? null;
|
|
20289
|
+
}
|
|
19960
20290
|
function missing(key, kind) {
|
|
19961
20291
|
return {
|
|
19962
20292
|
kind,
|
|
@@ -19965,6 +20295,7 @@ function missing(key, kind) {
|
|
|
19965
20295
|
detail: "Recall item not found",
|
|
19966
20296
|
session_id: null,
|
|
19967
20297
|
source_device_id: null,
|
|
20298
|
+
source_agent: null,
|
|
19968
20299
|
payload: null
|
|
19969
20300
|
};
|
|
19970
20301
|
}
|
|
@@ -20391,6 +20722,9 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20391
20722
|
const detected = detectProject(cwd);
|
|
20392
20723
|
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
20393
20724
|
let snapshot = await buildResumeSnapshot(db, cwd, input.user_id, input.current_device_id, limit);
|
|
20725
|
+
if (input.agent) {
|
|
20726
|
+
snapshot = filterResumeSnapshotByAgent(snapshot, input.agent, input.current_device_id);
|
|
20727
|
+
}
|
|
20394
20728
|
let repairResult = null;
|
|
20395
20729
|
const shouldRepair = repairIfNeeded && snapshot.recentChat.coverage_state !== "transcript-backed" && (snapshot.recentChat.messages.length > 0 || snapshot.recentSessions.length > 0 || snapshot.context?.continuity_state !== "cold");
|
|
20396
20730
|
if (shouldRepair) {
|
|
@@ -20401,6 +20735,9 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20401
20735
|
});
|
|
20402
20736
|
if (repairResult.imported_chat_messages > 0) {
|
|
20403
20737
|
snapshot = await buildResumeSnapshot(db, cwd, input.user_id, input.current_device_id, limit);
|
|
20738
|
+
if (input.agent) {
|
|
20739
|
+
snapshot = filterResumeSnapshotByAgent(snapshot, input.agent, input.current_device_id);
|
|
20740
|
+
}
|
|
20404
20741
|
}
|
|
20405
20742
|
}
|
|
20406
20743
|
const { context, handoff, recentChat, recentSessions, recall } = snapshot;
|
|
@@ -20441,6 +20778,7 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20441
20778
|
])).slice(0, 4);
|
|
20442
20779
|
return {
|
|
20443
20780
|
project_name: project?.name ?? context?.project_name ?? null,
|
|
20781
|
+
target_agent: input.agent ?? null,
|
|
20444
20782
|
continuity_state: context?.continuity_state ?? "cold",
|
|
20445
20783
|
continuity_summary: context?.continuity_summary ?? "No fresh repo-local continuity yet; older memory should be treated cautiously.",
|
|
20446
20784
|
resume_freshness: classifyResumeFreshness2(sourceTimestamp),
|
|
@@ -20490,6 +20828,13 @@ async function buildResumeSnapshot(db, cwd, userId, currentDeviceId, limit) {
|
|
|
20490
20828
|
user_id: userId,
|
|
20491
20829
|
current_device_id: currentDeviceId
|
|
20492
20830
|
});
|
|
20831
|
+
const recentHandoffs = getRecentHandoffs(db, {
|
|
20832
|
+
cwd,
|
|
20833
|
+
project_scoped: true,
|
|
20834
|
+
user_id: userId,
|
|
20835
|
+
current_device_id: currentDeviceId,
|
|
20836
|
+
limit: Math.max(limit, 4)
|
|
20837
|
+
}).handoffs;
|
|
20493
20838
|
const recentChat = getRecentChat(db, {
|
|
20494
20839
|
cwd,
|
|
20495
20840
|
project_scoped: true,
|
|
@@ -20519,15 +20864,50 @@ async function buildResumeSnapshot(db, cwd, userId, currentDeviceId, limit) {
|
|
|
20519
20864
|
return {
|
|
20520
20865
|
context,
|
|
20521
20866
|
handoff: handoffResult.handoff,
|
|
20867
|
+
recentHandoffs,
|
|
20522
20868
|
recentChat,
|
|
20523
20869
|
recentSessions,
|
|
20524
20870
|
recall,
|
|
20525
20871
|
recallIndex
|
|
20526
20872
|
};
|
|
20527
20873
|
}
|
|
20874
|
+
function filterResumeSnapshotByAgent(snapshot, agent, currentDeviceId) {
|
|
20875
|
+
const recentSessions = snapshot.recentSessions.filter((session) => session.agent === agent);
|
|
20876
|
+
const sessionIds = new Set(recentSessions.map((session) => session.session_id));
|
|
20877
|
+
const recentChatMessages = snapshot.recentChat.messages.filter((message) => message.agent === agent);
|
|
20878
|
+
const handoff = snapshot.recentHandoffs.filter((item) => item.session_id && sessionIds.has(item.session_id)).sort((a, b) => compareRecallCandidates(a.created_at_epoch, b.created_at_epoch, a.device_id ?? null, b.device_id ?? null, currentDeviceId))[0] ?? null;
|
|
20879
|
+
const recallIndex = {
|
|
20880
|
+
...snapshot.recallIndex,
|
|
20881
|
+
items: snapshot.recallIndex.items.filter((item) => item.source_agent === agent)
|
|
20882
|
+
};
|
|
20883
|
+
const recall = {
|
|
20884
|
+
...snapshot.recall,
|
|
20885
|
+
results: snapshot.recall.results.filter((entry) => entry.session_id ? sessionIds.has(entry.session_id) : false)
|
|
20886
|
+
};
|
|
20887
|
+
return {
|
|
20888
|
+
...snapshot,
|
|
20889
|
+
handoff,
|
|
20890
|
+
recentHandoffs: snapshot.recentHandoffs.filter((item) => item.session_id && sessionIds.has(item.session_id)),
|
|
20891
|
+
recentSessions,
|
|
20892
|
+
recentChat: {
|
|
20893
|
+
...snapshot.recentChat,
|
|
20894
|
+
messages: recentChatMessages,
|
|
20895
|
+
session_count: new Set(recentChatMessages.map((message) => message.session_id)).size
|
|
20896
|
+
},
|
|
20897
|
+
recall,
|
|
20898
|
+
recallIndex
|
|
20899
|
+
};
|
|
20900
|
+
}
|
|
20528
20901
|
function pickBestRecallItem2(items) {
|
|
20529
20902
|
return items.find((item) => item.kind !== "memory") ?? items[0] ?? null;
|
|
20530
20903
|
}
|
|
20904
|
+
function compareRecallCandidates(epochA, epochB, deviceA, deviceB, currentDeviceId) {
|
|
20905
|
+
const remoteBoostA = currentDeviceId && deviceA && deviceA !== currentDeviceId ? 1 : 0;
|
|
20906
|
+
const remoteBoostB = currentDeviceId && deviceB && deviceB !== currentDeviceId ? 1 : 0;
|
|
20907
|
+
if (remoteBoostA !== remoteBoostB)
|
|
20908
|
+
return remoteBoostB - remoteBoostA;
|
|
20909
|
+
return epochB - epochA;
|
|
20910
|
+
}
|
|
20531
20911
|
function extractCurrentThread(handoff) {
|
|
20532
20912
|
const narrative = handoff?.narrative ?? "";
|
|
20533
20913
|
const match = narrative.match(/Current thread:\s*(.+)/i);
|
|
@@ -20770,8 +21150,9 @@ function hasContent(value) {
|
|
|
20770
21150
|
// src/tools/stats.ts
|
|
20771
21151
|
function getMemoryStats(db) {
|
|
20772
21152
|
const activeObservations = db.getActiveObservationCount();
|
|
20773
|
-
const
|
|
20774
|
-
WHERE
|
|
21153
|
+
const handoffs = db.db.query(`SELECT COUNT(*) as count FROM observations
|
|
21154
|
+
WHERE ${getHandoffMessageFilterSql({ include_aging: true })}`).get()?.count ?? 0;
|
|
21155
|
+
const inboxMessages = getInboxMessageCount(db);
|
|
20775
21156
|
const userPrompts = db.db.query("SELECT COUNT(*) as count FROM user_prompts").get()?.count ?? 0;
|
|
20776
21157
|
const toolEvents = db.db.query("SELECT COUNT(*) as count FROM tool_events").get()?.count ?? 0;
|
|
20777
21158
|
const sessionSummaries = db.db.query("SELECT COUNT(*) as count FROM session_summaries").get()?.count ?? 0;
|
|
@@ -20785,7 +21166,9 @@ function getMemoryStats(db) {
|
|
|
20785
21166
|
active_observations: activeObservations,
|
|
20786
21167
|
user_prompts: userPrompts,
|
|
20787
21168
|
tool_events: toolEvents,
|
|
20788
|
-
messages,
|
|
21169
|
+
messages: inboxMessages,
|
|
21170
|
+
inbox_messages: inboxMessages,
|
|
21171
|
+
handoffs,
|
|
20789
21172
|
session_summaries: sessionSummaries,
|
|
20790
21173
|
decisions: signals.decisions_count,
|
|
20791
21174
|
lessons: signals.lessons_count,
|
|
@@ -22459,7 +22842,7 @@ process.on("SIGTERM", () => {
|
|
|
22459
22842
|
});
|
|
22460
22843
|
var server = new McpServer({
|
|
22461
22844
|
name: "engrm",
|
|
22462
|
-
version: "0.4.
|
|
22845
|
+
version: "0.4.35"
|
|
22463
22846
|
});
|
|
22464
22847
|
server.tool("save_observation", "Save an observation to memory", {
|
|
22465
22848
|
type: exports_external.enum([
|
|
@@ -22929,7 +23312,7 @@ server.tool("list_recall_items", "USE FIRST when continuity feels fuzzy. List th
|
|
|
22929
23312
|
}
|
|
22930
23313
|
const projectLine = result.project ? `Project: ${result.project}
|
|
22931
23314
|
` : "";
|
|
22932
|
-
const rows = result.items.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}] ${item.title}${item.source_device_id ? ` (${item.source_device_id})` : ""}
|
|
23315
|
+
const rows = result.items.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}${item.source_agent ? ` · ${item.source_agent}` : ""}] ${item.title}${item.source_device_id ? ` (${item.source_device_id})` : ""}
|
|
22933
23316
|
${item.detail}`).join(`
|
|
22934
23317
|
`);
|
|
22935
23318
|
return {
|
|
@@ -22974,6 +23357,7 @@ server.tool("load_recall_item", "USE AFTER list_recall_items. Load one exact rec
|
|
|
22974
23357
|
` + `Title: ${result.title}
|
|
22975
23358
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
22976
23359
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23360
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
22977
23361
|
|
|
22978
23362
|
` + `${result.payload.narrative ?? "(no narrative)"}`
|
|
22979
23363
|
}
|
|
@@ -22993,6 +23377,7 @@ server.tool("load_recall_item", "USE AFTER list_recall_items. Load one exact rec
|
|
|
22993
23377
|
` + `Title: ${result.title}
|
|
22994
23378
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
22995
23379
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23380
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
22996
23381
|
` + `Latest request: ${result.payload.latest_request ?? "(none)"}
|
|
22997
23382
|
` + `Current thread: ${result.payload.current_thread ?? "(none)"}
|
|
22998
23383
|
|
|
@@ -23014,6 +23399,7 @@ ${hotFiles}`
|
|
|
23014
23399
|
` + `Title: ${result.title}
|
|
23015
23400
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23016
23401
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23402
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23017
23403
|
|
|
23018
23404
|
` + `${result.payload.content}`
|
|
23019
23405
|
}
|
|
@@ -23028,6 +23414,7 @@ ${hotFiles}`
|
|
|
23028
23414
|
` + `Title: ${result.title}
|
|
23029
23415
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23030
23416
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23417
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23031
23418
|
` + `Type: ${result.payload.observation_type}
|
|
23032
23419
|
|
|
23033
23420
|
` + `${result.payload.narrative ?? result.payload.facts ?? "(no detail)"}`
|
|
@@ -23038,12 +23425,14 @@ ${hotFiles}`
|
|
|
23038
23425
|
server.tool("resume_thread", "USE FIRST when you want one direct 'where were we?' answer. Build a clear resume point for the current project by combining handoff, live recall, current thread, and recent chat continuity.", {
|
|
23039
23426
|
cwd: exports_external.string().optional().describe("Optional cwd override for the project to resume"),
|
|
23040
23427
|
limit: exports_external.number().optional().describe("Max recall hits/chat snippets to include"),
|
|
23428
|
+
agent: exports_external.string().optional().describe("Optional agent to resume specifically, such as claude-code or codex-cli"),
|
|
23041
23429
|
user_id: exports_external.string().optional().describe("Optional user override"),
|
|
23042
23430
|
repair_if_needed: exports_external.boolean().optional().describe("If true, attempt recall repair before resuming when continuity is still weak")
|
|
23043
23431
|
}, async (params) => {
|
|
23044
23432
|
const result = await resumeThread(db, config2, {
|
|
23045
23433
|
cwd: params.cwd ?? process.cwd(),
|
|
23046
23434
|
limit: params.limit,
|
|
23435
|
+
agent: params.agent,
|
|
23047
23436
|
user_id: params.user_id ?? config2.user_id,
|
|
23048
23437
|
current_device_id: config2.device_id,
|
|
23049
23438
|
repair_if_needed: params.repair_if_needed
|
|
@@ -23085,7 +23474,8 @@ server.tool("resume_thread", "USE FIRST when you want one direct 'where were we?
|
|
|
23085
23474
|
content: [
|
|
23086
23475
|
{
|
|
23087
23476
|
type: "text",
|
|
23088
|
-
text: `${projectLine}` +
|
|
23477
|
+
text: `${projectLine}` + `${result.target_agent ? `Target agent: ${result.target_agent}
|
|
23478
|
+
` : ""}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23089
23479
|
` + `Freshness: ${result.resume_freshness}
|
|
23090
23480
|
` + `Source: ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23091
23481
|
` + `Resume confidence: ${result.resume_confidence}
|
|
@@ -23244,13 +23634,7 @@ server.tool("check_messages", "Check for messages sent from other devices or ses
|
|
|
23244
23634
|
const markRead = params.mark_read !== false;
|
|
23245
23635
|
const readKey = `messages_read_${config2.device_id}`;
|
|
23246
23636
|
const lastReadId = parseInt(db.getSyncState(readKey) ?? "0", 10);
|
|
23247
|
-
const messages = db
|
|
23248
|
-
WHERE type = 'message'
|
|
23249
|
-
AND id > ?
|
|
23250
|
-
AND lifecycle IN ('active', 'pinned')
|
|
23251
|
-
AND device_id != ?
|
|
23252
|
-
AND (sensitivity != 'personal' OR user_id = ?)
|
|
23253
|
-
ORDER BY created_at_epoch DESC LIMIT 20`).all(lastReadId, config2.device_id, config2.user_id);
|
|
23637
|
+
const messages = getUnreadInboxMessages(db, config2.device_id, config2.user_id, lastReadId, 20);
|
|
23254
23638
|
if (messages.length === 0) {
|
|
23255
23639
|
return {
|
|
23256
23640
|
content: [{ type: "text", text: "No new messages." }]
|
|
@@ -23297,7 +23681,7 @@ server.tool("send_message", "Leave a cross-device or team note in Engrm's shared
|
|
|
23297
23681
|
]
|
|
23298
23682
|
};
|
|
23299
23683
|
});
|
|
23300
|
-
server.tool("recent_activity", "Inspect the most recent observations captured by Engrm", {
|
|
23684
|
+
server.tool("recent_activity", "Inspect the most recent observations, notes, and handoffs captured by Engrm", {
|
|
23301
23685
|
limit: exports_external.number().optional().describe("Max observations to return (default: 10)"),
|
|
23302
23686
|
project_scoped: exports_external.boolean().optional().describe("Scope to current project (default: true)"),
|
|
23303
23687
|
type: exports_external.string().optional().describe("Optional observation type filter")
|
|
@@ -23320,12 +23704,21 @@ server.tool("recent_activity", "Inspect the most recent observations captured by
|
|
|
23320
23704
|
const showProject = !result.project;
|
|
23321
23705
|
const header = showProject ? "| ID | Project | Type | Title | Created |" : "| ID | Type | Title | Created |";
|
|
23322
23706
|
const separator = showProject ? "|---|---|---|---|---|" : "|---|---|---|---|";
|
|
23707
|
+
const displayType = (obs) => {
|
|
23708
|
+
if (obs.message_kind === "draft-handoff")
|
|
23709
|
+
return "handoff:draft";
|
|
23710
|
+
if (obs.message_kind === "handoff")
|
|
23711
|
+
return "handoff";
|
|
23712
|
+
if (obs.message_kind === "inbox-note")
|
|
23713
|
+
return "note";
|
|
23714
|
+
return obs.type;
|
|
23715
|
+
};
|
|
23323
23716
|
const rows = result.observations.map((obs) => {
|
|
23324
23717
|
const date5 = obs.created_at.split("T")[0];
|
|
23325
23718
|
if (showProject) {
|
|
23326
|
-
return `| ${obs.id} | ${obs.project_name ?? "(unknown)"} | ${obs
|
|
23719
|
+
return `| ${obs.id} | ${obs.project_name ?? "(unknown)"} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23327
23720
|
}
|
|
23328
|
-
return `| ${obs.id} | ${obs
|
|
23721
|
+
return `| ${obs.id} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23329
23722
|
});
|
|
23330
23723
|
const projectLine = result.project ? `Project: ${result.project}
|
|
23331
23724
|
` : "";
|
|
@@ -23361,7 +23754,8 @@ server.tool("memory_stats", "Show high-level Engrm capture and sync statistics",
|
|
|
23361
23754
|
text: `Active observations: ${stats.active_observations}
|
|
23362
23755
|
` + `User prompts: ${stats.user_prompts}
|
|
23363
23756
|
` + `Tool events: ${stats.tool_events}
|
|
23364
|
-
` + `
|
|
23757
|
+
` + `Inbox notes: ${stats.inbox_messages}
|
|
23758
|
+
` + `Handoffs: ${stats.handoffs}
|
|
23365
23759
|
` + `Session summaries: ${stats.session_summaries}
|
|
23366
23760
|
` + `Summary coverage: learned ${stats.summaries_with_learned}, completed ${stats.summaries_with_completed}, next steps ${stats.summaries_with_next_steps}
|
|
23367
23761
|
` + `Installed packs: ${packs}
|
|
@@ -23405,6 +23799,8 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23405
23799
|
}).join(`
|
|
23406
23800
|
`) : "- (none)";
|
|
23407
23801
|
const handoffLines = result.recent_handoffs.length > 0 ? result.recent_handoffs.map((obs) => `- #${obs.id} ${obs.title}`).join(`
|
|
23802
|
+
`) : "- (none)";
|
|
23803
|
+
const inboxNoteLines = result.recent_inbox_notes.length > 0 ? result.recent_inbox_notes.map((obs) => `- #${obs.id} ${obs.title}`).join(`
|
|
23408
23804
|
`) : "- (none)";
|
|
23409
23805
|
const recentChatLines = result.recent_chat.length > 0 ? result.recent_chat.map((msg) => `- [${msg.role}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 180)}`).join(`
|
|
23410
23806
|
`) : "- (none)";
|
|
@@ -23425,7 +23821,7 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23425
23821
|
`) : "- (none)";
|
|
23426
23822
|
const topTypes = result.top_types.length > 0 ? result.top_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
23427
23823
|
`) : "- (none)";
|
|
23428
|
-
const recallPreviewLines = result.recall_index_preview.length > 0 ? result.recall_index_preview.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}] ${item.title}`).join(`
|
|
23824
|
+
const recallPreviewLines = result.recall_index_preview.length > 0 ? result.recall_index_preview.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}${item.source_agent ? ` · ${item.source_agent}` : ""}] ${item.title}`).join(`
|
|
23429
23825
|
`) : "- (none)";
|
|
23430
23826
|
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23431
23827
|
` : "";
|
|
@@ -23441,16 +23837,18 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23441
23837
|
content: [
|
|
23442
23838
|
{
|
|
23443
23839
|
type: "text",
|
|
23444
|
-
text: `${projectLine}` + `${captureLine}` +
|
|
23840
|
+
text: `${projectLine}` + `${captureLine}` + `${result.active_agents.length > 0 ? `Agents active: ${result.active_agents.join(", ")}
|
|
23841
|
+
` : ""}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23445
23842
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
23446
23843
|
` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23447
23844
|
` + `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})
|
|
23448
23845
|
` + `${typeof result.assistant_checkpoint_count === "number" ? `Assistant checkpoints: ${result.assistant_checkpoint_count}
|
|
23449
23846
|
` : ""}` + `Handoffs: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
|
|
23847
|
+
` + `Inbox notes: ${result.recent_inbox_notes.length}${result.latest_inbox_note_title ? ` · latest "${result.latest_inbox_note_title}"` : ""}
|
|
23450
23848
|
` + `${typeof result.estimated_read_tokens === "number" ? `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
23451
23849
|
` : ""}` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
23452
23850
|
|
|
23453
|
-
` + openExactLine + `Recall preview:
|
|
23851
|
+
` + openExactLine + resumeAgentLine + `Recall preview:
|
|
23454
23852
|
${recallPreviewLines}
|
|
23455
23853
|
|
|
23456
23854
|
` + `Next actions:
|
|
@@ -23472,6 +23870,9 @@ ${sessionLines}
|
|
|
23472
23870
|
` + `Recent handoffs:
|
|
23473
23871
|
${handoffLines}
|
|
23474
23872
|
|
|
23873
|
+
` + `Recent inbox notes:
|
|
23874
|
+
${inboxNoteLines}
|
|
23875
|
+
|
|
23475
23876
|
` + `Recent requests:
|
|
23476
23877
|
${requestLines}
|
|
23477
23878
|
|
|
@@ -23564,6 +23965,38 @@ ${projectLines}`
|
|
|
23564
23965
|
]
|
|
23565
23966
|
};
|
|
23566
23967
|
});
|
|
23968
|
+
server.tool("agent_memory_index", "Compare continuity and capture health across Claude Code, Codex, OpenClaw, and other agents for the current project or workspace.", {
|
|
23969
|
+
cwd: exports_external.string().optional().describe("Project path to inspect. Defaults to the current working directory."),
|
|
23970
|
+
project_scoped: exports_external.boolean().optional().describe("If true, limit results to the current project instead of the whole workspace."),
|
|
23971
|
+
user_id: exports_external.string().optional().describe("Optional user override; defaults to the configured user.")
|
|
23972
|
+
}, async (params) => {
|
|
23973
|
+
const result = getAgentMemoryIndex(db, {
|
|
23974
|
+
cwd: params.cwd ?? process.cwd(),
|
|
23975
|
+
project_scoped: params.project_scoped,
|
|
23976
|
+
user_id: params.user_id ?? config2.user_id
|
|
23977
|
+
});
|
|
23978
|
+
const rows = result.agents.length > 0 ? result.agents.map((agent) => {
|
|
23979
|
+
const lastSeen = agent.last_seen_epoch ? new Date(agent.last_seen_epoch * 1000).toISOString().replace("T", " ").slice(0, 16) : "unknown";
|
|
23980
|
+
const latest = agent.latest_summary ? ` latest="${agent.latest_summary.replace(/\s+/g, " ").trim().slice(0, 120)}"` : "";
|
|
23981
|
+
const devices = agent.devices.length > 0 ? ` devices=[${agent.devices.join(", ")}]` : "";
|
|
23982
|
+
const exact = agent.best_recall_key ? ` open=load_recall_item("${agent.best_recall_key}")` : "";
|
|
23983
|
+
return `- ${agent.agent}: continuity=${agent.continuity_state} capture=${agent.capture_state} resume=${agent.resume_freshness} 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}${exact} resume_call=resume_thread(agent="${agent.agent}")`;
|
|
23984
|
+
}).join(`
|
|
23985
|
+
`) : "- (none)";
|
|
23986
|
+
return {
|
|
23987
|
+
content: [
|
|
23988
|
+
{
|
|
23989
|
+
type: "text",
|
|
23990
|
+
text: `${result.project ? `Project: ${result.project}
|
|
23991
|
+
|
|
23992
|
+
` : ""}` + `Agent memory index:
|
|
23993
|
+
${rows}
|
|
23994
|
+
|
|
23995
|
+
` + `Suggested next step: ${result.suggested_tools.join(", ") || "(none)"}`
|
|
23996
|
+
}
|
|
23997
|
+
]
|
|
23998
|
+
};
|
|
23999
|
+
});
|
|
23567
24000
|
server.tool("tool_memory_index", "Show which tools are actually producing durable memory, which plugins they exercise, and what memory types they create.", {
|
|
23568
24001
|
cwd: exports_external.string().optional().describe("Project path to inspect. Defaults to the current working directory."),
|
|
23569
24002
|
project_scoped: exports_external.boolean().optional().describe("If true, limit results to the current project instead of the whole workspace."),
|
|
@@ -23649,10 +24082,12 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
23649
24082
|
type: "text",
|
|
23650
24083
|
text: `Project: ${result.project_name}
|
|
23651
24084
|
` + `Canonical ID: ${result.canonical_id}
|
|
24085
|
+
` + `Agents active: ${result.active_agents.join(", ") || "(none)"}
|
|
23652
24086
|
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23653
24087
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
23654
24088
|
` + `Open exact: ${result.best_recall_key ? `load_recall_item("${result.best_recall_key}")` : "(none)"}
|
|
23655
|
-
` +
|
|
24089
|
+
` + `${result.best_agent_resume_agent ? `Resume agent: resume_thread(agent="${result.best_agent_resume_agent}")
|
|
24090
|
+
` : ""}` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23656
24091
|
` + `Loaded observations: ${result.session_count}
|
|
23657
24092
|
` + `Searchable total: ${result.total_active}
|
|
23658
24093
|
` + `Recent requests: ${result.recent_requests}
|
|
@@ -23660,6 +24095,7 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
23660
24095
|
` + `Recent sessions: ${result.recent_sessions}
|
|
23661
24096
|
` + `Recent handoffs: ${result.recent_handoffs}
|
|
23662
24097
|
` + `Handoff split: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
|
|
24098
|
+
` + `Recent inbox notes: ${result.recent_inbox_notes}${result.latest_inbox_note_title ? ` · latest "${result.latest_inbox_note_title}"` : ""}
|
|
23663
24099
|
` + `Recent chat messages: ${result.recent_chat_messages}
|
|
23664
24100
|
` + `Chat recall: ${result.chat_coverage_state} · ${result.recent_chat_sessions} sessions (transcript ${result.chat_source_summary.transcript}, history ${result.chat_source_summary.history}, hook ${result.chat_source_summary.hook})
|
|
23665
24101
|
` + `Latest handoff: ${result.latest_handoff_title ?? "(none)"}
|
|
@@ -23730,7 +24166,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23730
24166
|
`) : "- (none)";
|
|
23731
24167
|
const topTitles = result.top_titles.length > 0 ? result.top_titles.map((item) => `- #${item.id} [${item.type}] ${item.title}`).join(`
|
|
23732
24168
|
`) : "- (none)";
|
|
23733
|
-
const recallPreviewLines = result.recall_index_preview.length > 0 ? result.recall_index_preview.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}] ${item.title}`).join(`
|
|
24169
|
+
const recallPreviewLines = result.recall_index_preview.length > 0 ? result.recall_index_preview.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}${item.source_agent ? ` · ${item.source_agent}` : ""}] ${item.title}`).join(`
|
|
23734
24170
|
`) : "- (none)";
|
|
23735
24171
|
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23736
24172
|
` : "";
|
|
@@ -23740,6 +24176,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23740
24176
|
type: "text",
|
|
23741
24177
|
text: `Project: ${result.project}
|
|
23742
24178
|
` + `Canonical ID: ${result.canonical_id}
|
|
24179
|
+
` + `Agents active: ${result.active_agents.join(", ") || "(none)"}
|
|
23743
24180
|
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23744
24181
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
23745
24182
|
` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
@@ -23748,6 +24185,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23748
24185
|
|
|
23749
24186
|
` + `Recent handoffs captured: ${result.recent_handoffs_count}
|
|
23750
24187
|
` + `Handoff split: ${result.saved_handoffs_count} saved, ${result.rolling_handoff_drafts_count} rolling drafts
|
|
24188
|
+
` + `Recent inbox notes: ${result.recent_inbox_notes_count}${result.latest_inbox_note_title ? ` · latest "${result.latest_inbox_note_title}"` : ""}
|
|
23751
24189
|
` + `Recent chat messages captured: ${result.recent_chat_count}
|
|
23752
24190
|
` + `Chat recall: ${result.chat_coverage_state} · ${result.recent_chat_sessions} sessions (transcript ${result.chat_source_summary.transcript}, history ${result.chat_source_summary.history}, hook ${result.chat_source_summary.hook})
|
|
23753
24191
|
|
|
@@ -23757,7 +24195,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23757
24195
|
` + `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
23758
24196
|
` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
23759
24197
|
|
|
23760
|
-
` + openExactLine + `Recall preview:
|
|
24198
|
+
` + openExactLine + resumeAgentLine + `Recall preview:
|
|
23761
24199
|
${recallPreviewLines}
|
|
23762
24200
|
|
|
23763
24201
|
` + `Next actions:
|
package/package.json
CHANGED