mcp-agents-memory 0.9.23 → 0.9.24

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/build/index.js +170 -15
  2. package/package.json +3 -2
package/build/index.js CHANGED
@@ -57524,6 +57524,51 @@ init_users();
57524
57524
  // src/cold_path/project_alias_promoter.ts
57525
57525
  init_db();
57526
57526
  init_users();
57527
+
57528
+ // src/cold_path/alias_reject_gate.ts
57529
+ function pairKey(a, b) {
57530
+ return a < b ? `${a}:${b}` : `${b}:${a}`;
57531
+ }
57532
+ function groupPairHistory(rows) {
57533
+ const out = /* @__PURE__ */ new Map();
57534
+ for (const r of rows) {
57535
+ if (r.sourceId === r.targetId) continue;
57536
+ const k = pairKey(r.sourceId, r.targetId);
57537
+ const list = out.get(k);
57538
+ if (list) list.push(r);
57539
+ else out.set(k, [r]);
57540
+ }
57541
+ return out;
57542
+ }
57543
+ var isUserRejection = (r) => r.status === "rejected" && r.decidedBy === "user";
57544
+ function userRejectedDirection(rows, sourceId, targetId) {
57545
+ let hit = null;
57546
+ for (const r of rows ?? []) {
57547
+ if (!isUserRejection(r) || r.sourceId !== sourceId || r.targetId !== targetId) continue;
57548
+ if (!hit || r.decidedAt.getTime() > hit.decidedAt.getTime()) hit = r;
57549
+ }
57550
+ return hit ? hit.id : null;
57551
+ }
57552
+ function skipBeforeJudge(rows) {
57553
+ const all = rows ?? [];
57554
+ const rejections = all.filter(isUserRejection);
57555
+ if (rejections.length === 0) return false;
57556
+ const { sourceId: a, targetId: b } = rejections[0];
57557
+ if (userRejectedDirection(all, a, b) !== null && userRejectedDirection(all, b, a) !== null) return true;
57558
+ const lastRejection = Math.max(...rejections.map((r) => r.decidedAt.getTime()));
57559
+ return all.some((r) => !isUserRejection(r) && r.createdAt.getTime() > lastRejection);
57560
+ }
57561
+ function repeatsToSweep(rows) {
57562
+ const out = [];
57563
+ for (const r of rows ?? []) {
57564
+ if (r.status !== "pending" || r.manual) continue;
57565
+ const rejectedId = userRejectedDirection(rows, r.sourceId, r.targetId);
57566
+ if (rejectedId !== null) out.push({ pendingId: r.id, rejectedId });
57567
+ }
57568
+ return out;
57569
+ }
57570
+
57571
+ // src/cold_path/project_alias_promoter.ts
57527
57572
  var QUALIFYING_RELATIONS = /* @__PURE__ */ new Set([
57528
57573
  "rename",
57529
57574
  "alias",
@@ -57675,9 +57720,6 @@ function nameSimilarity(a, b) {
57675
57720
  const dice = bigramDice(a, b);
57676
57721
  return Math.max(tokenScore, dice);
57677
57722
  }
57678
- function pairKey(a, b) {
57679
- return a < b ? `${a}:${b}` : `${b}:${a}`;
57680
- }
57681
57723
  function uniqueSortedNumbers(values) {
57682
57724
  return Array.from(new Set(values)).sort((a, b) => a - b);
57683
57725
  }
@@ -58025,7 +58067,7 @@ async function addVectorCandidates(userId, tags, tagsById, pairs) {
58025
58067
  }
58026
58068
  }
58027
58069
  }
58028
- async function buildCandidatePairs(userId, tags, limit2) {
58070
+ async function buildCandidatePairs(userId, tags, limit2, exclude = () => false) {
58029
58071
  const tagsById = new Map(tags.map((tag) => [tag.id, tag]));
58030
58072
  const pairs = /* @__PURE__ */ new Map();
58031
58073
  await addExplicitStatementCandidates(userId, tags, tagsById, pairs);
@@ -58040,7 +58082,7 @@ async function buildCandidatePairs(userId, tags, limit2) {
58040
58082
  pair.signals.candidate_sources = Array.from(pair.sources);
58041
58083
  }
58042
58084
  }
58043
- return Array.from(pairs.values()).sort((a, b) => {
58085
+ return Array.from(pairs.values()).filter((pair) => !exclude(pair)).sort((a, b) => {
58044
58086
  const aExplicit = a.sources.has("explicit_user_statement") ? 1 : 0;
58045
58087
  const bExplicit = b.sources.has("explicit_user_statement") ? 1 : 0;
58046
58088
  if (aExplicit !== bExplicit) return bExplicit - aExplicit;
@@ -58170,10 +58212,8 @@ async function computeAutoApplyEligible(userId, sourceId, targetId, pair, judgme
58170
58212
  if (await hasRecentRejectedPair(userId, sourceId, targetId)) return false;
58171
58213
  return true;
58172
58214
  }
58173
- async function upsertSuggestion(userId, sourceId, targetId, pair, judgment, autoApplyEligible) {
58174
- const spec = ROLE_REGISTRY.project_alias_judge;
58175
- const candidateSources = Array.from(pair.sources);
58176
- const signals = {
58215
+ function aliasSignals(pair, judgment, sourceId, targetId) {
58216
+ return {
58177
58217
  ...pair.signals,
58178
58218
  llm: {
58179
58219
  relation: judgment.relation,
@@ -58184,6 +58224,11 @@ async function upsertSuggestion(userId, sourceId, targetId, pair, judgment, auto
58184
58224
  direction_reversed_from_candidate: sourceId !== pair.sourceId || targetId !== pair.targetId
58185
58225
  }
58186
58226
  };
58227
+ }
58228
+ async function upsertSuggestion(userId, sourceId, targetId, pair, judgment, autoApplyEligible) {
58229
+ const spec = ROLE_REGISTRY.project_alias_judge;
58230
+ const candidateSources = Array.from(pair.sources);
58231
+ const signals = aliasSignals(pair, judgment, sourceId, targetId);
58187
58232
  const result = await db.query(
58188
58233
  `INSERT INTO project_tag_alias_suggestions (
58189
58234
  user_id,
@@ -58245,6 +58290,97 @@ async function upsertSuggestion(userId, sourceId, targetId, pair, judgment, auto
58245
58290
  );
58246
58291
  return Number(result.rows[0].id);
58247
58292
  }
58293
+ async function loadPairHistory(userId, onlyPair) {
58294
+ const result = await db.query(
58295
+ `SELECT id, source_root, target_root, status, decided_by, decided, created_at, manual
58296
+ FROM (SELECT id,
58297
+ canonical_project_tag_id(source_tag_id) AS source_root,
58298
+ canonical_project_tag_id(target_tag_id) AS target_root,
58299
+ status,
58300
+ decided_by,
58301
+ COALESCE(decided_at, updated_at, created_at) AS decided,
58302
+ created_at,
58303
+ 'manual_set_alias' = ANY(candidate_sources) AS manual
58304
+ FROM project_tag_alias_suggestions
58305
+ WHERE user_id = $1) h
58306
+ WHERE $2::bigint IS NULL
58307
+ OR (LEAST(source_root, target_root) = LEAST($2::bigint, $3::bigint)
58308
+ AND GREATEST(source_root, target_root) = GREATEST($2::bigint, $3::bigint))`,
58309
+ [userId, onlyPair?.[0] ?? null, onlyPair?.[1] ?? null]
58310
+ );
58311
+ return groupPairHistory(
58312
+ result.rows.filter((row) => row.source_root !== null && row.target_root !== null).map((row) => ({
58313
+ id: Number(row.id),
58314
+ sourceId: Number(row.source_root),
58315
+ targetId: Number(row.target_root),
58316
+ status: String(row.status),
58317
+ decidedBy: row.decided_by === null ? null : String(row.decided_by),
58318
+ decidedAt: toDate(row.decided),
58319
+ createdAt: toDate(row.created_at),
58320
+ manual: row.manual === true
58321
+ }))
58322
+ );
58323
+ }
58324
+ async function sweepRepeatedRejections(history) {
58325
+ let swept = 0;
58326
+ let errors = 0;
58327
+ for (const rows of history.values()) {
58328
+ for (const { pendingId, rejectedId } of repeatsToSweep(rows)) {
58329
+ try {
58330
+ const result = await db.query(
58331
+ `UPDATE project_tag_alias_suggestions
58332
+ SET status = 'rejected',
58333
+ decided_by = 'system',
58334
+ decision_reason = $2,
58335
+ signals = signals || jsonb_build_object('repeat_of_suggestion_id', $3::bigint),
58336
+ decided_at = NOW(),
58337
+ updated_at = NOW()
58338
+ WHERE id = $1
58339
+ AND status = 'pending'
58340
+ AND NOT ('manual_set_alias' = ANY(candidate_sources))`,
58341
+ [pendingId, `repeat of suggestion #${rejectedId} rejected by the user`, rejectedId]
58342
+ );
58343
+ swept += result.rowCount ?? 0;
58344
+ } catch (err3) {
58345
+ errors++;
58346
+ console.error(`[ProjectAliasPromoter] sweep of suggestion ${pendingId} failed:`, err3);
58347
+ }
58348
+ }
58349
+ }
58350
+ return { swept, errors };
58351
+ }
58352
+ async function recordRepeatOfRejected(userId, sourceId, targetId, pair, judgment, rejectedId) {
58353
+ const spec = ROLE_REGISTRY.project_alias_judge;
58354
+ await db.query(
58355
+ `INSERT INTO project_tag_alias_suggestions (
58356
+ user_id, source_tag_id, target_tag_id, relation, status, confidence,
58357
+ candidate_sources, evidence_memory_ids, conflict_memory_ids, signals,
58358
+ model_provider, model_name, rationale, auto_apply_eligible,
58359
+ decided_by, decision_reason, decided_at
58360
+ )
58361
+ VALUES (
58362
+ $1, $2, $3, $4, 'rejected', $5,
58363
+ $6::text[], $7::bigint[], $8::bigint[], $9::jsonb,
58364
+ $10, $11, $12, FALSE,
58365
+ 'system', $13, NOW()
58366
+ )`,
58367
+ [
58368
+ userId,
58369
+ sourceId,
58370
+ targetId,
58371
+ judgment.relation,
58372
+ Number(judgment.confidence.toFixed(4)),
58373
+ Array.from(pair.sources),
58374
+ judgment.evidence_memory_ids,
58375
+ judgment.conflict_memory_ids,
58376
+ JSON.stringify({ ...aliasSignals(pair, judgment, sourceId, targetId), repeat_of_suggestion_id: rejectedId }),
58377
+ spec.provider,
58378
+ spec.model_name,
58379
+ truncate(judgment.rationale, 4e3),
58380
+ `repeat of suggestion #${rejectedId} rejected by the user`
58381
+ ]
58382
+ );
58383
+ }
58248
58384
  function qualifiesForPending(judgment, minConfidence) {
58249
58385
  return QUALIFYING_RELATIONS.has(judgment.relation) && judgment.same_project && judgment.confidence >= minConfidence;
58250
58386
  }
@@ -58259,12 +58395,23 @@ async function runProjectAliasPromoter(opts = {}) {
58259
58395
  inserted: 0,
58260
58396
  autoApplied: 0,
58261
58397
  skipped: 0,
58262
- errors: 0
58398
+ errors: 0,
58399
+ blockedBeforeJudge: 0,
58400
+ blockedAfterJudge: 0,
58401
+ swept: 0
58263
58402
  };
58403
+ const history = await loadPairHistory(userId);
58404
+ const sweep = await sweepRepeatedRejections(history);
58405
+ summary.swept = sweep.swept;
58406
+ summary.errors += sweep.errors;
58264
58407
  const tags = await listCanonicalTags(userId);
58265
58408
  if (tags.length < 2) return summary;
58266
58409
  const tagsById = new Map(tags.map((tag) => [tag.id, tag]));
58267
- const pairs = await buildCandidatePairs(userId, tags, candidateLimit);
58410
+ const pairs = await buildCandidatePairs(userId, tags, candidateLimit, (pair) => {
58411
+ const skip = skipBeforeJudge(history.get(pairKey(pair.sourceId, pair.targetId)));
58412
+ if (skip) summary.blockedBeforeJudge++;
58413
+ return skip;
58414
+ });
58268
58415
  summary.candidates = pairs.length;
58269
58416
  if (pairs.length === 0) return summary;
58270
58417
  const tagIdsForPrompt = uniqueSortedNumbers(
@@ -58289,6 +58436,14 @@ async function runProjectAliasPromoter(opts = {}) {
58289
58436
  summary.skipped++;
58290
58437
  continue;
58291
58438
  }
58439
+ const key = pairKey(sourceId, targetId);
58440
+ const live = await loadPairHistory(userId, [sourceId, targetId]);
58441
+ const rejectedId = userRejectedDirection(history.get(key), sourceId, targetId) ?? userRejectedDirection(live.get(key), sourceId, targetId);
58442
+ if (rejectedId !== null) {
58443
+ await recordRepeatOfRejected(userId, sourceId, targetId, pair, judgment, rejectedId);
58444
+ summary.blockedAfterJudge++;
58445
+ continue;
58446
+ }
58292
58447
  const autoApplyEligible = await computeAutoApplyEligible(
58293
58448
  userId,
58294
58449
  sourceId,
@@ -58346,9 +58501,9 @@ async function maybeRunProjectAliasPromoter() {
58346
58501
  [userId]
58347
58502
  );
58348
58503
  const summary = await runProjectAliasPromoter({ userId });
58349
- if (summary.candidates > 0 || summary.inserted > 0 || summary.errors > 0) {
58504
+ if (summary.candidates > 0 || summary.inserted > 0 || summary.errors > 0 || summary.swept > 0 || summary.blockedBeforeJudge > 0) {
58350
58505
  console.error(
58351
- `\u{1F501} [ProjectAliasPromoter] done \u2014 candidates=${summary.candidates}, judged=${summary.judged}, inserted=${summary.inserted}, autoApplied=${summary.autoApplied}, skipped=${summary.skipped}, errors=${summary.errors}`
58506
+ `\u{1F501} [ProjectAliasPromoter] done \u2014 candidates=${summary.candidates}, judged=${summary.judged}, inserted=${summary.inserted}, autoApplied=${summary.autoApplied}, skipped=${summary.skipped}, errors=${summary.errors}, blockedBeforeJudge=${summary.blockedBeforeJudge}, blockedAfterJudge=${summary.blockedAfterJudge}, swept=${summary.swept}`
58352
58507
  );
58353
58508
  }
58354
58509
  } catch (err3) {
@@ -58444,13 +58599,13 @@ async function applyAliasSuggestion(suggestionId, opts = {}) {
58444
58599
  }
58445
58600
  if (!Number.isInteger(targetRoot) || targetRoot === sourceId) {
58446
58601
  const msg = "alias would point source to itself";
58447
- await markSuggestionTerminal(client2, suggestionId, "rejected", decidedBy, msg);
58602
+ await markSuggestionTerminal(client2, suggestionId, "rejected", "system", msg);
58448
58603
  await client2.query("COMMIT");
58449
58604
  return { suggestionId, applied: false, status: "rejected", reason: msg };
58450
58605
  }
58451
58606
  if (await targetChainContainsSource(client2, sourceId, targetId)) {
58452
58607
  const msg = "alias would create a project_tag alias cycle";
58453
- await markSuggestionTerminal(client2, suggestionId, "rejected", decidedBy, msg);
58608
+ await markSuggestionTerminal(client2, suggestionId, "rejected", "system", msg);
58454
58609
  await client2.query("COMMIT");
58455
58610
  return { suggestionId, applied: false, status: "rejected", reason: msg };
58456
58611
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-agents-memory",
3
- "version": "0.9.23",
3
+ "version": "0.9.24",
4
4
  "description": "Long-term memory for AI agents with provenance tracking",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -22,7 +22,8 @@
22
22
  "setup": "npm run build && node build/index.js setup",
23
23
  "migrate": "npm run build && node build/index.js migrate",
24
24
  "bench:tagger": "npx tsx scripts/ab_tagger.ts",
25
- "check:envelope": "esbuild scripts/check_buzz_envelope.ts --bundle --platform=node --format=esm --outfile=node_modules/.cache/mcp-agents-memory/check_buzz_envelope.mjs --log-level=warning && node node_modules/.cache/mcp-agents-memory/check_buzz_envelope.mjs"
25
+ "check:envelope": "esbuild scripts/check_buzz_envelope.ts --bundle --platform=node --format=esm --outfile=node_modules/.cache/mcp-agents-memory/check_buzz_envelope.mjs --log-level=warning && node node_modules/.cache/mcp-agents-memory/check_buzz_envelope.mjs",
26
+ "check:alias": "esbuild scripts/check_alias_reject.ts --bundle --platform=node --format=esm --outfile=node_modules/.cache/mcp-agents-memory/check_alias_reject.mjs --log-level=warning && node node_modules/.cache/mcp-agents-memory/check_alias_reject.mjs"
26
27
  },
27
28
  "keywords": [
28
29
  "mcp",