engrm 0.4.34 → 0.4.36
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/cli.js +6 -1
- package/dist/hooks/elicitation-result.js +6 -1
- package/dist/hooks/post-tool-use.js +6 -1
- package/dist/hooks/pre-compact.js +6 -1
- package/dist/hooks/sentinel.js +6 -1
- package/dist/hooks/session-start.js +107 -9
- package/dist/hooks/stop.js +7 -2
- package/dist/hooks/user-prompt-submit.js +6 -1
- package/dist/server.js +268 -39
- 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
|
package/dist/cli.js
CHANGED
|
@@ -1592,7 +1592,12 @@ class MemDatabase {
|
|
|
1592
1592
|
vecChatInsert(chatMessageId, embedding) {
|
|
1593
1593
|
if (!this.vecAvailable)
|
|
1594
1594
|
return;
|
|
1595
|
-
|
|
1595
|
+
const normalizedId = Number(chatMessageId);
|
|
1596
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
1597
|
+
return;
|
|
1598
|
+
try {
|
|
1599
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
1600
|
+
} catch {}
|
|
1596
1601
|
}
|
|
1597
1602
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
1598
1603
|
if (!this.vecAvailable)
|
|
@@ -2428,7 +2428,12 @@ class MemDatabase {
|
|
|
2428
2428
|
vecChatInsert(chatMessageId, embedding) {
|
|
2429
2429
|
if (!this.vecAvailable)
|
|
2430
2430
|
return;
|
|
2431
|
-
|
|
2431
|
+
const normalizedId = Number(chatMessageId);
|
|
2432
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
2433
|
+
return;
|
|
2434
|
+
try {
|
|
2435
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
2436
|
+
} catch {}
|
|
2432
2437
|
}
|
|
2433
2438
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
2434
2439
|
if (!this.vecAvailable)
|
|
@@ -1770,7 +1770,12 @@ class MemDatabase {
|
|
|
1770
1770
|
vecChatInsert(chatMessageId, embedding) {
|
|
1771
1771
|
if (!this.vecAvailable)
|
|
1772
1772
|
return;
|
|
1773
|
-
|
|
1773
|
+
const normalizedId = Number(chatMessageId);
|
|
1774
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
1775
|
+
return;
|
|
1776
|
+
try {
|
|
1777
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
1778
|
+
} catch {}
|
|
1774
1779
|
}
|
|
1775
1780
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
1776
1781
|
if (!this.vecAvailable)
|
|
@@ -1564,7 +1564,12 @@ class MemDatabase {
|
|
|
1564
1564
|
vecChatInsert(chatMessageId, embedding) {
|
|
1565
1565
|
if (!this.vecAvailable)
|
|
1566
1566
|
return;
|
|
1567
|
-
|
|
1567
|
+
const normalizedId = Number(chatMessageId);
|
|
1568
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
1569
|
+
return;
|
|
1570
|
+
try {
|
|
1571
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
1572
|
+
} catch {}
|
|
1568
1573
|
}
|
|
1569
1574
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
1570
1575
|
if (!this.vecAvailable)
|
package/dist/hooks/sentinel.js
CHANGED
|
@@ -1640,7 +1640,12 @@ class MemDatabase {
|
|
|
1640
1640
|
vecChatInsert(chatMessageId, embedding) {
|
|
1641
1641
|
if (!this.vecAvailable)
|
|
1642
1642
|
return;
|
|
1643
|
-
|
|
1643
|
+
const normalizedId = Number(chatMessageId);
|
|
1644
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
1645
|
+
return;
|
|
1646
|
+
try {
|
|
1647
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
1648
|
+
} catch {}
|
|
1644
1649
|
}
|
|
1645
1650
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
1646
1651
|
if (!this.vecAvailable)
|
|
@@ -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.36";
|
|
3148
3229
|
function hashFile(filePath) {
|
|
3149
3230
|
try {
|
|
3150
3231
|
if (!existsSync3(filePath))
|
|
@@ -5143,7 +5224,12 @@ class MemDatabase {
|
|
|
5143
5224
|
vecChatInsert(chatMessageId, embedding) {
|
|
5144
5225
|
if (!this.vecAvailable)
|
|
5145
5226
|
return;
|
|
5146
|
-
|
|
5227
|
+
const normalizedId = Number(chatMessageId);
|
|
5228
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
5229
|
+
return;
|
|
5230
|
+
try {
|
|
5231
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
5232
|
+
} catch {}
|
|
5147
5233
|
}
|
|
5148
5234
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
5149
5235
|
if (!this.vecAvailable)
|
|
@@ -5494,12 +5580,7 @@ async function main() {
|
|
|
5494
5580
|
try {
|
|
5495
5581
|
const readKey = `messages_read_${config.device_id}`;
|
|
5496
5582
|
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;
|
|
5583
|
+
msgCount = getUnreadInboxMessageCount(db, config.device_id, config.user_id, lastReadId);
|
|
5503
5584
|
} catch {}
|
|
5504
5585
|
const splash = formatSplashScreen({
|
|
5505
5586
|
projectName: context.project_name,
|
|
@@ -5653,6 +5734,7 @@ function formatVisibleStartupBrief(context) {
|
|
|
5653
5734
|
const projectSignals = buildProjectSignalLine(context);
|
|
5654
5735
|
const shownItems = new Set;
|
|
5655
5736
|
const latestHandoffLines = buildLatestHandoffLines(context);
|
|
5737
|
+
const inboxNoteLines = buildRecentInboxNoteLines(context);
|
|
5656
5738
|
const freshContinuity = hasFreshContinuitySignal(context);
|
|
5657
5739
|
if (latestHandoffLines.length > 0) {
|
|
5658
5740
|
lines.push(`${c2.cyan}Latest handoff:${c2.reset}`);
|
|
@@ -5661,6 +5743,13 @@ function formatVisibleStartupBrief(context) {
|
|
|
5661
5743
|
rememberShownItem(shownItems, item);
|
|
5662
5744
|
}
|
|
5663
5745
|
}
|
|
5746
|
+
if (inboxNoteLines.length > 0) {
|
|
5747
|
+
lines.push(`${c2.cyan}Inbox notes:${c2.reset}`);
|
|
5748
|
+
for (const item of inboxNoteLines) {
|
|
5749
|
+
lines.push(` - ${truncateInline(item, 160)}`);
|
|
5750
|
+
rememberShownItem(shownItems, item);
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5664
5753
|
lines.push(`${c2.cyan}Continuity:${c2.reset} ${continuityState} \u2014 ${truncateInline(describeContinuityState(continuityState), 160)}`);
|
|
5665
5754
|
const resumeReadiness = buildResumeReadinessLine(context);
|
|
5666
5755
|
if (resumeReadiness) {
|
|
@@ -5786,6 +5875,10 @@ function buildLatestHandoffLines(context) {
|
|
|
5786
5875
|
}
|
|
5787
5876
|
return Array.from(new Set(lines.filter(Boolean))).slice(0, 2);
|
|
5788
5877
|
}
|
|
5878
|
+
function buildRecentInboxNoteLines(context) {
|
|
5879
|
+
const notes = context.observations.filter((obs) => classifyMessageObservation(obs) === "inbox-note").slice(0, 2);
|
|
5880
|
+
return Array.from(new Set(notes.map((obs) => obs.title.trim()).filter((title) => title.length > 0)));
|
|
5881
|
+
}
|
|
5789
5882
|
function buildResumeReadinessLine(context) {
|
|
5790
5883
|
const latestSession = context.recentSessions?.[0] ?? null;
|
|
5791
5884
|
const latestHandoff = context.recentHandoffs?.[0] ?? null;
|
|
@@ -5888,10 +5981,12 @@ function formatInspectHints(context, visibleObservationIds = [], recallItems = [
|
|
|
5888
5981
|
return [];
|
|
5889
5982
|
const ids = visibleObservationIds.slice(0, 5);
|
|
5890
5983
|
const openNowItem = recallItems.find((item) => item.kind !== "memory") ?? null;
|
|
5984
|
+
const resumeAgent = activeAgents.length > 1 ? context.recentSessions?.[0]?.agent ?? null : null;
|
|
5891
5985
|
const fetchHint = ids.length > 0 ? `get_observations([${ids.join(", ")}])` : null;
|
|
5892
5986
|
return [
|
|
5893
5987
|
`${c2.dim}Next look:${c2.reset} ${unique.join(" \xB7 ")}`,
|
|
5894
5988
|
...openNowItem ? [`${c2.dim}Open now:${c2.reset} load_recall_item("${openNowItem.key}")`] : [],
|
|
5989
|
+
...resumeAgent ? [`${c2.dim}Resume agent:${c2.reset} resume_thread(agent="${resumeAgent}")`] : [],
|
|
5895
5990
|
...fetchHint ? [`${c2.dim}Pull detail:${c2.reset} ${fetchHint}`] : []
|
|
5896
5991
|
];
|
|
5897
5992
|
}
|
|
@@ -5904,7 +5999,7 @@ function formatStartupRecallPreview(recallItems) {
|
|
|
5904
5999
|
return [];
|
|
5905
6000
|
return [
|
|
5906
6001
|
`${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)}`)
|
|
6002
|
+
...items.map((item) => `${item.key} [${item.kind} \xB7 ${item.freshness}${item.source_agent ? ` \xB7 ${item.source_agent}` : ""}] ${truncateInline(item.title, 110)}`)
|
|
5908
6003
|
];
|
|
5909
6004
|
}
|
|
5910
6005
|
function buildStartupRecallItems(context) {
|
|
@@ -5919,6 +6014,7 @@ function buildStartupRecallItems(context) {
|
|
|
5919
6014
|
kind: "handoff",
|
|
5920
6015
|
freshness,
|
|
5921
6016
|
title,
|
|
6017
|
+
source_agent: (context.recentSessions ?? []).find((session) => session.session_id === handoff.session_id)?.agent ?? null,
|
|
5922
6018
|
score: freshnessScore(freshness) + 40
|
|
5923
6019
|
});
|
|
5924
6020
|
}
|
|
@@ -5933,6 +6029,7 @@ function buildStartupRecallItems(context) {
|
|
|
5933
6029
|
kind: "thread",
|
|
5934
6030
|
freshness,
|
|
5935
6031
|
title,
|
|
6032
|
+
source_agent: session.agent ?? null,
|
|
5936
6033
|
score: freshnessScore(freshness) + 30
|
|
5937
6034
|
});
|
|
5938
6035
|
}
|
|
@@ -5945,6 +6042,7 @@ function buildStartupRecallItems(context) {
|
|
|
5945
6042
|
kind: "chat",
|
|
5946
6043
|
freshness,
|
|
5947
6044
|
title: `[${message.role}] ${message.content.replace(/\s+/g, " ").trim()}`,
|
|
6045
|
+
source_agent: message.agent ?? null,
|
|
5948
6046
|
score: freshnessScore(freshness) + 20
|
|
5949
6047
|
});
|
|
5950
6048
|
}
|
package/dist/hooks/stop.js
CHANGED
|
@@ -1797,7 +1797,12 @@ class MemDatabase {
|
|
|
1797
1797
|
vecChatInsert(chatMessageId, embedding) {
|
|
1798
1798
|
if (!this.vecAvailable)
|
|
1799
1799
|
return;
|
|
1800
|
-
|
|
1800
|
+
const normalizedId = Number(chatMessageId);
|
|
1801
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
1802
|
+
return;
|
|
1803
|
+
try {
|
|
1804
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
1805
|
+
} catch {}
|
|
1801
1806
|
}
|
|
1802
1807
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
1803
1808
|
if (!this.vecAvailable)
|
|
@@ -3082,7 +3087,7 @@ function buildBeacon(db, config, sessionId, metrics) {
|
|
|
3082
3087
|
sentinel_used: valueSignals.security_findings_count > 0,
|
|
3083
3088
|
risk_score: riskScore,
|
|
3084
3089
|
stacks_detected: stacks,
|
|
3085
|
-
client_version: "0.4.
|
|
3090
|
+
client_version: "0.4.36",
|
|
3086
3091
|
context_observations_injected: metrics?.contextObsInjected ?? 0,
|
|
3087
3092
|
context_total_available: metrics?.contextTotalAvailable ?? 0,
|
|
3088
3093
|
recall_attempts: metrics?.recallAttempts ?? 0,
|
|
@@ -1708,7 +1708,12 @@ class MemDatabase {
|
|
|
1708
1708
|
vecChatInsert(chatMessageId, embedding) {
|
|
1709
1709
|
if (!this.vecAvailable)
|
|
1710
1710
|
return;
|
|
1711
|
-
|
|
1711
|
+
const normalizedId = Number(chatMessageId);
|
|
1712
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
1713
|
+
return;
|
|
1714
|
+
try {
|
|
1715
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
1716
|
+
} catch {}
|
|
1712
1717
|
}
|
|
1713
1718
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
1714
1719
|
if (!this.vecAvailable)
|
package/dist/server.js
CHANGED
|
@@ -15114,7 +15114,12 @@ class MemDatabase {
|
|
|
15114
15114
|
vecChatInsert(chatMessageId, embedding) {
|
|
15115
15115
|
if (!this.vecAvailable)
|
|
15116
15116
|
return;
|
|
15117
|
-
|
|
15117
|
+
const normalizedId = Number(chatMessageId);
|
|
15118
|
+
if (!Number.isInteger(normalizedId) || normalizedId <= 0)
|
|
15119
|
+
return;
|
|
15120
|
+
try {
|
|
15121
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(normalizedId, new Uint8Array(embedding.buffer));
|
|
15122
|
+
} catch {}
|
|
15118
15123
|
}
|
|
15119
15124
|
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
15120
15125
|
if (!this.vecAvailable)
|
|
@@ -16827,6 +16832,69 @@ function pinObservation(db, input) {
|
|
|
16827
16832
|
return { success: true };
|
|
16828
16833
|
}
|
|
16829
16834
|
|
|
16835
|
+
// src/tools/inbox-messages.ts
|
|
16836
|
+
function buildHandoffMessageFilterSql(lifecycle) {
|
|
16837
|
+
return `
|
|
16838
|
+
type = 'message'
|
|
16839
|
+
AND lifecycle IN (${lifecycle})
|
|
16840
|
+
AND (
|
|
16841
|
+
COALESCE(source_tool, '') IN ('create_handoff', 'rolling_handoff')
|
|
16842
|
+
OR title LIKE 'Handoff:%'
|
|
16843
|
+
OR title LIKE 'Handoff Draft:%'
|
|
16844
|
+
OR (
|
|
16845
|
+
concepts IS NOT NULL AND (
|
|
16846
|
+
concepts LIKE '%"handoff"%'
|
|
16847
|
+
OR concepts LIKE '%"session-handoff"%'
|
|
16848
|
+
OR concepts LIKE '%"draft-handoff"%'
|
|
16849
|
+
)
|
|
16850
|
+
)
|
|
16851
|
+
)
|
|
16852
|
+
`;
|
|
16853
|
+
}
|
|
16854
|
+
var HANDOFF_MESSAGE_FILTER_SQL = buildHandoffMessageFilterSql("'active', 'pinned'");
|
|
16855
|
+
var INBOX_MESSAGE_FILTER_SQL = `
|
|
16856
|
+
type = 'message'
|
|
16857
|
+
AND lifecycle IN ('active', 'pinned')
|
|
16858
|
+
AND NOT (${HANDOFF_MESSAGE_FILTER_SQL})
|
|
16859
|
+
`;
|
|
16860
|
+
function getHandoffMessageFilterSql(options) {
|
|
16861
|
+
return buildHandoffMessageFilterSql(options?.include_aging ? "'active', 'aging', 'pinned'" : "'active', 'pinned'");
|
|
16862
|
+
}
|
|
16863
|
+
function classifyMessageObservation(observation) {
|
|
16864
|
+
if (observation.type !== "message")
|
|
16865
|
+
return null;
|
|
16866
|
+
const concepts = parseConcepts(observation.concepts);
|
|
16867
|
+
const isDraft = observation.title.startsWith("Handoff Draft:") || observation.source_tool === "rolling_handoff" || concepts.includes("draft-handoff") || concepts.includes("auto-handoff");
|
|
16868
|
+
if (isDraft)
|
|
16869
|
+
return "draft-handoff";
|
|
16870
|
+
const isHandoff = observation.title.startsWith("Handoff:") || observation.source_tool === "create_handoff" || concepts.includes("handoff") || concepts.includes("session-handoff");
|
|
16871
|
+
if (isHandoff)
|
|
16872
|
+
return "handoff";
|
|
16873
|
+
return "inbox-note";
|
|
16874
|
+
}
|
|
16875
|
+
function parseConcepts(value) {
|
|
16876
|
+
if (!value)
|
|
16877
|
+
return [];
|
|
16878
|
+
try {
|
|
16879
|
+
const parsed = JSON.parse(value);
|
|
16880
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
16881
|
+
} catch {
|
|
16882
|
+
return [];
|
|
16883
|
+
}
|
|
16884
|
+
}
|
|
16885
|
+
function getInboxMessageCount(db) {
|
|
16886
|
+
return db.db.query(`SELECT COUNT(*) as c FROM observations
|
|
16887
|
+
WHERE ${INBOX_MESSAGE_FILTER_SQL}`).get()?.c ?? 0;
|
|
16888
|
+
}
|
|
16889
|
+
function getUnreadInboxMessages(db, currentDeviceId, userId, lastReadId, limit = 20) {
|
|
16890
|
+
return db.db.query(`SELECT id, title, narrative, user_id, device_id, created_at FROM observations
|
|
16891
|
+
WHERE ${INBOX_MESSAGE_FILTER_SQL}
|
|
16892
|
+
AND id > ?
|
|
16893
|
+
AND device_id != ?
|
|
16894
|
+
AND (sensitivity != 'personal' OR user_id = ?)
|
|
16895
|
+
ORDER BY created_at_epoch DESC LIMIT ?`).all(lastReadId, currentDeviceId, userId, limit);
|
|
16896
|
+
}
|
|
16897
|
+
|
|
16830
16898
|
// src/tools/recent.ts
|
|
16831
16899
|
function getRecentActivity(db, input) {
|
|
16832
16900
|
const limit = Math.max(1, Math.min(input.limit ?? 10, 50));
|
|
@@ -16865,7 +16933,10 @@ function getRecentActivity(db, input) {
|
|
|
16865
16933
|
LEFT JOIN projects ON projects.id = observations.project_id
|
|
16866
16934
|
WHERE ${conditions.join(" AND ")}
|
|
16867
16935
|
ORDER BY observations.created_at_epoch DESC
|
|
16868
|
-
LIMIT ?`).all(...params)
|
|
16936
|
+
LIMIT ?`).all(...params).map((observation) => ({
|
|
16937
|
+
...observation,
|
|
16938
|
+
message_kind: classifyMessageObservation(observation)
|
|
16939
|
+
}));
|
|
16869
16940
|
return {
|
|
16870
16941
|
observations,
|
|
16871
16942
|
project: projectName
|
|
@@ -18359,6 +18430,7 @@ function listRecallItems(db, input) {
|
|
|
18359
18430
|
user_id: input.user_id,
|
|
18360
18431
|
limit: Math.min(limit * 2, 24)
|
|
18361
18432
|
}).observations;
|
|
18433
|
+
const sessionAgentById = new Map(sessions.filter((session) => Boolean(session.session_id)).map((session) => [session.session_id, session.agent ?? null]));
|
|
18362
18434
|
const items = [
|
|
18363
18435
|
...handoffs.map((handoff) => ({
|
|
18364
18436
|
key: `handoff:${handoff.id}`,
|
|
@@ -18368,7 +18440,8 @@ function listRecallItems(db, input) {
|
|
|
18368
18440
|
created_at_epoch: handoff.created_at_epoch,
|
|
18369
18441
|
freshness: classifyFreshness(handoff.created_at_epoch),
|
|
18370
18442
|
session_id: handoff.session_id,
|
|
18371
|
-
source_device_id: handoff.device_id ?? null
|
|
18443
|
+
source_device_id: handoff.device_id ?? null,
|
|
18444
|
+
source_agent: handoff.session_id ? sessionAgentById.get(handoff.session_id) ?? null : null
|
|
18372
18445
|
})),
|
|
18373
18446
|
...sessions.filter((session) => Boolean(session.request || session.completed || session.current_thread)).map((session) => ({
|
|
18374
18447
|
key: `session:${session.session_id}`,
|
|
@@ -18378,7 +18451,8 @@ function listRecallItems(db, input) {
|
|
|
18378
18451
|
created_at_epoch: session.completed_at_epoch ?? session.started_at_epoch ?? 0,
|
|
18379
18452
|
freshness: classifyFreshness(session.completed_at_epoch ?? session.started_at_epoch ?? null),
|
|
18380
18453
|
session_id: session.session_id,
|
|
18381
|
-
source_device_id: session.device_id ?? null
|
|
18454
|
+
source_device_id: session.device_id ?? null,
|
|
18455
|
+
source_agent: session.agent ?? null
|
|
18382
18456
|
})),
|
|
18383
18457
|
...dedupeChatIndex(chat).map((message) => {
|
|
18384
18458
|
const origin = getChatCaptureOrigin(message);
|
|
@@ -18390,7 +18464,8 @@ function listRecallItems(db, input) {
|
|
|
18390
18464
|
created_at_epoch: message.created_at_epoch,
|
|
18391
18465
|
freshness: classifyFreshness(message.created_at_epoch),
|
|
18392
18466
|
session_id: message.session_id,
|
|
18393
|
-
source_device_id: message.device_id ?? null
|
|
18467
|
+
source_device_id: message.device_id ?? null,
|
|
18468
|
+
source_agent: message.agent ?? null
|
|
18394
18469
|
};
|
|
18395
18470
|
}),
|
|
18396
18471
|
...observations.filter((obs) => obs.type !== "message").filter((obs) => !looksLikeFileOperationTitle3(obs.title)).map((obs) => ({
|
|
@@ -18401,7 +18476,8 @@ function listRecallItems(db, input) {
|
|
|
18401
18476
|
created_at_epoch: obs.created_at_epoch,
|
|
18402
18477
|
freshness: classifyFreshness(obs.created_at_epoch),
|
|
18403
18478
|
session_id: obs.session_id ?? null,
|
|
18404
|
-
source_device_id: obs.device_id ?? null
|
|
18479
|
+
source_device_id: obs.device_id ?? null,
|
|
18480
|
+
source_agent: null
|
|
18405
18481
|
}))
|
|
18406
18482
|
];
|
|
18407
18483
|
const deduped = dedupeRecallItems(items).sort((a, b) => compareRecallItems(a, b, input.current_device_id)).slice(0, limit);
|
|
@@ -18572,6 +18648,7 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18572
18648
|
}).handoffs;
|
|
18573
18649
|
const rollingHandoffDraftsCount = recentHandoffsCount.filter((handoff) => isDraftHandoff(handoff)).length;
|
|
18574
18650
|
const savedHandoffsCount = recentHandoffsCount.length - rollingHandoffDraftsCount;
|
|
18651
|
+
const recentInboxNotes = observations.filter((obs) => classifyMessageObservation(obs) === "inbox-note").slice(0, 5);
|
|
18575
18652
|
const recentChat = getRecentChat(db, {
|
|
18576
18653
|
cwd,
|
|
18577
18654
|
project_scoped: true,
|
|
@@ -18617,11 +18694,13 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18617
18694
|
key: item.key,
|
|
18618
18695
|
kind: item.kind,
|
|
18619
18696
|
freshness: item.freshness,
|
|
18620
|
-
title: item.title
|
|
18697
|
+
title: item.title,
|
|
18698
|
+
source_agent: item.source_agent
|
|
18621
18699
|
})),
|
|
18622
18700
|
best_recall_key: bestRecallItem?.key ?? null,
|
|
18623
18701
|
best_recall_title: bestRecallItem?.title ?? null,
|
|
18624
18702
|
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
18703
|
+
best_agent_resume_agent: activeAgents.length > 1 ? latestSession?.agent ?? null : null,
|
|
18625
18704
|
resume_freshness: classifyResumeFreshness(sourceTimestamp),
|
|
18626
18705
|
resume_source_session_id: latestSession?.session_id ?? null,
|
|
18627
18706
|
resume_source_device_id: latestSession?.device_id ?? null,
|
|
@@ -18634,6 +18713,8 @@ function getProjectMemoryIndex(db, input) {
|
|
|
18634
18713
|
recent_handoffs_count: recentHandoffsCount.length,
|
|
18635
18714
|
rolling_handoff_drafts_count: rollingHandoffDraftsCount,
|
|
18636
18715
|
saved_handoffs_count: savedHandoffsCount,
|
|
18716
|
+
recent_inbox_notes_count: recentInboxNotes.length,
|
|
18717
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
18637
18718
|
recent_chat_count: recentChatCount,
|
|
18638
18719
|
recent_chat_sessions: recentChat.session_count,
|
|
18639
18720
|
chat_source_summary: recentChat.source_summary,
|
|
@@ -18811,6 +18892,7 @@ function getMemoryConsole(db, input) {
|
|
|
18811
18892
|
}).handoffs;
|
|
18812
18893
|
const rollingHandoffDrafts = recentHandoffs.filter((handoff) => isDraftHandoff(handoff)).length;
|
|
18813
18894
|
const savedHandoffs = recentHandoffs.length - rollingHandoffDrafts;
|
|
18895
|
+
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
18896
|
const recentChat = getRecentChat(db, {
|
|
18815
18897
|
cwd,
|
|
18816
18898
|
project_scoped: projectScoped,
|
|
@@ -18842,11 +18924,13 @@ function getMemoryConsole(db, input) {
|
|
|
18842
18924
|
key: item.key,
|
|
18843
18925
|
kind: item.kind,
|
|
18844
18926
|
freshness: item.freshness,
|
|
18845
|
-
title: item.title
|
|
18927
|
+
title: item.title,
|
|
18928
|
+
source_agent: item.source_agent
|
|
18846
18929
|
})),
|
|
18847
18930
|
best_recall_key: projectIndex?.best_recall_key ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.key ?? null,
|
|
18848
18931
|
best_recall_title: projectIndex?.best_recall_title ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.title ?? null,
|
|
18849
18932
|
best_recall_kind: projectIndex?.best_recall_kind ?? (recallIndex.items.find((item) => item.kind !== "memory") ?? recallIndex.items[0] ?? null)?.kind ?? null,
|
|
18933
|
+
best_agent_resume_agent: projectIndex?.best_agent_resume_agent ?? (activeAgents.length > 1 ? sessions[0]?.agent ?? null : null),
|
|
18850
18934
|
resume_freshness: projectIndex?.resume_freshness ?? "stale",
|
|
18851
18935
|
resume_source_session_id: projectIndex?.resume_source_session_id ?? sessions[0]?.session_id ?? null,
|
|
18852
18936
|
resume_source_device_id: projectIndex?.resume_source_device_id ?? sessions[0]?.device_id ?? null,
|
|
@@ -18857,6 +18941,8 @@ function getMemoryConsole(db, input) {
|
|
|
18857
18941
|
recent_handoffs: recentHandoffs,
|
|
18858
18942
|
rolling_handoff_drafts: rollingHandoffDrafts,
|
|
18859
18943
|
saved_handoffs: savedHandoffs,
|
|
18944
|
+
recent_inbox_notes: recentInboxNotes,
|
|
18945
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
18860
18946
|
recent_chat: recentChat.messages,
|
|
18861
18947
|
recent_chat_sessions: projectIndex?.recent_chat_sessions ?? recentChat.session_count,
|
|
18862
18948
|
chat_source_summary: projectIndex?.chat_source_summary ?? recentChat.source_summary,
|
|
@@ -18866,6 +18952,7 @@ function getMemoryConsole(db, input) {
|
|
|
18866
18952
|
recent_outcomes: projectIndex?.recent_outcomes ?? [],
|
|
18867
18953
|
hot_files: projectIndex?.hot_files ?? [],
|
|
18868
18954
|
provenance_summary: projectIndex?.provenance_summary ?? [],
|
|
18955
|
+
provenance_type_mix: collectProvenanceTypeMix(observations),
|
|
18869
18956
|
assistant_checkpoint_count: projectIndex?.assistant_checkpoint_count,
|
|
18870
18957
|
assistant_checkpoint_types: projectIndex?.assistant_checkpoint_types ?? [],
|
|
18871
18958
|
top_types: projectIndex?.top_types ?? [],
|
|
@@ -18873,6 +18960,24 @@ function getMemoryConsole(db, input) {
|
|
|
18873
18960
|
suggested_tools: projectIndex?.suggested_tools ?? buildFallbackSuggestedTools(sessions.length, requests.length, tools.length, observations.length, recentHandoffs.length, recentChat.messages.length, recentChat.coverage_state, activeAgents.length)
|
|
18874
18961
|
};
|
|
18875
18962
|
}
|
|
18963
|
+
function collectProvenanceTypeMix(observations) {
|
|
18964
|
+
const grouped = new Map;
|
|
18965
|
+
for (const observation of observations) {
|
|
18966
|
+
if (!observation.source_tool)
|
|
18967
|
+
continue;
|
|
18968
|
+
const typeCounts = grouped.get(observation.source_tool) ?? new Map;
|
|
18969
|
+
typeCounts.set(observation.type, (typeCounts.get(observation.type) ?? 0) + 1);
|
|
18970
|
+
grouped.set(observation.source_tool, typeCounts);
|
|
18971
|
+
}
|
|
18972
|
+
return Array.from(grouped.entries()).map(([tool, typeCounts]) => {
|
|
18973
|
+
const topTypes = Array.from(typeCounts.entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count || a.type.localeCompare(b.type)).slice(0, 4);
|
|
18974
|
+
return {
|
|
18975
|
+
tool,
|
|
18976
|
+
count: topTypes.reduce((sum, item) => sum + item.count, 0),
|
|
18977
|
+
top_types: topTypes
|
|
18978
|
+
};
|
|
18979
|
+
}).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
18980
|
+
}
|
|
18876
18981
|
function buildFallbackSuggestedTools(sessionCount, requestCount, toolCount, observationCount, handoffCount, chatCount, chatCoverageState, activeAgentCount) {
|
|
18877
18982
|
const suggested = [];
|
|
18878
18983
|
if (sessionCount > 0)
|
|
@@ -19125,6 +19230,7 @@ function toChatEvent(message) {
|
|
|
19125
19230
|
};
|
|
19126
19231
|
}
|
|
19127
19232
|
function toObservationEvent(obs) {
|
|
19233
|
+
const messageKind = classifyMessageObservation(obs);
|
|
19128
19234
|
if (looksLikeHandoff(obs)) {
|
|
19129
19235
|
const handoffKind = isDraftHandoff(obs) ? "draft" : "saved";
|
|
19130
19236
|
return {
|
|
@@ -19139,6 +19245,8 @@ function toObservationEvent(obs) {
|
|
|
19139
19245
|
};
|
|
19140
19246
|
}
|
|
19141
19247
|
const detailBits = [];
|
|
19248
|
+
if (messageKind === "inbox-note")
|
|
19249
|
+
detailBits.push("inbox note");
|
|
19142
19250
|
if (obs.source_tool)
|
|
19143
19251
|
detailBits.push(`via ${obs.source_tool}`);
|
|
19144
19252
|
if (typeof obs.source_prompt_number === "number") {
|
|
@@ -19633,6 +19741,12 @@ function getAgentMemoryIndex(db, input) {
|
|
|
19633
19741
|
chatCoverage.set(row.agent, current);
|
|
19634
19742
|
}
|
|
19635
19743
|
const recentSessions = db.getRecentSessions(projectId, 200, input.user_id).filter((session) => !isInternalAgent(session.agent));
|
|
19744
|
+
const recallItems = listRecallItems(db, {
|
|
19745
|
+
cwd,
|
|
19746
|
+
project_scoped: projectScoped,
|
|
19747
|
+
user_id: input.user_id,
|
|
19748
|
+
limit: 30
|
|
19749
|
+
}).items;
|
|
19636
19750
|
const latestByAgent = new Map;
|
|
19637
19751
|
const devicesByAgent = new Map;
|
|
19638
19752
|
for (const session of recentSessions) {
|
|
@@ -19665,6 +19779,7 @@ function getAgentMemoryIndex(db, input) {
|
|
|
19665
19779
|
hook_count: 0
|
|
19666
19780
|
};
|
|
19667
19781
|
const latestSession = latestByAgent.get(agent) ?? null;
|
|
19782
|
+
const bestRecall = pickBestRecallForAgent(recallItems, agent);
|
|
19668
19783
|
return {
|
|
19669
19784
|
agent,
|
|
19670
19785
|
session_count: session.session_count,
|
|
@@ -19680,7 +19795,11 @@ function getAgentMemoryIndex(db, input) {
|
|
|
19680
19795
|
last_seen_epoch: session.last_seen_epoch,
|
|
19681
19796
|
latest_session_id: latestSession?.session_id ?? null,
|
|
19682
19797
|
latest_summary: latestSession?.current_thread ?? latestSession?.request ?? latestSession?.completed ?? null,
|
|
19683
|
-
devices: Array.from(devicesByAgent.get(agent) ?? []).sort()
|
|
19798
|
+
devices: Array.from(devicesByAgent.get(agent) ?? []).sort(),
|
|
19799
|
+
best_recall_key: bestRecall?.key ?? null,
|
|
19800
|
+
best_recall_title: bestRecall?.title ?? null,
|
|
19801
|
+
best_recall_kind: bestRecall?.kind ?? null,
|
|
19802
|
+
resume_freshness: bestRecall?.freshness ?? "stale"
|
|
19684
19803
|
};
|
|
19685
19804
|
}).sort((a, b) => {
|
|
19686
19805
|
const epochA = a.last_seen_epoch ?? 0;
|
|
@@ -19720,6 +19839,12 @@ function buildSuggestedTools2(agents) {
|
|
|
19720
19839
|
if (agents.length === 0)
|
|
19721
19840
|
return [];
|
|
19722
19841
|
const suggestions = ["recent_sessions", "capture_quality"];
|
|
19842
|
+
if (agents.length > 1) {
|
|
19843
|
+
suggestions.push("list_recall_items");
|
|
19844
|
+
}
|
|
19845
|
+
if (agents.some((agent) => agent.best_recall_key)) {
|
|
19846
|
+
suggestions.push("load_recall_item");
|
|
19847
|
+
}
|
|
19723
19848
|
if (agents.some((agent) => agent.continuity_state !== "fresh")) {
|
|
19724
19849
|
suggestions.push("resume_thread");
|
|
19725
19850
|
}
|
|
@@ -19728,9 +19853,12 @@ function buildSuggestedTools2(agents) {
|
|
|
19728
19853
|
}
|
|
19729
19854
|
return suggestions;
|
|
19730
19855
|
}
|
|
19856
|
+
function pickBestRecallForAgent(items, agent) {
|
|
19857
|
+
return items.find((item) => item.source_agent === agent) ?? null;
|
|
19858
|
+
}
|
|
19731
19859
|
|
|
19732
19860
|
// src/tools/tool-memory-index.ts
|
|
19733
|
-
function
|
|
19861
|
+
function parseConcepts2(value) {
|
|
19734
19862
|
if (!value)
|
|
19735
19863
|
return [];
|
|
19736
19864
|
try {
|
|
@@ -19799,7 +19927,7 @@ function getToolMemoryIndex(db, input = {}) {
|
|
|
19799
19927
|
ORDER BY o.created_at_epoch DESC, o.id DESC
|
|
19800
19928
|
LIMIT 50`).all(...rowParams);
|
|
19801
19929
|
const topPlugins = Array.from(observationRows.reduce((acc, obs) => {
|
|
19802
|
-
for (const concept of
|
|
19930
|
+
for (const concept of parseConcepts2(obs.concepts)) {
|
|
19803
19931
|
if (!concept.startsWith("plugin:"))
|
|
19804
19932
|
continue;
|
|
19805
19933
|
const plugin = concept.slice("plugin:".length);
|
|
@@ -19828,7 +19956,7 @@ function getToolMemoryIndex(db, input = {}) {
|
|
|
19828
19956
|
}
|
|
19829
19957
|
|
|
19830
19958
|
// src/tools/session-tool-memory.ts
|
|
19831
|
-
function
|
|
19959
|
+
function parseConcepts3(value) {
|
|
19832
19960
|
if (!value)
|
|
19833
19961
|
return [];
|
|
19834
19962
|
try {
|
|
@@ -19860,7 +19988,7 @@ function getSessionToolMemory(db, input) {
|
|
|
19860
19988
|
}, new Map).entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count || a.type.localeCompare(b.type)).slice(0, 5);
|
|
19861
19989
|
const sampleTitles = groupedObservations.map((obs) => obs.title).filter((title, index, all) => all.indexOf(title) === index).slice(0, 4);
|
|
19862
19990
|
const topPlugins = Array.from(groupedObservations.reduce((acc, obs) => {
|
|
19863
|
-
for (const concept of
|
|
19991
|
+
for (const concept of parseConcepts3(obs.concepts)) {
|
|
19864
19992
|
if (!concept.startsWith("plugin:"))
|
|
19865
19993
|
continue;
|
|
19866
19994
|
const plugin = concept.slice("plugin:".length);
|
|
@@ -19916,6 +20044,13 @@ function getSessionContext(db, input) {
|
|
|
19916
20044
|
user_id: input.user_id,
|
|
19917
20045
|
limit: 8
|
|
19918
20046
|
});
|
|
20047
|
+
const recentActivity = getRecentActivity(db, {
|
|
20048
|
+
cwd,
|
|
20049
|
+
project_scoped: true,
|
|
20050
|
+
user_id: input.user_id,
|
|
20051
|
+
limit: 12
|
|
20052
|
+
});
|
|
20053
|
+
const recentInboxNotes = recentActivity.observations.filter((obs) => obs.message_kind === "inbox-note").slice(0, 5);
|
|
19919
20054
|
const recentChatMessages = recentChat.messages.length;
|
|
19920
20055
|
const recallIndex = listRecallItems(db, {
|
|
19921
20056
|
cwd,
|
|
@@ -19946,11 +20081,13 @@ function getSessionContext(db, input) {
|
|
|
19946
20081
|
key: item.key,
|
|
19947
20082
|
kind: item.kind,
|
|
19948
20083
|
freshness: item.freshness,
|
|
19949
|
-
title: item.title
|
|
20084
|
+
title: item.title,
|
|
20085
|
+
source_agent: item.source_agent
|
|
19950
20086
|
})),
|
|
19951
20087
|
best_recall_key: bestRecallItem?.key ?? null,
|
|
19952
20088
|
best_recall_title: bestRecallItem?.title ?? null,
|
|
19953
20089
|
best_recall_kind: bestRecallItem?.kind ?? null,
|
|
20090
|
+
best_agent_resume_agent: activeAgents.length > 1 ? latestSession?.agent ?? null : null,
|
|
19954
20091
|
resume_freshness: classifyResumeFreshness(resumeTimestamp),
|
|
19955
20092
|
resume_source_session_id: latestSession?.session_id ?? null,
|
|
19956
20093
|
resume_source_device_id: latestSession?.device_id ?? null,
|
|
@@ -19964,6 +20101,8 @@ function getSessionContext(db, input) {
|
|
|
19964
20101
|
rolling_handoff_drafts: rollingHandoffDrafts,
|
|
19965
20102
|
saved_handoffs: savedHandoffs,
|
|
19966
20103
|
latest_handoff_title: latestHandoffTitle,
|
|
20104
|
+
recent_inbox_notes: recentInboxNotes.length,
|
|
20105
|
+
latest_inbox_note_title: recentInboxNotes[0]?.title ?? null,
|
|
19967
20106
|
recent_chat_messages: recentChatMessages,
|
|
19968
20107
|
recent_chat_sessions: recentChat.session_count,
|
|
19969
20108
|
chat_source_summary: recentChat.source_summary,
|
|
@@ -20053,6 +20192,7 @@ function loadRecallItem(db, input) {
|
|
|
20053
20192
|
detail: "Malformed recall key",
|
|
20054
20193
|
session_id: null,
|
|
20055
20194
|
source_device_id: null,
|
|
20195
|
+
source_agent: null,
|
|
20056
20196
|
payload: null
|
|
20057
20197
|
};
|
|
20058
20198
|
}
|
|
@@ -20074,6 +20214,7 @@ function loadRecallItem(db, input) {
|
|
|
20074
20214
|
detail: summarizeNarrative(result.handoff.narrative),
|
|
20075
20215
|
session_id: result.handoff.session_id ?? null,
|
|
20076
20216
|
source_device_id: result.handoff.device_id ?? null,
|
|
20217
|
+
source_agent: result.handoff.session_id ? lookupSessionAgent(db, result.handoff.session_id) : null,
|
|
20077
20218
|
payload: {
|
|
20078
20219
|
type: "handoff",
|
|
20079
20220
|
handoff_id: result.handoff.id,
|
|
@@ -20093,6 +20234,7 @@ function loadRecallItem(db, input) {
|
|
|
20093
20234
|
detail: story.summary?.next_steps ?? story.summary?.completed ?? null,
|
|
20094
20235
|
session_id: story.session.session_id,
|
|
20095
20236
|
source_device_id: story.session.device_id ?? null,
|
|
20237
|
+
source_agent: story.session.agent ?? null,
|
|
20096
20238
|
payload: {
|
|
20097
20239
|
type: "thread",
|
|
20098
20240
|
latest_request: story.latest_request,
|
|
@@ -20122,6 +20264,7 @@ function loadRecallItem(db, input) {
|
|
|
20122
20264
|
detail: message.content,
|
|
20123
20265
|
session_id: message.session_id,
|
|
20124
20266
|
source_device_id: message.device_id ?? null,
|
|
20267
|
+
source_agent: message.agent ?? null,
|
|
20125
20268
|
payload: {
|
|
20126
20269
|
type: "chat",
|
|
20127
20270
|
role: message.role,
|
|
@@ -20147,6 +20290,7 @@ function loadRecallItem(db, input) {
|
|
|
20147
20290
|
detail: obs.narrative ?? obs.facts ?? null,
|
|
20148
20291
|
session_id: obs.session_id ?? null,
|
|
20149
20292
|
source_device_id: obs.device_id ?? null,
|
|
20293
|
+
source_agent: obs.session_id ? lookupSessionAgent(db, obs.session_id) : obs.agent?.startsWith("engrm-") ? null : obs.agent ?? null,
|
|
20150
20294
|
payload: {
|
|
20151
20295
|
type: "memory",
|
|
20152
20296
|
observation_id: obs.id,
|
|
@@ -20163,6 +20307,10 @@ function summarizeNarrative(value) {
|
|
|
20163
20307
|
return null;
|
|
20164
20308
|
return value.split(/\n+/).map((line) => line.trim()).find(Boolean) ?? null;
|
|
20165
20309
|
}
|
|
20310
|
+
function lookupSessionAgent(db, sessionId) {
|
|
20311
|
+
const row = db.db.query("SELECT agent FROM sessions WHERE session_id = ? LIMIT 1").get(sessionId);
|
|
20312
|
+
return row?.agent ?? null;
|
|
20313
|
+
}
|
|
20166
20314
|
function missing(key, kind) {
|
|
20167
20315
|
return {
|
|
20168
20316
|
kind,
|
|
@@ -20171,6 +20319,7 @@ function missing(key, kind) {
|
|
|
20171
20319
|
detail: "Recall item not found",
|
|
20172
20320
|
session_id: null,
|
|
20173
20321
|
source_device_id: null,
|
|
20322
|
+
source_agent: null,
|
|
20174
20323
|
payload: null
|
|
20175
20324
|
};
|
|
20176
20325
|
}
|
|
@@ -20561,7 +20710,12 @@ async function repairRecall(db, config2, input = {}) {
|
|
|
20561
20710
|
let sessionsWithImports = 0;
|
|
20562
20711
|
for (const session of targetSessions) {
|
|
20563
20712
|
const sessionCwd = session.project_id !== null ? db.getProjectById(session.project_id)?.local_path ?? cwd : cwd;
|
|
20564
|
-
|
|
20713
|
+
let syncResult = { imported: 0, total: 0 };
|
|
20714
|
+
try {
|
|
20715
|
+
syncResult = await syncTranscriptChat(db, config2, session.session_id, sessionCwd, input.transcript_path);
|
|
20716
|
+
} catch {
|
|
20717
|
+
syncResult = { imported: 0, total: 0 };
|
|
20718
|
+
}
|
|
20565
20719
|
const chatMessages = db.getSessionChatMessages(session.session_id, 200);
|
|
20566
20720
|
const prompts = db.getSessionUserPrompts(session.session_id, 200);
|
|
20567
20721
|
const sourceSummary = summarizeChatSources(chatMessages);
|
|
@@ -20597,6 +20751,9 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20597
20751
|
const detected = detectProject(cwd);
|
|
20598
20752
|
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
20599
20753
|
let snapshot = await buildResumeSnapshot(db, cwd, input.user_id, input.current_device_id, limit);
|
|
20754
|
+
if (input.agent) {
|
|
20755
|
+
snapshot = filterResumeSnapshotByAgent(snapshot, input.agent, input.current_device_id);
|
|
20756
|
+
}
|
|
20600
20757
|
let repairResult = null;
|
|
20601
20758
|
const shouldRepair = repairIfNeeded && snapshot.recentChat.coverage_state !== "transcript-backed" && (snapshot.recentChat.messages.length > 0 || snapshot.recentSessions.length > 0 || snapshot.context?.continuity_state !== "cold");
|
|
20602
20759
|
if (shouldRepair) {
|
|
@@ -20607,6 +20764,9 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20607
20764
|
});
|
|
20608
20765
|
if (repairResult.imported_chat_messages > 0) {
|
|
20609
20766
|
snapshot = await buildResumeSnapshot(db, cwd, input.user_id, input.current_device_id, limit);
|
|
20767
|
+
if (input.agent) {
|
|
20768
|
+
snapshot = filterResumeSnapshotByAgent(snapshot, input.agent, input.current_device_id);
|
|
20769
|
+
}
|
|
20610
20770
|
}
|
|
20611
20771
|
}
|
|
20612
20772
|
const { context, handoff, recentChat, recentSessions, recall } = snapshot;
|
|
@@ -20647,6 +20807,7 @@ async function resumeThread(db, config2, input = {}) {
|
|
|
20647
20807
|
])).slice(0, 4);
|
|
20648
20808
|
return {
|
|
20649
20809
|
project_name: project?.name ?? context?.project_name ?? null,
|
|
20810
|
+
target_agent: input.agent ?? null,
|
|
20650
20811
|
continuity_state: context?.continuity_state ?? "cold",
|
|
20651
20812
|
continuity_summary: context?.continuity_summary ?? "No fresh repo-local continuity yet; older memory should be treated cautiously.",
|
|
20652
20813
|
resume_freshness: classifyResumeFreshness2(sourceTimestamp),
|
|
@@ -20696,6 +20857,13 @@ async function buildResumeSnapshot(db, cwd, userId, currentDeviceId, limit) {
|
|
|
20696
20857
|
user_id: userId,
|
|
20697
20858
|
current_device_id: currentDeviceId
|
|
20698
20859
|
});
|
|
20860
|
+
const recentHandoffs = getRecentHandoffs(db, {
|
|
20861
|
+
cwd,
|
|
20862
|
+
project_scoped: true,
|
|
20863
|
+
user_id: userId,
|
|
20864
|
+
current_device_id: currentDeviceId,
|
|
20865
|
+
limit: Math.max(limit, 4)
|
|
20866
|
+
}).handoffs;
|
|
20699
20867
|
const recentChat = getRecentChat(db, {
|
|
20700
20868
|
cwd,
|
|
20701
20869
|
project_scoped: true,
|
|
@@ -20725,15 +20893,50 @@ async function buildResumeSnapshot(db, cwd, userId, currentDeviceId, limit) {
|
|
|
20725
20893
|
return {
|
|
20726
20894
|
context,
|
|
20727
20895
|
handoff: handoffResult.handoff,
|
|
20896
|
+
recentHandoffs,
|
|
20728
20897
|
recentChat,
|
|
20729
20898
|
recentSessions,
|
|
20730
20899
|
recall,
|
|
20731
20900
|
recallIndex
|
|
20732
20901
|
};
|
|
20733
20902
|
}
|
|
20903
|
+
function filterResumeSnapshotByAgent(snapshot, agent, currentDeviceId) {
|
|
20904
|
+
const recentSessions = snapshot.recentSessions.filter((session) => session.agent === agent);
|
|
20905
|
+
const sessionIds = new Set(recentSessions.map((session) => session.session_id));
|
|
20906
|
+
const recentChatMessages = snapshot.recentChat.messages.filter((message) => message.agent === agent);
|
|
20907
|
+
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;
|
|
20908
|
+
const recallIndex = {
|
|
20909
|
+
...snapshot.recallIndex,
|
|
20910
|
+
items: snapshot.recallIndex.items.filter((item) => item.source_agent === agent)
|
|
20911
|
+
};
|
|
20912
|
+
const recall = {
|
|
20913
|
+
...snapshot.recall,
|
|
20914
|
+
results: snapshot.recall.results.filter((entry) => entry.session_id ? sessionIds.has(entry.session_id) : false)
|
|
20915
|
+
};
|
|
20916
|
+
return {
|
|
20917
|
+
...snapshot,
|
|
20918
|
+
handoff,
|
|
20919
|
+
recentHandoffs: snapshot.recentHandoffs.filter((item) => item.session_id && sessionIds.has(item.session_id)),
|
|
20920
|
+
recentSessions,
|
|
20921
|
+
recentChat: {
|
|
20922
|
+
...snapshot.recentChat,
|
|
20923
|
+
messages: recentChatMessages,
|
|
20924
|
+
session_count: new Set(recentChatMessages.map((message) => message.session_id)).size
|
|
20925
|
+
},
|
|
20926
|
+
recall,
|
|
20927
|
+
recallIndex
|
|
20928
|
+
};
|
|
20929
|
+
}
|
|
20734
20930
|
function pickBestRecallItem2(items) {
|
|
20735
20931
|
return items.find((item) => item.kind !== "memory") ?? items[0] ?? null;
|
|
20736
20932
|
}
|
|
20933
|
+
function compareRecallCandidates(epochA, epochB, deviceA, deviceB, currentDeviceId) {
|
|
20934
|
+
const remoteBoostA = currentDeviceId && deviceA && deviceA !== currentDeviceId ? 1 : 0;
|
|
20935
|
+
const remoteBoostB = currentDeviceId && deviceB && deviceB !== currentDeviceId ? 1 : 0;
|
|
20936
|
+
if (remoteBoostA !== remoteBoostB)
|
|
20937
|
+
return remoteBoostB - remoteBoostA;
|
|
20938
|
+
return epochB - epochA;
|
|
20939
|
+
}
|
|
20737
20940
|
function extractCurrentThread(handoff) {
|
|
20738
20941
|
const narrative = handoff?.narrative ?? "";
|
|
20739
20942
|
const match = narrative.match(/Current thread:\s*(.+)/i);
|
|
@@ -20976,8 +21179,9 @@ function hasContent(value) {
|
|
|
20976
21179
|
// src/tools/stats.ts
|
|
20977
21180
|
function getMemoryStats(db) {
|
|
20978
21181
|
const activeObservations = db.getActiveObservationCount();
|
|
20979
|
-
const
|
|
20980
|
-
WHERE
|
|
21182
|
+
const handoffs = db.db.query(`SELECT COUNT(*) as count FROM observations
|
|
21183
|
+
WHERE ${getHandoffMessageFilterSql({ include_aging: true })}`).get()?.count ?? 0;
|
|
21184
|
+
const inboxMessages = getInboxMessageCount(db);
|
|
20981
21185
|
const userPrompts = db.db.query("SELECT COUNT(*) as count FROM user_prompts").get()?.count ?? 0;
|
|
20982
21186
|
const toolEvents = db.db.query("SELECT COUNT(*) as count FROM tool_events").get()?.count ?? 0;
|
|
20983
21187
|
const sessionSummaries = db.db.query("SELECT COUNT(*) as count FROM session_summaries").get()?.count ?? 0;
|
|
@@ -20991,7 +21195,9 @@ function getMemoryStats(db) {
|
|
|
20991
21195
|
active_observations: activeObservations,
|
|
20992
21196
|
user_prompts: userPrompts,
|
|
20993
21197
|
tool_events: toolEvents,
|
|
20994
|
-
messages,
|
|
21198
|
+
messages: inboxMessages,
|
|
21199
|
+
inbox_messages: inboxMessages,
|
|
21200
|
+
handoffs,
|
|
20995
21201
|
session_summaries: sessionSummaries,
|
|
20996
21202
|
decisions: signals.decisions_count,
|
|
20997
21203
|
lessons: signals.lessons_count,
|
|
@@ -22665,7 +22871,7 @@ process.on("SIGTERM", () => {
|
|
|
22665
22871
|
});
|
|
22666
22872
|
var server = new McpServer({
|
|
22667
22873
|
name: "engrm",
|
|
22668
|
-
version: "0.4.
|
|
22874
|
+
version: "0.4.36"
|
|
22669
22875
|
});
|
|
22670
22876
|
server.tool("save_observation", "Save an observation to memory", {
|
|
22671
22877
|
type: exports_external.enum([
|
|
@@ -23135,7 +23341,7 @@ server.tool("list_recall_items", "USE FIRST when continuity feels fuzzy. List th
|
|
|
23135
23341
|
}
|
|
23136
23342
|
const projectLine = result.project ? `Project: ${result.project}
|
|
23137
23343
|
` : "";
|
|
23138
|
-
const rows = result.items.map((item) => `- ${item.key} [${item.kind} · ${item.freshness}] ${item.title}${item.source_device_id ? ` (${item.source_device_id})` : ""}
|
|
23344
|
+
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
23345
|
${item.detail}`).join(`
|
|
23140
23346
|
`);
|
|
23141
23347
|
return {
|
|
@@ -23180,6 +23386,7 @@ server.tool("load_recall_item", "USE AFTER list_recall_items. Load one exact rec
|
|
|
23180
23386
|
` + `Title: ${result.title}
|
|
23181
23387
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23182
23388
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23389
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23183
23390
|
|
|
23184
23391
|
` + `${result.payload.narrative ?? "(no narrative)"}`
|
|
23185
23392
|
}
|
|
@@ -23199,6 +23406,7 @@ server.tool("load_recall_item", "USE AFTER list_recall_items. Load one exact rec
|
|
|
23199
23406
|
` + `Title: ${result.title}
|
|
23200
23407
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23201
23408
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23409
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23202
23410
|
` + `Latest request: ${result.payload.latest_request ?? "(none)"}
|
|
23203
23411
|
` + `Current thread: ${result.payload.current_thread ?? "(none)"}
|
|
23204
23412
|
|
|
@@ -23220,6 +23428,7 @@ ${hotFiles}`
|
|
|
23220
23428
|
` + `Title: ${result.title}
|
|
23221
23429
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23222
23430
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23431
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23223
23432
|
|
|
23224
23433
|
` + `${result.payload.content}`
|
|
23225
23434
|
}
|
|
@@ -23234,6 +23443,7 @@ ${hotFiles}`
|
|
|
23234
23443
|
` + `Title: ${result.title}
|
|
23235
23444
|
` + `Session: ${result.session_id ?? "(unknown)"}
|
|
23236
23445
|
` + `Source: ${result.source_device_id ?? "(unknown)"}
|
|
23446
|
+
` + `Agent: ${result.source_agent ?? "(unknown)"}
|
|
23237
23447
|
` + `Type: ${result.payload.observation_type}
|
|
23238
23448
|
|
|
23239
23449
|
` + `${result.payload.narrative ?? result.payload.facts ?? "(no detail)"}`
|
|
@@ -23244,12 +23454,14 @@ ${hotFiles}`
|
|
|
23244
23454
|
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
23455
|
cwd: exports_external.string().optional().describe("Optional cwd override for the project to resume"),
|
|
23246
23456
|
limit: exports_external.number().optional().describe("Max recall hits/chat snippets to include"),
|
|
23457
|
+
agent: exports_external.string().optional().describe("Optional agent to resume specifically, such as claude-code or codex-cli"),
|
|
23247
23458
|
user_id: exports_external.string().optional().describe("Optional user override"),
|
|
23248
23459
|
repair_if_needed: exports_external.boolean().optional().describe("If true, attempt recall repair before resuming when continuity is still weak")
|
|
23249
23460
|
}, async (params) => {
|
|
23250
23461
|
const result = await resumeThread(db, config2, {
|
|
23251
23462
|
cwd: params.cwd ?? process.cwd(),
|
|
23252
23463
|
limit: params.limit,
|
|
23464
|
+
agent: params.agent,
|
|
23253
23465
|
user_id: params.user_id ?? config2.user_id,
|
|
23254
23466
|
current_device_id: config2.device_id,
|
|
23255
23467
|
repair_if_needed: params.repair_if_needed
|
|
@@ -23291,7 +23503,8 @@ server.tool("resume_thread", "USE FIRST when you want one direct 'where were we?
|
|
|
23291
23503
|
content: [
|
|
23292
23504
|
{
|
|
23293
23505
|
type: "text",
|
|
23294
|
-
text: `${projectLine}` +
|
|
23506
|
+
text: `${projectLine}` + `${result.target_agent ? `Target agent: ${result.target_agent}
|
|
23507
|
+
` : ""}` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23295
23508
|
` + `Freshness: ${result.resume_freshness}
|
|
23296
23509
|
` + `Source: ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23297
23510
|
` + `Resume confidence: ${result.resume_confidence}
|
|
@@ -23450,13 +23663,7 @@ server.tool("check_messages", "Check for messages sent from other devices or ses
|
|
|
23450
23663
|
const markRead = params.mark_read !== false;
|
|
23451
23664
|
const readKey = `messages_read_${config2.device_id}`;
|
|
23452
23665
|
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);
|
|
23666
|
+
const messages = getUnreadInboxMessages(db, config2.device_id, config2.user_id, lastReadId, 20);
|
|
23460
23667
|
if (messages.length === 0) {
|
|
23461
23668
|
return {
|
|
23462
23669
|
content: [{ type: "text", text: "No new messages." }]
|
|
@@ -23503,7 +23710,7 @@ server.tool("send_message", "Leave a cross-device or team note in Engrm's shared
|
|
|
23503
23710
|
]
|
|
23504
23711
|
};
|
|
23505
23712
|
});
|
|
23506
|
-
server.tool("recent_activity", "Inspect the most recent observations captured by Engrm", {
|
|
23713
|
+
server.tool("recent_activity", "Inspect the most recent observations, notes, and handoffs captured by Engrm", {
|
|
23507
23714
|
limit: exports_external.number().optional().describe("Max observations to return (default: 10)"),
|
|
23508
23715
|
project_scoped: exports_external.boolean().optional().describe("Scope to current project (default: true)"),
|
|
23509
23716
|
type: exports_external.string().optional().describe("Optional observation type filter")
|
|
@@ -23526,12 +23733,21 @@ server.tool("recent_activity", "Inspect the most recent observations captured by
|
|
|
23526
23733
|
const showProject = !result.project;
|
|
23527
23734
|
const header = showProject ? "| ID | Project | Type | Title | Created |" : "| ID | Type | Title | Created |";
|
|
23528
23735
|
const separator = showProject ? "|---|---|---|---|---|" : "|---|---|---|---|";
|
|
23736
|
+
const displayType = (obs) => {
|
|
23737
|
+
if (obs.message_kind === "draft-handoff")
|
|
23738
|
+
return "handoff:draft";
|
|
23739
|
+
if (obs.message_kind === "handoff")
|
|
23740
|
+
return "handoff";
|
|
23741
|
+
if (obs.message_kind === "inbox-note")
|
|
23742
|
+
return "note";
|
|
23743
|
+
return obs.type;
|
|
23744
|
+
};
|
|
23529
23745
|
const rows = result.observations.map((obs) => {
|
|
23530
23746
|
const date5 = obs.created_at.split("T")[0];
|
|
23531
23747
|
if (showProject) {
|
|
23532
|
-
return `| ${obs.id} | ${obs.project_name ?? "(unknown)"} | ${obs
|
|
23748
|
+
return `| ${obs.id} | ${obs.project_name ?? "(unknown)"} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23533
23749
|
}
|
|
23534
|
-
return `| ${obs.id} | ${obs
|
|
23750
|
+
return `| ${obs.id} | ${displayType(obs)} | ${obs.title} | ${date5} |`;
|
|
23535
23751
|
});
|
|
23536
23752
|
const projectLine = result.project ? `Project: ${result.project}
|
|
23537
23753
|
` : "";
|
|
@@ -23567,7 +23783,8 @@ server.tool("memory_stats", "Show high-level Engrm capture and sync statistics",
|
|
|
23567
23783
|
text: `Active observations: ${stats.active_observations}
|
|
23568
23784
|
` + `User prompts: ${stats.user_prompts}
|
|
23569
23785
|
` + `Tool events: ${stats.tool_events}
|
|
23570
|
-
` + `
|
|
23786
|
+
` + `Inbox notes: ${stats.inbox_messages}
|
|
23787
|
+
` + `Handoffs: ${stats.handoffs}
|
|
23571
23788
|
` + `Session summaries: ${stats.session_summaries}
|
|
23572
23789
|
` + `Summary coverage: learned ${stats.summaries_with_learned}, completed ${stats.summaries_with_completed}, next steps ${stats.summaries_with_next_steps}
|
|
23573
23790
|
` + `Installed packs: ${packs}
|
|
@@ -23611,6 +23828,8 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23611
23828
|
}).join(`
|
|
23612
23829
|
`) : "- (none)";
|
|
23613
23830
|
const handoffLines = result.recent_handoffs.length > 0 ? result.recent_handoffs.map((obs) => `- #${obs.id} ${obs.title}`).join(`
|
|
23831
|
+
`) : "- (none)";
|
|
23832
|
+
const inboxNoteLines = result.recent_inbox_notes.length > 0 ? result.recent_inbox_notes.map((obs) => `- #${obs.id} ${obs.title}`).join(`
|
|
23614
23833
|
`) : "- (none)";
|
|
23615
23834
|
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
23835
|
`) : "- (none)";
|
|
@@ -23625,13 +23844,13 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23625
23844
|
`) : "- (none)";
|
|
23626
23845
|
const provenanceLines = result.provenance_summary.length > 0 ? result.provenance_summary.map((item) => `- ${item.tool}: ${item.count}`).join(`
|
|
23627
23846
|
`) : "- (none)";
|
|
23628
|
-
const
|
|
23847
|
+
const provenanceMixLines = result.provenance_type_mix.length > 0 ? result.provenance_type_mix.map((item) => `- ${item.tool}: ${item.top_types.map((entry) => `${entry.type} ${entry.count}`).join(", ")}`).join(`
|
|
23629
23848
|
`) : "- (none)";
|
|
23630
23849
|
const checkpointTypeLines = result.assistant_checkpoint_types.length > 0 ? result.assistant_checkpoint_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
23631
23850
|
`) : "- (none)";
|
|
23632
23851
|
const topTypes = result.top_types.length > 0 ? result.top_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
23633
23852
|
`) : "- (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(`
|
|
23853
|
+
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
23854
|
`) : "- (none)";
|
|
23636
23855
|
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23637
23856
|
` : "";
|
|
@@ -23654,10 +23873,11 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
|
|
|
23654
23873
|
` + `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
23874
|
` + `${typeof result.assistant_checkpoint_count === "number" ? `Assistant checkpoints: ${result.assistant_checkpoint_count}
|
|
23656
23875
|
` : ""}` + `Handoffs: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
|
|
23876
|
+
` + `Inbox notes: ${result.recent_inbox_notes.length}${result.latest_inbox_note_title ? ` · latest "${result.latest_inbox_note_title}"` : ""}
|
|
23657
23877
|
` + `${typeof result.estimated_read_tokens === "number" ? `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
23658
23878
|
` : ""}` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
23659
23879
|
|
|
23660
|
-
` + openExactLine + `Recall preview:
|
|
23880
|
+
` + openExactLine + resumeAgentLine + `Recall preview:
|
|
23661
23881
|
${recallPreviewLines}
|
|
23662
23882
|
|
|
23663
23883
|
` + `Next actions:
|
|
@@ -23679,6 +23899,9 @@ ${sessionLines}
|
|
|
23679
23899
|
` + `Recent handoffs:
|
|
23680
23900
|
${handoffLines}
|
|
23681
23901
|
|
|
23902
|
+
` + `Recent inbox notes:
|
|
23903
|
+
${inboxNoteLines}
|
|
23904
|
+
|
|
23682
23905
|
` + `Recent requests:
|
|
23683
23906
|
${requestLines}
|
|
23684
23907
|
|
|
@@ -23741,6 +23964,8 @@ server.tool("capture_quality", "Show how healthy Engrm capture is across the wor
|
|
|
23741
23964
|
const provenanceLines = result.provenance_summary.length > 0 ? result.provenance_summary.map((item) => `- ${item.tool}: ${item.count}`).join(`
|
|
23742
23965
|
`) : "- (none)";
|
|
23743
23966
|
const checkpointTypeLines = result.assistant_checkpoint_types.length > 0 ? result.assistant_checkpoint_types.map((item) => `- ${item.type}: ${item.count}`).join(`
|
|
23967
|
+
`) : "- (none)";
|
|
23968
|
+
const provenanceMixLines = result.provenance_type_mix.length > 0 ? result.provenance_type_mix.map((item) => `- ${item.tool}: ${item.top_types.map((entry) => `${entry.type} ${entry.count}`).join(", ")}`).join(`
|
|
23744
23969
|
`) : "- (none)";
|
|
23745
23970
|
const projectLines = result.top_projects.length > 0 ? result.top_projects.map((project) => `- ${project.name} [${project.raw_capture_state}] obs=${project.observation_count} sessions=${project.session_count} prompts=${project.prompt_count} tools=${project.tool_event_count} checkpoints=${project.assistant_checkpoint_count} chat=${project.chat_message_count} (${project.chat_coverage_state})`).join(`
|
|
23746
23971
|
`) : "- (none)";
|
|
@@ -23785,7 +24010,8 @@ server.tool("agent_memory_index", "Compare continuity and capture health across
|
|
|
23785
24010
|
const lastSeen = agent.last_seen_epoch ? new Date(agent.last_seen_epoch * 1000).toISOString().replace("T", " ").slice(0, 16) : "unknown";
|
|
23786
24011
|
const latest = agent.latest_summary ? ` latest="${agent.latest_summary.replace(/\s+/g, " ").trim().slice(0, 120)}"` : "";
|
|
23787
24012
|
const devices = agent.devices.length > 0 ? ` devices=[${agent.devices.join(", ")}]` : "";
|
|
23788
|
-
|
|
24013
|
+
const exact = agent.best_recall_key ? ` open=load_recall_item("${agent.best_recall_key}")` : "";
|
|
24014
|
+
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
24015
|
}).join(`
|
|
23790
24016
|
`) : "- (none)";
|
|
23791
24017
|
return {
|
|
@@ -23891,7 +24117,8 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
23891
24117
|
` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
|
|
23892
24118
|
` + `Recall index: ${result.recall_mode} · ${result.recall_items_ready} items ready
|
|
23893
24119
|
` + `Open exact: ${result.best_recall_key ? `load_recall_item("${result.best_recall_key}")` : "(none)"}
|
|
23894
|
-
` +
|
|
24120
|
+
` + `${result.best_agent_resume_agent ? `Resume agent: resume_thread(agent="${result.best_agent_resume_agent}")
|
|
24121
|
+
` : ""}` + `Resume readiness: ${result.resume_freshness} · ${result.resume_source_session_id ?? "(unknown session)"}${result.resume_source_device_id ? ` (${result.resume_source_device_id})` : ""}
|
|
23895
24122
|
` + `Loaded observations: ${result.session_count}
|
|
23896
24123
|
` + `Searchable total: ${result.total_active}
|
|
23897
24124
|
` + `Recent requests: ${result.recent_requests}
|
|
@@ -23899,6 +24126,7 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
|
|
|
23899
24126
|
` + `Recent sessions: ${result.recent_sessions}
|
|
23900
24127
|
` + `Recent handoffs: ${result.recent_handoffs}
|
|
23901
24128
|
` + `Handoff split: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
|
|
24129
|
+
` + `Recent inbox notes: ${result.recent_inbox_notes}${result.latest_inbox_note_title ? ` · latest "${result.latest_inbox_note_title}"` : ""}
|
|
23902
24130
|
` + `Recent chat messages: ${result.recent_chat_messages}
|
|
23903
24131
|
` + `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
24132
|
` + `Latest handoff: ${result.latest_handoff_title ?? "(none)"}
|
|
@@ -23969,7 +24197,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23969
24197
|
`) : "- (none)";
|
|
23970
24198
|
const topTitles = result.top_titles.length > 0 ? result.top_titles.map((item) => `- #${item.id} [${item.type}] ${item.title}`).join(`
|
|
23971
24199
|
`) : "- (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(`
|
|
24200
|
+
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
24201
|
`) : "- (none)";
|
|
23974
24202
|
const openExactLine = result.best_recall_key ? `Open exact: load_recall_item("${result.best_recall_key}")${result.best_recall_title ? ` # ${result.best_recall_title}` : ""}
|
|
23975
24203
|
` : "";
|
|
@@ -23988,6 +24216,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23988
24216
|
|
|
23989
24217
|
` + `Recent handoffs captured: ${result.recent_handoffs_count}
|
|
23990
24218
|
` + `Handoff split: ${result.saved_handoffs_count} saved, ${result.rolling_handoff_drafts_count} rolling drafts
|
|
24219
|
+
` + `Recent inbox notes: ${result.recent_inbox_notes_count}${result.latest_inbox_note_title ? ` · latest "${result.latest_inbox_note_title}"` : ""}
|
|
23991
24220
|
` + `Recent chat messages captured: ${result.recent_chat_count}
|
|
23992
24221
|
` + `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
24222
|
|
|
@@ -23997,7 +24226,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
|
|
|
23997
24226
|
` + `Estimated read cost: ~${result.estimated_read_tokens}t
|
|
23998
24227
|
` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
|
|
23999
24228
|
|
|
24000
|
-
` + openExactLine + `Recall preview:
|
|
24229
|
+
` + openExactLine + resumeAgentLine + `Recall preview:
|
|
24001
24230
|
${recallPreviewLines}
|
|
24002
24231
|
|
|
24003
24232
|
` + `Next actions:
|
package/package.json
CHANGED