@prom.codes/memory-mcp 0.18.0 → 0.19.0

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 +104 -11
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -28,9 +28,11 @@ var DOCUMENT_LANGUAGE_IDS = [
28
28
  "yaml",
29
29
  "toml"
30
30
  ];
31
+ var COMPOSITE_LANGUAGE_IDS = ["vue"];
31
32
  var LANGUAGE_IDS = [
32
33
  ...GRAMMAR_LANGUAGE_IDS,
33
- ...DOCUMENT_LANGUAGE_IDS
34
+ ...DOCUMENT_LANGUAGE_IDS,
35
+ ...COMPOSITE_LANGUAGE_IDS
34
36
  ];
35
37
 
36
38
  // ../shared/dist/update-check.js
@@ -259,15 +261,19 @@ function notify(log, name, current, latest) {
259
261
 
260
262
  // ../shared/dist/update-info.js
261
263
  async function buildUpdateStatus(pkgName, currentVersion, options = {}) {
262
- const base = { current: currentVersion, command: await resolveUpgradeCommand() };
263
264
  if (options.isDevBuild === true) {
264
265
  return {
265
- ...base,
266
+ current: currentVersion,
267
+ // A workspace build does not need an npm-version probe: the command is
268
+ // invariant across supported npm releases, and avoiding the subprocess
269
+ // keeps status responsive during deferred-root startup.
270
+ command: UPGRADE_COMMAND,
266
271
  latest: null,
267
272
  updateAvailable: null,
268
273
  note: "dev build (workspace) \u2014 version comparison skipped"
269
274
  };
270
275
  }
276
+ const base = { current: currentVersion, command: await resolveUpgradeCommand() };
271
277
  let latest = null;
272
278
  try {
273
279
  latest = await getLatestVersion(pkgName, {
@@ -388,7 +394,12 @@ function startHeartbeat(options) {
388
394
  // ../shared/dist/idle-watchdog.js
389
395
  var DEFAULT_IDLE_EXIT_MS = 30 * 6e4;
390
396
  var IDLE_CHECK_INTERVAL_MS = 6e4;
397
+ var DEFAULT_PROBE_TIMEOUT_MS = 5e3;
398
+ var DEFAULT_PROBE_RETRIES = 2;
399
+ var DEFAULT_MAX_PROBE_EXTENSIONS = 8;
391
400
  var IDLE_EXIT_ENV = "PROMETHEUS_IDLE_EXIT_MS";
401
+ var IDLE_MAX_PINGS_ENV = "PROMETHEUS_IDLE_MAX_PINGS";
402
+ var IDLE_PROBE_MS_ENV = "PROMETHEUS_IDLE_PROBE_MS";
392
403
  function parseIdleExitMs(env) {
393
404
  const raw = (env[IDLE_EXIT_ENV] ?? "").trim();
394
405
  if (raw === "")
@@ -396,6 +407,20 @@ function parseIdleExitMs(env) {
396
407
  const n = Number(raw);
397
408
  return Number.isFinite(n) && n >= 0 ? n : void 0;
398
409
  }
410
+ function parseMaxProbeExtensions(env) {
411
+ const raw = (env[IDLE_MAX_PINGS_ENV] ?? "").trim();
412
+ if (raw === "")
413
+ return void 0;
414
+ const n = Number(raw);
415
+ return Number.isInteger(n) && n >= 0 ? n : void 0;
416
+ }
417
+ function parseProbeTimeoutMs(env) {
418
+ const raw = (env[IDLE_PROBE_MS_ENV] ?? "").trim();
419
+ if (raw === "")
420
+ return void 0;
421
+ const n = Number(raw);
422
+ return Number.isFinite(n) && n > 0 ? n : void 0;
423
+ }
399
424
  function createIdleWatchdog(options) {
400
425
  const env = options.env ?? process.env;
401
426
  const idleMs = options.idleMs ?? parseIdleExitMs(env) ?? DEFAULT_IDLE_EXIT_MS;
@@ -406,18 +431,72 @@ function createIdleWatchdog(options) {
406
431
  }, stop() {
407
432
  }, idleMs: 0 };
408
433
  }
434
+ const probe = options.probe;
435
+ const probeTimeoutMs = options.probeTimeoutMs ?? parseProbeTimeoutMs(env) ?? DEFAULT_PROBE_TIMEOUT_MS;
436
+ const probeRetries = Math.max(1, options.probeRetries ?? DEFAULT_PROBE_RETRIES);
437
+ const maxProbeExtensions = options.maxProbeExtensions ?? parseMaxProbeExtensions(env) ?? DEFAULT_MAX_PROBE_EXTENSIONS;
438
+ const yieldToIo = options.yieldToIo ?? ((fn) => {
439
+ const t = setImmediate(fn);
440
+ t.unref?.();
441
+ });
409
442
  let lastActivity = now();
410
443
  let stopped = false;
411
444
  let fired = false;
445
+ let probing = false;
446
+ let failedProbes = 0;
447
+ let probeOnlyExtensions = 0;
448
+ const fire = (idleFor, why) => {
449
+ fired = true;
450
+ clearInterval(timer);
451
+ const detail = why === "unanswered" ? ` and ${probeRetries} liveness pings went unanswered (${Math.round(probeTimeoutMs / 1e3)}s each)` : why === "capped" ? ` \u2014 the client answered ${probeOnlyExtensions} liveness pings in a row but sent no request in any of those windows, so this server is attached to a session nobody is using (raise or disable with ${IDLE_MAX_PINGS_ENV})` : "";
452
+ options.onIdle(`idle for ${Math.round(idleFor / 1e3)}s with no client activity` + detail + ` (set ${IDLE_EXIT_ENV}=0 to disable)`);
453
+ };
454
+ const launchProbe = () => {
455
+ probing = true;
456
+ const startedAt = now();
457
+ let settled = false;
458
+ let timeoutTimer = null;
459
+ const finish = (alive) => {
460
+ if (settled)
461
+ return;
462
+ settled = true;
463
+ if (timeoutTimer !== null)
464
+ clearTimeout(timeoutTimer);
465
+ probing = false;
466
+ if (stopped || fired)
467
+ return;
468
+ if (alive || lastActivity > startedAt) {
469
+ failedProbes = 0;
470
+ lastActivity = now();
471
+ probeOnlyExtensions += 1;
472
+ if (maxProbeExtensions > 0 && probeOnlyExtensions >= maxProbeExtensions) {
473
+ fire(idleMs * probeOnlyExtensions, "capped");
474
+ }
475
+ return;
476
+ }
477
+ failedProbes += 1;
478
+ if (failedProbes < probeRetries)
479
+ return;
480
+ fire(now() - lastActivity, "unanswered");
481
+ };
482
+ timeoutTimer = setTimeout(() => {
483
+ yieldToIo(() => finish(false));
484
+ }, probeTimeoutMs);
485
+ timeoutTimer.unref?.();
486
+ void probe().then(() => finish(true), () => finish(false));
487
+ };
412
488
  const timer = setInterval(() => {
413
489
  if (stopped || fired)
414
490
  return;
415
491
  const idleFor = now() - lastActivity;
416
- if (idleFor >= idleMs) {
417
- fired = true;
418
- clearInterval(timer);
419
- options.onIdle(`idle for ${Math.round(idleFor / 1e3)}s with no client activity (set ${IDLE_EXIT_ENV}=0 to disable)`);
492
+ if (idleFor < idleMs)
493
+ return;
494
+ if (probe === void 0) {
495
+ fire(idleFor, "silent");
496
+ return;
420
497
  }
498
+ if (!probing)
499
+ launchProbe();
421
500
  }, checkIntervalMs);
422
501
  timer.unref?.();
423
502
  return {
@@ -426,6 +505,10 @@ function createIdleWatchdog(options) {
426
505
  if (stopped)
427
506
  return;
428
507
  lastActivity = now();
508
+ if (!probing) {
509
+ failedProbes = 0;
510
+ probeOnlyExtensions = 0;
511
+ }
429
512
  },
430
513
  stop() {
431
514
  if (stopped)
@@ -4648,7 +4731,7 @@ ${f.value}`);
4648
4731
  embeddingsError = err instanceof Error ? err.message : String(err);
4649
4732
  }
4650
4733
  }
4651
- const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.18.0", { isDevBuild: false });
4734
+ const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.19.0", { isDevBuild: false });
4652
4735
  let recorder;
4653
4736
  try {
4654
4737
  const scopes = ["project-local", "project", "user"];
@@ -4730,7 +4813,7 @@ ${f.value}`);
4730
4813
  // dist/server.js
4731
4814
  var SERVER_IDENTITY = {
4732
4815
  name: "prom.codes-memory",
4733
- version: "0.18.0",
4816
+ version: "0.19.0",
4734
4817
  title: "prom.codes Memory"
4735
4818
  };
4736
4819
  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 prom.codes 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.";
@@ -4803,9 +4886,19 @@ async function main() {
4803
4886
  process.stdin.once("end", () => void shutdown("stdin closed (client exited)"));
4804
4887
  process.stdin.once("close", () => void shutdown("stdin closed (client exited)"));
4805
4888
  server.server.onclose = () => void shutdown("transport closed (client exited)");
4806
- watchdog = createIdleWatchdog({ onIdle: (reason) => void shutdown(reason), env });
4889
+ watchdog = createIdleWatchdog({
4890
+ onIdle: (reason) => void shutdown(reason),
4891
+ env,
4892
+ // Before reaping, ask the client via MCP ping: a live-but-quiet session
4893
+ // (e.g. Claude Code with deferred tools, no memory call for 30 min —
4894
+ // 2026-08-12) answers and is never dropped; only an abandoned server
4895
+ // whose pong never comes still reaps itself.
4896
+ probe: async () => {
4897
+ await server.server.ping();
4898
+ }
4899
+ });
4807
4900
  if (watchdog.idleMs > 0) {
4808
- process.stderr.write(`prom.codes-memory: idle self-exit armed (${Math.round(watchdog.idleMs / 6e4)} min of no client activity)
4901
+ process.stderr.write(`prom.codes-memory: idle self-exit armed (${Math.round(watchdog.idleMs / 6e4)} min of no client activity; client is pinged before exit)
4809
4902
  `);
4810
4903
  }
4811
4904
  const armIdleWatch = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/memory-mcp",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {