@ghostpaw/affinity 0.1.6 → 0.2.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.
- package/dist/index.cjs +304 -15
- package/dist/index.d.cts +58 -8
- package/dist/index.d.ts +58 -8
- package/dist/index.js +304 -15
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -390,11 +390,32 @@ function initContactMergesTables(db) {
|
|
|
390
390
|
);
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
// src/merges/init_dismissed_duplicates_tables.ts
|
|
394
|
+
function initDismissedDuplicatesTables(db) {
|
|
395
|
+
db.exec(`
|
|
396
|
+
CREATE TABLE IF NOT EXISTS dismissed_duplicates (
|
|
397
|
+
left_id INTEGER NOT NULL,
|
|
398
|
+
right_id INTEGER NOT NULL,
|
|
399
|
+
reason TEXT,
|
|
400
|
+
dismissed_at INTEGER NOT NULL,
|
|
401
|
+
PRIMARY KEY (left_id, right_id),
|
|
402
|
+
CHECK (left_id < right_id)
|
|
403
|
+
)
|
|
404
|
+
`);
|
|
405
|
+
db.exec(
|
|
406
|
+
"CREATE INDEX IF NOT EXISTS idx_dismissed_duplicates_left ON dismissed_duplicates(left_id)"
|
|
407
|
+
);
|
|
408
|
+
db.exec(
|
|
409
|
+
"CREATE INDEX IF NOT EXISTS idx_dismissed_duplicates_right ON dismissed_duplicates(right_id)"
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
393
413
|
// src/init_affinity_tables.ts
|
|
394
414
|
function initAffinityTables(db) {
|
|
395
415
|
initContactsTables(db);
|
|
396
416
|
initContactRollupsTables(db);
|
|
397
417
|
initContactMergesTables(db);
|
|
418
|
+
initDismissedDuplicatesTables(db);
|
|
398
419
|
initIdentitiesTables(db);
|
|
399
420
|
initLinksTables(db);
|
|
400
421
|
initLinkRollupsTables(db);
|
|
@@ -502,6 +523,7 @@ __export(read_exports, {
|
|
|
502
523
|
getMergeHistory: () => getMergeHistory,
|
|
503
524
|
getOwnerProfile: () => getOwnerProfile,
|
|
504
525
|
listContacts: () => listContacts,
|
|
526
|
+
listDismissedDuplicates: () => listDismissedDuplicates,
|
|
505
527
|
listDuplicateCandidates: () => listDuplicateCandidates,
|
|
506
528
|
listMoments: () => listMoments,
|
|
507
529
|
listObservedLinks: () => listObservedLinks,
|
|
@@ -1354,8 +1376,24 @@ function listDuplicateCandidates(db, filters, options) {
|
|
|
1354
1376
|
}
|
|
1355
1377
|
}
|
|
1356
1378
|
}
|
|
1379
|
+
const dismissedRows = db.prepare("SELECT left_id, right_id FROM dismissed_duplicates").all();
|
|
1380
|
+
const dismissedSet = new Set(
|
|
1381
|
+
dismissedRows.map((r) => pairKey(r.left_id, r.right_id))
|
|
1382
|
+
);
|
|
1383
|
+
const includeDismissed = filters?.includeDismissed === true;
|
|
1384
|
+
for (const candidate of pairs.values()) {
|
|
1385
|
+
if (dismissedSet.has(
|
|
1386
|
+
pairKey(candidate.leftContactId, candidate.rightContactId)
|
|
1387
|
+
)) {
|
|
1388
|
+
candidate.dismissed = true;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1357
1391
|
const minScore = filters?.minScore ?? 0;
|
|
1358
|
-
return [...pairs.values()].filter((candidate) =>
|
|
1392
|
+
return [...pairs.values()].filter((candidate) => {
|
|
1393
|
+
if (candidate.matchScore < minScore) return false;
|
|
1394
|
+
if (candidate.dismissed === true && !includeDismissed) return false;
|
|
1395
|
+
return true;
|
|
1396
|
+
}).sort((left, right) => {
|
|
1359
1397
|
const leftExact = left.matchScore === 1 ? 1 : 0;
|
|
1360
1398
|
const rightExact = right.matchScore === 1 ? 1 : 0;
|
|
1361
1399
|
return rightExact - leftExact || right.matchScore - left.matchScore;
|
|
@@ -3418,6 +3456,12 @@ function mergeContacts(db, input) {
|
|
|
3418
3456
|
`INSERT INTO contact_merges (winner_contact_id, loser_contact_id, merged_at, reason_summary, manual)
|
|
3419
3457
|
VALUES (?, ?, ?, ?, 1)`
|
|
3420
3458
|
).run(winnerId, loserId, now, reasonSummary);
|
|
3459
|
+
db.prepare(
|
|
3460
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
|
|
3461
|
+
).run(loserId, loserId);
|
|
3462
|
+
db.prepare(
|
|
3463
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
|
|
3464
|
+
).run(winnerId, winnerId);
|
|
3421
3465
|
db.prepare(
|
|
3422
3466
|
"DELETE FROM link_rollups WHERE link_id IN (SELECT id FROM links WHERE removed_at IS NOT NULL)"
|
|
3423
3467
|
).run();
|
|
@@ -3445,6 +3489,117 @@ function mergeContacts(db, input) {
|
|
|
3445
3489
|
});
|
|
3446
3490
|
}
|
|
3447
3491
|
|
|
3492
|
+
// src/merges/receipts.ts
|
|
3493
|
+
function buildDismissalMutationReceipt(primary, partial) {
|
|
3494
|
+
const normalized = normalizeMutationReceiptPartial(partial);
|
|
3495
|
+
return {
|
|
3496
|
+
primary,
|
|
3497
|
+
created: normalized.created,
|
|
3498
|
+
updated: normalized.updated,
|
|
3499
|
+
archived: normalized.archived,
|
|
3500
|
+
removed: normalized.removed,
|
|
3501
|
+
affectedLinks: normalized.affectedLinks,
|
|
3502
|
+
derivedEffects: normalized.derivedEffects
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// src/merges/dismiss_duplicate.ts
|
|
3507
|
+
function canonicalize(a, b) {
|
|
3508
|
+
return {
|
|
3509
|
+
leftContactId: Math.min(a, b),
|
|
3510
|
+
rightContactId: Math.max(a, b)
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
function dismissDuplicate(db, leftContactId, rightContactId, reason, now) {
|
|
3514
|
+
return withTransaction(db, () => {
|
|
3515
|
+
if (leftContactId === rightContactId) {
|
|
3516
|
+
throw new AffinityValidationError(
|
|
3517
|
+
"cannot dismiss a contact as a duplicate of itself"
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
const left = getContactRowById(db, leftContactId);
|
|
3521
|
+
if (!left) {
|
|
3522
|
+
throw new AffinityNotFoundError(`contact ${leftContactId} not found`);
|
|
3523
|
+
}
|
|
3524
|
+
if (left.lifecycle_state === "merged") {
|
|
3525
|
+
throw new AffinityStateError(
|
|
3526
|
+
`contact ${leftContactId} is merged and cannot be dismissed`
|
|
3527
|
+
);
|
|
3528
|
+
}
|
|
3529
|
+
const right = getContactRowById(db, rightContactId);
|
|
3530
|
+
if (!right) {
|
|
3531
|
+
throw new AffinityNotFoundError(`contact ${rightContactId} not found`);
|
|
3532
|
+
}
|
|
3533
|
+
if (right.lifecycle_state === "merged") {
|
|
3534
|
+
throw new AffinityStateError(
|
|
3535
|
+
`contact ${rightContactId} is merged and cannot be dismissed`
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
const pair = canonicalize(leftContactId, rightContactId);
|
|
3539
|
+
const resolvedNow = resolveNow(now);
|
|
3540
|
+
const resolvedReason = reason === void 0 || reason === null ? null : (() => {
|
|
3541
|
+
const t = reason.trim();
|
|
3542
|
+
return t.length === 0 ? null : t;
|
|
3543
|
+
})();
|
|
3544
|
+
db.prepare(
|
|
3545
|
+
`INSERT INTO dismissed_duplicates (left_id, right_id, reason, dismissed_at)
|
|
3546
|
+
VALUES (?, ?, ?, ?)
|
|
3547
|
+
ON CONFLICT(left_id, right_id) DO UPDATE SET
|
|
3548
|
+
reason = excluded.reason,
|
|
3549
|
+
dismissed_at = excluded.dismissed_at`
|
|
3550
|
+
).run(pair.leftContactId, pair.rightContactId, resolvedReason, resolvedNow);
|
|
3551
|
+
return buildDismissalMutationReceipt(pair);
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
// src/merges/undismiss_duplicate.ts
|
|
3556
|
+
function canonicalize2(a, b) {
|
|
3557
|
+
return {
|
|
3558
|
+
leftContactId: Math.min(a, b),
|
|
3559
|
+
rightContactId: Math.max(a, b)
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
function undismissDuplicate(db, leftContactId, rightContactId) {
|
|
3563
|
+
return withTransaction(db, () => {
|
|
3564
|
+
if (leftContactId === rightContactId) {
|
|
3565
|
+
throw new AffinityValidationError(
|
|
3566
|
+
"cannot undismiss a contact from itself"
|
|
3567
|
+
);
|
|
3568
|
+
}
|
|
3569
|
+
const left = getContactRowById(db, leftContactId);
|
|
3570
|
+
if (!left) {
|
|
3571
|
+
throw new AffinityNotFoundError(`contact ${leftContactId} not found`);
|
|
3572
|
+
}
|
|
3573
|
+
const right = getContactRowById(db, rightContactId);
|
|
3574
|
+
if (!right) {
|
|
3575
|
+
throw new AffinityNotFoundError(`contact ${rightContactId} not found`);
|
|
3576
|
+
}
|
|
3577
|
+
const pair = canonicalize2(leftContactId, rightContactId);
|
|
3578
|
+
db.prepare(
|
|
3579
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? AND right_id = ?"
|
|
3580
|
+
).run(pair.leftContactId, pair.rightContactId);
|
|
3581
|
+
return buildDismissalMutationReceipt(pair);
|
|
3582
|
+
});
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
// src/merges/list_dismissed_duplicates.ts
|
|
3586
|
+
function listDismissedDuplicates(db, options) {
|
|
3587
|
+
assertDefaultOrdering("listDismissedDuplicates", options);
|
|
3588
|
+
const { limit, offset } = resolveListLimitOffset(options);
|
|
3589
|
+
const rows = db.prepare(
|
|
3590
|
+
`SELECT left_id, right_id, reason, dismissed_at
|
|
3591
|
+
FROM dismissed_duplicates
|
|
3592
|
+
ORDER BY dismissed_at DESC
|
|
3593
|
+
LIMIT ? OFFSET ?`
|
|
3594
|
+
).all(limit, offset);
|
|
3595
|
+
return rows.map((row) => ({
|
|
3596
|
+
leftContactId: row.left_id,
|
|
3597
|
+
rightContactId: row.right_id,
|
|
3598
|
+
reason: row.reason,
|
|
3599
|
+
dismissedAt: row.dismissed_at
|
|
3600
|
+
}));
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3448
3603
|
// src/events/get_contact_journal.ts
|
|
3449
3604
|
function getContactJournal(db, contactId, options) {
|
|
3450
3605
|
assertDefaultOrdering("getContactJournal", options);
|
|
@@ -5333,6 +5488,9 @@ __export(tools_exports, {
|
|
|
5333
5488
|
manageDateAnchorTool: () => manageDateAnchorTool,
|
|
5334
5489
|
manageDateAnchorToolHandler: () => manageDateAnchorToolHandler,
|
|
5335
5490
|
manageDateAnchorToolName: () => manageDateAnchorToolName,
|
|
5491
|
+
manageDuplicateDismissalTool: () => manageDuplicateDismissalTool,
|
|
5492
|
+
manageDuplicateDismissalToolHandler: () => manageDuplicateDismissalToolHandler,
|
|
5493
|
+
manageDuplicateDismissalToolName: () => manageDuplicateDismissalToolName,
|
|
5336
5494
|
manageIdentityTool: () => manageIdentityTool,
|
|
5337
5495
|
manageIdentityToolHandler: () => manageIdentityToolHandler,
|
|
5338
5496
|
manageIdentityToolName: () => manageIdentityToolName,
|
|
@@ -5455,6 +5613,7 @@ var reviewAffinityToolName = "review_affinity";
|
|
|
5455
5613
|
var inspectAffinityItemToolName = "inspect_affinity_item";
|
|
5456
5614
|
var manageContactToolName = "manage_contact";
|
|
5457
5615
|
var mergeContactsToolName = "merge_contacts";
|
|
5616
|
+
var manageDuplicateDismissalToolName = "manage_duplicate_dismissal";
|
|
5458
5617
|
var manageIdentityToolName = "manage_identity";
|
|
5459
5618
|
var manageRelationshipToolName = "manage_relationship";
|
|
5460
5619
|
var recordEventToolName = "record_event";
|
|
@@ -6706,7 +6865,7 @@ var manageContactTool = defineAffinityTool({
|
|
|
6706
6865
|
handler: manageContactToolHandler
|
|
6707
6866
|
});
|
|
6708
6867
|
|
|
6709
|
-
// src/tools/
|
|
6868
|
+
// src/tools/manage_duplicate_dismissal_tool.ts
|
|
6710
6869
|
function contactLocatorSchema4(description) {
|
|
6711
6870
|
return {
|
|
6712
6871
|
type: "object",
|
|
@@ -6724,6 +6883,86 @@ function contactLocatorSchema4(description) {
|
|
|
6724
6883
|
description
|
|
6725
6884
|
};
|
|
6726
6885
|
}
|
|
6886
|
+
function manageDuplicateDismissalToolHandler(db, input) {
|
|
6887
|
+
return withToolHandling(() => {
|
|
6888
|
+
const left = resolveContactLocator(db, input.left, "left");
|
|
6889
|
+
if (!left.ok) {
|
|
6890
|
+
return left.result;
|
|
6891
|
+
}
|
|
6892
|
+
const right = resolveContactLocator(db, input.right, "right");
|
|
6893
|
+
if (!right.ok) {
|
|
6894
|
+
return right.result;
|
|
6895
|
+
}
|
|
6896
|
+
if (input.action === "dismiss") {
|
|
6897
|
+
const receipt2 = dismissDuplicate(
|
|
6898
|
+
db,
|
|
6899
|
+
left.value.id,
|
|
6900
|
+
right.value.id,
|
|
6901
|
+
input.reason ?? null
|
|
6902
|
+
);
|
|
6903
|
+
return toolSuccess(
|
|
6904
|
+
`Dismissed pair #${receipt2.primary.leftContactId} / #${receipt2.primary.rightContactId}.`,
|
|
6905
|
+
{ action: "dismiss", primary: receipt2.primary }
|
|
6906
|
+
);
|
|
6907
|
+
}
|
|
6908
|
+
const receipt = undismissDuplicate(db, left.value.id, right.value.id);
|
|
6909
|
+
return toolSuccess(
|
|
6910
|
+
`Un-dismissed pair #${receipt.primary.leftContactId} / #${receipt.primary.rightContactId}.`,
|
|
6911
|
+
{ action: "undismiss", primary: receipt.primary }
|
|
6912
|
+
);
|
|
6913
|
+
}, "Duplicate dismissal failed.");
|
|
6914
|
+
}
|
|
6915
|
+
var manageDuplicateDismissalTool = defineAffinityTool({
|
|
6916
|
+
name: manageDuplicateDismissalToolName,
|
|
6917
|
+
description: "Dismiss or un-dismiss a duplicate candidate pair so it does not resurface in listDuplicateCandidates.",
|
|
6918
|
+
whenToUse: "Use dismiss after confirming two contacts are different people (not duplicates). Use undismiss to re-surface a pair for re-evaluation.",
|
|
6919
|
+
whenNotToUse: "Do not use this to merge contacts \u2014 use merge_contacts for confirmed duplicates.",
|
|
6920
|
+
sideEffects: "writes_state",
|
|
6921
|
+
readOnly: false,
|
|
6922
|
+
supportsClarification: true,
|
|
6923
|
+
targetKinds: ["contact"],
|
|
6924
|
+
inputDescriptions: {
|
|
6925
|
+
action: "dismiss to suppress the pair; undismiss to re-surface it.",
|
|
6926
|
+
left: "One contact of the pair.",
|
|
6927
|
+
right: "The other contact of the pair.",
|
|
6928
|
+
reason: "Optional reason for the dismissal (audit trail)."
|
|
6929
|
+
},
|
|
6930
|
+
outputDescription: "Returns the dismissal receipt with the canonical pair identifiers.",
|
|
6931
|
+
inputSchema: objectSchema(
|
|
6932
|
+
{
|
|
6933
|
+
action: enumSchema("Action to perform.", ["dismiss", "undismiss"]),
|
|
6934
|
+
left: contactLocatorSchema4(
|
|
6935
|
+
"Left contact. Provide contactId or identity."
|
|
6936
|
+
),
|
|
6937
|
+
right: contactLocatorSchema4(
|
|
6938
|
+
"Right contact. Provide contactId or identity."
|
|
6939
|
+
),
|
|
6940
|
+
reason: nullableStringSchema("Optional reason for the dismissal.")
|
|
6941
|
+
},
|
|
6942
|
+
["action", "left", "right"],
|
|
6943
|
+
"Dismiss or un-dismiss a duplicate candidate pair."
|
|
6944
|
+
),
|
|
6945
|
+
handler: manageDuplicateDismissalToolHandler
|
|
6946
|
+
});
|
|
6947
|
+
|
|
6948
|
+
// src/tools/manage_date_anchor_tool.ts
|
|
6949
|
+
function contactLocatorSchema5(description) {
|
|
6950
|
+
return {
|
|
6951
|
+
type: "object",
|
|
6952
|
+
properties: {
|
|
6953
|
+
contactId: integerSchema("Exact contact id."),
|
|
6954
|
+
identity: objectSchema(
|
|
6955
|
+
{
|
|
6956
|
+
type: stringSchema("Identity type."),
|
|
6957
|
+
value: stringSchema("Identity value.")
|
|
6958
|
+
},
|
|
6959
|
+
["type", "value"],
|
|
6960
|
+
"Contact identity locator."
|
|
6961
|
+
)
|
|
6962
|
+
},
|
|
6963
|
+
description
|
|
6964
|
+
};
|
|
6965
|
+
}
|
|
6727
6966
|
function linkLocatorSchema3(description) {
|
|
6728
6967
|
return {
|
|
6729
6968
|
type: "object",
|
|
@@ -6814,7 +7053,7 @@ var manageDateAnchorTool = defineAffinityTool({
|
|
|
6814
7053
|
target: objectSchema(
|
|
6815
7054
|
{
|
|
6816
7055
|
kind: enumSchema("Target kind.", ["contact", "link"]),
|
|
6817
|
-
contact:
|
|
7056
|
+
contact: contactLocatorSchema5(
|
|
6818
7057
|
"Contact locator. Required when kind=contact. Provide contactId or identity."
|
|
6819
7058
|
),
|
|
6820
7059
|
link: linkLocatorSchema3(
|
|
@@ -6968,6 +7207,9 @@ function addIdentity(db, contactId, input) {
|
|
|
6968
7207
|
throw new AffinityInvariantError("inserted identity not found");
|
|
6969
7208
|
}
|
|
6970
7209
|
refreshContactRollup(db, contactId, now);
|
|
7210
|
+
db.prepare(
|
|
7211
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
|
|
7212
|
+
).run(contactId, contactId);
|
|
6971
7213
|
return buildIdentityMutationReceipt(mapIdentityRowToIdentityRecord(row), {
|
|
6972
7214
|
created: [{ kind: "identity", id }]
|
|
6973
7215
|
});
|
|
@@ -6991,6 +7233,9 @@ function removeIdentity(db, identityId, removedAt) {
|
|
|
6991
7233
|
"UPDATE identities SET removed_at = ?, updated_at = ? WHERE id = ?"
|
|
6992
7234
|
).run(now, now, identityId);
|
|
6993
7235
|
refreshContactRollup(db, row.contact_id, now);
|
|
7236
|
+
db.prepare(
|
|
7237
|
+
"DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
|
|
7238
|
+
).run(row.contact_id, row.contact_id);
|
|
6994
7239
|
return buildIdentityMutationReceipt(primary, {
|
|
6995
7240
|
removed: [{ kind: "identity", id: identityId }]
|
|
6996
7241
|
});
|
|
@@ -7069,7 +7314,7 @@ function verifyIdentity(db, identityId, verifiedAt) {
|
|
|
7069
7314
|
}
|
|
7070
7315
|
|
|
7071
7316
|
// src/tools/manage_identity_tool.ts
|
|
7072
|
-
function
|
|
7317
|
+
function contactLocatorSchema6(description) {
|
|
7073
7318
|
return {
|
|
7074
7319
|
type: "object",
|
|
7075
7320
|
properties: {
|
|
@@ -7148,7 +7393,7 @@ var manageIdentityTool = defineAffinityTool({
|
|
|
7148
7393
|
"verify",
|
|
7149
7394
|
"remove"
|
|
7150
7395
|
]),
|
|
7151
|
-
contact:
|
|
7396
|
+
contact: contactLocatorSchema6(
|
|
7152
7397
|
"Target contact. Required when action=add. Provide contactId or identity."
|
|
7153
7398
|
),
|
|
7154
7399
|
input: objectSchema(
|
|
@@ -7188,7 +7433,7 @@ var manageIdentityTool = defineAffinityTool({
|
|
|
7188
7433
|
});
|
|
7189
7434
|
|
|
7190
7435
|
// src/tools/manage_relationship_tool.ts
|
|
7191
|
-
function
|
|
7436
|
+
function contactLocatorSchema7(description) {
|
|
7192
7437
|
return {
|
|
7193
7438
|
type: "object",
|
|
7194
7439
|
properties: {
|
|
@@ -7344,10 +7589,10 @@ var manageRelationshipTool = defineAffinityTool({
|
|
|
7344
7589
|
"set_structural_tie",
|
|
7345
7590
|
"remove_structural_tie"
|
|
7346
7591
|
]),
|
|
7347
|
-
from:
|
|
7592
|
+
from: contactLocatorSchema7(
|
|
7348
7593
|
"Source contact. Required when action=seed_social_link or set_structural_tie. Provide contactId or identity."
|
|
7349
7594
|
),
|
|
7350
|
-
to:
|
|
7595
|
+
to: contactLocatorSchema7(
|
|
7351
7596
|
"Target contact. Required when action=seed_social_link or set_structural_tie. Provide contactId or identity."
|
|
7352
7597
|
),
|
|
7353
7598
|
kind: stringSchema(
|
|
@@ -7389,7 +7634,7 @@ var manageRelationshipTool = defineAffinityTool({
|
|
|
7389
7634
|
});
|
|
7390
7635
|
|
|
7391
7636
|
// src/tools/merge_contacts_tool.ts
|
|
7392
|
-
function
|
|
7637
|
+
function contactLocatorSchema8(description) {
|
|
7393
7638
|
return {
|
|
7394
7639
|
type: "object",
|
|
7395
7640
|
properties: {
|
|
@@ -7446,10 +7691,10 @@ var mergeContactsTool = defineAffinityTool({
|
|
|
7446
7691
|
outputDescription: "Returns the merge receipt with rewired entities and affected-link information.",
|
|
7447
7692
|
inputSchema: objectSchema(
|
|
7448
7693
|
{
|
|
7449
|
-
winner:
|
|
7694
|
+
winner: contactLocatorSchema8(
|
|
7450
7695
|
"Winner contact. Provide contactId or identity."
|
|
7451
7696
|
),
|
|
7452
|
-
loser:
|
|
7697
|
+
loser: contactLocatorSchema8(
|
|
7453
7698
|
"Loser contact. Provide contactId or identity."
|
|
7454
7699
|
),
|
|
7455
7700
|
reasonSummary: stringSchema("Optional merge reason."),
|
|
@@ -7620,7 +7865,7 @@ function buildChartOptions(input) {
|
|
|
7620
7865
|
...input.contactIds === void 0 ? {} : { contactIds: input.contactIds }
|
|
7621
7866
|
};
|
|
7622
7867
|
}
|
|
7623
|
-
function
|
|
7868
|
+
function contactLocatorSchema9(description) {
|
|
7624
7869
|
return {
|
|
7625
7870
|
type: "object",
|
|
7626
7871
|
properties: {
|
|
@@ -7711,6 +7956,14 @@ function validateReviewInput(input) {
|
|
|
7711
7956
|
"Use `contactIds` only with duplicate-candidate or chart review views."
|
|
7712
7957
|
);
|
|
7713
7958
|
}
|
|
7959
|
+
if (input.includeDismissed !== void 0 && input.view !== "contacts.duplicates") {
|
|
7960
|
+
return toolFailure(
|
|
7961
|
+
"protocol",
|
|
7962
|
+
"invalid_input",
|
|
7963
|
+
"The `includeDismissed` filter is not valid for this review view.",
|
|
7964
|
+
"Use `includeDismissed` only with the contacts.duplicates review view."
|
|
7965
|
+
);
|
|
7966
|
+
}
|
|
7714
7967
|
return null;
|
|
7715
7968
|
}
|
|
7716
7969
|
function duplicateCandidateItem(candidate) {
|
|
@@ -7767,6 +8020,15 @@ function mergeHistoryItem(record) {
|
|
|
7767
8020
|
snippet: record.reasonSummary ?? void 0
|
|
7768
8021
|
};
|
|
7769
8022
|
}
|
|
8023
|
+
function dismissedPairItem(record) {
|
|
8024
|
+
return {
|
|
8025
|
+
kind: "contact",
|
|
8026
|
+
id: record.leftContactId,
|
|
8027
|
+
title: `Dismissed pair: #${record.leftContactId} and #${record.rightContactId}`,
|
|
8028
|
+
subtitle: record.reason ?? "no reason given",
|
|
8029
|
+
snippet: `dismissed at ${record.dismissedAt}`
|
|
8030
|
+
};
|
|
8031
|
+
}
|
|
7770
8032
|
function compactFilters(input) {
|
|
7771
8033
|
return Object.fromEntries(
|
|
7772
8034
|
Object.entries(input).filter(
|
|
@@ -7802,7 +8064,8 @@ function reviewAffinityToolHandler(db, input) {
|
|
|
7802
8064
|
{
|
|
7803
8065
|
...input.contactIds === void 0 ? {} : { contactIds: input.contactIds },
|
|
7804
8066
|
...input.exactOnly === void 0 ? {} : { exactOnly: input.exactOnly },
|
|
7805
|
-
...input.minScore === void 0 ? {} : { minScore: input.minScore }
|
|
8067
|
+
...input.minScore === void 0 ? {} : { minScore: input.minScore },
|
|
8068
|
+
...input.includeDismissed === void 0 ? {} : { includeDismissed: input.includeDismissed }
|
|
7806
8069
|
},
|
|
7807
8070
|
options
|
|
7808
8071
|
).map(duplicateCandidateItem);
|
|
@@ -7983,6 +8246,9 @@ function reviewAffinityToolHandler(db, input) {
|
|
|
7983
8246
|
);
|
|
7984
8247
|
break;
|
|
7985
8248
|
}
|
|
8249
|
+
case "merges.dismissed":
|
|
8250
|
+
items = listDismissedDuplicates(db, options).map(dismissedPairItem);
|
|
8251
|
+
break;
|
|
7986
8252
|
}
|
|
7987
8253
|
return toolSuccess(
|
|
7988
8254
|
summarizeCount(items.length, "item"),
|
|
@@ -8033,6 +8299,7 @@ var reviewAffinityTool = defineAffinityTool({
|
|
|
8033
8299
|
horizonDays: "Optional horizon filter for commitments or upcoming dates.",
|
|
8034
8300
|
exactOnly: "Whether duplicate review should only include exact identity matches.",
|
|
8035
8301
|
minScore: "Optional minimum duplicate score.",
|
|
8302
|
+
includeDismissed: "Whether dismissed pairs should be included in duplicate review (marked with dismissed flag).",
|
|
8036
8303
|
since: "Optional lower timestamp bound for time-based views.",
|
|
8037
8304
|
until: "Optional upper timestamp bound for time-based views.",
|
|
8038
8305
|
limit: "Optional result limit.",
|
|
@@ -8054,9 +8321,10 @@ var reviewAffinityTool = defineAffinityTool({
|
|
|
8054
8321
|
"links.radar",
|
|
8055
8322
|
"dates.upcoming",
|
|
8056
8323
|
"graph.chart",
|
|
8057
|
-
"merges.history"
|
|
8324
|
+
"merges.history",
|
|
8325
|
+
"merges.dismissed"
|
|
8058
8326
|
]),
|
|
8059
|
-
contact:
|
|
8327
|
+
contact: contactLocatorSchema9("Optional target contact locator."),
|
|
8060
8328
|
link: linkLocatorSchema5("Optional target link locator."),
|
|
8061
8329
|
contactIds: arraySchema(
|
|
8062
8330
|
integerSchema("Contact id."),
|
|
@@ -8107,6 +8375,9 @@ var reviewAffinityTool = defineAffinityTool({
|
|
|
8107
8375
|
horizonDays: integerSchema("Optional horizon-days filter."),
|
|
8108
8376
|
exactOnly: booleanSchema("Whether duplicate review is exact-only."),
|
|
8109
8377
|
minScore: numberSchema("Optional duplicate minimum score."),
|
|
8378
|
+
includeDismissed: booleanSchema(
|
|
8379
|
+
"Whether dismissed pairs are included in duplicate review (marked with dismissed flag)."
|
|
8380
|
+
),
|
|
8110
8381
|
since: integerSchema("Optional lower timestamp bound."),
|
|
8111
8382
|
until: integerSchema("Optional upper timestamp bound."),
|
|
8112
8383
|
limit: integerSchema("Optional result limit."),
|
|
@@ -8205,6 +8476,7 @@ var affinityTools = [
|
|
|
8205
8476
|
inspectAffinityItemTool,
|
|
8206
8477
|
manageContactTool,
|
|
8207
8478
|
mergeContactsTool,
|
|
8479
|
+
manageDuplicateDismissalTool,
|
|
8208
8480
|
manageIdentityTool,
|
|
8209
8481
|
manageRelationshipTool,
|
|
8210
8482
|
recordEventTool,
|
|
@@ -8306,6 +8578,21 @@ var affinityToolMappings = [
|
|
|
8306
8578
|
action: "set_lifecycle"
|
|
8307
8579
|
},
|
|
8308
8580
|
{ source: "mergeContacts", tool: mergeContactsToolName },
|
|
8581
|
+
{
|
|
8582
|
+
source: "dismissDuplicate",
|
|
8583
|
+
tool: manageDuplicateDismissalToolName,
|
|
8584
|
+
action: "dismiss"
|
|
8585
|
+
},
|
|
8586
|
+
{
|
|
8587
|
+
source: "undismissDuplicate",
|
|
8588
|
+
tool: manageDuplicateDismissalToolName,
|
|
8589
|
+
action: "undismiss"
|
|
8590
|
+
},
|
|
8591
|
+
{
|
|
8592
|
+
source: "listDismissedDuplicates",
|
|
8593
|
+
tool: reviewAffinityToolName,
|
|
8594
|
+
view: "merges.dismissed"
|
|
8595
|
+
},
|
|
8309
8596
|
{ source: "addIdentity", tool: manageIdentityToolName, action: "add" },
|
|
8310
8597
|
{ source: "reviseIdentity", tool: manageIdentityToolName, action: "revise" },
|
|
8311
8598
|
{ source: "verifyIdentity", tool: manageIdentityToolName, action: "verify" },
|
|
@@ -8387,6 +8674,7 @@ __export(write_exports, {
|
|
|
8387
8674
|
addDateAnchor: () => addDateAnchor,
|
|
8388
8675
|
addIdentity: () => addIdentity,
|
|
8389
8676
|
createContact: () => createContact,
|
|
8677
|
+
dismissDuplicate: () => dismissDuplicate,
|
|
8390
8678
|
mergeContacts: () => mergeContacts,
|
|
8391
8679
|
overrideLinkState: () => overrideLinkState,
|
|
8392
8680
|
recordCommitment: () => recordCommitment,
|
|
@@ -8407,6 +8695,7 @@ __export(write_exports, {
|
|
|
8407
8695
|
setAttribute: () => setAttribute,
|
|
8408
8696
|
setContactLifecycle: () => setContactLifecycle,
|
|
8409
8697
|
setStructuralTie: () => setStructuralTie,
|
|
8698
|
+
undismissDuplicate: () => undismissDuplicate,
|
|
8410
8699
|
unsetAttribute: () => unsetAttribute,
|
|
8411
8700
|
verifyIdentity: () => verifyIdentity
|
|
8412
8701
|
});
|