@prom.codes/memory-mcp 0.11.0 → 0.11.2
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/dist/bin.js +113 -14
- 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
|
-
|
|
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";
|
|
@@ -1020,6 +1076,7 @@ var DEFAULT_BATCH4 = 100;
|
|
|
1020
1076
|
var DEFAULT_BATCH_CHARS2 = 4e5;
|
|
1021
1077
|
var DEFAULT_RETRIES4 = 4;
|
|
1022
1078
|
var DEFAULT_BACKOFF4 = 250;
|
|
1079
|
+
var DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
1023
1080
|
function sleep4(ms, signal) {
|
|
1024
1081
|
return new Promise((resolve3, reject) => {
|
|
1025
1082
|
if (signal?.aborted === true) {
|
|
@@ -1052,6 +1109,7 @@ var PrometheusRerankProvider = class {
|
|
|
1052
1109
|
#maxBatchChars;
|
|
1053
1110
|
#maxRetries;
|
|
1054
1111
|
#retryBaseMs;
|
|
1112
|
+
#timeoutMs;
|
|
1055
1113
|
#fetch;
|
|
1056
1114
|
#creditsUsed = 0;
|
|
1057
1115
|
constructor(opts) {
|
|
@@ -1068,8 +1126,12 @@ var PrometheusRerankProvider = class {
|
|
|
1068
1126
|
this.#url = `${(opts.baseUrl ?? DEFAULT_BASE2).replace(/\/+$/, "")}/rerank`;
|
|
1069
1127
|
this.#batchSize = opts.batchSize ?? DEFAULT_BATCH4;
|
|
1070
1128
|
this.#maxBatchChars = opts.maxBatchChars ?? DEFAULT_BATCH_CHARS2;
|
|
1129
|
+
if (opts.timeoutMs !== void 0 && (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs < 0)) {
|
|
1130
|
+
throw new Error(`PrometheusRerankProvider: timeoutMs must be a non-negative integer (0 disables), got ${opts.timeoutMs}`);
|
|
1131
|
+
}
|
|
1071
1132
|
this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES4;
|
|
1072
1133
|
this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF4;
|
|
1134
|
+
this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
1073
1135
|
this.#fetch = opts.fetch ?? fetch;
|
|
1074
1136
|
}
|
|
1075
1137
|
/** Cumulative credits charged across all rerank calls of this instance. */
|
|
@@ -1128,8 +1190,6 @@ var PrometheusRerankProvider = class {
|
|
|
1128
1190
|
},
|
|
1129
1191
|
body: JSON.stringify({ query, documents: batch.map((c) => c.text) })
|
|
1130
1192
|
};
|
|
1131
|
-
if (signal !== void 0)
|
|
1132
|
-
init.signal = signal;
|
|
1133
1193
|
const payload = await this.#requestJson(init, signal);
|
|
1134
1194
|
if (payload?.ok !== true || !Array.isArray(payload.results)) {
|
|
1135
1195
|
throw nonRetryable4("prometheus-rerank: malformed rerank response");
|
|
@@ -1161,8 +1221,25 @@ var PrometheusRerankProvider = class {
|
|
|
1161
1221
|
let attempt = 0;
|
|
1162
1222
|
let lastError = null;
|
|
1163
1223
|
while (attempt <= this.#maxRetries) {
|
|
1224
|
+
const controller = new AbortController();
|
|
1225
|
+
let timedOut = false;
|
|
1226
|
+
let timer;
|
|
1227
|
+
if (this.#timeoutMs > 0) {
|
|
1228
|
+
timer = setTimeout(() => {
|
|
1229
|
+
timedOut = true;
|
|
1230
|
+
controller.abort();
|
|
1231
|
+
}, this.#timeoutMs);
|
|
1232
|
+
timer.unref?.();
|
|
1233
|
+
}
|
|
1234
|
+
const onParentAbort = () => controller.abort();
|
|
1235
|
+
if (signal !== void 0) {
|
|
1236
|
+
if (signal.aborted)
|
|
1237
|
+
controller.abort();
|
|
1238
|
+
else
|
|
1239
|
+
signal.addEventListener("abort", onParentAbort, { once: true });
|
|
1240
|
+
}
|
|
1164
1241
|
try {
|
|
1165
|
-
const res = await this.#fetch(this.#url, init);
|
|
1242
|
+
const res = await this.#fetch(this.#url, { ...init, signal: controller.signal });
|
|
1166
1243
|
if (res.status >= 500 && res.status < 600) {
|
|
1167
1244
|
lastError = new Error(`prometheus-rerank: HTTP ${res.status}`);
|
|
1168
1245
|
attempt += 1;
|
|
@@ -1178,15 +1255,21 @@ var PrometheusRerankProvider = class {
|
|
|
1178
1255
|
}
|
|
1179
1256
|
return await res.json();
|
|
1180
1257
|
} catch (err) {
|
|
1181
|
-
if (err?.name === "AbortError")
|
|
1258
|
+
if (err?.name === "AbortError" && !timedOut)
|
|
1182
1259
|
throw err;
|
|
1183
1260
|
if (err?.nonRetryable === true)
|
|
1184
1261
|
throw err;
|
|
1262
|
+
const retryErr = timedOut ? new Error(`prometheus-rerank: request timed out after ${this.#timeoutMs}ms`) : err;
|
|
1185
1263
|
if (attempt >= this.#maxRetries)
|
|
1186
|
-
throw
|
|
1187
|
-
lastError =
|
|
1264
|
+
throw retryErr;
|
|
1265
|
+
lastError = retryErr;
|
|
1188
1266
|
attempt += 1;
|
|
1189
1267
|
await sleep4(this.#retryBaseMs * 2 ** (attempt - 1), signal);
|
|
1268
|
+
} finally {
|
|
1269
|
+
if (timer !== void 0)
|
|
1270
|
+
clearTimeout(timer);
|
|
1271
|
+
if (signal !== void 0)
|
|
1272
|
+
signal.removeEventListener("abort", onParentAbort);
|
|
1190
1273
|
}
|
|
1191
1274
|
}
|
|
1192
1275
|
throw lastError instanceof Error ? lastError : new Error(`prometheus-rerank: exhausted ${this.#maxRetries} retries`);
|
|
@@ -3315,7 +3398,7 @@ ${f.value}`);
|
|
|
3315
3398
|
embeddingsError = err instanceof Error ? err.message : String(err);
|
|
3316
3399
|
}
|
|
3317
3400
|
}
|
|
3318
|
-
const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.11.
|
|
3401
|
+
const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.11.2", { isDevBuild: false });
|
|
3319
3402
|
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
3403
|
return textResult({
|
|
3321
3404
|
installed: true,
|
|
@@ -3351,7 +3434,7 @@ ${f.value}`);
|
|
|
3351
3434
|
// dist/server.js
|
|
3352
3435
|
var SERVER_IDENTITY = {
|
|
3353
3436
|
name: "prometheus-memory-mcp",
|
|
3354
|
-
version: "0.11.
|
|
3437
|
+
version: "0.11.2",
|
|
3355
3438
|
title: "prom.codes Memory"
|
|
3356
3439
|
};
|
|
3357
3440
|
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 +3468,7 @@ async function main() {
|
|
|
3385
3468
|
registerTools(server, () => composedReady, {
|
|
3386
3469
|
onToolCall: (tool) => heartbeat.update({ lastTool: tool, lastToolCallAt: Date.now() })
|
|
3387
3470
|
});
|
|
3471
|
+
let watchdog = null;
|
|
3388
3472
|
let shuttingDown = false;
|
|
3389
3473
|
const shutdown = async (reason) => {
|
|
3390
3474
|
if (shuttingDown)
|
|
@@ -3392,6 +3476,7 @@ async function main() {
|
|
|
3392
3476
|
shuttingDown = true;
|
|
3393
3477
|
process.stderr.write(`prometheus-memory-mcp: ${reason}, shutting down
|
|
3394
3478
|
`);
|
|
3479
|
+
watchdog?.stop();
|
|
3395
3480
|
heartbeat.stop();
|
|
3396
3481
|
try {
|
|
3397
3482
|
await server.close();
|
|
@@ -3406,6 +3491,18 @@ async function main() {
|
|
|
3406
3491
|
process.stdin.once("end", () => void shutdown("stdin closed (client exited)"));
|
|
3407
3492
|
process.stdin.once("close", () => void shutdown("stdin closed (client exited)"));
|
|
3408
3493
|
server.server.onclose = () => void shutdown("transport closed (client exited)");
|
|
3494
|
+
watchdog = createIdleWatchdog({ onIdle: (reason) => void shutdown(reason), env });
|
|
3495
|
+
if (watchdog.idleMs > 0) {
|
|
3496
|
+
process.stderr.write(`prometheus-memory-mcp: idle self-exit armed (${Math.round(watchdog.idleMs / 6e4)} min of no client activity)
|
|
3497
|
+
`);
|
|
3498
|
+
}
|
|
3499
|
+
const armIdleWatch = () => {
|
|
3500
|
+
const prev = transport.onmessage?.bind(transport);
|
|
3501
|
+
transport.onmessage = ((...args) => {
|
|
3502
|
+
watchdog?.touch();
|
|
3503
|
+
prev?.(...args);
|
|
3504
|
+
});
|
|
3505
|
+
};
|
|
3409
3506
|
const boot = (override, via) => {
|
|
3410
3507
|
composed = composeFromEnv({
|
|
3411
3508
|
env,
|
|
@@ -3440,6 +3537,7 @@ async function main() {
|
|
|
3440
3537
|
if (eagerVia !== null) {
|
|
3441
3538
|
boot(void 0, eagerVia);
|
|
3442
3539
|
await server.connect(transport);
|
|
3540
|
+
armIdleWatch();
|
|
3443
3541
|
return;
|
|
3444
3542
|
}
|
|
3445
3543
|
let booted = false;
|
|
@@ -3463,6 +3561,7 @@ async function main() {
|
|
|
3463
3561
|
void resolveAndBoot();
|
|
3464
3562
|
};
|
|
3465
3563
|
await server.connect(transport);
|
|
3564
|
+
armIdleWatch();
|
|
3466
3565
|
const t = setTimeout(() => void resolveAndBoot(), 5e3);
|
|
3467
3566
|
t.unref?.();
|
|
3468
3567
|
}
|