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