@prom.codes/memory-mcp 0.11.0 → 0.11.1

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.
Files changed (2) hide show
  1. package/dist/bin.js +80 -8
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -89,6 +89,9 @@ function isNewerVersion(latest, current) {
89
89
  return true;
90
90
  return false;
91
91
  }
92
+ function cachedLatestIsStale(cachedLatest, minVersion) {
93
+ return minVersion !== void 0 && cachedLatest !== null && isNewerVersion(minVersion, cachedLatest);
94
+ }
92
95
  function cachePath(dir, name) {
93
96
  const safe = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
94
97
  return join(dir, `.update-check-${safe}.json`);
@@ -175,7 +178,7 @@ async function checkForUpdate(options) {
175
178
  const now = Date.now();
176
179
  if (!force) {
177
180
  const cached = await readCache(file);
178
- if (cached !== null && now - cached.checkedAt < cacheTtlMs) {
181
+ if (cached !== null && now - cached.checkedAt < cacheTtlMs && !cachedLatestIsStale(cached.latest, version)) {
179
182
  const updateAvailable2 = cached.latest !== null && isNewerVersion(cached.latest, version);
180
183
  if (updateAvailable2)
181
184
  notify(log, name, version, cached.latest);
@@ -202,15 +205,16 @@ async function checkForUpdate(options) {
202
205
  return { ...base, latest, checked: true, updateAvailable };
203
206
  }
204
207
  async function getLatestVersion(name, options = {}) {
205
- const { env = process.env, fetch: fetchImpl = globalThis.fetch, cacheDir = join(homedir(), ".prometheus"), cacheTtlMs = DEFAULT_TTL_MS, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
208
+ const { env = process.env, fetch: fetchImpl = globalThis.fetch, cacheDir = join(homedir(), ".prometheus"), cacheTtlMs = DEFAULT_TTL_MS, timeoutMs = DEFAULT_TIMEOUT_MS, minVersion } = options;
206
209
  const file = cachePath(cacheDir, name);
207
210
  const cached = await readCache(file);
208
211
  const now = Date.now();
209
- if (cached !== null && cached.latest !== null && now - cached.checkedAt < cacheTtlMs) {
212
+ const stale = cachedLatestIsStale(cached?.latest ?? null, minVersion);
213
+ if (cached !== null && cached.latest !== null && now - cached.checkedAt < cacheTtlMs && !stale) {
210
214
  return cached.latest;
211
215
  }
212
216
  if (OPT_OUT_RE.test(env.PROMETHEUS_NO_UPDATE_CHECK ?? "") || typeof fetchImpl !== "function") {
213
- return cached?.latest ?? null;
217
+ return stale ? null : cached?.latest ?? null;
214
218
  }
215
219
  const latest = await fetchLatest(name, fetchImpl, timeoutMs);
216
220
  if (latest !== null) {
@@ -218,7 +222,7 @@ async function getLatestVersion(name, options = {}) {
218
222
  await writeCache(file, { checkedAt: now, latest });
219
223
  return latest;
220
224
  }
221
- return cached?.latest ?? null;
225
+ return stale ? null : cached?.latest ?? null;
222
226
  }
223
227
  function notify(log, name, current, latest) {
224
228
  log(`${name}: a newer version (${latest}) is available \u2014 you are on ${current}. npx users get it automatically on the next restart; for a global install run \`npm update -g ${name}\`. (Set PROMETHEUS_NO_UPDATE_CHECK=1 to silence.)
@@ -242,7 +246,8 @@ async function buildUpdateStatus(pkgName, currentVersion, options = {}) {
242
246
  ...options.env !== void 0 ? { env: options.env } : {},
243
247
  ...options.fetch !== void 0 ? { fetch: options.fetch } : {},
244
248
  ...options.cacheDir !== void 0 ? { cacheDir: options.cacheDir } : {},
245
- timeoutMs: options.timeoutMs ?? 1500
249
+ timeoutMs: options.timeoutMs ?? 1500,
250
+ minVersion: currentVersion
246
251
  });
247
252
  } catch {
248
253
  latest = null;
@@ -338,6 +343,57 @@ function startHeartbeat(options) {
338
343
  };
339
344
  }
340
345
 
346
+ // ../shared/dist/idle-watchdog.js
347
+ var DEFAULT_IDLE_EXIT_MS = 30 * 6e4;
348
+ var IDLE_CHECK_INTERVAL_MS = 6e4;
349
+ var IDLE_EXIT_ENV = "PROMETHEUS_IDLE_EXIT_MS";
350
+ function parseIdleExitMs(env) {
351
+ const raw = (env[IDLE_EXIT_ENV] ?? "").trim();
352
+ if (raw === "")
353
+ return void 0;
354
+ const n = Number(raw);
355
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
356
+ }
357
+ function createIdleWatchdog(options) {
358
+ const env = options.env ?? process.env;
359
+ const idleMs = options.idleMs ?? parseIdleExitMs(env) ?? DEFAULT_IDLE_EXIT_MS;
360
+ const now = options.now ?? Date.now;
361
+ const checkIntervalMs = options.checkIntervalMs ?? Math.max(1e3, Math.min(IDLE_CHECK_INTERVAL_MS, idleMs));
362
+ if (!(idleMs > 0)) {
363
+ return { touch() {
364
+ }, stop() {
365
+ }, idleMs: 0 };
366
+ }
367
+ let lastActivity = now();
368
+ let stopped = false;
369
+ let fired = false;
370
+ const timer = setInterval(() => {
371
+ if (stopped || fired)
372
+ return;
373
+ const idleFor = now() - lastActivity;
374
+ if (idleFor >= idleMs) {
375
+ fired = true;
376
+ clearInterval(timer);
377
+ options.onIdle(`idle for ${Math.round(idleFor / 1e3)}s with no client activity (set ${IDLE_EXIT_ENV}=0 to disable)`);
378
+ }
379
+ }, checkIntervalMs);
380
+ timer.unref?.();
381
+ return {
382
+ idleMs,
383
+ touch() {
384
+ if (stopped)
385
+ return;
386
+ lastActivity = now();
387
+ },
388
+ stop() {
389
+ if (stopped)
390
+ return;
391
+ stopped = true;
392
+ clearInterval(timer);
393
+ }
394
+ };
395
+ }
396
+
341
397
  // dist/composition.js
342
398
  import { createHash } from "node:crypto";
343
399
  import { homedir as homedir4 } from "node:os";
@@ -3315,7 +3371,7 @@ ${f.value}`);
3315
3371
  embeddingsError = err instanceof Error ? err.message : String(err);
3316
3372
  }
3317
3373
  }
3318
- const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.11.0", { isDevBuild: false });
3374
+ const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.11.1", { isDevBuild: false });
3319
3375
  const summary = deps.rootIsHomeOrFsRoot ? `Memory at ${dbPath}: ${stats.total} records, but the workspace resolved to ${workspaceRoot} (home/root) \u2014 open a project folder so memories scope and mirror correctly.` : `Memory at ${dbPath}: ${stats.total} records for project "${projectName}".${update.updateAvailable === true ? ` Update available: ${update.current} \u2192 ${update.latest} (ask me to run update via the context server's update_servers tool).` : ""}`;
3320
3376
  return textResult({
3321
3377
  installed: true,
@@ -3351,7 +3407,7 @@ ${f.value}`);
3351
3407
  // dist/server.js
3352
3408
  var SERVER_IDENTITY = {
3353
3409
  name: "prometheus-memory-mcp",
3354
- version: "0.11.0",
3410
+ version: "0.11.1",
3355
3411
  title: "prom.codes Memory"
3356
3412
  };
3357
3413
  var SERVER_INSTRUCTIONS = "Persistent agent memory for this workspace \u2014 USE IT PROACTIVELY; the user will not tell you to. Protocol:\n1. ONE-TIME: if this workspace has no Prometheus memory rule yet, call memory_setup now (idempotent) so the protocol is installed into the runtime rule files and survives future sessions. (The server also auto-installs it on startup when a project rule file already exists \u2014 memory_setup covers the rest.)\n2. SESSION START: before any non-trivial task, call memory_read to recall facts, decisions and procedures from earlier sessions.\n3. DURING WORK: when the user states a durable preference, decision, correction or project fact, store it with memory_write (semantic for facts, procedural for how-tos) \u2014 without being asked.\n4. LOOK-UP: use memory_search for keyword recall when memory_read is not specific enough.\n5. SESSION END: consolidate what was learned with memory_capture.\nCall memory_status anytime to check what is stored and whether the rule is installed. Never store secrets, API keys or credentials \u2014 such writes are rejected.";
@@ -3385,6 +3441,7 @@ async function main() {
3385
3441
  registerTools(server, () => composedReady, {
3386
3442
  onToolCall: (tool) => heartbeat.update({ lastTool: tool, lastToolCallAt: Date.now() })
3387
3443
  });
3444
+ let watchdog = null;
3388
3445
  let shuttingDown = false;
3389
3446
  const shutdown = async (reason) => {
3390
3447
  if (shuttingDown)
@@ -3392,6 +3449,7 @@ async function main() {
3392
3449
  shuttingDown = true;
3393
3450
  process.stderr.write(`prometheus-memory-mcp: ${reason}, shutting down
3394
3451
  `);
3452
+ watchdog?.stop();
3395
3453
  heartbeat.stop();
3396
3454
  try {
3397
3455
  await server.close();
@@ -3406,6 +3464,18 @@ async function main() {
3406
3464
  process.stdin.once("end", () => void shutdown("stdin closed (client exited)"));
3407
3465
  process.stdin.once("close", () => void shutdown("stdin closed (client exited)"));
3408
3466
  server.server.onclose = () => void shutdown("transport closed (client exited)");
3467
+ watchdog = createIdleWatchdog({ onIdle: (reason) => void shutdown(reason), env });
3468
+ if (watchdog.idleMs > 0) {
3469
+ process.stderr.write(`prometheus-memory-mcp: idle self-exit armed (${Math.round(watchdog.idleMs / 6e4)} min of no client activity)
3470
+ `);
3471
+ }
3472
+ const armIdleWatch = () => {
3473
+ const prev = transport.onmessage?.bind(transport);
3474
+ transport.onmessage = ((...args) => {
3475
+ watchdog?.touch();
3476
+ prev?.(...args);
3477
+ });
3478
+ };
3409
3479
  const boot = (override, via) => {
3410
3480
  composed = composeFromEnv({
3411
3481
  env,
@@ -3440,6 +3510,7 @@ async function main() {
3440
3510
  if (eagerVia !== null) {
3441
3511
  boot(void 0, eagerVia);
3442
3512
  await server.connect(transport);
3513
+ armIdleWatch();
3443
3514
  return;
3444
3515
  }
3445
3516
  let booted = false;
@@ -3463,6 +3534,7 @@ async function main() {
3463
3534
  void resolveAndBoot();
3464
3535
  };
3465
3536
  await server.connect(transport);
3537
+ armIdleWatch();
3466
3538
  const t = setTimeout(() => void resolveAndBoot(), 5e3);
3467
3539
  t.unref?.();
3468
3540
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/memory-mcp",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {