engrm 0.4.42 → 0.4.43

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
@@ -110,6 +110,7 @@ Add this to `~/.engrm/settings.json`:
110
110
  "port": 3767,
111
111
  "bearer_tokens": ["replace-with-a-long-random-token"]
112
112
  },
113
+ "tool_profile": "memory",
113
114
  "fleet": {
114
115
  "project_name": "shared-experience",
115
116
  "namespace": "ns_fleet_shared",
@@ -154,6 +155,8 @@ Fleet writes:
154
155
  - sync to the dedicated fleet namespace/key instead of the normal org namespace
155
156
  - get an extra outbound scrub pass that redacts hostnames, IPs, and MACs before upload
156
157
 
158
+ For Hermes-style shared learning deployments, set `"tool_profile": "memory"` to expose a reduced Engrm tool set focused on durable memory, recall, and thread resumption instead of the full developer-oriented surface.
159
+
157
160
  ---
158
161
 
159
162
  ## How It Works
@@ -664,7 +667,7 @@ Engrm auto-registers in:
664
667
  - **Local storage:** SQLite via `better-sqlite3`, FTS5 full-text search, `sqlite-vec` for embeddings
665
668
  - **Embeddings:** all-MiniLM-L6-v2 via `@xenova/transformers` (384 dims, ~23MB)
666
669
  - **Remote backend:** Candengo Vector (BGE-M3, Qdrant, hybrid dense+sparse search)
667
- - **MCP:** `@modelcontextprotocol/sdk` (stdio transport)
670
+ - **MCP:** `@modelcontextprotocol/sdk` (stdio for local agents, Streamable HTTP + SSE compatibility for Hermes-style remote clients)
668
671
  - **AI extraction:** `@anthropic-ai/claude-agent-sdk` (optional, for richer observations)
669
672
 
670
673
  ---
package/dist/cli.js CHANGED
@@ -114,7 +114,8 @@ function createDefaultConfig() {
114
114
  project_name: "shared-experience",
115
115
  namespace: "",
116
116
  api_key: ""
117
- }
117
+ },
118
+ tool_profile: "full"
118
119
  };
119
120
  }
120
121
  function loadConfig() {
@@ -185,7 +186,8 @@ function loadConfig() {
185
186
  project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
186
187
  namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
187
188
  api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
188
- }
189
+ },
190
+ tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
189
191
  };
190
192
  }
191
193
  function saveConfig(config) {
@@ -240,6 +242,11 @@ function asObserverMode(value, fallback) {
240
242
  return value;
241
243
  return fallback;
242
244
  }
245
+ function asToolProfile(value, fallback) {
246
+ if (value === "full" || value === "memory")
247
+ return value;
248
+ return fallback;
249
+ }
243
250
  function asTeams(value, fallback) {
244
251
  if (!Array.isArray(value))
245
252
  return fallback;
@@ -4006,7 +4013,8 @@ function createDefaultConfig2() {
4006
4013
  project_name: "shared-experience",
4007
4014
  namespace: "",
4008
4015
  api_key: ""
4009
- }
4016
+ },
4017
+ tool_profile: "full"
4010
4018
  };
4011
4019
  }
4012
4020
  function loadConfig2() {
@@ -4077,7 +4085,8 @@ function loadConfig2() {
4077
4085
  project_name: asString2(config["fleet"]?.["project_name"], defaults.fleet.project_name),
4078
4086
  namespace: asString2(config["fleet"]?.["namespace"], defaults.fleet.namespace),
4079
4087
  api_key: asString2(config["fleet"]?.["api_key"], defaults.fleet.api_key)
4080
- }
4088
+ },
4089
+ tool_profile: asToolProfile2(config["tool_profile"], defaults.tool_profile)
4081
4090
  };
4082
4091
  }
4083
4092
  function saveConfig2(config) {
@@ -4132,12 +4141,35 @@ function asObserverMode2(value, fallback) {
4132
4141
  return value;
4133
4142
  return fallback;
4134
4143
  }
4144
+ function asToolProfile2(value, fallback) {
4145
+ if (value === "full" || value === "memory")
4146
+ return value;
4147
+ return fallback;
4148
+ }
4135
4149
  function asTeams2(value, fallback) {
4136
4150
  if (!Array.isArray(value))
4137
4151
  return fallback;
4138
4152
  return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
4139
4153
  }
4140
4154
 
4155
+ // src/tool-profiles.ts
4156
+ var MEMORY_PROFILE_TOOLS = [
4157
+ "save_observation",
4158
+ "search_recall",
4159
+ "resume_thread",
4160
+ "list_recall_items",
4161
+ "load_recall_item",
4162
+ "recent_chat",
4163
+ "search_chat",
4164
+ "refresh_chat_recall",
4165
+ "repair_recall"
4166
+ ];
4167
+ function getEnabledToolNames(profile) {
4168
+ if (!profile || profile === "full")
4169
+ return null;
4170
+ return new Set(MEMORY_PROFILE_TOOLS);
4171
+ }
4172
+
4141
4173
  // src/tools/capture-status.ts
4142
4174
  var LEGACY_CODEX_SERVER_NAME2 = `candengo-${"mem"}`;
4143
4175
  function getCaptureStatus(db, input = {}) {
@@ -4242,6 +4274,8 @@ function getCaptureStatus(db, input = {}) {
4242
4274
  http_enabled: Boolean(config?.http?.enabled || process.env.ENGRM_HTTP_PORT),
4243
4275
  http_port: config?.http?.port ?? (process.env.ENGRM_HTTP_PORT ? Number(process.env.ENGRM_HTTP_PORT) : null),
4244
4276
  http_bearer_token_count: config?.http?.bearer_tokens?.length ?? 0,
4277
+ tool_profile: config?.tool_profile ?? "full",
4278
+ enabled_tool_count: config ? getEnabledToolNames(config.tool_profile)?.size ?? null : null,
4245
4279
  fleet_project_name: config?.fleet?.project_name ?? null,
4246
4280
  fleet_configured: Boolean(config?.fleet?.namespace && config?.fleet?.api_key),
4247
4281
  claude_mcp_registered: claudeMcpRegistered,
@@ -4724,6 +4758,7 @@ function handleStatus() {
4724
4758
  console.log(` Sync: ${config.sync.enabled ? "enabled" : "disabled"}`);
4725
4759
  console.log(` HTTP MCP: ${config.http.enabled ? `enabled (:${config.http.port})` : "disabled"}`);
4726
4760
  console.log(` HTTP tokens: ${config.http.bearer_tokens.length}`);
4761
+ console.log(` Tool profile: ${config.tool_profile ?? "full"}`);
4727
4762
  console.log(` Fleet project: ${config.fleet.project_name || "(not set)"}`);
4728
4763
  console.log(` Fleet sync: ${config.fleet.namespace && config.fleet.api_key ? "configured" : "not configured"}`);
4729
4764
  const claudeJson = join8(homedir5(), ".claude.json");
@@ -5083,6 +5118,7 @@ async function handleDoctor() {
5083
5118
  } else {
5084
5119
  info("HTTP MCP disabled");
5085
5120
  }
5121
+ info(`Tool profile: ${config.tool_profile ?? "full"}`);
5086
5122
  if (config.fleet.project_name) {
5087
5123
  if (config.fleet.namespace && config.fleet.api_key) {
5088
5124
  pass(`Fleet project '${config.fleet.project_name}' is configured`);
@@ -986,7 +986,8 @@ function createDefaultConfig() {
986
986
  project_name: "shared-experience",
987
987
  namespace: "",
988
988
  api_key: ""
989
- }
989
+ },
990
+ tool_profile: "full"
990
991
  };
991
992
  }
992
993
  function loadConfig() {
@@ -1057,7 +1058,8 @@ function loadConfig() {
1057
1058
  project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
1058
1059
  namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
1059
1060
  api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
1060
- }
1061
+ },
1062
+ tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
1061
1063
  };
1062
1064
  }
1063
1065
  function saveConfig(config) {
@@ -1112,6 +1114,11 @@ function asObserverMode(value, fallback) {
1112
1114
  return value;
1113
1115
  return fallback;
1114
1116
  }
1117
+ function asToolProfile(value, fallback) {
1118
+ if (value === "full" || value === "memory")
1119
+ return value;
1120
+ return fallback;
1121
+ }
1115
1122
  function asTeams(value, fallback) {
1116
1123
  if (!Array.isArray(value))
1117
1124
  return fallback;
@@ -292,7 +292,8 @@ function createDefaultConfig() {
292
292
  project_name: "shared-experience",
293
293
  namespace: "",
294
294
  api_key: ""
295
- }
295
+ },
296
+ tool_profile: "full"
296
297
  };
297
298
  }
298
299
  function loadConfig() {
@@ -363,7 +364,8 @@ function loadConfig() {
363
364
  project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
364
365
  namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
365
366
  api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
366
- }
367
+ },
368
+ tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
367
369
  };
368
370
  }
369
371
  function saveConfig(config) {
@@ -418,6 +420,11 @@ function asObserverMode(value, fallback) {
418
420
  return value;
419
421
  return fallback;
420
422
  }
423
+ function asToolProfile(value, fallback) {
424
+ if (value === "full" || value === "memory")
425
+ return value;
426
+ return fallback;
427
+ }
421
428
  function asTeams(value, fallback) {
422
429
  if (!Array.isArray(value))
423
430
  return fallback;
@@ -86,7 +86,8 @@ function createDefaultConfig() {
86
86
  project_name: "shared-experience",
87
87
  namespace: "",
88
88
  api_key: ""
89
- }
89
+ },
90
+ tool_profile: "full"
90
91
  };
91
92
  }
92
93
  function loadConfig() {
@@ -157,7 +158,8 @@ function loadConfig() {
157
158
  project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
158
159
  namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
159
160
  api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
160
- }
161
+ },
162
+ tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
161
163
  };
162
164
  }
163
165
  function saveConfig(config) {
@@ -212,6 +214,11 @@ function asObserverMode(value, fallback) {
212
214
  return value;
213
215
  return fallback;
214
216
  }
217
+ function asToolProfile(value, fallback) {
218
+ if (value === "full" || value === "memory")
219
+ return value;
220
+ return fallback;
221
+ }
215
222
  function asTeams(value, fallback) {
216
223
  if (!Array.isArray(value))
217
224
  return fallback;
@@ -162,7 +162,8 @@ function createDefaultConfig() {
162
162
  project_name: "shared-experience",
163
163
  namespace: "",
164
164
  api_key: ""
165
- }
165
+ },
166
+ tool_profile: "full"
166
167
  };
167
168
  }
168
169
  function loadConfig() {
@@ -233,7 +234,8 @@ function loadConfig() {
233
234
  project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
234
235
  namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
235
236
  api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
236
- }
237
+ },
238
+ tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
237
239
  };
238
240
  }
239
241
  function saveConfig(config) {
@@ -288,6 +290,11 @@ function asObserverMode(value, fallback) {
288
290
  return value;
289
291
  return fallback;
290
292
  }
293
+ function asToolProfile(value, fallback) {
294
+ if (value === "full" || value === "memory")
295
+ return value;
296
+ return fallback;
297
+ }
291
298
  function asTeams(value, fallback) {
292
299
  if (!Array.isArray(value))
293
300
  return fallback;
@@ -3480,7 +3480,8 @@ function createDefaultConfig() {
3480
3480
  project_name: "shared-experience",
3481
3481
  namespace: "",
3482
3482
  api_key: ""
3483
- }
3483
+ },
3484
+ tool_profile: "full"
3484
3485
  };
3485
3486
  }
3486
3487
  function loadConfig() {
@@ -3551,7 +3552,8 @@ function loadConfig() {
3551
3552
  project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
3552
3553
  namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
3553
3554
  api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
3554
- }
3555
+ },
3556
+ tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
3555
3557
  };
3556
3558
  }
3557
3559
  function saveConfig(config) {
@@ -3606,6 +3608,11 @@ function asObserverMode(value, fallback) {
3606
3608
  return value;
3607
3609
  return fallback;
3608
3610
  }
3611
+ function asToolProfile(value, fallback) {
3612
+ if (value === "full" || value === "memory")
3613
+ return value;
3614
+ return fallback;
3615
+ }
3609
3616
  function asTeams(value, fallback) {
3610
3617
  if (!Array.isArray(value))
3611
3618
  return fallback;
@@ -3905,6 +3912,7 @@ class VectorClient {
3905
3912
  apiKey;
3906
3913
  siteId;
3907
3914
  namespace;
3915
+ timeoutMs;
3908
3916
  constructor(config, overrides = {}) {
3909
3917
  const baseUrl = getBaseUrl(config);
3910
3918
  const apiKey = overrides.apiKey ?? getApiKey(config);
@@ -3915,6 +3923,7 @@ class VectorClient {
3915
3923
  this.apiKey = apiKey;
3916
3924
  this.siteId = overrides.siteId ?? config.site_id;
3917
3925
  this.namespace = overrides.namespace ?? config.namespace;
3926
+ this.timeoutMs = overrides.timeoutMs ?? 1e4;
3918
3927
  }
3919
3928
  static isConfigured(config) {
3920
3929
  return getApiKey(config) !== null && getBaseUrl(config) !== null;
@@ -3977,6 +3986,7 @@ class VectorClient {
3977
3986
  if (body && method !== "GET") {
3978
3987
  init.body = JSON.stringify(body);
3979
3988
  }
3989
+ init.signal = AbortSignal.timeout(this.timeoutMs);
3980
3990
  const response = await fetch(url, init);
3981
3991
  if (!response.ok) {
3982
3992
  const text = await response.text().catch(() => "");
@@ -399,7 +399,8 @@ function createDefaultConfig() {
399
399
  project_name: "shared-experience",
400
400
  namespace: "",
401
401
  api_key: ""
402
- }
402
+ },
403
+ tool_profile: "full"
403
404
  };
404
405
  }
405
406
  function loadConfig() {
@@ -470,7 +471,8 @@ function loadConfig() {
470
471
  project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
471
472
  namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
472
473
  api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
473
- }
474
+ },
475
+ tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
474
476
  };
475
477
  }
476
478
  function saveConfig(config) {
@@ -525,6 +527,11 @@ function asObserverMode(value, fallback) {
525
527
  return value;
526
528
  return fallback;
527
529
  }
530
+ function asToolProfile(value, fallback) {
531
+ if (value === "full" || value === "memory")
532
+ return value;
533
+ return fallback;
534
+ }
528
535
  function asTeams(value, fallback) {
529
536
  if (!Array.isArray(value))
530
537
  return fallback;
@@ -2271,6 +2278,7 @@ class VectorClient {
2271
2278
  apiKey;
2272
2279
  siteId;
2273
2280
  namespace;
2281
+ timeoutMs;
2274
2282
  constructor(config, overrides = {}) {
2275
2283
  const baseUrl = getBaseUrl(config);
2276
2284
  const apiKey = overrides.apiKey ?? getApiKey(config);
@@ -2281,6 +2289,7 @@ class VectorClient {
2281
2289
  this.apiKey = apiKey;
2282
2290
  this.siteId = overrides.siteId ?? config.site_id;
2283
2291
  this.namespace = overrides.namespace ?? config.namespace;
2292
+ this.timeoutMs = overrides.timeoutMs ?? 1e4;
2284
2293
  }
2285
2294
  static isConfigured(config) {
2286
2295
  return getApiKey(config) !== null && getBaseUrl(config) !== null;
@@ -2343,6 +2352,7 @@ class VectorClient {
2343
2352
  if (body && method !== "GET") {
2344
2353
  init.body = JSON.stringify(body);
2345
2354
  }
2355
+ init.signal = AbortSignal.timeout(this.timeoutMs);
2346
2356
  const response = await fetch(url, init);
2347
2357
  if (!response.ok) {
2348
2358
  const text = await response.text().catch(() => "");
@@ -2811,7 +2821,7 @@ function buildSummaryVectorDocument(summary, config, project, targetOrObservatio
2811
2821
  }
2812
2822
  };
2813
2823
  }
2814
- async function pushOutbox(db, config, batchSize = 50) {
2824
+ async function pushOutbox(db, config, batchSize = 50, options = {}) {
2815
2825
  const entries = getPendingEntries(db, batchSize);
2816
2826
  let pushed = 0;
2817
2827
  let failed = 0;
@@ -2922,7 +2932,8 @@ async function pushOutbox(db, config, batchSize = 50) {
2922
2932
  const client = new VectorClient(config, {
2923
2933
  apiKey: target.apiKey,
2924
2934
  namespace: target.namespace,
2925
- siteId: target.siteId
2935
+ siteId: target.siteId,
2936
+ timeoutMs: options.timeoutMs
2926
2937
  });
2927
2938
  try {
2928
2939
  await client.batchIngest(items.map((b) => b.doc));
@@ -3003,118 +3014,14 @@ function summarizeObservationSourceTools(observations) {
3003
3014
  });
3004
3015
  }
3005
3016
 
3006
- // src/embeddings/embedder.ts
3007
- var _available = null;
3008
- var _pipeline = null;
3009
- var MODEL_NAME = "Xenova/all-MiniLM-L6-v2";
3010
- async function embedText(text) {
3011
- const pipe = await getPipeline();
3012
- if (!pipe)
3013
- return null;
3014
- try {
3015
- const output = await pipe(text, { pooling: "mean", normalize: true });
3016
- return new Float32Array(output.data);
3017
- } catch {
3018
- return null;
3019
- }
3020
- }
3021
- function composeEmbeddingText(obs) {
3022
- const parts = [obs.title];
3023
- if (obs.narrative)
3024
- parts.push(obs.narrative);
3025
- if (obs.facts) {
3026
- try {
3027
- const facts = JSON.parse(obs.facts);
3028
- if (Array.isArray(facts) && facts.length > 0) {
3029
- parts.push(facts.map((f) => `- ${f}`).join(`
3030
- `));
3031
- }
3032
- } catch {
3033
- parts.push(obs.facts);
3034
- }
3035
- }
3036
- if (obs.concepts) {
3037
- try {
3038
- const concepts = JSON.parse(obs.concepts);
3039
- if (Array.isArray(concepts) && concepts.length > 0) {
3040
- parts.push(concepts.join(", "));
3041
- }
3042
- } catch {}
3043
- }
3044
- return parts.join(`
3045
-
3046
- `);
3047
- }
3048
- function composeChatEmbeddingText(text) {
3049
- return text.replace(/\s+/g, " ").trim().slice(0, 2000);
3050
- }
3051
- async function getPipeline() {
3052
- if (_pipeline)
3053
- return _pipeline;
3054
- if (_available === false)
3055
- return null;
3056
- try {
3057
- const { pipeline } = await import("@xenova/transformers");
3058
- _pipeline = await pipeline("feature-extraction", MODEL_NAME);
3059
- _available = true;
3060
- return _pipeline;
3061
- } catch (err) {
3062
- _available = false;
3063
- console.error(`[engrm] Local embedding model unavailable: ${err instanceof Error ? err.message : String(err)}`);
3064
- return null;
3065
- }
3066
- }
3067
-
3068
- // src/sync/pull.ts
3069
- async function pullSettings(client, config) {
3070
- try {
3071
- const settings = await client.fetchSettings();
3072
- if (!settings)
3073
- return false;
3074
- let changed = false;
3075
- if (settings.transcript_analysis !== undefined) {
3076
- const ta = settings.transcript_analysis;
3077
- if (typeof ta === "object" && ta !== null) {
3078
- const taObj = ta;
3079
- if (taObj.enabled !== undefined && taObj.enabled !== config.transcript_analysis.enabled) {
3080
- config.transcript_analysis.enabled = !!taObj.enabled;
3081
- changed = true;
3082
- }
3083
- }
3084
- }
3085
- if (settings.observer !== undefined) {
3086
- const obs = settings.observer;
3087
- if (typeof obs === "object" && obs !== null) {
3088
- const obsObj = obs;
3089
- if (obsObj.enabled !== undefined && obsObj.enabled !== config.observer.enabled) {
3090
- config.observer.enabled = !!obsObj.enabled;
3091
- changed = true;
3092
- }
3093
- if (obsObj.model !== undefined && typeof obsObj.model === "string" && obsObj.model !== config.observer.model) {
3094
- config.observer.model = obsObj.model;
3095
- changed = true;
3096
- }
3097
- }
3098
- }
3099
- if (changed) {
3100
- saveConfig(config);
3101
- }
3102
- return changed;
3103
- } catch {
3104
- return false;
3105
- }
3106
- }
3107
-
3108
3017
  // src/sync/push-once.ts
3109
- async function pushOnce(db, config) {
3018
+ async function pushOnce(db, config, options = {}) {
3110
3019
  if (!config.sync.enabled)
3111
3020
  return 0;
3112
3021
  if (!VectorClient.isConfigured(config))
3113
3022
  return 0;
3114
3023
  try {
3115
- const client = new VectorClient(config);
3116
- const result = await pushOutbox(db, config, config.sync.batch_size);
3117
- await pullSettings(client, config);
3024
+ const result = await pushOutbox(db, config, config.sync.batch_size, { timeoutMs: options.timeoutMs ?? 4000 });
3118
3025
  return result.pushed;
3119
3026
  } catch {
3120
3027
  return 0;
@@ -3531,6 +3438,68 @@ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:f
3531
3438
  import { join as join5 } from "node:path";
3532
3439
  import { homedir as homedir3 } from "node:os";
3533
3440
 
3441
+ // src/embeddings/embedder.ts
3442
+ var _available = null;
3443
+ var _pipeline = null;
3444
+ var MODEL_NAME = "Xenova/all-MiniLM-L6-v2";
3445
+ async function embedText(text) {
3446
+ const pipe = await getPipeline();
3447
+ if (!pipe)
3448
+ return null;
3449
+ try {
3450
+ const output = await pipe(text, { pooling: "mean", normalize: true });
3451
+ return new Float32Array(output.data);
3452
+ } catch {
3453
+ return null;
3454
+ }
3455
+ }
3456
+ function composeEmbeddingText(obs) {
3457
+ const parts = [obs.title];
3458
+ if (obs.narrative)
3459
+ parts.push(obs.narrative);
3460
+ if (obs.facts) {
3461
+ try {
3462
+ const facts = JSON.parse(obs.facts);
3463
+ if (Array.isArray(facts) && facts.length > 0) {
3464
+ parts.push(facts.map((f) => `- ${f}`).join(`
3465
+ `));
3466
+ }
3467
+ } catch {
3468
+ parts.push(obs.facts);
3469
+ }
3470
+ }
3471
+ if (obs.concepts) {
3472
+ try {
3473
+ const concepts = JSON.parse(obs.concepts);
3474
+ if (Array.isArray(concepts) && concepts.length > 0) {
3475
+ parts.push(concepts.join(", "));
3476
+ }
3477
+ } catch {}
3478
+ }
3479
+ return parts.join(`
3480
+
3481
+ `);
3482
+ }
3483
+ function composeChatEmbeddingText(text) {
3484
+ return text.replace(/\s+/g, " ").trim().slice(0, 2000);
3485
+ }
3486
+ async function getPipeline() {
3487
+ if (_pipeline)
3488
+ return _pipeline;
3489
+ if (_available === false)
3490
+ return null;
3491
+ try {
3492
+ const { pipeline } = await import("@xenova/transformers");
3493
+ _pipeline = await pipeline("feature-extraction", MODEL_NAME);
3494
+ _available = true;
3495
+ return _pipeline;
3496
+ } catch (err) {
3497
+ _available = false;
3498
+ console.error(`[engrm] Local embedding model unavailable: ${err instanceof Error ? err.message : String(err)}`);
3499
+ return null;
3500
+ }
3501
+ }
3502
+
3534
3503
  // src/tools/save.ts
3535
3504
  import { relative, isAbsolute } from "node:path";
3536
3505
 
@@ -4924,7 +4893,7 @@ async function main() {
4924
4893
  }
4925
4894
  } catch {}
4926
4895
  }
4927
- await pushOnce(db, config);
4896
+ await pushOnce(db, config, { timeoutMs: 4000 });
4928
4897
  try {
4929
4898
  if (event.session_id) {
4930
4899
  const metrics = readSessionMetrics(event.session_id);
@@ -230,7 +230,8 @@ function createDefaultConfig() {
230
230
  project_name: "shared-experience",
231
231
  namespace: "",
232
232
  api_key: ""
233
- }
233
+ },
234
+ tool_profile: "full"
234
235
  };
235
236
  }
236
237
  function loadConfig() {
@@ -301,7 +302,8 @@ function loadConfig() {
301
302
  project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
302
303
  namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
303
304
  api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
304
- }
305
+ },
306
+ tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
305
307
  };
306
308
  }
307
309
  function saveConfig(config) {
@@ -356,6 +358,11 @@ function asObserverMode(value, fallback) {
356
358
  return value;
357
359
  return fallback;
358
360
  }
361
+ function asToolProfile(value, fallback) {
362
+ if (value === "full" || value === "memory")
363
+ return value;
364
+ return fallback;
365
+ }
359
366
  function asTeams(value, fallback) {
360
367
  if (!Array.isArray(value))
361
368
  return fallback;
package/dist/server.js CHANGED
@@ -13643,7 +13643,8 @@ function createDefaultConfig() {
13643
13643
  project_name: "shared-experience",
13644
13644
  namespace: "",
13645
13645
  api_key: ""
13646
- }
13646
+ },
13647
+ tool_profile: "full"
13647
13648
  };
13648
13649
  }
13649
13650
  function loadConfig() {
@@ -13714,7 +13715,8 @@ function loadConfig() {
13714
13715
  project_name: asString(config2["fleet"]?.["project_name"], defaults.fleet.project_name),
13715
13716
  namespace: asString(config2["fleet"]?.["namespace"], defaults.fleet.namespace),
13716
13717
  api_key: asString(config2["fleet"]?.["api_key"], defaults.fleet.api_key)
13717
- }
13718
+ },
13719
+ tool_profile: asToolProfile(config2["tool_profile"], defaults.tool_profile)
13718
13720
  };
13719
13721
  }
13720
13722
  function saveConfig(config2) {
@@ -13769,6 +13771,11 @@ function asObserverMode(value, fallback) {
13769
13771
  return value;
13770
13772
  return fallback;
13771
13773
  }
13774
+ function asToolProfile(value, fallback) {
13775
+ if (value === "full" || value === "memory")
13776
+ return value;
13777
+ return fallback;
13778
+ }
13772
13779
  function asTeams(value, fallback) {
13773
13780
  if (!Array.isArray(value))
13774
13781
  return fallback;
@@ -19471,6 +19478,26 @@ function getActivityFeed(db, input) {
19471
19478
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
19472
19479
  import { homedir as homedir2 } from "node:os";
19473
19480
  import { join as join3 } from "node:path";
19481
+
19482
+ // src/tool-profiles.ts
19483
+ var MEMORY_PROFILE_TOOLS = [
19484
+ "save_observation",
19485
+ "search_recall",
19486
+ "resume_thread",
19487
+ "list_recall_items",
19488
+ "load_recall_item",
19489
+ "recent_chat",
19490
+ "search_chat",
19491
+ "refresh_chat_recall",
19492
+ "repair_recall"
19493
+ ];
19494
+ function getEnabledToolNames(profile) {
19495
+ if (!profile || profile === "full")
19496
+ return null;
19497
+ return new Set(MEMORY_PROFILE_TOOLS);
19498
+ }
19499
+
19500
+ // src/tools/capture-status.ts
19474
19501
  var LEGACY_CODEX_SERVER_NAME = `candengo-${"mem"}`;
19475
19502
  function getCaptureStatus(db, input = {}) {
19476
19503
  const hours = Math.max(1, Math.min(input.lookback_hours ?? 24, 24 * 30));
@@ -19574,6 +19601,8 @@ function getCaptureStatus(db, input = {}) {
19574
19601
  http_enabled: Boolean(config2?.http?.enabled || process.env.ENGRM_HTTP_PORT),
19575
19602
  http_port: config2?.http?.port ?? (process.env.ENGRM_HTTP_PORT ? Number(process.env.ENGRM_HTTP_PORT) : null),
19576
19603
  http_bearer_token_count: config2?.http?.bearer_tokens?.length ?? 0,
19604
+ tool_profile: config2?.tool_profile ?? "full",
19605
+ enabled_tool_count: config2 ? getEnabledToolNames(config2.tool_profile)?.size ?? null : null,
19577
19606
  fleet_project_name: config2?.fleet?.project_name ?? null,
19578
19607
  fleet_configured: Boolean(config2?.fleet?.namespace && config2?.fleet?.api_key),
19579
19608
  claude_mcp_registered: claudeMcpRegistered,
@@ -21598,6 +21627,7 @@ class VectorClient {
21598
21627
  apiKey;
21599
21628
  siteId;
21600
21629
  namespace;
21630
+ timeoutMs;
21601
21631
  constructor(config2, overrides = {}) {
21602
21632
  const baseUrl = getBaseUrl(config2);
21603
21633
  const apiKey = overrides.apiKey ?? getApiKey(config2);
@@ -21608,6 +21638,7 @@ class VectorClient {
21608
21638
  this.apiKey = apiKey;
21609
21639
  this.siteId = overrides.siteId ?? config2.site_id;
21610
21640
  this.namespace = overrides.namespace ?? config2.namespace;
21641
+ this.timeoutMs = overrides.timeoutMs ?? 1e4;
21611
21642
  }
21612
21643
  static isConfigured(config2) {
21613
21644
  return getApiKey(config2) !== null && getBaseUrl(config2) !== null;
@@ -21670,6 +21701,7 @@ class VectorClient {
21670
21701
  if (body && method !== "GET") {
21671
21702
  init.body = JSON.stringify(body);
21672
21703
  }
21704
+ init.signal = AbortSignal.timeout(this.timeoutMs);
21673
21705
  const response = await fetch(url2, init);
21674
21706
  if (!response.ok) {
21675
21707
  const text = await response.text().catch(() => "");
@@ -21919,7 +21951,7 @@ function buildSummaryVectorDocument(summary, config2, project, targetOrObservati
21919
21951
  }
21920
21952
  };
21921
21953
  }
21922
- async function pushOutbox(db, config2, batchSize = 50) {
21954
+ async function pushOutbox(db, config2, batchSize = 50, options = {}) {
21923
21955
  const entries = getPendingEntries(db, batchSize);
21924
21956
  let pushed = 0;
21925
21957
  let failed = 0;
@@ -22030,7 +22062,8 @@ async function pushOutbox(db, config2, batchSize = 50) {
22030
22062
  const client = new VectorClient(config2, {
22031
22063
  apiKey: target.apiKey,
22032
22064
  namespace: target.namespace,
22033
- siteId: target.siteId
22065
+ siteId: target.siteId,
22066
+ timeoutMs: options.timeoutMs
22034
22067
  });
22035
22068
  try {
22036
22069
  await client.batchIngest(items.map((b) => b.doc));
@@ -23085,6 +23118,14 @@ function buildServer() {
23085
23118
  name: "engrm",
23086
23119
  version: "0.4.42"
23087
23120
  });
23121
+ const enabledToolNames = getEnabledToolNames(config2.tool_profile);
23122
+ const originalTool = server.tool.bind(server);
23123
+ server.tool = (name, ...args) => {
23124
+ if (enabledToolNames && !enabledToolNames.has(name)) {
23125
+ return server;
23126
+ }
23127
+ return originalTool(name, ...args);
23128
+ };
23088
23129
  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.", {
23089
23130
  type: exports_external.enum([
23090
23131
  "bugfix",
@@ -24146,6 +24187,7 @@ ${observationLines}`
24146
24187
  text: `Schema: v${result.schema_version} (${result.schema_current ? "current" : "outdated"})
24147
24188
  ` + `HTTP MCP: ${result.http_enabled ? `enabled${result.http_port ? ` (:${result.http_port})` : ""}` : "disabled"}
24148
24189
  ` + `HTTP bearer tokens: ${result.http_bearer_token_count}
24190
+ ` + `Tool profile: ${result.tool_profile}${typeof result.enabled_tool_count === "number" ? ` (${result.enabled_tool_count} tools)` : ""}
24149
24191
  ` + `Fleet project: ${result.fleet_project_name ?? "none"}
24150
24192
  ` + `Fleet sync: ${result.fleet_configured ? "configured" : "not configured"}
24151
24193
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engrm",
3
- "version": "0.4.42",
3
+ "version": "0.4.43",
4
4
  "description": "Shared memory across devices, sessions, and agents, with thin MCP tools for durable capture, live continuity, and Hermes-ready remote MCP support",
5
5
  "mcpName": "io.github.dr12hes/engrm",
6
6
  "type": "module",