engrm 0.4.34 → 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 +4 -0
- package/dist/hooks/session-start.js +101 -8
- package/dist/hooks/stop.js +1 -1
- package/dist/server.js +234 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -405,8 +405,12 @@ What each tool is good for:
|
|
|
405
405
|
- `capture_quality` shows whether chat recall is transcript-backed, history-backed, or still hook-only across the workspace
|
|
406
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
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
|
|
408
410
|
- `memory_console` gives the quickest project snapshot, including whether continuity is `fresh`, `thin`, or `cold`
|
|
409
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
|
|
410
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
|
|
411
415
|
- `load_recall_item` completes that protocol by letting agents open one exact recall key directly after listing
|
|
412
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;
|
|
@@ -5888,10 +5976,12 @@ function formatInspectHints(context, visibleObservationIds = [], recallItems = [
|
|
|
5888
5976
|
return [];
|
|
5889
5977
|
const ids = visibleObservationIds.slice(0, 5);
|
|
5890
5978
|
const openNowItem = recallItems.find((item) => item.kind !== "memory") ?? null;
|
|
5979
|
+
const resumeAgent = activeAgents.length > 1 ? context.recentSessions?.[0]?.agent ?? null : null;
|
|
5891
5980
|
const fetchHint = ids.length > 0 ? `get_observations([${ids.join(", ")}])` : null;
|
|
5892
5981
|
return [
|
|
5893
5982
|
`${c2.dim}Next look:${c2.reset} ${unique.join(" \xB7 ")}`,
|
|
5894
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}")`] : [],
|
|
5895
5985
|
...fetchHint ? [`${c2.dim}Pull detail:${c2.reset} ${fetchHint}`] : []
|
|
5896
5986
|
];
|
|
5897
5987
|
}
|
|
@@ -5904,7 +5994,7 @@ function formatStartupRecallPreview(recallItems) {
|
|
|
5904
5994
|
return [];
|
|
5905
5995
|
return [
|
|
5906
5996
|
`${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)}`)
|
|
5997
|
+
...items.map((item) => `${item.key} [${item.kind} \xB7 ${item.freshness}${item.source_agent ? ` \xB7 ${item.source_agent}` : ""}] ${truncateInline(item.title, 110)}`)
|
|
5908
5998
|
];
|
|
5909
5999
|
}
|
|
5910
6000
|
function buildStartupRecallItems(context) {
|
|
@@ -5919,6 +6009,7 @@ function buildStartupRecallItems(context) {
|
|
|
5919
6009
|
kind: "handoff",
|
|
5920
6010
|
freshness,
|
|
5921
6011
|
title,
|
|
6012
|
+
source_agent: (context.recentSessions ?? []).find((session) => session.session_id === handoff.session_id)?.agent ?? null,
|
|
5922
6013
|
score: freshnessScore(freshness) + 40
|
|
5923
6014
|
});
|
|
5924
6015
|
}
|
|
@@ -5933,6 +6024,7 @@ function buildStartupRecallItems(context) {
|
|
|
5933
6024
|
kind: "thread",
|
|
5934
6025
|
freshness,
|
|
5935
6026
|
title,
|
|
6027
|
+
source_agent: session.agent ?? null,
|
|
5936
6028
|
score: freshnessScore(freshness) + 30
|
|
5937
6029
|
});
|
|
5938
6030
|
}
|
|
@@ -5945,6 +6037,7 @@ function buildStartupRecallItems(context) {
|
|
|
5945
6037
|
kind: "chat",
|
|
5946
6038
|
freshness,
|
|
5947
6039
|
title: `[${message.role}] ${message.content.replace(/\s+/g, " ").trim()}`,
|
|
6040
|
+
source_agent: message.agent ?? null,
|
|
5948
6041
|
score: freshnessScore(freshness) + 20
|
|
5949
6042
|
});
|
|
5950
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,
|
|
@@ -18617,11 +18689,13 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18617
18689
|
key: item.key,
|
|
18618
18690
|
kind: item.kind,
|
|
18619
18691
|
freshness: item.freshness,
|
|
18620
|
-
title: item.title
|
|
18692
|
+
title: item.title,
|
|
18693
|
+
source_agent: item.source_agent
|
|
18621
18694
|
})),
|
|
18622
18695
|
best_recall_key: bestRecallItem?.key ?? null,
|
|
18623
18696
|
best_recall_title: bestRecallItem?.title ?? null,
|
|
18624
18697
|
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
18698
|
+
best_agent_resume_agent: activeAgents.length > 1 ? latestSession?.agent ?? null : null,
|
|
18625
18699
|
resume_freshness: classifyResumeFreshness(sourceTimestamp),
|
|
18626
18700
|
resume_source_session_id: latestSession?.session_id ?? null,
|
|
18627
18701
|
resume_source_device_id: latestSession?.device_id ?? null,
|
|
@@ -18634,6 +18708,8 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18634
18708
|
recent_handoffs_count: recentHandoffsCount.length,
|
|
18635
18709
|
rolling_handoff_drafts_count: rollingHandoffDraftsCount,
|
|
18636
18710
|
saved_handoffs_count: savedHandoffsCount,
|
|
18711
|
+
recent_inbox_notes_count: recentInboxNotes.length,
|
|
18712
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
18637
18713
|
recent_chat_count: recentChatCount,
|
|
18638
18714
|
recent_chat_sessions: recentChat.session_count,
|
|
18639
18715
|
chat_source_summary: recentChat.source_summary,
|
|
@@ -18811,6 +18887,7 @@ function getMemoryConsole(db, input) {
|
|
|
18811
18887
|
}).handoffs;
|
|
18812
18888
|
const rollingHandoffDrafts = recentHandoffs.filter((handoff) => isDraftHandoff(handoff)).length;
|
|
18813
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 }));
|
|
18814
18891
|
const recentChat = getRecentChat(db, {
|
|
18815
18892
|
cwd,
|
|
18816
18893
|
project_scoped: projectScoped,
|
|
@@ -18842,11 +18919,13 @@ function getMemoryConsole(db, input) {
|
|
|
18842
18919
|
key: item.key,
|
|
18843
18920
|
kind: item.kind,
|
|
18844
18921
|
freshness: item.freshness,
|
|
18845
|
-
title: item.title
|
|
18922
|
+
title: item.title,
|
|
18923
|
+
source_agent: item.source_agent
|
|
18846
18924
|
})),
|
|
18847
18925
|
best_recall_key: projectIndex?.best_recall_key ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.key ?? null,
|
|
18848
18926
|
best_recall_title: projectIndex?.best_recall_title ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.title ?? null,
|
|
18849
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),
|
|
18850
18929
|
resume_freshness: projectIndex?.resume_freshness ?? "stale",
|
|
18851
18930
|
resume_source_session_id: projectIndex?.resume_source_session_id ?? sessions[0]?.session_id ?? null,
|
|
18852
18931
|
resume_source_device_id: projectIndex?.resume_source_device_id ?? sessions[0]?.device_id ?? null,
|
|
@@ -18857,6 +18936,8 @@ function getMemoryConsole(db, input) {
|
|
|
18857
18936
|
recent_handoffs: recentHandoffs,
|
|
18858
18937
|
rolling_handoff_drafts: rollingHandoffDrafts,
|
|
18859
18938
|
saved_handoffs: savedHandoffs,
|
|
18939
|
+
recent_inbox_notes: recentInboxNotes,
|
|
18940
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
18860
18941
|
recent_chat: recentChat.messages,
|
|
18861
18942
|
recent_chat_sessions: projectIndex?.recent_chat_sessions ?? recentChat.session_count,
|
|
18862
18943
|
chat_source_summary: projectIndex?.chat_source_summary ?? recentChat.source_summary,
|
|
@@ -19125,6 +19206,7 @@ function toChatEvent(message) {
|
|
|
19125
19206
|
};
|
|
19126
19207
|
}
|
|
19127
19208
|
function toObservationEvent(obs) {
|
|
19209
|
+
const messageKind = classifyMessageObservation(obs);
|
|
19128
19210
|
if (looksLikeHandoff(obs)) {
|
|
19129
19211
|
const handoffKind = isDraftHandoff(obs) ? "draft" : "saved";
|
|
19130
19212
|
return {
|
|
@@ -19139,6 +19221,8 @@ function toObservationEvent(obs) {
|
|
|
19139
19221
|
};
|
|
19140
19222
|
}
|
|
19141
19223
|
const detailBits = [];
|
|
19224
|
+
if (messageKind === "inbox-note")
|
|
19225
|
+
detailBits.push("inbox note");
|
|
19142
19226
|
if (obs.source_tool)
|
|
19143
19227
|
detailBits.push(`via ${obs.source_tool}`);
|
|
19144
19228
|
if (typeof obs.source_prompt_number === "number") {
|
|
@@ -19633,6 +19717,12 @@ function getAgentMemoryIndex(db, input) {
|
|
|
19633
19717
|
chatCoverage.set(row.agent, current);
|
|
19634
19718
|
}
|
|
19635
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;
|
|
19636
19726
|
const latestByAgent = new Map;
|
|
19637
19727
|
const devicesByAgent = new Map;
|
|
19638
19728
|
for (const session of recentSessions) {
|
|
@@ -19665,6 +19755,7 @@ function getAgentMemoryIndex(db, input) {
|
|
|
19665
19755
|
hook_count: 0
|
|
19666
19756
|
};
|
|
19667
19757
|
const latestSession = latestByAgent.get(agent) ?? null;
|
|
19758
|
+
const bestRecall = pickBestRecallForAgent(recallItems, agent);
|
|
19668
19759
|
return {
|
|
19669
19760
|
agent,
|
|
19670
19761
|
session_count: session.session_count,
|
|
@@ -19680,7 +19771,11 @@ function getAgentMemoryIndex(db, input) {
|
|
|
19680
19771
|
last_seen_epoch: session.last_seen_epoch,
|
|
19681
19772
|
latest_session_id: latestSession?.session_id ?? null,
|
|
19682
19773
|
latest_summary: latestSession?.current_thread ?? latestSession?.request ?? latestSession?.completed ?? null,
|
|
19683
|
-
devices: Array.from(devicesByAgent.get(agent) ?? []).sort()
|
|
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"
|
|
19684
19779
|
};
|
|
19685
19780
|
}).sort((a, b) => {
|
|
19686
19781
|
const epochA = a.last_seen_epoch ?? 0;
|
|
@@ -19720,6 +19815,12 @@ function buildSuggestedTools2(agents) {
|
|
|
19720
19815
|
if (agents.length === 0)
|
|
19721
19816
|
return [];
|
|
19722
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
|
+
}
|
|
19723
19824
|
if (agents.some((agent) => agent.continuity_state !== "fresh")) {
|
|
19724
19825
|
suggestions.push("resume_thread");
|
|
19725
19826
|
}
|
|
@@ -19728,9 +19829,12 @@ function buildSuggestedTools2(agents) {
|
|
|
19728
19829
|
}
|
|
19729
19830
|
return suggestions;
|
|
19730
19831
|
}
|
|
19832
|
+
function pickBestRecallForAgent(items, agent) {
|
|
19833
|
+
return items.find((item) => item.source_agent === agent) ?? null;
|
|
19834
|
+
}
|
|
19731
19835
|
|
|
19732
19836
|
// src/tools/tool-memory-index.ts
|
|
19733
|
-
function
|
|
19837
|
+
function parseConcepts2(value) {
|
|
19734
19838
|
if (!value)
|
|
19735
19839
|
return [];
|
|
19736
19840
|
try {
|
|
@@ -19799,7 +19903,7 @@ function getToolMemoryIndex(db, input = {}) {
|
|
|
19799
19903
|
ORDER BY o.created_at_epoch DESC, o.id DESC
|
|
19800
19904
|
LIMIT 50`).all(...rowParams);
|
|
19801
19905
|
const topPlugins = Array.from(observationRows.reduce((acc, obs) => {
|
|
19802
|
-
for (const concept of
|
|
19906
|
+
for (const concept of parseConcepts2(obs.concepts)) {
|
|
19803
19907
|
if (!concept.startsWith("plugin:"))
|
|
19804
19908
|
continue;
|
|
19805
19909
|
const plugin = concept.slice("plugin:".length);
|
|
@@ -19828,7 +19932,7 @@ function getToolMemoryIndex(db, input = {}) {
|
|
|
19828
19932
|
}
|
|
19829
19933
|
|
|
19830
19934
|
// src/tools/session-tool-memory.ts
|
|
19831
|
-
function
|
|
19935
|
+
function parseConcepts3(value) {
|
|
19832
19936
|
if (!value)
|
|
19833
19937
|
return [];
|
|
19834
19938
|
try {
|
|
@@ -19860,7 +19964,7 @@ function getSessionToolMemory(db, input) {
|
|
|
19860
19964
|
}, new Map).entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count || a.type.localeCompare(b.type)).slice(0, 5);
|
|
19861
19965
|
const sampleTitles = groupedObservations.map((obs) => obs.title).filter((title, index, all) => all.indexOf(title) === index).slice(0, 4);
|
|
19862
19966
|
const topPlugins = Array.from(groupedObservations.reduce((acc, obs) => {
|
|
19863
|
-
for (const concept of
|
|
19967
|
+
for (const concept of parseConcepts3(obs.concepts)) {
|
|
19864
19968
|
if (!concept.startsWith("plugin:"))
|
|
19865
19969
|
continue;
|
|
19866
19970
|
const plugin = concept.slice("plugin:".length);
|
|
@@ -19916,6 +20020,13 @@ function getSessionContext(db, input) {
|
|
|
19916
20020
|
user_id: input.user_id,
|
|
19917
20021
|
limit: 8
|
|
19918
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);
|
|
19919
20030
|
const recentChatMessages = recentChat.messages.length;
|
|
19920
20031
|
const recallIndex = listRecallItems(db, {
|
|
19921
20032
|
cwd,
|
|
@@ -19946,11 +20057,13 @@ function getSessionContext(db, input) {
|
|
|
19946
20057
|
key: item.key,
|
|
19947
20058
|
kind: item.kind,
|
|
19948
20059
|
freshness: item.freshness,
|
|
19949
|
-
title: item.title
|
|
20060
|
+
title: item.title,
|
|
20061
|
+
source_agent: item.source_agent
|
|
19950
20062
|
})),
|
|
19951
20063
|
best_recall_key: bestRecallItem?.key ?? null,
|
|
19952
20064
|
best_recall_title: bestRecallItem?.title ?? null,
|
|
19953
20065
|
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
20066
|
+
best_agent_resume_agent: activeAgents.length > 1 ? latestSession?.agent ?? null : null,
|
|
19954
20067
|
resume_freshness: classifyResumeFreshness(resumeTimestamp),
|
|
19955
20068
|
resume_source_session_id: latestSession?.session_id ?? null,
|
|
19956
20069
|
resume_source_device_id: latestSession?.device_id ?? null,
|
|
@@ -19964,6 +20077,8 @@ function getSessionContext(db, input) {
|
|
|
19964
20077
|
rolling_handoff_drafts: rollingHandoffDrafts,
|
|
19965
20078
|
saved_handoffs: savedHandoffs,
|
|
19966
20079
|
latest_handoff_title: latestHandoffTitle,
|
|
20080
|
+
recent_inbox_notes: recentInboxNotes.length,
|
|
20081
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
19967
20082
|
recent_chat_messages: recentChatMessages,
|
|
19968
20083
|
recent_chat_sessions: recentChat.session_count,
|
|
19969
20084
|
chat_source_summary: recentChat.source_summary,
|
|
@@ -20053,6 +20168,7 @@ function loadRecallItem(db, input) {
|
|
|
20053
20168
|
detail: "Malformed recall key",
|
|
20054
20169
|
session_id: null,
|
|
20055
20170
|
source_device_id: null,
|
|
20171
|
+
source_agent: null,
|
|
20056
20172
|
payload: null
|
|
20057
20173
|
};
|
|
20058
20174
|
}
|
|
@@ -20074,6 +20190,7 @@ function loadRecallItem(db, input) {
|
|
|
20074
20190
|
detail: summarizeNarrative(result.handoff.narrative),
|
|
20075
20191
|
session_id: result.handoff.session_id ?? null,
|
|
20076
20192
|
source_device_id: result.handoff.device_id ?? null,
|
|
20193
|
+
source_agent: result.handoff.session_id ? lookupSessionAgent(db, result.handoff.session_id) : null,
|
|
20077
20194
|
payload: {
|
|
20078
20195
|
type: "handoff",
|
|
20079
20196
|
handoff_id: result.handoff.id,
|
|
@@ -20093,6 +20210,7 @@ function loadRecallItem(db, input) {
|
|
|
20093
20210
|
detail: story.summary?.next_steps ?? story.summary?.completed ?? null,
|
|
20094
20211
|
session_id: story.session.session_id,
|
|
20095
20212
|
source_device_id: story.session.device_id ?? null,
|
|
20213
|
+
source_agent: story.session.agent ?? null,
|
|
20096
20214
|
payload: {
|
|
20097
20215
|
type: "thread",
|
|
20098
20216
|
latest_request: story.latest_request,
|
|
@@ -20122,6 +20240,7 @@ function loadRecallItem(db, input) {
|
|
|
20122
20240
|
detail: message.content,
|
|
20123
20241
|
session_id: message.session_id,
|
|
20124
20242
|
source_device_id: message.device_id ?? null,
|
|
20243
|
+
source_agent: message.agent ?? null,
|
|
20125
20244
|
payload: {
|
|
20126
20245
|
type: "chat",
|
|
20127
20246
|
role: message.role,
|
|
@@ -20147,6 +20266,7 @@ function loadRecallItem(db, input) {
|
|
|
20147
20266
|
detail: obs.narrative ?? obs.facts ?? null,
|
|
20148
20267
|
session_id: obs.session_id ?? null,
|
|
20149
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,
|
|
20150
20270
|
payload: {
|
|
20151
20271
|
type: "memory",
|
|
20152
20272
|
observation_id: obs.id,
|
|
@@ -20163,6 +20283,10 @@ function summarizeNarrative(value) {
|
|
|
20163
20283
|
return null;
|
|
20164
20284
|
return value.split(/\n+/).map((line) => line.trim()).find(Boolean) ?? null;
|
|
20165
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
|
+
}
|
|
20166
20290
|
function missing(key, kind) {
|
|
20167
20291
|
return {
|
|
20168
20292
|
kind,
|
|
@@ -20171,6 +20295,7 @@ function missing(key, kind) {
|
|
|
20171
20295
|
detail: "Recall item not found",
|
|
20172
20296
|
session_id: null,
|
|
20173
20297
|
source_device_id: null,
|
|
20298
|
+
source_agent: null,
|
|
20174
20299
|
payload: null
|
|
20175
20300
|
};
|
|
20176
20301
|
}
|
|
@@ -20597,6 +20722,9 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20597
20722
|
const detected = detectProject(cwd);
|
|
20598
20723
|
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
20599
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
|
+
}
|
|
20600
20728
|
let repairResult = null;
|
|
20601
20729
|
const shouldRepair = repairIfNeeded && snapshot.recentChat.coverage_state !== "transcript-backed" && (snapshot.recentChat.messages.length > 0 || snapshot.recentSessions.length > 0 || snapshot.context?.continuity_state !== "cold");
|
|
20602
20730
|
if (shouldRepair) {
|
|
@@ -20607,6 +20735,9 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20607
20735
|
});
|
|
20608
20736
|
if (repairResult.imported_chat_messages > 0) {
|
|
20609
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
|
+
}
|
|
20610
20741
|
}
|
|
20611
20742
|
}
|
|
20612
20743
|
const { context, handoff, recentChat, recentSessions, recall } = snapshot;
|
|
@@ -20647,6 +20778,7 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20647
20778
|
])).slice(0, 4);
|
|
20648
20779
|
return {
|
|
20649
20780
|
project_name: project?.name ?? context?.project_name ?? null,
|
|
20781
|
+
target_agent: input.agent ?? null,
|
|
20650
20782
|
continuity_state: context?.continuity_state ?? "cold",
|
|
20651
20783
|
continuity_summary: context?.continuity_summary ?? "No fresh repo-local continuity yet; older memory should be treated cautiously.",
|
|
20652
20784
|
resume_freshness: classifyResumeFreshness2(sourceTimestamp),
|
|
@@ -20696,6 +20828,13 @@ async function buildResumeSnapshot(db, cwd, userId, currentDeviceId, limit) {
|
|
|
20696
20828
|
user_id: userId,
|
|
20697
20829
|
current_device_id: currentDeviceId
|
|
20698
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;
|
|
20699
20838
|
const recentChat = getRecentChat(db, {
|
|
20700
20839
|
cwd,
|
|
20701
20840
|
project_scoped: true,
|
|
@@ -20725,15 +20864,50 @@ async function buildResumeSnapshot(db, cwd, userId, currentDeviceId, limit) {
|
|
|
20725
20864
|
return {
|
|
20726
20865
|
context,
|
|
20727
20866
|
handoff: handoffResult.handoff,
|
|
20867
|
+
recentHandoffs,
|
|
20728
20868
|
recentChat,
|
|
20729
20869
|
recentSessions,
|
|
20730
20870
|
recall,
|
|
20731
20871
|
recallIndex
|
|
20732
20872
|
};
|
|
20733
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
|
+
}
|
|
20734
20901
|
function pickBestRecallItem2(items) {
|
|
20735
20902
|
return items.find((item) => item.kind !== "memory") ?? items[0] ?? null;
|
|
20736
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
|
+
}
|
|
20737
20911
|
function extractCurrentThread(handoff) {
|
|
20738
20912
|
const narrative = handoff?.narrative ?? "";
|
|
20739
20913
|
const match = narrative.match(/Current thread:\s*(.+)/i);
|
|
@@ -20976,8 +21150,9 @@ function hasContent(value) {
|
|
|
20976
21150
|
// src/tools/stats.ts
|
|
20977
21151
|
function getMemoryStats(db) {
|
|
20978
21152
|
const activeObservations = db.getActiveObservationCount();
|
|
20979
|
-
const
|
|
20980
|
-
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);
|
|
20981
21156
|
const userPrompts = db.db.query("SELECT COUNT(*) as count FROM user_prompts").get()?.count ?? 0;
|
|
20982
21157
|
const toolEvents = db.db.query("SELECT COUNT(*) as count FROM tool_events").get()?.count ?? 0;
|
|
20983
21158
|
const sessionSummaries = db.db.query("SELECT COUNT(*) as count FROM session_summaries").get()?.count ?? 0;
|
|
@@ -20991,7 +21166,9 @@ function getMemoryStats(db) {
|
|
|
20991
21166
|
active_observations: activeObservations,
|
|
20992
21167
|
user_prompts: userPrompts,
|
|
20993
21168
|
tool_events: toolEvents,
|
|
20994
|
-
messages,
|
|
21169
|
+
messages: inboxMessages,
|
|
21170
|
+
inbox_messages: inboxMessages,
|
|
21171
|
+
handoffs,
|
|
20995
21172
|
session_summaries: sessionSummaries,
|
|
20996
21173
|
decisions: signals.decisions_count,
|
|
20997
21174
|
lessons: signals.lessons_count,
|
|
@@ -22665,7 +22842,7 @@ process.on("SIGTERM", () => {
|
|
|
22665
22842
|
});
|
|
22666
22843
|
var server = new McpServer({
|
|
22667
22844
|
name: "engrm",
|
|
22668
|
-
version: "0.4.
|
|
22845
|
+
version: "0.4.35"
|
|
22669
22846
|
});
|
|
22670
22847
|
server.tool("save_observation", "Save an observation to memory", {
|
|
22671
22848
|
type: exports_external.enum([
|
|
@@ -23135,7 +23312,7 @@ server.tool("list_recall_items", "USE FIRST when continuity feels fuzzy. List th
|
|
|
23135
23312
|
}
|
|
23136
23313
|
const projectLine = result.project ? `Project: ${result.project}
|
|
23137
23314
|
` : "";
|
|
23138
|
-
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})` : ""}
|
|
23139
23316
|
${item.detail}`).join(`
|
|
23140
23317
|
`);
|
|
23141
23318
|
return {
|
|
@@ -23180,6 +23357,7 @@ server.tool("load_recall_item", "USE AFTER list_recall_items. Load one exact rec
|
|
|
23180
23357
|
` + `Title: ${result.title}
|
|
23181
23358
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23182
23359
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23360
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23183
23361
|
|
|
23184
23362
|
` + `${result.payload.narrative ?? "(no narrative)"}`
|
|
23185
23363
|
}
|
|
@@ -23199,6 +23377,7 @@ server.tool("load_recall_item", "USE AFTER list_recall_items. Load one exact rec
|
|
|
23199
23377
|
` + `Title: ${result.title}
|
|
23200
23378
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23201
23379
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23380
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23202
23381
|
` + `Latest request: ${result.payload.latest_request ?? "(none)"}
|
|
23203
23382
|
` + `Current thread: ${result.payload.current_thread ?? "(none)"}
|
|
23204
23383
|
|
|
@@ -23220,6 +23399,7 @@ ${hotFiles}`
|
|
|
23220
23399
|
` + `Title: ${result.title}
|
|
23221
23400
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23222
23401
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23402
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23223
23403
|
|
|
23224
23404
|
` + `${result.payload.content}`
|
|
23225
23405
|
}
|
|
@@ -23234,6 +23414,7 @@ ${hotFiles}`
|
|
|
23234
23414
|
` + `Title: ${result.title}
|
|
23235
23415
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23236
23416
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23417
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23237
23418
|
` + `Type: ${result.payload.observation_type}
|
|
23238
23419
|
|
|
23239
23420
|
` + `${result.payload.narrative ?? result.payload.facts ?? "(no detail)"}`
|
|
@@ -23244,12 +23425,14 @@ ${hotFiles}`
|
|
|
23244
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.", {
|
|
23245
23426
|
cwd: exports_external.string().optional().describe("Optional cwd override for the project to resume"),
|
|
23246
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"),
|
|
23247
23429
|
user_id: exports_external.string().optional().describe("Optional user override"),
|
|
23248
23430
|
repair_if_needed: exports_external.boolean().optional().describe("If true, attempt recall repair before resuming when continuity is still weak")
|
|
23249
23431
|
}, async (params) => {
|
|
23250
23432
|
const result = await resumeThread(db, config2, {
|
|
23251
23433
|
cwd: params.cwd ?? process.cwd(),
|
|
23252
23434
|
limit: params.limit,
|
|
23435
|
+
agent: params.agent,
|
|
23253
23436
|
user_id: params.user_id ?? config2.user_id,
|
|
23254
23437
|
current_device_id: config2.device_id,
|
|
23255
23438
|
repair_if_needed: params.repair_if_needed
|
|
@@ -23291,7 +23474,8 @@ server.tool("resume_thread", "USE FIRST when you want one direct 'where were we?
|
|
|
23291
23474
|
content: [
|
|
23292
23475
|
{
|
|
23293
23476
|
type: "text",
|
|
23294
|
-
text: `${projectLine}` +
|
|
23477
|
+
text: `${projectLine}` + `${result.target_agent ? `Target agent: ${result.target_agent}
|
|
23478
|
+
` : ""}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23295
23479
|
` + `Freshness: ${result.resume_freshness}
|
|
23296
23480
|
` + `Source: ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23297
23481
|
` + `Resume confidence: ${result.resume_confidence}
|
|
@@ -23450,13 +23634,7 @@ server.tool("check_messages", "Check for messages sent from other devices or ses
|
|
|
23450
23634
|
const markRead = params.mark_read !== false;
|
|
23451
23635
|
const readKey = `messages_read_${config2.device_id}`;
|
|
23452
23636
|
const lastReadId = parseInt(db.getSyncState(readKey) ?? "0", 10);
|
|
23453
|
-
const messages = db
|
|
23454
|
-
WHERE type = 'message'
|
|
23455
|
-
AND id > ?
|
|
23456
|
-
AND lifecycle IN ('active', 'pinned')
|
|
23457
|
-
AND device_id != ?
|
|
23458
|
-
AND (sensitivity != 'personal' OR user_id = ?)
|
|
23459
|
-
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);
|
|
23460
23638
|
if (messages.length === 0) {
|
|
23461
23639
|
return {
|
|
23462
23640
|
content: [{ type: "text", text: "No new messages." }]
|
|
@@ -23503,7 +23681,7 @@ server.tool("send_message", "Leave a cross-device or team note in Engrm's shared
|
|
|
23503
23681
|
]
|
|
23504
23682
|
};
|
|
23505
23683
|
});
|
|
23506
|
-
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", {
|
|
23507
23685
|
limit: exports_external.number().optional().describe("Max observations to return (default: 10)"),
|
|
23508
23686
|
project_scoped: exports_external.boolean().optional().describe("Scope to current project (default: true)"),
|
|
23509
23687
|
type: exports_external.string().optional().describe("Optional observation type filter")
|
|
@@ -23526,12 +23704,21 @@ server.tool("recent_activity", "Inspect the most recent observations captured by
|
|
|
23526
23704
|
const showProject = !result.project;
|
|
23527
23705
|
const header = showProject ? "| ID | Project | Type | Title | Created |" : "| ID | Type | Title | Created |";
|
|
23528
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
|
+
};
|
|
23529
23716
|
const rows = result.observations.map((obs) => {
|
|
23530
23717
|
const date5 = obs.created_at.split("T")[0];
|
|
23531
23718
|
if (showProject) {
|
|
23532
|
-
return `| ${obs.id} | ${obs.project_name ?? "(unknown)"} | ${obs
|
|
23719
|
+
return `| ${obs.id} | ${obs.project_name ?? "(unknown)"} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23533
23720
|
}
|
|
23534
|
-
return `| ${obs.id} | ${obs
|
|
23721
|
+
return `| ${obs.id} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23535
23722
|
});
|
|
23536
23723
|
const projectLine = result.project ? `Project: ${result.project}
|
|
23537
23724
|
` : "";
|
|
@@ -23567,7 +23754,8 @@ server.tool("memory_stats", "Show high-level Engrm capture and sync statistics",
|
|
|
23567
23754
|
text: `Active observations: ${stats.active_observations}
|
|
23568
23755
|
` + `User prompts: ${stats.user_prompts}
|
|
23569
23756
|
` + `Tool events: ${stats.tool_events}
|
|
23570
|
-
` + `
|
|
23757
|
+
` + `Inbox notes: ${stats.inbox_messages}
|
|
23758
|
+
` + `Handoffs: ${stats.handoffs}
|
|
23571
23759
|
` + `Session summaries: ${stats.session_summaries}
|
|
23572
23760
|
` + `Summary coverage: learned ${stats.summaries_with_learned}, completed ${stats.summaries_with_completed}, next steps ${stats.summaries_with_next_steps}
|
|
23573
23761
|
` + `Installed packs: ${packs}
|
|
@@ -23611,6 +23799,8 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23611
23799
|
}).join(`
|
|
23612
23800
|
`) : "- (none)";
|
|
23613
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(`
|
|
23614
23804
|
`) : "- (none)";
|
|
23615
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(`
|
|
23616
23806
|
`) : "- (none)";
|
|
@@ -23631,7 +23821,7 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23631
23821
|
`) : "- (none)";
|
|
23632
23822
|
const topTypes = result.top_types.length > 0 ? result.top_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
23633
23823
|
`) : "- (none)";
|
|
23634
|
-
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(`
|
|
23635
23825
|
`) : "- (none)";
|
|
23636
23826
|
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23637
23827
|
` : "";
|
|
@@ -23654,10 +23844,11 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23654
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})
|
|
23655
23845
|
` + `${typeof result.assistant_checkpoint_count === "number" ? `Assistant checkpoints: ${result.assistant_checkpoint_count}
|
|
23656
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}"` : ""}
|
|
23657
23848
|
` + `${typeof result.estimated_read_tokens === "number" ? `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
23658
23849
|
` : ""}` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
23659
23850
|
|
|
23660
|
-
` + openExactLine + `Recall preview:
|
|
23851
|
+
` + openExactLine + resumeAgentLine + `Recall preview:
|
|
23661
23852
|
${recallPreviewLines}
|
|
23662
23853
|
|
|
23663
23854
|
` + `Next actions:
|
|
@@ -23679,6 +23870,9 @@ ${sessionLines}
|
|
|
23679
23870
|
` + `Recent handoffs:
|
|
23680
23871
|
${handoffLines}
|
|
23681
23872
|
|
|
23873
|
+
` + `Recent inbox notes:
|
|
23874
|
+
${inboxNoteLines}
|
|
23875
|
+
|
|
23682
23876
|
` + `Recent requests:
|
|
23683
23877
|
${requestLines}
|
|
23684
23878
|
|
|
@@ -23785,7 +23979,8 @@ server.tool("agent_memory_index", "Compare continuity and capture health across
|
|
|
23785
23979
|
const lastSeen = agent.last_seen_epoch ? new Date(agent.last_seen_epoch * 1000).toISOString().replace("T", " ").slice(0, 16) : "unknown";
|
|
23786
23980
|
const latest = agent.latest_summary ? ` latest="${agent.latest_summary.replace(/\s+/g, " ").trim().slice(0, 120)}"` : "";
|
|
23787
23981
|
const devices = agent.devices.length > 0 ? ` devices=[${agent.devices.join(", ")}]` : "";
|
|
23788
|
-
|
|
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}")`;
|
|
23789
23984
|
}).join(`
|
|
23790
23985
|
`) : "- (none)";
|
|
23791
23986
|
return {
|
|
@@ -23891,7 +24086,8 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
23891
24086
|
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23892
24087
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
23893
24088
|
` + `Open exact: ${result.best_recall_key ? `load_recall_item("${result.best_recall_key}")` : "(none)"}
|
|
23894
|
-
` +
|
|
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})` : ""}
|
|
23895
24091
|
` + `Loaded observations: ${result.session_count}
|
|
23896
24092
|
` + `Searchable total: ${result.total_active}
|
|
23897
24093
|
` + `Recent requests: ${result.recent_requests}
|
|
@@ -23899,6 +24095,7 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
23899
24095
|
` + `Recent sessions: ${result.recent_sessions}
|
|
23900
24096
|
` + `Recent handoffs: ${result.recent_handoffs}
|
|
23901
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}"` : ""}
|
|
23902
24099
|
` + `Recent chat messages: ${result.recent_chat_messages}
|
|
23903
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})
|
|
23904
24101
|
` + `Latest handoff: ${result.latest_handoff_title ?? "(none)"}
|
|
@@ -23969,7 +24166,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23969
24166
|
`) : "- (none)";
|
|
23970
24167
|
const topTitles = result.top_titles.length > 0 ? result.top_titles.map((item) => `- #${item.id} [${item.type}] ${item.title}`).join(`
|
|
23971
24168
|
`) : "- (none)";
|
|
23972
|
-
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(`
|
|
23973
24170
|
`) : "- (none)";
|
|
23974
24171
|
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23975
24172
|
` : "";
|
|
@@ -23988,6 +24185,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23988
24185
|
|
|
23989
24186
|
` + `Recent handoffs captured: ${result.recent_handoffs_count}
|
|
23990
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}"` : ""}
|
|
23991
24189
|
` + `Recent chat messages captured: ${result.recent_chat_count}
|
|
23992
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})
|
|
23993
24191
|
|
|
@@ -23997,7 +24195,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23997
24195
|
` + `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
23998
24196
|
` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
23999
24197
|
|
|
24000
|
-
` + openExactLine + `Recall preview:
|
|
24198
|
+
` + openExactLine + resumeAgentLine + `Recall preview:
|
|
24001
24199
|
${recallPreviewLines}
|
|
24002
24200
|
|
|
24003
24201
|
` + `Next actions:
|
package/package.json
CHANGED