engrm 0.4.15 → 0.4.17
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 +34 -0
- package/dist/hooks/session-start.js +87 -5
- package/dist/hooks/stop.js +66 -30
- package/dist/server.js +29 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -228,6 +228,28 @@ The MCP server exposes tools that supported agents can call directly:
|
|
|
228
228
|
| `capture_repo_scan` | Run a lightweight repo scan and save reduced findings as memory |
|
|
229
229
|
| `capture_openclaw_content` | Save OpenClaw content, research, and follow-up work as plugin memory |
|
|
230
230
|
|
|
231
|
+
### Public MCP Starter Set
|
|
232
|
+
|
|
233
|
+
If you are evaluating Engrm as an MCP server, start with this small set first:
|
|
234
|
+
|
|
235
|
+
- `capture_git_worktree`
|
|
236
|
+
- save a meaningful local git diff before it disappears into commit history
|
|
237
|
+
- `capture_repo_scan`
|
|
238
|
+
- capture a quick architecture, implementation, or risk scan as reusable memory
|
|
239
|
+
- `capture_openclaw_content`
|
|
240
|
+
- save posted content, research, outcomes, and next actions from OpenClaw work
|
|
241
|
+
- `tool_memory_index`
|
|
242
|
+
- verify which tools are actually producing durable memory and which plugins they exercise
|
|
243
|
+
- `capture_quality`
|
|
244
|
+
- check whether raw chronology is healthy across the workspace before judging memory quality
|
|
245
|
+
|
|
246
|
+
These are the tools we should be comfortable pointing people to publicly first:
|
|
247
|
+
|
|
248
|
+
- thin input surface
|
|
249
|
+
- local-first execution
|
|
250
|
+
- durable memory output instead of raw transcript dumping
|
|
251
|
+
- easy local inspection after capture
|
|
252
|
+
|
|
231
253
|
### Thin Tools, Thick Memory
|
|
232
254
|
|
|
233
255
|
Engrm now has a real thin-tool layer, not just a plugin spec.
|
|
@@ -301,6 +323,18 @@ That lets you:
|
|
|
301
323
|
- capture the current repo state with a thin tool
|
|
302
324
|
- verify whether that tool produced reusable memory
|
|
303
325
|
|
|
326
|
+
### MCP Examples
|
|
327
|
+
|
|
328
|
+
These are the kinds of prompts Engrm's current MCP slice is designed for:
|
|
329
|
+
|
|
330
|
+
- "Capture this current git worktree as memory before I switch tasks."
|
|
331
|
+
- "Run a lightweight repo scan focused on auth and validation."
|
|
332
|
+
- "Show which tools are actually creating durable memory in this repo."
|
|
333
|
+
- "Tell me whether raw prompt/tool capture is healthy on this machine."
|
|
334
|
+
- "Save this OpenClaw research/posting run as reusable memory."
|
|
335
|
+
|
|
336
|
+
For concrete example flows and reducer outputs, see [MCP_EXAMPLES.md](/Volumes/Data/devs/candengo-mem/MCP_EXAMPLES.md).
|
|
337
|
+
|
|
304
338
|
### Observation Types
|
|
305
339
|
|
|
306
340
|
| Type | What it captures |
|
|
@@ -1154,7 +1154,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
|
|
|
1154
1154
|
import { join as join3 } from "node:path";
|
|
1155
1155
|
import { homedir } from "node:os";
|
|
1156
1156
|
var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
|
|
1157
|
-
var CLIENT_VERSION = "0.4.
|
|
1157
|
+
var CLIENT_VERSION = "0.4.17";
|
|
1158
1158
|
function hashFile(filePath) {
|
|
1159
1159
|
try {
|
|
1160
1160
|
if (!existsSync3(filePath))
|
|
@@ -3133,11 +3133,13 @@ function formatSplashScreen(data) {
|
|
|
3133
3133
|
lines.push("");
|
|
3134
3134
|
lines.push(` ${c2.dim}Dashboard: https://engrm.dev/dashboard${c2.reset}`);
|
|
3135
3135
|
const brief = formatVisibleStartupBrief(data.context);
|
|
3136
|
+
const handoffShownItems = new Set;
|
|
3136
3137
|
if (brief.length > 0) {
|
|
3137
3138
|
lines.push("");
|
|
3138
3139
|
lines.push(` ${c2.bold}Handoff${c2.reset}`);
|
|
3139
3140
|
for (const line of brief) {
|
|
3140
3141
|
lines.push(` ${line}`);
|
|
3142
|
+
rememberShownItem(handoffShownItems, line);
|
|
3141
3143
|
}
|
|
3142
3144
|
}
|
|
3143
3145
|
const economics = formatContextEconomics(data);
|
|
@@ -3154,7 +3156,7 @@ function formatSplashScreen(data) {
|
|
|
3154
3156
|
lines.push(` ${line}`);
|
|
3155
3157
|
}
|
|
3156
3158
|
}
|
|
3157
|
-
const contextIndex = formatContextIndex(data.context);
|
|
3159
|
+
const contextIndex = formatContextIndex(data.context, handoffShownItems);
|
|
3158
3160
|
if (contextIndex.length > 0) {
|
|
3159
3161
|
lines.push("");
|
|
3160
3162
|
for (const line of contextIndex) {
|
|
@@ -3297,8 +3299,8 @@ function formatLegend() {
|
|
|
3297
3299
|
`${c2.dim}Legend:${c2.reset} #id | \u25A0 bugfix | \u25B2 feature | \u2248 refactor | \u25CF change | \u25A1 discovery | \u25C7 decision`
|
|
3298
3300
|
];
|
|
3299
3301
|
}
|
|
3300
|
-
function formatContextIndex(context) {
|
|
3301
|
-
const rows = context
|
|
3302
|
+
function formatContextIndex(context, shownItems) {
|
|
3303
|
+
const rows = pickContextIndexObservations(context, shownItems).map((obs) => {
|
|
3302
3304
|
const icon = observationIcon(obs.type);
|
|
3303
3305
|
const fileHint = extractPrimaryFileHint(obs);
|
|
3304
3306
|
return `${icon} #${obs.id} ${truncateInline(obs.title, 110)}${fileHint ? ` ${c2.dim}(${fileHint})${c2.reset}` : ""}`;
|
|
@@ -3496,6 +3498,69 @@ function extractPrimaryFileHint(obs) {
|
|
|
3496
3498
|
const firstModified = parseJsonArraySafe(obs.files_modified)[0];
|
|
3497
3499
|
return firstModified ?? firstRead ?? null;
|
|
3498
3500
|
}
|
|
3501
|
+
function pickContextIndexObservations(context, shownItems) {
|
|
3502
|
+
const now = Date.now();
|
|
3503
|
+
const hidden = shownItems ?? new Set;
|
|
3504
|
+
const picked = [];
|
|
3505
|
+
const scoreObservation = (obs) => {
|
|
3506
|
+
let score = 0;
|
|
3507
|
+
const ageMs = Math.max(0, now - new Date(obs.created_at).getTime());
|
|
3508
|
+
const ageDays = ageMs / 86400000;
|
|
3509
|
+
score += Math.max(0, 30 - ageDays) * 0.2;
|
|
3510
|
+
score += obs.quality * 2;
|
|
3511
|
+
switch (obs.type) {
|
|
3512
|
+
case "bugfix":
|
|
3513
|
+
score += 2.4;
|
|
3514
|
+
break;
|
|
3515
|
+
case "feature":
|
|
3516
|
+
score += 2.2;
|
|
3517
|
+
break;
|
|
3518
|
+
case "change":
|
|
3519
|
+
score += 1.6;
|
|
3520
|
+
break;
|
|
3521
|
+
case "discovery":
|
|
3522
|
+
score += 1.4;
|
|
3523
|
+
break;
|
|
3524
|
+
case "refactor":
|
|
3525
|
+
score += 1.2;
|
|
3526
|
+
break;
|
|
3527
|
+
case "decision":
|
|
3528
|
+
if (ageDays <= 7)
|
|
3529
|
+
score += 1.1;
|
|
3530
|
+
else if (ageDays <= 21)
|
|
3531
|
+
score += 0.2;
|
|
3532
|
+
else if (ageDays <= 45)
|
|
3533
|
+
score -= 1.2;
|
|
3534
|
+
else
|
|
3535
|
+
score -= 2.8;
|
|
3536
|
+
break;
|
|
3537
|
+
default:
|
|
3538
|
+
score += 0.4;
|
|
3539
|
+
break;
|
|
3540
|
+
}
|
|
3541
|
+
if (extractPrimaryFileHint(obs))
|
|
3542
|
+
score += 0.4;
|
|
3543
|
+
if (context.recentOutcomes?.some((item) => titlesRoughlyMatch(item, obs.title)))
|
|
3544
|
+
score += 2.5;
|
|
3545
|
+
return score;
|
|
3546
|
+
};
|
|
3547
|
+
for (const obs of context.observations.filter((obs2) => obs2.type !== "digest").filter((obs2) => {
|
|
3548
|
+
const normalized = normalizeStartupItem(obs2.title);
|
|
3549
|
+
return normalized && !hidden.has(normalized);
|
|
3550
|
+
}).sort((a, b) => {
|
|
3551
|
+
const scoreDiff = scoreObservation(b) - scoreObservation(a);
|
|
3552
|
+
if (scoreDiff !== 0)
|
|
3553
|
+
return scoreDiff;
|
|
3554
|
+
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
|
3555
|
+
})) {
|
|
3556
|
+
if (picked.some((existing) => titlesRoughlyMatch(existing.title, obs.title)))
|
|
3557
|
+
continue;
|
|
3558
|
+
picked.push(obs);
|
|
3559
|
+
if (picked.length >= 6)
|
|
3560
|
+
break;
|
|
3561
|
+
}
|
|
3562
|
+
return picked;
|
|
3563
|
+
}
|
|
3499
3564
|
function parseJsonArraySafe(value) {
|
|
3500
3565
|
if (!value)
|
|
3501
3566
|
return [];
|
|
@@ -3566,7 +3631,24 @@ function hasRequestSection(lines) {
|
|
|
3566
3631
|
return lines.some((line) => line.includes("Request:"));
|
|
3567
3632
|
}
|
|
3568
3633
|
function normalizeStartupItem(value) {
|
|
3569
|
-
return stripInlineSectionLabel2(value).replace(/^#?\d+:\s*/, "").replace(/^-\s*/, "").toLowerCase().replace(/\s+/g, " ").trim();
|
|
3634
|
+
return stripInlineSectionLabel2(value).replace(/^#?\d+:\s*/, "").replace(/^-\s*/, "").replace(/\([^)]*\)/g, " ").replace(/[^a-z0-9\s]/gi, " ").toLowerCase().replace(/\s+/g, " ").trim();
|
|
3635
|
+
}
|
|
3636
|
+
function titlesRoughlyMatch(left, right) {
|
|
3637
|
+
const a = normalizeStartupItem(left ?? "");
|
|
3638
|
+
const b = normalizeStartupItem(right ?? "");
|
|
3639
|
+
if (!a || !b)
|
|
3640
|
+
return false;
|
|
3641
|
+
if (a === b)
|
|
3642
|
+
return true;
|
|
3643
|
+
if (a.includes(b) || b.includes(a))
|
|
3644
|
+
return true;
|
|
3645
|
+
const aTokens = a.split(" ").filter((token) => token.length >= 4);
|
|
3646
|
+
const bTokens = b.split(" ").filter((token) => token.length >= 4);
|
|
3647
|
+
if (!aTokens.length || !bTokens.length)
|
|
3648
|
+
return false;
|
|
3649
|
+
const shared = aTokens.filter((token) => bTokens.includes(token));
|
|
3650
|
+
const minSize = Math.min(aTokens.length, bTokens.length);
|
|
3651
|
+
return shared.length >= Math.max(3, Math.ceil(minSize * 0.6));
|
|
3570
3652
|
}
|
|
3571
3653
|
function isMeaningfulPrompt2(value) {
|
|
3572
3654
|
if (!value)
|
package/dist/hooks/stop.js
CHANGED
|
@@ -2535,7 +2535,7 @@ function buildBeacon(db, config, sessionId, metrics) {
|
|
|
2535
2535
|
sentinel_used: valueSignals.security_findings_count > 0,
|
|
2536
2536
|
risk_score: riskScore,
|
|
2537
2537
|
stacks_detected: stacks,
|
|
2538
|
-
client_version: "0.4.
|
|
2538
|
+
client_version: "0.4.17",
|
|
2539
2539
|
context_observations_injected: metrics?.contextObsInjected ?? 0,
|
|
2540
2540
|
context_total_available: metrics?.contextTotalAvailable ?? 0,
|
|
2541
2541
|
recall_attempts: metrics?.recallAttempts ?? 0,
|
|
@@ -3558,42 +3558,40 @@ async function main() {
|
|
|
3558
3558
|
try {
|
|
3559
3559
|
if (event.session_id) {
|
|
3560
3560
|
db.completeSession(event.session_id);
|
|
3561
|
+
if (event.last_assistant_message) {
|
|
3562
|
+
try {
|
|
3563
|
+
createAssistantCheckpoint(db, event.session_id, event.cwd, event.last_assistant_message);
|
|
3564
|
+
} catch {}
|
|
3565
|
+
}
|
|
3561
3566
|
const existing = db.getSessionSummary(event.session_id);
|
|
3562
3567
|
if (!existing) {
|
|
3563
3568
|
const observations = db.getObservationsBySession(event.session_id);
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
console.log(formatRiskTrafficLight(riskResult));
|
|
3587
|
-
}
|
|
3569
|
+
const session = db.getSessionMetrics(event.session_id);
|
|
3570
|
+
const summary = extractRetrospective(observations, event.session_id, session?.project_id ?? null, config.user_id) ?? buildFallbackSessionSummary(db, event.session_id, session?.project_id ?? null, config.user_id, event.last_assistant_message);
|
|
3571
|
+
if (summary) {
|
|
3572
|
+
const row = db.insertSessionSummary(summary);
|
|
3573
|
+
db.addToOutbox("summary", row.id);
|
|
3574
|
+
let securityFindings = [];
|
|
3575
|
+
try {
|
|
3576
|
+
if (session?.project_id) {
|
|
3577
|
+
securityFindings = db.getSecurityFindings(session.project_id, { limit: 100 }).filter((f) => f.session_id === event.session_id);
|
|
3578
|
+
}
|
|
3579
|
+
} catch {}
|
|
3580
|
+
const riskResult = computeRiskScore({
|
|
3581
|
+
observations,
|
|
3582
|
+
securityFindings,
|
|
3583
|
+
filesTouchedCount: session?.files_touched_count ?? 0,
|
|
3584
|
+
toolCallsCount: session?.tool_calls_count ?? 0
|
|
3585
|
+
});
|
|
3586
|
+
try {
|
|
3587
|
+
db.setSessionRiskScore(event.session_id, riskResult.score);
|
|
3588
|
+
} catch {}
|
|
3589
|
+
printRetrospective(summary);
|
|
3590
|
+
console.log(formatRiskTrafficLight(riskResult));
|
|
3588
3591
|
}
|
|
3589
3592
|
}
|
|
3590
3593
|
}
|
|
3591
3594
|
if (event.last_assistant_message) {
|
|
3592
|
-
if (event.session_id) {
|
|
3593
|
-
try {
|
|
3594
|
-
createAssistantCheckpoint(db, event.session_id, event.cwd, event.last_assistant_message);
|
|
3595
|
-
} catch {}
|
|
3596
|
-
}
|
|
3597
3595
|
const unsaved = detectUnsavedPlans(event.last_assistant_message);
|
|
3598
3596
|
if (unsaved.length > 0) {
|
|
3599
3597
|
console.error("");
|
|
@@ -3640,6 +3638,44 @@ async function main() {
|
|
|
3640
3638
|
}
|
|
3641
3639
|
process.exit(0);
|
|
3642
3640
|
}
|
|
3641
|
+
function buildFallbackSessionSummary(db, sessionId, projectId, userId, lastAssistantMessage) {
|
|
3642
|
+
const prompts = db.getSessionUserPrompts(sessionId, 10).filter((prompt) => isMeaningfulSummaryPrompt(prompt));
|
|
3643
|
+
const checkpoint = lastAssistantMessage ? extractAssistantCheckpoint(lastAssistantMessage) : null;
|
|
3644
|
+
const request = selectFallbackRequest(prompts);
|
|
3645
|
+
const completed = checkpoint ? buildCheckpointCompleted(checkpoint) : null;
|
|
3646
|
+
if (!request && !completed)
|
|
3647
|
+
return null;
|
|
3648
|
+
return {
|
|
3649
|
+
session_id: sessionId,
|
|
3650
|
+
project_id: projectId,
|
|
3651
|
+
user_id: userId,
|
|
3652
|
+
request,
|
|
3653
|
+
investigated: null,
|
|
3654
|
+
learned: null,
|
|
3655
|
+
completed,
|
|
3656
|
+
next_steps: null
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
function selectFallbackRequest(prompts) {
|
|
3660
|
+
const preferred = [...prompts].reverse().find((prompt) => !/^\[;ease$/i.test(prompt.prompt.trim()));
|
|
3661
|
+
return preferred?.prompt?.replace(/\s+/g, " ").trim() ?? null;
|
|
3662
|
+
}
|
|
3663
|
+
function isMeaningfulSummaryPrompt(prompt) {
|
|
3664
|
+
const compact = prompt.prompt.replace(/\s+/g, " ").trim();
|
|
3665
|
+
if (compact.length < 8)
|
|
3666
|
+
return false;
|
|
3667
|
+
if (/^\[;ease$/i.test(compact))
|
|
3668
|
+
return false;
|
|
3669
|
+
return /[a-z]{3,}/i.test(compact);
|
|
3670
|
+
}
|
|
3671
|
+
function buildCheckpointCompleted(checkpoint) {
|
|
3672
|
+
const lines = [`- ${checkpoint.title}`];
|
|
3673
|
+
for (const fact of checkpoint.facts.slice(0, 2)) {
|
|
3674
|
+
lines.push(` - ${fact}`);
|
|
3675
|
+
}
|
|
3676
|
+
return lines.join(`
|
|
3677
|
+
`);
|
|
3678
|
+
}
|
|
3643
3679
|
function createSessionDigest(db, sessionId, cwd) {
|
|
3644
3680
|
const observations = db.getObservationsBySession(sessionId);
|
|
3645
3681
|
if (observations.length < 2)
|
package/dist/server.js
CHANGED
|
@@ -19764,7 +19764,7 @@ process.on("SIGTERM", () => {
|
|
|
19764
19764
|
});
|
|
19765
19765
|
var server = new McpServer({
|
|
19766
19766
|
name: "engrm",
|
|
19767
|
-
version: "0.4.
|
|
19767
|
+
version: "0.4.17"
|
|
19768
19768
|
});
|
|
19769
19769
|
server.tool("save_observation", "Save an observation to memory", {
|
|
19770
19770
|
type: exports_external.enum([
|
|
@@ -19940,11 +19940,11 @@ Facts: ${reduced.facts.join("; ")}` : "";
|
|
|
19940
19940
|
]
|
|
19941
19941
|
};
|
|
19942
19942
|
});
|
|
19943
|
-
server.tool("capture_git_worktree", "
|
|
19944
|
-
cwd: exports_external.string().optional().describe("Git repo path
|
|
19945
|
-
staged: exports_external.boolean().optional().describe("
|
|
19946
|
-
summary: exports_external.string().optional().describe("Optional human summary or commit-style title"),
|
|
19947
|
-
session_id: exports_external.string().optional()
|
|
19943
|
+
server.tool("capture_git_worktree", "Capture the current git worktree as durable memory. Best for saving a meaningful local diff before context is lost.", {
|
|
19944
|
+
cwd: exports_external.string().optional().describe("Git repo path. Defaults to the current working directory."),
|
|
19945
|
+
staged: exports_external.boolean().optional().describe("If true, capture staged changes instead of unstaged worktree changes."),
|
|
19946
|
+
summary: exports_external.string().optional().describe("Optional human summary or commit-style title to steer the saved memory."),
|
|
19947
|
+
session_id: exports_external.string().optional().describe("Optional session ID to link this capture to active work.")
|
|
19948
19948
|
}, async (params) => {
|
|
19949
19949
|
let worktree;
|
|
19950
19950
|
try {
|
|
@@ -20000,12 +20000,12 @@ server.tool("capture_git_worktree", "Read the current git worktree diff, reduce
|
|
|
20000
20000
|
]
|
|
20001
20001
|
};
|
|
20002
20002
|
});
|
|
20003
|
-
server.tool("capture_repo_scan", "Run a lightweight repository scan
|
|
20004
|
-
cwd: exports_external.string().optional().describe("Repo path to scan
|
|
20005
|
-
focus: exports_external.array(exports_external.string()).optional().describe("Optional
|
|
20006
|
-
max_findings: exports_external.number().optional().describe("Maximum findings to keep"),
|
|
20007
|
-
summary: exports_external.string().optional().describe("Optional human summary for the
|
|
20008
|
-
session_id: exports_external.string().optional()
|
|
20003
|
+
server.tool("capture_repo_scan", "Run a lightweight repository scan and save reduced findings as durable memory. Best for quick architecture, risk, or implementation scans.", {
|
|
20004
|
+
cwd: exports_external.string().optional().describe("Repo path to scan. Defaults to the current working directory."),
|
|
20005
|
+
focus: exports_external.array(exports_external.string()).optional().describe("Optional topics to bias the scan toward, for example 'billing', 'auth', or 'validation'."),
|
|
20006
|
+
max_findings: exports_external.number().optional().describe("Maximum findings to keep before reduction."),
|
|
20007
|
+
summary: exports_external.string().optional().describe("Optional human summary for the saved memory."),
|
|
20008
|
+
session_id: exports_external.string().optional().describe("Optional session ID to link this scan to active work.")
|
|
20009
20009
|
}, async (params) => {
|
|
20010
20010
|
let scan;
|
|
20011
20011
|
try {
|
|
@@ -20063,15 +20063,15 @@ Findings: ${findingSummary}` : ""}`
|
|
|
20063
20063
|
]
|
|
20064
20064
|
};
|
|
20065
20065
|
});
|
|
20066
|
-
server.tool("capture_openclaw_content", "
|
|
20067
|
-
title: exports_external.string().optional().describe("Short content or
|
|
20068
|
-
posted: exports_external.array(exports_external.string()).optional().describe("Concrete posted items or shipped content outcomes"),
|
|
20069
|
-
researched: exports_external.array(exports_external.string()).optional().describe("Research or discovery items"),
|
|
20070
|
-
outcomes: exports_external.array(exports_external.string()).optional().describe("Meaningful outcomes from the run"),
|
|
20071
|
-
next_actions: exports_external.array(exports_external.string()).optional().describe("Real follow-up actions"),
|
|
20072
|
-
links: exports_external.array(exports_external.string()).optional().describe("Thread or source URLs"),
|
|
20073
|
-
session_id: exports_external.string().optional(),
|
|
20074
|
-
cwd: exports_external.string().optional()
|
|
20066
|
+
server.tool("capture_openclaw_content", "Capture OpenClaw content, research, and follow-up work as durable memory. Best for preserving posted outcomes, discoveries, and next actions.", {
|
|
20067
|
+
title: exports_external.string().optional().describe("Short content, campaign, or research title."),
|
|
20068
|
+
posted: exports_external.array(exports_external.string()).optional().describe("Concrete posted items or shipped content outcomes."),
|
|
20069
|
+
researched: exports_external.array(exports_external.string()).optional().describe("Research or discovery items worth retaining."),
|
|
20070
|
+
outcomes: exports_external.array(exports_external.string()).optional().describe("Meaningful outcomes from the run."),
|
|
20071
|
+
next_actions: exports_external.array(exports_external.string()).optional().describe("Real follow-up actions that remain."),
|
|
20072
|
+
links: exports_external.array(exports_external.string()).optional().describe("Thread or source URLs tied to the work."),
|
|
20073
|
+
session_id: exports_external.string().optional().describe("Optional session ID to link this memory to active work."),
|
|
20074
|
+
cwd: exports_external.string().optional().describe("Optional project path for attribution.")
|
|
20075
20075
|
}, async (params) => {
|
|
20076
20076
|
const reduced = reduceOpenClawContentToMemory({
|
|
20077
20077
|
...params,
|
|
@@ -20536,9 +20536,9 @@ server.tool("capture_status", "Show whether Engrm hook registration and recent p
|
|
|
20536
20536
|
]
|
|
20537
20537
|
};
|
|
20538
20538
|
});
|
|
20539
|
-
server.tool("capture_quality", "Show
|
|
20540
|
-
limit: exports_external.number().optional(),
|
|
20541
|
-
user_id: exports_external.string().optional()
|
|
20539
|
+
server.tool("capture_quality", "Show how healthy Engrm capture is across the workspace: raw chronology coverage, checkpoints, and provenance by tool.", {
|
|
20540
|
+
limit: exports_external.number().optional().describe("Maximum projects to include in the top-projects section."),
|
|
20541
|
+
user_id: exports_external.string().optional().describe("Optional user override; defaults to the configured user.")
|
|
20542
20542
|
}, async (params) => {
|
|
20543
20543
|
const result = getCaptureQuality(db, {
|
|
20544
20544
|
limit: params.limit,
|
|
@@ -20575,11 +20575,11 @@ ${projectLines}`
|
|
|
20575
20575
|
]
|
|
20576
20576
|
};
|
|
20577
20577
|
});
|
|
20578
|
-
server.tool("tool_memory_index", "Show which
|
|
20579
|
-
cwd: exports_external.string().optional(),
|
|
20580
|
-
project_scoped: exports_external.boolean().optional(),
|
|
20581
|
-
limit: exports_external.number().optional(),
|
|
20582
|
-
user_id: exports_external.string().optional()
|
|
20578
|
+
server.tool("tool_memory_index", "Show which tools are actually producing durable memory, which plugins they exercise, and what memory types they create.", {
|
|
20579
|
+
cwd: exports_external.string().optional().describe("Project path to inspect. Defaults to the current working directory."),
|
|
20580
|
+
project_scoped: exports_external.boolean().optional().describe("If true, limit results to the current project instead of the whole workspace."),
|
|
20581
|
+
limit: exports_external.number().optional().describe("Maximum tools to include."),
|
|
20582
|
+
user_id: exports_external.string().optional().describe("Optional user override; defaults to the configured user.")
|
|
20583
20583
|
}, async (params) => {
|
|
20584
20584
|
const result = getToolMemoryIndex(db, {
|
|
20585
20585
|
cwd: params.cwd ?? process.cwd(),
|