@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 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) => candidate.matchScore >= minScore).sort((left, right) => {
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, contactId, now) {
3335
+ function softDeleteSelfLoops(db, now) {
3298
3336
  const rows = db.prepare(
3299
- `SELECT id FROM links
3300
- WHERE removed_at IS NULL AND from_contact_id = to_contact_id AND from_contact_id = ?`
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, winnerId, now)) {
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/manage_date_anchor_tool.ts
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: contactLocatorSchema4(
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 contactLocatorSchema5(description) {
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: contactLocatorSchema5(
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 contactLocatorSchema6(description) {
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: contactLocatorSchema6(
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: contactLocatorSchema6(
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 contactLocatorSchema7(description) {
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: contactLocatorSchema7(
7693
+ winner: contactLocatorSchema8(
7450
7694
  "Winner contact. Provide contactId or identity."
7451
7695
  ),
7452
- loser: contactLocatorSchema7(
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 contactLocatorSchema8(description) {
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: contactLocatorSchema8("Optional target contact locator."),
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
  });