@ghostpaw/affinity 0.1.6 → 0.2.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.
- package/dist/index.cjs +308 -20
- package/dist/index.d.cts +58 -8
- package/dist/index.d.ts +58 -8
- package/dist/index.js +308 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -388,11 +388,32 @@ function initContactMergesTables(db) {
|
|
|
388
388
|
);
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
// src/merges/init_dismissed_duplicates_tables.ts
|
|
392
|
+
function initDismissedDuplicatesTables(db) {
|
|
393
|
+
db.exec(`
|
|
394
|
+
CREATE TABLE IF NOT EXISTS dismissed_duplicates (
|
|
395
|
+
left_id INTEGER NOT NULL,
|
|
396
|
+
right_id INTEGER NOT NULL,
|
|
397
|
+
reason TEXT,
|
|
398
|
+
dismissed_at INTEGER NOT NULL,
|
|
399
|
+
PRIMARY KEY (left_id, right_id),
|
|
400
|
+
CHECK (left_id < right_id)
|
|
401
|
+
)
|
|
402
|
+
`);
|
|
403
|
+
db.exec(
|
|
404
|
+
"CREATE INDEX IF NOT EXISTS idx_dismissed_duplicates_left ON dismissed_duplicates(left_id)"
|
|
405
|
+
);
|
|
406
|
+
db.exec(
|
|
407
|
+
"CREATE INDEX IF NOT EXISTS idx_dismissed_duplicates_right ON dismissed_duplicates(right_id)"
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
391
411
|
// src/init_affinity_tables.ts
|
|
392
412
|
function initAffinityTables(db) {
|
|
393
413
|
initContactsTables(db);
|
|
394
414
|
initContactRollupsTables(db);
|
|
395
415
|
initContactMergesTables(db);
|
|
416
|
+
initDismissedDuplicatesTables(db);
|
|
396
417
|
initIdentitiesTables(db);
|
|
397
418
|
initLinksTables(db);
|
|
398
419
|
initLinkRollupsTables(db);
|
|
@@ -500,6 +521,7 @@ __export(read_exports, {
|
|
|
500
521
|
getMergeHistory: () => getMergeHistory,
|
|
501
522
|
getOwnerProfile: () => getOwnerProfile,
|
|
502
523
|
listContacts: () => listContacts,
|
|
524
|
+
listDismissedDuplicates: () => listDismissedDuplicates,
|
|
503
525
|
listDuplicateCandidates: () => listDuplicateCandidates,
|
|
504
526
|
listMoments: () => listMoments,
|
|
505
527
|
listObservedLinks: () => listObservedLinks,
|
|
@@ -1352,8 +1374,24 @@ function listDuplicateCandidates(db, filters, options) {
|
|
|
1352
1374
|
}
|
|
1353
1375
|
}
|
|
1354
1376
|
}
|
|
1377
|
+
const dismissedRows = db.prepare("SELECT left_id, right_id FROM dismissed_duplicates").all();
|
|
1378
|
+
const dismissedSet = new Set(
|
|
1379
|
+
dismissedRows.map((r) => pairKey(r.left_id, r.right_id))
|
|
1380
|
+
);
|
|
1381
|
+
const includeDismissed = filters?.includeDismissed === true;
|
|
1382
|
+
for (const candidate of pairs.values()) {
|
|
1383
|
+
if (dismissedSet.has(
|
|
1384
|
+
pairKey(candidate.leftContactId, candidate.rightContactId)
|
|
1385
|
+
)) {
|
|
1386
|
+
candidate.dismissed = true;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1355
1389
|
const minScore = filters?.minScore ?? 0;
|
|
1356
|
-
return [...pairs.values()].filter((candidate) =>
|
|
1390
|
+
return [...pairs.values()].filter((candidate) => {
|
|
1391
|
+
if (candidate.matchScore < minScore) return false;
|
|
1392
|
+
if (candidate.dismissed === true && !includeDismissed) return false;
|
|
1393
|
+
return true;
|
|
1394
|
+
}).sort((left, right) => {
|
|
1357
1395
|
const leftExact = left.matchScore === 1 ? 1 : 0;
|
|
1358
1396
|
const rightExact = right.matchScore === 1 ? 1 : 0;
|
|
1359
1397
|
return rightExact - leftExact || right.matchScore - left.matchScore;
|
|
@@ -3292,11 +3330,10 @@ function dedupeLiveLinks(db, now) {
|
|
|
3292
3330
|
}
|
|
3293
3331
|
return [...removed];
|
|
3294
3332
|
}
|
|
3295
|
-
function softDeleteSelfLoops(db,
|
|
3333
|
+
function softDeleteSelfLoops(db, now) {
|
|
3296
3334
|
const rows = db.prepare(
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
).all(contactId);
|
|
3335
|
+
"SELECT id FROM links WHERE removed_at IS NULL AND from_contact_id = to_contact_id"
|
|
3336
|
+
).all();
|
|
3300
3337
|
const ids = [];
|
|
3301
3338
|
for (const r of rows) {
|
|
3302
3339
|
db.prepare(
|
|
@@ -3399,7 +3436,7 @@ function mergeContacts(db, input) {
|
|
|
3399
3436
|
WHERE to_contact_id = ? AND removed_at IS NULL`
|
|
3400
3437
|
).run(winnerId, now, loserId);
|
|
3401
3438
|
const affectedLinks = /* @__PURE__ */ new Set();
|
|
3402
|
-
for (const id of softDeleteSelfLoops(db,
|
|
3439
|
+
for (const id of softDeleteSelfLoops(db, now)) {
|
|
3403
3440
|
affectedLinks.add(id);
|
|
3404
3441
|
}
|
|
3405
3442
|
for (const id of dedupeLiveLinks(db, now)) {
|
|
@@ -3416,6 +3453,12 @@ function mergeContacts(db, input) {
|
|
|
3416
3453
|
`INSERT INTO contact_merges (winner_contact_id, loser_contact_id, merged_at, reason_summary, manual)
|
|
3417
3454
|
VALUES (?, ?, ?, ?, 1)`
|
|
3418
3455
|
).run(winnerId, loserId, now, reasonSummary);
|
|
3456
|
+
db.prepare(
|
|
3457
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
|
|
3458
|
+
).run(loserId, loserId);
|
|
3459
|
+
db.prepare(
|
|
3460
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
|
|
3461
|
+
).run(winnerId, winnerId);
|
|
3419
3462
|
db.prepare(
|
|
3420
3463
|
"DELETE FROM link_rollups WHERE link_id IN (SELECT id FROM links WHERE removed_at IS NOT NULL)"
|
|
3421
3464
|
).run();
|
|
@@ -3443,6 +3486,117 @@ function mergeContacts(db, input) {
|
|
|
3443
3486
|
});
|
|
3444
3487
|
}
|
|
3445
3488
|
|
|
3489
|
+
// src/merges/receipts.ts
|
|
3490
|
+
function buildDismissalMutationReceipt(primary, partial) {
|
|
3491
|
+
const normalized = normalizeMutationReceiptPartial(partial);
|
|
3492
|
+
return {
|
|
3493
|
+
primary,
|
|
3494
|
+
created: normalized.created,
|
|
3495
|
+
updated: normalized.updated,
|
|
3496
|
+
archived: normalized.archived,
|
|
3497
|
+
removed: normalized.removed,
|
|
3498
|
+
affectedLinks: normalized.affectedLinks,
|
|
3499
|
+
derivedEffects: normalized.derivedEffects
|
|
3500
|
+
};
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
// src/merges/dismiss_duplicate.ts
|
|
3504
|
+
function canonicalize(a, b) {
|
|
3505
|
+
return {
|
|
3506
|
+
leftContactId: Math.min(a, b),
|
|
3507
|
+
rightContactId: Math.max(a, b)
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3510
|
+
function dismissDuplicate(db, leftContactId, rightContactId, reason, now) {
|
|
3511
|
+
return withTransaction(db, () => {
|
|
3512
|
+
if (leftContactId === rightContactId) {
|
|
3513
|
+
throw new AffinityValidationError(
|
|
3514
|
+
"cannot dismiss a contact as a duplicate of itself"
|
|
3515
|
+
);
|
|
3516
|
+
}
|
|
3517
|
+
const left = getContactRowById(db, leftContactId);
|
|
3518
|
+
if (!left) {
|
|
3519
|
+
throw new AffinityNotFoundError(`contact ${leftContactId} not found`);
|
|
3520
|
+
}
|
|
3521
|
+
if (left.lifecycle_state === "merged") {
|
|
3522
|
+
throw new AffinityStateError(
|
|
3523
|
+
`contact ${leftContactId} is merged and cannot be dismissed`
|
|
3524
|
+
);
|
|
3525
|
+
}
|
|
3526
|
+
const right = getContactRowById(db, rightContactId);
|
|
3527
|
+
if (!right) {
|
|
3528
|
+
throw new AffinityNotFoundError(`contact ${rightContactId} not found`);
|
|
3529
|
+
}
|
|
3530
|
+
if (right.lifecycle_state === "merged") {
|
|
3531
|
+
throw new AffinityStateError(
|
|
3532
|
+
`contact ${rightContactId} is merged and cannot be dismissed`
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
const pair = canonicalize(leftContactId, rightContactId);
|
|
3536
|
+
const resolvedNow = resolveNow(now);
|
|
3537
|
+
const resolvedReason = reason === void 0 || reason === null ? null : (() => {
|
|
3538
|
+
const t = reason.trim();
|
|
3539
|
+
return t.length === 0 ? null : t;
|
|
3540
|
+
})();
|
|
3541
|
+
db.prepare(
|
|
3542
|
+
`INSERT INTO dismissed_duplicates (left_id, right_id, reason, dismissed_at)
|
|
3543
|
+
VALUES (?, ?, ?, ?)
|
|
3544
|
+
ON CONFLICT(left_id, right_id) DO UPDATE SET
|
|
3545
|
+
reason = excluded.reason,
|
|
3546
|
+
dismissed_at = excluded.dismissed_at`
|
|
3547
|
+
).run(pair.leftContactId, pair.rightContactId, resolvedReason, resolvedNow);
|
|
3548
|
+
return buildDismissalMutationReceipt(pair);
|
|
3549
|
+
});
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
// src/merges/undismiss_duplicate.ts
|
|
3553
|
+
function canonicalize2(a, b) {
|
|
3554
|
+
return {
|
|
3555
|
+
leftContactId: Math.min(a, b),
|
|
3556
|
+
rightContactId: Math.max(a, b)
|
|
3557
|
+
};
|
|
3558
|
+
}
|
|
3559
|
+
function undismissDuplicate(db, leftContactId, rightContactId) {
|
|
3560
|
+
return withTransaction(db, () => {
|
|
3561
|
+
if (leftContactId === rightContactId) {
|
|
3562
|
+
throw new AffinityValidationError(
|
|
3563
|
+
"cannot undismiss a contact from itself"
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
const left = getContactRowById(db, leftContactId);
|
|
3567
|
+
if (!left) {
|
|
3568
|
+
throw new AffinityNotFoundError(`contact ${leftContactId} not found`);
|
|
3569
|
+
}
|
|
3570
|
+
const right = getContactRowById(db, rightContactId);
|
|
3571
|
+
if (!right) {
|
|
3572
|
+
throw new AffinityNotFoundError(`contact ${rightContactId} not found`);
|
|
3573
|
+
}
|
|
3574
|
+
const pair = canonicalize2(leftContactId, rightContactId);
|
|
3575
|
+
db.prepare(
|
|
3576
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? AND right_id = ?"
|
|
3577
|
+
).run(pair.leftContactId, pair.rightContactId);
|
|
3578
|
+
return buildDismissalMutationReceipt(pair);
|
|
3579
|
+
});
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
// src/merges/list_dismissed_duplicates.ts
|
|
3583
|
+
function listDismissedDuplicates(db, options) {
|
|
3584
|
+
assertDefaultOrdering("listDismissedDuplicates", options);
|
|
3585
|
+
const { limit, offset } = resolveListLimitOffset(options);
|
|
3586
|
+
const rows = db.prepare(
|
|
3587
|
+
`SELECT left_id, right_id, reason, dismissed_at
|
|
3588
|
+
FROM dismissed_duplicates
|
|
3589
|
+
ORDER BY dismissed_at DESC
|
|
3590
|
+
LIMIT ? OFFSET ?`
|
|
3591
|
+
).all(limit, offset);
|
|
3592
|
+
return rows.map((row) => ({
|
|
3593
|
+
leftContactId: row.left_id,
|
|
3594
|
+
rightContactId: row.right_id,
|
|
3595
|
+
reason: row.reason,
|
|
3596
|
+
dismissedAt: row.dismissed_at
|
|
3597
|
+
}));
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3446
3600
|
// src/events/get_contact_journal.ts
|
|
3447
3601
|
function getContactJournal(db, contactId, options) {
|
|
3448
3602
|
assertDefaultOrdering("getContactJournal", options);
|
|
@@ -5331,6 +5485,9 @@ __export(tools_exports, {
|
|
|
5331
5485
|
manageDateAnchorTool: () => manageDateAnchorTool,
|
|
5332
5486
|
manageDateAnchorToolHandler: () => manageDateAnchorToolHandler,
|
|
5333
5487
|
manageDateAnchorToolName: () => manageDateAnchorToolName,
|
|
5488
|
+
manageDuplicateDismissalTool: () => manageDuplicateDismissalTool,
|
|
5489
|
+
manageDuplicateDismissalToolHandler: () => manageDuplicateDismissalToolHandler,
|
|
5490
|
+
manageDuplicateDismissalToolName: () => manageDuplicateDismissalToolName,
|
|
5334
5491
|
manageIdentityTool: () => manageIdentityTool,
|
|
5335
5492
|
manageIdentityToolHandler: () => manageIdentityToolHandler,
|
|
5336
5493
|
manageIdentityToolName: () => manageIdentityToolName,
|
|
@@ -5453,6 +5610,7 @@ var reviewAffinityToolName = "review_affinity";
|
|
|
5453
5610
|
var inspectAffinityItemToolName = "inspect_affinity_item";
|
|
5454
5611
|
var manageContactToolName = "manage_contact";
|
|
5455
5612
|
var mergeContactsToolName = "merge_contacts";
|
|
5613
|
+
var manageDuplicateDismissalToolName = "manage_duplicate_dismissal";
|
|
5456
5614
|
var manageIdentityToolName = "manage_identity";
|
|
5457
5615
|
var manageRelationshipToolName = "manage_relationship";
|
|
5458
5616
|
var recordEventToolName = "record_event";
|
|
@@ -6704,7 +6862,7 @@ var manageContactTool = defineAffinityTool({
|
|
|
6704
6862
|
handler: manageContactToolHandler
|
|
6705
6863
|
});
|
|
6706
6864
|
|
|
6707
|
-
// src/tools/
|
|
6865
|
+
// src/tools/manage_duplicate_dismissal_tool.ts
|
|
6708
6866
|
function contactLocatorSchema4(description) {
|
|
6709
6867
|
return {
|
|
6710
6868
|
type: "object",
|
|
@@ -6722,6 +6880,86 @@ function contactLocatorSchema4(description) {
|
|
|
6722
6880
|
description
|
|
6723
6881
|
};
|
|
6724
6882
|
}
|
|
6883
|
+
function manageDuplicateDismissalToolHandler(db, input) {
|
|
6884
|
+
return withToolHandling(() => {
|
|
6885
|
+
const left = resolveContactLocator(db, input.left, "left");
|
|
6886
|
+
if (!left.ok) {
|
|
6887
|
+
return left.result;
|
|
6888
|
+
}
|
|
6889
|
+
const right = resolveContactLocator(db, input.right, "right");
|
|
6890
|
+
if (!right.ok) {
|
|
6891
|
+
return right.result;
|
|
6892
|
+
}
|
|
6893
|
+
if (input.action === "dismiss") {
|
|
6894
|
+
const receipt2 = dismissDuplicate(
|
|
6895
|
+
db,
|
|
6896
|
+
left.value.id,
|
|
6897
|
+
right.value.id,
|
|
6898
|
+
input.reason ?? null
|
|
6899
|
+
);
|
|
6900
|
+
return toolSuccess(
|
|
6901
|
+
`Dismissed pair #${receipt2.primary.leftContactId} / #${receipt2.primary.rightContactId}.`,
|
|
6902
|
+
{ action: "dismiss", primary: receipt2.primary }
|
|
6903
|
+
);
|
|
6904
|
+
}
|
|
6905
|
+
const receipt = undismissDuplicate(db, left.value.id, right.value.id);
|
|
6906
|
+
return toolSuccess(
|
|
6907
|
+
`Un-dismissed pair #${receipt.primary.leftContactId} / #${receipt.primary.rightContactId}.`,
|
|
6908
|
+
{ action: "undismiss", primary: receipt.primary }
|
|
6909
|
+
);
|
|
6910
|
+
}, "Duplicate dismissal failed.");
|
|
6911
|
+
}
|
|
6912
|
+
var manageDuplicateDismissalTool = defineAffinityTool({
|
|
6913
|
+
name: manageDuplicateDismissalToolName,
|
|
6914
|
+
description: "Dismiss or un-dismiss a duplicate candidate pair so it does not resurface in listDuplicateCandidates.",
|
|
6915
|
+
whenToUse: "Use dismiss after confirming two contacts are different people (not duplicates). Use undismiss to re-surface a pair for re-evaluation.",
|
|
6916
|
+
whenNotToUse: "Do not use this to merge contacts \u2014 use merge_contacts for confirmed duplicates.",
|
|
6917
|
+
sideEffects: "writes_state",
|
|
6918
|
+
readOnly: false,
|
|
6919
|
+
supportsClarification: true,
|
|
6920
|
+
targetKinds: ["contact"],
|
|
6921
|
+
inputDescriptions: {
|
|
6922
|
+
action: "dismiss to suppress the pair; undismiss to re-surface it.",
|
|
6923
|
+
left: "One contact of the pair.",
|
|
6924
|
+
right: "The other contact of the pair.",
|
|
6925
|
+
reason: "Optional reason for the dismissal (audit trail)."
|
|
6926
|
+
},
|
|
6927
|
+
outputDescription: "Returns the dismissal receipt with the canonical pair identifiers.",
|
|
6928
|
+
inputSchema: objectSchema(
|
|
6929
|
+
{
|
|
6930
|
+
action: enumSchema("Action to perform.", ["dismiss", "undismiss"]),
|
|
6931
|
+
left: contactLocatorSchema4(
|
|
6932
|
+
"Left contact. Provide contactId or identity."
|
|
6933
|
+
),
|
|
6934
|
+
right: contactLocatorSchema4(
|
|
6935
|
+
"Right contact. Provide contactId or identity."
|
|
6936
|
+
),
|
|
6937
|
+
reason: nullableStringSchema("Optional reason for the dismissal.")
|
|
6938
|
+
},
|
|
6939
|
+
["action", "left", "right"],
|
|
6940
|
+
"Dismiss or un-dismiss a duplicate candidate pair."
|
|
6941
|
+
),
|
|
6942
|
+
handler: manageDuplicateDismissalToolHandler
|
|
6943
|
+
});
|
|
6944
|
+
|
|
6945
|
+
// src/tools/manage_date_anchor_tool.ts
|
|
6946
|
+
function contactLocatorSchema5(description) {
|
|
6947
|
+
return {
|
|
6948
|
+
type: "object",
|
|
6949
|
+
properties: {
|
|
6950
|
+
contactId: integerSchema("Exact contact id."),
|
|
6951
|
+
identity: objectSchema(
|
|
6952
|
+
{
|
|
6953
|
+
type: stringSchema("Identity type."),
|
|
6954
|
+
value: stringSchema("Identity value.")
|
|
6955
|
+
},
|
|
6956
|
+
["type", "value"],
|
|
6957
|
+
"Contact identity locator."
|
|
6958
|
+
)
|
|
6959
|
+
},
|
|
6960
|
+
description
|
|
6961
|
+
};
|
|
6962
|
+
}
|
|
6725
6963
|
function linkLocatorSchema3(description) {
|
|
6726
6964
|
return {
|
|
6727
6965
|
type: "object",
|
|
@@ -6812,7 +7050,7 @@ var manageDateAnchorTool = defineAffinityTool({
|
|
|
6812
7050
|
target: objectSchema(
|
|
6813
7051
|
{
|
|
6814
7052
|
kind: enumSchema("Target kind.", ["contact", "link"]),
|
|
6815
|
-
contact:
|
|
7053
|
+
contact: contactLocatorSchema5(
|
|
6816
7054
|
"Contact locator. Required when kind=contact. Provide contactId or identity."
|
|
6817
7055
|
),
|
|
6818
7056
|
link: linkLocatorSchema3(
|
|
@@ -6966,6 +7204,9 @@ function addIdentity(db, contactId, input) {
|
|
|
6966
7204
|
throw new AffinityInvariantError("inserted identity not found");
|
|
6967
7205
|
}
|
|
6968
7206
|
refreshContactRollup(db, contactId, now);
|
|
7207
|
+
db.prepare(
|
|
7208
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
|
|
7209
|
+
).run(contactId, contactId);
|
|
6969
7210
|
return buildIdentityMutationReceipt(mapIdentityRowToIdentityRecord(row), {
|
|
6970
7211
|
created: [{ kind: "identity", id }]
|
|
6971
7212
|
});
|
|
@@ -6989,6 +7230,9 @@ function removeIdentity(db, identityId, removedAt) {
|
|
|
6989
7230
|
"UPDATE identities SET removed_at = ?, updated_at = ? WHERE id = ?"
|
|
6990
7231
|
).run(now, now, identityId);
|
|
6991
7232
|
refreshContactRollup(db, row.contact_id, now);
|
|
7233
|
+
db.prepare(
|
|
7234
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
|
|
7235
|
+
).run(row.contact_id, row.contact_id);
|
|
6992
7236
|
return buildIdentityMutationReceipt(primary, {
|
|
6993
7237
|
removed: [{ kind: "identity", id: identityId }]
|
|
6994
7238
|
});
|
|
@@ -7067,7 +7311,7 @@ function verifyIdentity(db, identityId, verifiedAt) {
|
|
|
7067
7311
|
}
|
|
7068
7312
|
|
|
7069
7313
|
// src/tools/manage_identity_tool.ts
|
|
7070
|
-
function
|
|
7314
|
+
function contactLocatorSchema6(description) {
|
|
7071
7315
|
return {
|
|
7072
7316
|
type: "object",
|
|
7073
7317
|
properties: {
|
|
@@ -7146,7 +7390,7 @@ var manageIdentityTool = defineAffinityTool({
|
|
|
7146
7390
|
"verify",
|
|
7147
7391
|
"remove"
|
|
7148
7392
|
]),
|
|
7149
|
-
contact:
|
|
7393
|
+
contact: contactLocatorSchema6(
|
|
7150
7394
|
"Target contact. Required when action=add. Provide contactId or identity."
|
|
7151
7395
|
),
|
|
7152
7396
|
input: objectSchema(
|
|
@@ -7186,7 +7430,7 @@ var manageIdentityTool = defineAffinityTool({
|
|
|
7186
7430
|
});
|
|
7187
7431
|
|
|
7188
7432
|
// src/tools/manage_relationship_tool.ts
|
|
7189
|
-
function
|
|
7433
|
+
function contactLocatorSchema7(description) {
|
|
7190
7434
|
return {
|
|
7191
7435
|
type: "object",
|
|
7192
7436
|
properties: {
|
|
@@ -7342,10 +7586,10 @@ var manageRelationshipTool = defineAffinityTool({
|
|
|
7342
7586
|
"set_structural_tie",
|
|
7343
7587
|
"remove_structural_tie"
|
|
7344
7588
|
]),
|
|
7345
|
-
from:
|
|
7589
|
+
from: contactLocatorSchema7(
|
|
7346
7590
|
"Source contact. Required when action=seed_social_link or set_structural_tie. Provide contactId or identity."
|
|
7347
7591
|
),
|
|
7348
|
-
to:
|
|
7592
|
+
to: contactLocatorSchema7(
|
|
7349
7593
|
"Target contact. Required when action=seed_social_link or set_structural_tie. Provide contactId or identity."
|
|
7350
7594
|
),
|
|
7351
7595
|
kind: stringSchema(
|
|
@@ -7387,7 +7631,7 @@ var manageRelationshipTool = defineAffinityTool({
|
|
|
7387
7631
|
});
|
|
7388
7632
|
|
|
7389
7633
|
// src/tools/merge_contacts_tool.ts
|
|
7390
|
-
function
|
|
7634
|
+
function contactLocatorSchema8(description) {
|
|
7391
7635
|
return {
|
|
7392
7636
|
type: "object",
|
|
7393
7637
|
properties: {
|
|
@@ -7444,10 +7688,10 @@ var mergeContactsTool = defineAffinityTool({
|
|
|
7444
7688
|
outputDescription: "Returns the merge receipt with rewired entities and affected-link information.",
|
|
7445
7689
|
inputSchema: objectSchema(
|
|
7446
7690
|
{
|
|
7447
|
-
winner:
|
|
7691
|
+
winner: contactLocatorSchema8(
|
|
7448
7692
|
"Winner contact. Provide contactId or identity."
|
|
7449
7693
|
),
|
|
7450
|
-
loser:
|
|
7694
|
+
loser: contactLocatorSchema8(
|
|
7451
7695
|
"Loser contact. Provide contactId or identity."
|
|
7452
7696
|
),
|
|
7453
7697
|
reasonSummary: stringSchema("Optional merge reason."),
|
|
@@ -7618,7 +7862,7 @@ function buildChartOptions(input) {
|
|
|
7618
7862
|
...input.contactIds === void 0 ? {} : { contactIds: input.contactIds }
|
|
7619
7863
|
};
|
|
7620
7864
|
}
|
|
7621
|
-
function
|
|
7865
|
+
function contactLocatorSchema9(description) {
|
|
7622
7866
|
return {
|
|
7623
7867
|
type: "object",
|
|
7624
7868
|
properties: {
|
|
@@ -7709,6 +7953,14 @@ function validateReviewInput(input) {
|
|
|
7709
7953
|
"Use `contactIds` only with duplicate-candidate or chart review views."
|
|
7710
7954
|
);
|
|
7711
7955
|
}
|
|
7956
|
+
if (input.includeDismissed !== void 0 && input.view !== "contacts.duplicates") {
|
|
7957
|
+
return toolFailure(
|
|
7958
|
+
"protocol",
|
|
7959
|
+
"invalid_input",
|
|
7960
|
+
"The `includeDismissed` filter is not valid for this review view.",
|
|
7961
|
+
"Use `includeDismissed` only with the contacts.duplicates review view."
|
|
7962
|
+
);
|
|
7963
|
+
}
|
|
7712
7964
|
return null;
|
|
7713
7965
|
}
|
|
7714
7966
|
function duplicateCandidateItem(candidate) {
|
|
@@ -7765,6 +8017,15 @@ function mergeHistoryItem(record) {
|
|
|
7765
8017
|
snippet: record.reasonSummary ?? void 0
|
|
7766
8018
|
};
|
|
7767
8019
|
}
|
|
8020
|
+
function dismissedPairItem(record) {
|
|
8021
|
+
return {
|
|
8022
|
+
kind: "contact",
|
|
8023
|
+
id: record.leftContactId,
|
|
8024
|
+
title: `Dismissed pair: #${record.leftContactId} and #${record.rightContactId}`,
|
|
8025
|
+
subtitle: record.reason ?? "no reason given",
|
|
8026
|
+
snippet: `dismissed at ${record.dismissedAt}`
|
|
8027
|
+
};
|
|
8028
|
+
}
|
|
7768
8029
|
function compactFilters(input) {
|
|
7769
8030
|
return Object.fromEntries(
|
|
7770
8031
|
Object.entries(input).filter(
|
|
@@ -7800,7 +8061,8 @@ function reviewAffinityToolHandler(db, input) {
|
|
|
7800
8061
|
{
|
|
7801
8062
|
...input.contactIds === void 0 ? {} : { contactIds: input.contactIds },
|
|
7802
8063
|
...input.exactOnly === void 0 ? {} : { exactOnly: input.exactOnly },
|
|
7803
|
-
...input.minScore === void 0 ? {} : { minScore: input.minScore }
|
|
8064
|
+
...input.minScore === void 0 ? {} : { minScore: input.minScore },
|
|
8065
|
+
...input.includeDismissed === void 0 ? {} : { includeDismissed: input.includeDismissed }
|
|
7804
8066
|
},
|
|
7805
8067
|
options
|
|
7806
8068
|
).map(duplicateCandidateItem);
|
|
@@ -7981,6 +8243,9 @@ function reviewAffinityToolHandler(db, input) {
|
|
|
7981
8243
|
);
|
|
7982
8244
|
break;
|
|
7983
8245
|
}
|
|
8246
|
+
case "merges.dismissed":
|
|
8247
|
+
items = listDismissedDuplicates(db, options).map(dismissedPairItem);
|
|
8248
|
+
break;
|
|
7984
8249
|
}
|
|
7985
8250
|
return toolSuccess(
|
|
7986
8251
|
summarizeCount(items.length, "item"),
|
|
@@ -8031,6 +8296,7 @@ var reviewAffinityTool = defineAffinityTool({
|
|
|
8031
8296
|
horizonDays: "Optional horizon filter for commitments or upcoming dates.",
|
|
8032
8297
|
exactOnly: "Whether duplicate review should only include exact identity matches.",
|
|
8033
8298
|
minScore: "Optional minimum duplicate score.",
|
|
8299
|
+
includeDismissed: "Whether dismissed pairs should be included in duplicate review (marked with dismissed flag).",
|
|
8034
8300
|
since: "Optional lower timestamp bound for time-based views.",
|
|
8035
8301
|
until: "Optional upper timestamp bound for time-based views.",
|
|
8036
8302
|
limit: "Optional result limit.",
|
|
@@ -8052,9 +8318,10 @@ var reviewAffinityTool = defineAffinityTool({
|
|
|
8052
8318
|
"links.radar",
|
|
8053
8319
|
"dates.upcoming",
|
|
8054
8320
|
"graph.chart",
|
|
8055
|
-
"merges.history"
|
|
8321
|
+
"merges.history",
|
|
8322
|
+
"merges.dismissed"
|
|
8056
8323
|
]),
|
|
8057
|
-
contact:
|
|
8324
|
+
contact: contactLocatorSchema9("Optional target contact locator."),
|
|
8058
8325
|
link: linkLocatorSchema5("Optional target link locator."),
|
|
8059
8326
|
contactIds: arraySchema(
|
|
8060
8327
|
integerSchema("Contact id."),
|
|
@@ -8105,6 +8372,9 @@ var reviewAffinityTool = defineAffinityTool({
|
|
|
8105
8372
|
horizonDays: integerSchema("Optional horizon-days filter."),
|
|
8106
8373
|
exactOnly: booleanSchema("Whether duplicate review is exact-only."),
|
|
8107
8374
|
minScore: numberSchema("Optional duplicate minimum score."),
|
|
8375
|
+
includeDismissed: booleanSchema(
|
|
8376
|
+
"Whether dismissed pairs are included in duplicate review (marked with dismissed flag)."
|
|
8377
|
+
),
|
|
8108
8378
|
since: integerSchema("Optional lower timestamp bound."),
|
|
8109
8379
|
until: integerSchema("Optional upper timestamp bound."),
|
|
8110
8380
|
limit: integerSchema("Optional result limit."),
|
|
@@ -8203,6 +8473,7 @@ var affinityTools = [
|
|
|
8203
8473
|
inspectAffinityItemTool,
|
|
8204
8474
|
manageContactTool,
|
|
8205
8475
|
mergeContactsTool,
|
|
8476
|
+
manageDuplicateDismissalTool,
|
|
8206
8477
|
manageIdentityTool,
|
|
8207
8478
|
manageRelationshipTool,
|
|
8208
8479
|
recordEventTool,
|
|
@@ -8304,6 +8575,21 @@ var affinityToolMappings = [
|
|
|
8304
8575
|
action: "set_lifecycle"
|
|
8305
8576
|
},
|
|
8306
8577
|
{ source: "mergeContacts", tool: mergeContactsToolName },
|
|
8578
|
+
{
|
|
8579
|
+
source: "dismissDuplicate",
|
|
8580
|
+
tool: manageDuplicateDismissalToolName,
|
|
8581
|
+
action: "dismiss"
|
|
8582
|
+
},
|
|
8583
|
+
{
|
|
8584
|
+
source: "undismissDuplicate",
|
|
8585
|
+
tool: manageDuplicateDismissalToolName,
|
|
8586
|
+
action: "undismiss"
|
|
8587
|
+
},
|
|
8588
|
+
{
|
|
8589
|
+
source: "listDismissedDuplicates",
|
|
8590
|
+
tool: reviewAffinityToolName,
|
|
8591
|
+
view: "merges.dismissed"
|
|
8592
|
+
},
|
|
8307
8593
|
{ source: "addIdentity", tool: manageIdentityToolName, action: "add" },
|
|
8308
8594
|
{ source: "reviseIdentity", tool: manageIdentityToolName, action: "revise" },
|
|
8309
8595
|
{ source: "verifyIdentity", tool: manageIdentityToolName, action: "verify" },
|
|
@@ -8385,6 +8671,7 @@ __export(write_exports, {
|
|
|
8385
8671
|
addDateAnchor: () => addDateAnchor,
|
|
8386
8672
|
addIdentity: () => addIdentity,
|
|
8387
8673
|
createContact: () => createContact,
|
|
8674
|
+
dismissDuplicate: () => dismissDuplicate,
|
|
8388
8675
|
mergeContacts: () => mergeContacts,
|
|
8389
8676
|
overrideLinkState: () => overrideLinkState,
|
|
8390
8677
|
recordCommitment: () => recordCommitment,
|
|
@@ -8405,6 +8692,7 @@ __export(write_exports, {
|
|
|
8405
8692
|
setAttribute: () => setAttribute,
|
|
8406
8693
|
setContactLifecycle: () => setContactLifecycle,
|
|
8407
8694
|
setStructuralTie: () => setStructuralTie,
|
|
8695
|
+
undismissDuplicate: () => undismissDuplicate,
|
|
8408
8696
|
unsetAttribute: () => unsetAttribute,
|
|
8409
8697
|
verifyIdentity: () => verifyIdentity
|
|
8410
8698
|
});
|
package/package.json
CHANGED