engrm 0.4.36 → 0.4.38

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 CHANGED
@@ -288,6 +288,25 @@ How to use it:
288
288
  - `load_recall_item` opens an exact handoff, thread, chat, or memory key returned by the index
289
289
  - `repair_recall` is the repair step when continuity is still thin, hook-only, or under-captured
290
290
 
291
+ ### Explicit Save Protocol
292
+
293
+ When something should be remembered on purpose, do not wait for an end-of-session
294
+ digest if an explicit write is more appropriate.
295
+
296
+ Use:
297
+
298
+ - `save_observation`
299
+ - direct durable memory write for a bugfix, decision, discovery, pattern,
300
+ feature, or change
301
+ - `create_handoff` / `refresh_handoff`
302
+ - preserve the active thread for another device or a later session
303
+ - `capture_openclaw_content`
304
+ - save OpenClaw-style research, posting, outcomes, and next actions as
305
+ reusable memory
306
+
307
+ Automatic session digests are a safety net.
308
+ They are not the only path for preserving important work.
309
+
291
310
  ### Thin Tools, Thick Memory
292
311
 
293
312
  Engrm now has a real thin-tool layer, not just a plugin spec.
@@ -3225,7 +3225,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
3225
3225
  import { join as join3 } from "node:path";
3226
3226
  import { homedir } from "node:os";
3227
3227
  var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
3228
- var CLIENT_VERSION = "0.4.36";
3228
+ var CLIENT_VERSION = "0.4.38";
3229
3229
  function hashFile(filePath) {
3230
3230
  try {
3231
3231
  if (!existsSync3(filePath))
@@ -3087,7 +3087,7 @@ function buildBeacon(db, config, sessionId, metrics) {
3087
3087
  sentinel_used: valueSignals.security_findings_count > 0,
3088
3088
  risk_score: riskScore,
3089
3089
  stacks_detected: stacks,
3090
- client_version: "0.4.36",
3090
+ client_version: "0.4.38",
3091
3091
  context_observations_injected: metrics?.contextObsInjected ?? 0,
3092
3092
  context_total_available: metrics?.contextTotalAvailable ?? 0,
3093
3093
  recall_attempts: metrics?.recallAttempts ?? 0,
package/dist/server.js CHANGED
@@ -16329,7 +16329,14 @@ async function searchObservations(db, input) {
16329
16329
  }
16330
16330
  }
16331
16331
  const safeQuery = sanitizeFtsQuery(query);
16332
- const ftsResults = safeQuery ? db.searchFts(safeQuery, projectId, undefined, limit * 2, input.user_id) : [];
16332
+ let ftsResults = [];
16333
+ if (safeQuery) {
16334
+ try {
16335
+ ftsResults = db.searchFts(safeQuery, projectId, undefined, limit * 2, input.user_id);
16336
+ } catch {
16337
+ ftsResults = [];
16338
+ }
16339
+ }
16333
16340
  let vecResults = [];
16334
16341
  const queryEmbedding = await embedText(query);
16335
16342
  if (queryEmbedding && db.vecAvailable) {
@@ -16392,11 +16399,13 @@ function mergeResults(ftsResults, vecResults, limit) {
16392
16399
  return Array.from(scores.entries()).map(([id, score]) => ({ id, score })).sort((a, b) => b.score - a.score).slice(0, limit);
16393
16400
  }
16394
16401
  function sanitizeFtsQuery(query) {
16395
- let safe = query.replace(/[{}()[\]^~*:]/g, " ");
16396
- safe = safe.replace(/\s+/g, " ").trim();
16397
- if (!safe)
16402
+ const normalized = String(query ?? "").normalize("NFKC").replace(/[^\p{L}\p{N}_\s]+/gu, " ").replace(/\s+/g, " ").trim();
16403
+ if (!normalized)
16404
+ return "";
16405
+ const terms = normalized.split(" ").map((term) => term.trim()).filter(Boolean).slice(0, 16);
16406
+ if (terms.length === 0)
16398
16407
  return "";
16399
- return safe;
16408
+ return terms.map((term) => `"${term}"`).join(" ");
16400
16409
  }
16401
16410
 
16402
16411
  // src/tools/recent-chat.ts
@@ -22871,9 +22880,9 @@ process.on("SIGTERM", () => {
22871
22880
  });
22872
22881
  var server = new McpServer({
22873
22882
  name: "engrm",
22874
- version: "0.4.36"
22883
+ version: "0.4.38"
22875
22884
  });
22876
- server.tool("save_observation", "Save an observation to memory", {
22885
+ server.tool("save_observation", "Directly save a durable memory item now. Use this when something should be remembered on purpose instead of waiting for an end-of-session digest.", {
22877
22886
  type: exports_external.enum([
22878
22887
  "bugfix",
22879
22888
  "discovery",
@@ -23170,7 +23179,7 @@ Findings: ${findingSummary}` : ""}`
23170
23179
  ]
23171
23180
  };
23172
23181
  });
23173
- 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.", {
23182
+ server.tool("capture_openclaw_content", "Directly save OpenClaw content, research, and follow-up work as durable memory. Best for preserving posted outcomes, discoveries, and next actions during or right after the run.", {
23174
23183
  title: exports_external.string().optional().describe("Short content, campaign, or research title."),
23175
23184
  posted: exports_external.array(exports_external.string()).optional().describe("Concrete posted items or shipped content outcomes."),
23176
23185
  researched: exports_external.array(exports_external.string()).optional().describe("Research or discovery items worth retaining."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engrm",
3
- "version": "0.4.36",
3
+ "version": "0.4.38",
4
4
  "description": "Shared memory across devices, sessions, and agents, with thin MCP tools for durable capture and live continuity",
5
5
  "mcpName": "io.github.dr12hes/engrm",
6
6
  "type": "module",