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.
- package/build/index.js +170 -15
- 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
|
-
|
|
58174
|
-
|
|
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",
|
|
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",
|
|
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.
|
|
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",
|