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