@anuma/sdk 2.2.1 → 2.3.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.
@@ -12716,6 +12716,7 @@ init_portalLlm();
12716
12716
  init_operations3();
12717
12717
  init_constants();
12718
12718
  init_embeddings();
12719
+ init_vector();
12719
12720
  init_searchTool();
12720
12721
  var DEFAULT_AUTO_MERGE_THRESHOLD = 0.85;
12721
12722
  var DEFAULT_SCOPE = "private";
@@ -12777,6 +12778,15 @@ async function retain(content, ctx, options = {}) {
12777
12778
  }
12778
12779
  const embedding = await generateEmbedding(trimmed, ctx.embeddingOptions);
12779
12780
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
12781
+ if (options.respectTombstones) {
12782
+ const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
12783
+ threshold,
12784
+ folderId: options.folderId
12785
+ });
12786
+ if (tombstoneId) {
12787
+ return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
12788
+ }
12789
+ }
12780
12790
  const created = await createVaultMemoryOp(ctx.vaultCtx, {
12781
12791
  content: trimmed,
12782
12792
  scope: resolvedScope,
@@ -12806,6 +12816,32 @@ async function retain(content, ctx, options = {}) {
12806
12816
  function unionStrings(a, b) {
12807
12817
  return [.../* @__PURE__ */ new Set([...a, ...b])];
12808
12818
  }
12819
+ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
12820
+ const rows = await getAllVaultMemoriesOp(ctx.vaultCtx, {
12821
+ includeDeleted: true,
12822
+ scopes: [scope],
12823
+ ...opts.folderId !== void 0 && { folderId: opts.folderId }
12824
+ });
12825
+ let bestId = null;
12826
+ let bestSim = opts.threshold;
12827
+ for (const row of rows) {
12828
+ if (!row.isDeleted || !row.embedding) continue;
12829
+ if (row.embeddingModel !== embeddingModel) continue;
12830
+ let vec;
12831
+ try {
12832
+ vec = JSON.parse(row.embedding);
12833
+ } catch {
12834
+ continue;
12835
+ }
12836
+ if (!Array.isArray(vec) || vec.length !== embedding.length) continue;
12837
+ const sim = cosineSimilarity(embedding, vec);
12838
+ if (sim >= bestSim) {
12839
+ bestSim = sim;
12840
+ bestId = row.uniqueId;
12841
+ }
12842
+ }
12843
+ return bestId;
12844
+ }
12809
12845
  function pickEventTimeUpdate(existing, incoming) {
12810
12846
  if (incoming === void 0 || incoming === null) return void 0;
12811
12847
  if (existing.eventTimeStart !== null) return void 0;
@@ -13079,6 +13115,8 @@ async function extractAndRetain(messages, retainCtx, options) {
13079
13115
  const result = await retain(candidate.content, retainCtx, {
13080
13116
  source: "auto-extracted",
13081
13117
  sourceChunkIds: candidate.sourceMessageIds,
13118
+ // Don't let extraction silently resurrect a fact the user deleted.
13119
+ respectTombstones: true,
13082
13120
  ...options.scope !== void 0 && { scope: options.scope },
13083
13121
  ...options.folderId !== void 0 && { folderId: options.folderId },
13084
13122
  ...consolidateOptions !== void 0 && { consolidateOptions },
@@ -13086,7 +13124,7 @@ async function extractAndRetain(messages, retainCtx, options) {
13086
13124
  });
13087
13125
  succeededCandidates.push(candidate);
13088
13126
  results.push(result);
13089
- if (options.entityCtx && candidate.entities.length > 0) {
13127
+ if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
13090
13128
  try {
13091
13129
  if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
13092
13130
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
@@ -13330,6 +13368,7 @@ function createAutoExtractor(options) {
13330
13368
  );
13331
13369
  stateFor(conversationId).watermark = window2[window2.length - 1].id;
13332
13370
  for (let i = 0; i < results.length; i++) {
13371
+ if (results[i].action === "suppressed") continue;
13333
13372
  options.onMemoryExtracted?.({
13334
13373
  candidate: candidates[i],
13335
13374
  result: results[i],
@@ -5096,7 +5096,7 @@ interface RecallResult {
5096
5096
  /** Diagnostic: total memories in the vault when fact lane was queried. */
5097
5097
  vaultSize?: number;
5098
5098
  }
5099
- type RetainAction = "create" | "merge" | "update" | "skip";
5099
+ type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed";
5100
5100
  type RetainSource = "manual" | "auto-extracted" | "capsule";
5101
5101
  /** Why the consolidator fell back to "create" instead of a real decision. */
5102
5102
  type ConsolidationFallbackReason = "llm_error" | "invalid_response";
@@ -5107,6 +5107,14 @@ interface RetainOptions {
5107
5107
  folderId?: string | null;
5108
5108
  /** When provided, applies merge-on-write logic instead of plain insert. */
5109
5109
  enableAutoMerge?: boolean;
5110
+ /**
5111
+ * When true, a would-be create is suppressed if it matches a soft-deleted
5112
+ * ("tombstoned") memory above the auto-merge threshold — so auto-extraction
5113
+ * can't silently resurrect a fact the user deleted. Off by default so manual
5114
+ * and other `retain()` callers are unaffected; auto-extraction opts in.
5115
+ * Returns `action: 'suppressed'` with the matched `tombstoneId`.
5116
+ */
5117
+ respectTombstones?: boolean;
5110
5118
  /** Cosine similarity threshold for auto-merge. Default: 0.85. */
5111
5119
  autoMergeThreshold?: number;
5112
5120
  /**
@@ -5156,7 +5164,12 @@ interface RetainResult {
5156
5164
  memoryId: string;
5157
5165
  /** When action is 'merge' or 'update', the prior memory's id. */
5158
5166
  targetId?: string;
5159
- /** Updated proof_count after this write. */
5167
+ /**
5168
+ * When action is 'suppressed', the id of the soft-deleted memory that blocked
5169
+ * re-creation. `memoryId` is set to the same id (no new memory was written).
5170
+ */
5171
+ tombstoneId?: string;
5172
+ /** Updated proof_count after this write. 0 when nothing was written (suppressed). */
5160
5173
  proofCount: number;
5161
5174
  }
5162
5175
 
@@ -5096,7 +5096,7 @@ interface RecallResult {
5096
5096
  /** Diagnostic: total memories in the vault when fact lane was queried. */
5097
5097
  vaultSize?: number;
5098
5098
  }
5099
- type RetainAction = "create" | "merge" | "update" | "skip";
5099
+ type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed";
5100
5100
  type RetainSource = "manual" | "auto-extracted" | "capsule";
5101
5101
  /** Why the consolidator fell back to "create" instead of a real decision. */
5102
5102
  type ConsolidationFallbackReason = "llm_error" | "invalid_response";
@@ -5107,6 +5107,14 @@ interface RetainOptions {
5107
5107
  folderId?: string | null;
5108
5108
  /** When provided, applies merge-on-write logic instead of plain insert. */
5109
5109
  enableAutoMerge?: boolean;
5110
+ /**
5111
+ * When true, a would-be create is suppressed if it matches a soft-deleted
5112
+ * ("tombstoned") memory above the auto-merge threshold — so auto-extraction
5113
+ * can't silently resurrect a fact the user deleted. Off by default so manual
5114
+ * and other `retain()` callers are unaffected; auto-extraction opts in.
5115
+ * Returns `action: 'suppressed'` with the matched `tombstoneId`.
5116
+ */
5117
+ respectTombstones?: boolean;
5110
5118
  /** Cosine similarity threshold for auto-merge. Default: 0.85. */
5111
5119
  autoMergeThreshold?: number;
5112
5120
  /**
@@ -5156,7 +5164,12 @@ interface RetainResult {
5156
5164
  memoryId: string;
5157
5165
  /** When action is 'merge' or 'update', the prior memory's id. */
5158
5166
  targetId?: string;
5159
- /** Updated proof_count after this write. */
5167
+ /**
5168
+ * When action is 'suppressed', the id of the soft-deleted memory that blocked
5169
+ * re-creation. `memoryId` is set to the same id (no new memory was written).
5170
+ */
5171
+ tombstoneId?: string;
5172
+ /** Updated proof_count after this write. 0 when nothing was written (suppressed). */
5160
5173
  proofCount: number;
5161
5174
  }
5162
5175
 
@@ -5919,6 +5919,15 @@ async function retain(content, ctx, options = {}) {
5919
5919
  }
5920
5920
  const embedding = await generateEmbedding(trimmed, ctx.embeddingOptions);
5921
5921
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
5922
+ if (options.respectTombstones) {
5923
+ const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
5924
+ threshold,
5925
+ folderId: options.folderId
5926
+ });
5927
+ if (tombstoneId) {
5928
+ return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
5929
+ }
5930
+ }
5922
5931
  const created = await createVaultMemoryOp(ctx.vaultCtx, {
5923
5932
  content: trimmed,
5924
5933
  scope: resolvedScope,
@@ -5948,6 +5957,32 @@ async function retain(content, ctx, options = {}) {
5948
5957
  function unionStrings(a, b) {
5949
5958
  return [.../* @__PURE__ */ new Set([...a, ...b])];
5950
5959
  }
5960
+ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
5961
+ const rows = await getAllVaultMemoriesOp(ctx.vaultCtx, {
5962
+ includeDeleted: true,
5963
+ scopes: [scope],
5964
+ ...opts.folderId !== void 0 && { folderId: opts.folderId }
5965
+ });
5966
+ let bestId = null;
5967
+ let bestSim = opts.threshold;
5968
+ for (const row of rows) {
5969
+ if (!row.isDeleted || !row.embedding) continue;
5970
+ if (row.embeddingModel !== embeddingModel) continue;
5971
+ let vec;
5972
+ try {
5973
+ vec = JSON.parse(row.embedding);
5974
+ } catch {
5975
+ continue;
5976
+ }
5977
+ if (!Array.isArray(vec) || vec.length !== embedding.length) continue;
5978
+ const sim = cosineSimilarity(embedding, vec);
5979
+ if (sim >= bestSim) {
5980
+ bestSim = sim;
5981
+ bestId = row.uniqueId;
5982
+ }
5983
+ }
5984
+ return bestId;
5985
+ }
5951
5986
  function pickEventTimeUpdate(existing, incoming) {
5952
5987
  if (incoming === void 0 || incoming === null) return void 0;
5953
5988
  if (existing.eventTimeStart !== null) return void 0;
@@ -6221,6 +6256,8 @@ async function extractAndRetain(messages, retainCtx, options) {
6221
6256
  const result = await retain(candidate.content, retainCtx, {
6222
6257
  source: "auto-extracted",
6223
6258
  sourceChunkIds: candidate.sourceMessageIds,
6259
+ // Don't let extraction silently resurrect a fact the user deleted.
6260
+ respectTombstones: true,
6224
6261
  ...options.scope !== void 0 && { scope: options.scope },
6225
6262
  ...options.folderId !== void 0 && { folderId: options.folderId },
6226
6263
  ...consolidateOptions !== void 0 && { consolidateOptions },
@@ -6228,7 +6265,7 @@ async function extractAndRetain(messages, retainCtx, options) {
6228
6265
  });
6229
6266
  succeededCandidates.push(candidate);
6230
6267
  results.push(result);
6231
- if (options.entityCtx && candidate.entities.length > 0) {
6268
+ if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
6232
6269
  try {
6233
6270
  if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
6234
6271
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
@@ -6470,6 +6507,7 @@ function createAutoExtractor(options) {
6470
6507
  );
6471
6508
  stateFor(conversationId).watermark = window[window.length - 1].id;
6472
6509
  for (let i = 0; i < results.length; i++) {
6510
+ if (results[i].action === "suppressed") continue;
6473
6511
  options.onMemoryExtracted?.({
6474
6512
  candidate: candidates[i],
6475
6513
  result: results[i],
@@ -14067,6 +14067,7 @@ init_portalLlm();
14067
14067
  init_operations3();
14068
14068
  init_constants();
14069
14069
  init_embeddings();
14070
+ init_vector();
14070
14071
  init_searchTool();
14071
14072
  var DEFAULT_AUTO_MERGE_THRESHOLD = 0.85;
14072
14073
  var DEFAULT_SCOPE = "private";
@@ -14128,6 +14129,15 @@ async function retain(content, ctx, options = {}) {
14128
14129
  }
14129
14130
  const embedding = await generateEmbedding(trimmed, ctx.embeddingOptions);
14130
14131
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
14132
+ if (options.respectTombstones) {
14133
+ const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
14134
+ threshold,
14135
+ folderId: options.folderId
14136
+ });
14137
+ if (tombstoneId) {
14138
+ return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
14139
+ }
14140
+ }
14131
14141
  const created = await createVaultMemoryOp(ctx.vaultCtx, {
14132
14142
  content: trimmed,
14133
14143
  scope: resolvedScope,
@@ -14157,6 +14167,32 @@ async function retain(content, ctx, options = {}) {
14157
14167
  function unionStrings(a, b) {
14158
14168
  return [.../* @__PURE__ */ new Set([...a, ...b])];
14159
14169
  }
14170
+ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
14171
+ const rows = await getAllVaultMemoriesOp(ctx.vaultCtx, {
14172
+ includeDeleted: true,
14173
+ scopes: [scope],
14174
+ ...opts.folderId !== void 0 && { folderId: opts.folderId }
14175
+ });
14176
+ let bestId = null;
14177
+ let bestSim = opts.threshold;
14178
+ for (const row of rows) {
14179
+ if (!row.isDeleted || !row.embedding) continue;
14180
+ if (row.embeddingModel !== embeddingModel) continue;
14181
+ let vec;
14182
+ try {
14183
+ vec = JSON.parse(row.embedding);
14184
+ } catch {
14185
+ continue;
14186
+ }
14187
+ if (!Array.isArray(vec) || vec.length !== embedding.length) continue;
14188
+ const sim = cosineSimilarity(embedding, vec);
14189
+ if (sim >= bestSim) {
14190
+ bestSim = sim;
14191
+ bestId = row.uniqueId;
14192
+ }
14193
+ }
14194
+ return bestId;
14195
+ }
14160
14196
  function pickEventTimeUpdate(existing, incoming) {
14161
14197
  if (incoming === void 0 || incoming === null) return void 0;
14162
14198
  if (existing.eventTimeStart !== null) return void 0;
@@ -14430,6 +14466,8 @@ async function extractAndRetain(messages, retainCtx, options) {
14430
14466
  const result = await retain(candidate.content, retainCtx, {
14431
14467
  source: "auto-extracted",
14432
14468
  sourceChunkIds: candidate.sourceMessageIds,
14469
+ // Don't let extraction silently resurrect a fact the user deleted.
14470
+ respectTombstones: true,
14433
14471
  ...options.scope !== void 0 && { scope: options.scope },
14434
14472
  ...options.folderId !== void 0 && { folderId: options.folderId },
14435
14473
  ...consolidateOptions !== void 0 && { consolidateOptions },
@@ -14437,7 +14475,7 @@ async function extractAndRetain(messages, retainCtx, options) {
14437
14475
  });
14438
14476
  succeededCandidates.push(candidate);
14439
14477
  results.push(result);
14440
- if (options.entityCtx && candidate.entities.length > 0) {
14478
+ if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
14441
14479
  try {
14442
14480
  if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
14443
14481
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
@@ -14681,6 +14719,7 @@ function createAutoExtractor(options) {
14681
14719
  );
14682
14720
  stateFor(conversationId).watermark = window2[window2.length - 1].id;
14683
14721
  for (let i = 0; i < results.length; i++) {
14722
+ if (results[i].action === "suppressed") continue;
14684
14723
  options.onMemoryExtracted?.({
14685
14724
  candidate: candidates[i],
14686
14725
  result: results[i],
@@ -6256,7 +6256,7 @@ interface RecallResult {
6256
6256
  /** Diagnostic: total memories in the vault when fact lane was queried. */
6257
6257
  vaultSize?: number;
6258
6258
  }
6259
- type RetainAction = "create" | "merge" | "update" | "skip";
6259
+ type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed";
6260
6260
  type RetainSource = "manual" | "auto-extracted" | "capsule";
6261
6261
  /** Why the consolidator fell back to "create" instead of a real decision. */
6262
6262
  type ConsolidationFallbackReason = "llm_error" | "invalid_response";
@@ -6267,6 +6267,14 @@ interface RetainOptions {
6267
6267
  folderId?: string | null;
6268
6268
  /** When provided, applies merge-on-write logic instead of plain insert. */
6269
6269
  enableAutoMerge?: boolean;
6270
+ /**
6271
+ * When true, a would-be create is suppressed if it matches a soft-deleted
6272
+ * ("tombstoned") memory above the auto-merge threshold — so auto-extraction
6273
+ * can't silently resurrect a fact the user deleted. Off by default so manual
6274
+ * and other `retain()` callers are unaffected; auto-extraction opts in.
6275
+ * Returns `action: 'suppressed'` with the matched `tombstoneId`.
6276
+ */
6277
+ respectTombstones?: boolean;
6270
6278
  /** Cosine similarity threshold for auto-merge. Default: 0.85. */
6271
6279
  autoMergeThreshold?: number;
6272
6280
  /**
@@ -6316,7 +6324,12 @@ interface RetainResult {
6316
6324
  memoryId: string;
6317
6325
  /** When action is 'merge' or 'update', the prior memory's id. */
6318
6326
  targetId?: string;
6319
- /** Updated proof_count after this write. */
6327
+ /**
6328
+ * When action is 'suppressed', the id of the soft-deleted memory that blocked
6329
+ * re-creation. `memoryId` is set to the same id (no new memory was written).
6330
+ */
6331
+ tombstoneId?: string;
6332
+ /** Updated proof_count after this write. 0 when nothing was written (suppressed). */
6320
6333
  proofCount: number;
6321
6334
  }
6322
6335
 
@@ -6256,7 +6256,7 @@ interface RecallResult {
6256
6256
  /** Diagnostic: total memories in the vault when fact lane was queried. */
6257
6257
  vaultSize?: number;
6258
6258
  }
6259
- type RetainAction = "create" | "merge" | "update" | "skip";
6259
+ type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed";
6260
6260
  type RetainSource = "manual" | "auto-extracted" | "capsule";
6261
6261
  /** Why the consolidator fell back to "create" instead of a real decision. */
6262
6262
  type ConsolidationFallbackReason = "llm_error" | "invalid_response";
@@ -6267,6 +6267,14 @@ interface RetainOptions {
6267
6267
  folderId?: string | null;
6268
6268
  /** When provided, applies merge-on-write logic instead of plain insert. */
6269
6269
  enableAutoMerge?: boolean;
6270
+ /**
6271
+ * When true, a would-be create is suppressed if it matches a soft-deleted
6272
+ * ("tombstoned") memory above the auto-merge threshold — so auto-extraction
6273
+ * can't silently resurrect a fact the user deleted. Off by default so manual
6274
+ * and other `retain()` callers are unaffected; auto-extraction opts in.
6275
+ * Returns `action: 'suppressed'` with the matched `tombstoneId`.
6276
+ */
6277
+ respectTombstones?: boolean;
6270
6278
  /** Cosine similarity threshold for auto-merge. Default: 0.85. */
6271
6279
  autoMergeThreshold?: number;
6272
6280
  /**
@@ -6316,7 +6324,12 @@ interface RetainResult {
6316
6324
  memoryId: string;
6317
6325
  /** When action is 'merge' or 'update', the prior memory's id. */
6318
6326
  targetId?: string;
6319
- /** Updated proof_count after this write. */
6327
+ /**
6328
+ * When action is 'suppressed', the id of the soft-deleted memory that blocked
6329
+ * re-creation. `memoryId` is set to the same id (no new memory was written).
6330
+ */
6331
+ tombstoneId?: string;
6332
+ /** Updated proof_count after this write. 0 when nothing was written (suppressed). */
6320
6333
  proofCount: number;
6321
6334
  }
6322
6335
 
@@ -6741,6 +6741,15 @@ async function retain(content, ctx, options = {}) {
6741
6741
  }
6742
6742
  const embedding = await generateEmbedding(trimmed, ctx.embeddingOptions);
6743
6743
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
6744
+ if (options.respectTombstones) {
6745
+ const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
6746
+ threshold,
6747
+ folderId: options.folderId
6748
+ });
6749
+ if (tombstoneId) {
6750
+ return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
6751
+ }
6752
+ }
6744
6753
  const created = await createVaultMemoryOp(ctx.vaultCtx, {
6745
6754
  content: trimmed,
6746
6755
  scope: resolvedScope,
@@ -6770,6 +6779,32 @@ async function retain(content, ctx, options = {}) {
6770
6779
  function unionStrings(a, b) {
6771
6780
  return [.../* @__PURE__ */ new Set([...a, ...b])];
6772
6781
  }
6782
+ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
6783
+ const rows = await getAllVaultMemoriesOp(ctx.vaultCtx, {
6784
+ includeDeleted: true,
6785
+ scopes: [scope],
6786
+ ...opts.folderId !== void 0 && { folderId: opts.folderId }
6787
+ });
6788
+ let bestId = null;
6789
+ let bestSim = opts.threshold;
6790
+ for (const row of rows) {
6791
+ if (!row.isDeleted || !row.embedding) continue;
6792
+ if (row.embeddingModel !== embeddingModel) continue;
6793
+ let vec;
6794
+ try {
6795
+ vec = JSON.parse(row.embedding);
6796
+ } catch {
6797
+ continue;
6798
+ }
6799
+ if (!Array.isArray(vec) || vec.length !== embedding.length) continue;
6800
+ const sim = cosineSimilarity(embedding, vec);
6801
+ if (sim >= bestSim) {
6802
+ bestSim = sim;
6803
+ bestId = row.uniqueId;
6804
+ }
6805
+ }
6806
+ return bestId;
6807
+ }
6773
6808
  function pickEventTimeUpdate(existing, incoming) {
6774
6809
  if (incoming === void 0 || incoming === null) return void 0;
6775
6810
  if (existing.eventTimeStart !== null) return void 0;
@@ -7043,6 +7078,8 @@ async function extractAndRetain(messages, retainCtx, options) {
7043
7078
  const result = await retain(candidate.content, retainCtx, {
7044
7079
  source: "auto-extracted",
7045
7080
  sourceChunkIds: candidate.sourceMessageIds,
7081
+ // Don't let extraction silently resurrect a fact the user deleted.
7082
+ respectTombstones: true,
7046
7083
  ...options.scope !== void 0 && { scope: options.scope },
7047
7084
  ...options.folderId !== void 0 && { folderId: options.folderId },
7048
7085
  ...consolidateOptions !== void 0 && { consolidateOptions },
@@ -7050,7 +7087,7 @@ async function extractAndRetain(messages, retainCtx, options) {
7050
7087
  });
7051
7088
  succeededCandidates.push(candidate);
7052
7089
  results.push(result);
7053
- if (options.entityCtx && candidate.entities.length > 0) {
7090
+ if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
7054
7091
  try {
7055
7092
  if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
7056
7093
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
@@ -7292,6 +7329,7 @@ function createAutoExtractor(options) {
7292
7329
  );
7293
7330
  stateFor(conversationId).watermark = window2[window2.length - 1].id;
7294
7331
  for (let i = 0; i < results.length; i++) {
7332
+ if (results[i].action === "suppressed") continue;
7295
7333
  options.onMemoryExtracted?.({
7296
7334
  candidate: candidates[i],
7297
7335
  result: results[i],
@@ -25740,6 +25740,7 @@ init_portalLlm();
25740
25740
  init_operations3();
25741
25741
  init_constants();
25742
25742
  init_embeddings();
25743
+ init_vector();
25743
25744
  init_searchTool();
25744
25745
  var DEFAULT_AUTO_MERGE_THRESHOLD = 0.85;
25745
25746
  var DEFAULT_SCOPE = "private";
@@ -25801,6 +25802,15 @@ async function retain(content, ctx, options = {}) {
25801
25802
  }
25802
25803
  const embedding = await generateEmbedding(trimmed, ctx.embeddingOptions);
25803
25804
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
25805
+ if (options.respectTombstones) {
25806
+ const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
25807
+ threshold,
25808
+ folderId: options.folderId
25809
+ });
25810
+ if (tombstoneId) {
25811
+ return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
25812
+ }
25813
+ }
25804
25814
  const created = await createVaultMemoryOp(ctx.vaultCtx, {
25805
25815
  content: trimmed,
25806
25816
  scope: resolvedScope,
@@ -25830,6 +25840,32 @@ async function retain(content, ctx, options = {}) {
25830
25840
  function unionStrings(a, b) {
25831
25841
  return [.../* @__PURE__ */ new Set([...a, ...b])];
25832
25842
  }
25843
+ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
25844
+ const rows = await getAllVaultMemoriesOp(ctx.vaultCtx, {
25845
+ includeDeleted: true,
25846
+ scopes: [scope],
25847
+ ...opts.folderId !== void 0 && { folderId: opts.folderId }
25848
+ });
25849
+ let bestId = null;
25850
+ let bestSim = opts.threshold;
25851
+ for (const row of rows) {
25852
+ if (!row.isDeleted || !row.embedding) continue;
25853
+ if (row.embeddingModel !== embeddingModel) continue;
25854
+ let vec;
25855
+ try {
25856
+ vec = JSON.parse(row.embedding);
25857
+ } catch {
25858
+ continue;
25859
+ }
25860
+ if (!Array.isArray(vec) || vec.length !== embedding.length) continue;
25861
+ const sim = cosineSimilarity(embedding, vec);
25862
+ if (sim >= bestSim) {
25863
+ bestSim = sim;
25864
+ bestId = row.uniqueId;
25865
+ }
25866
+ }
25867
+ return bestId;
25868
+ }
25833
25869
  function pickEventTimeUpdate(existing, incoming) {
25834
25870
  if (incoming === void 0 || incoming === null) return void 0;
25835
25871
  if (existing.eventTimeStart !== null) return void 0;
@@ -26103,6 +26139,8 @@ async function extractAndRetain(messages, retainCtx, options) {
26103
26139
  const result = await retain(candidate.content, retainCtx, {
26104
26140
  source: "auto-extracted",
26105
26141
  sourceChunkIds: candidate.sourceMessageIds,
26142
+ // Don't let extraction silently resurrect a fact the user deleted.
26143
+ respectTombstones: true,
26106
26144
  ...options.scope !== void 0 && { scope: options.scope },
26107
26145
  ...options.folderId !== void 0 && { folderId: options.folderId },
26108
26146
  ...consolidateOptions !== void 0 && { consolidateOptions },
@@ -26110,7 +26148,7 @@ async function extractAndRetain(messages, retainCtx, options) {
26110
26148
  });
26111
26149
  succeededCandidates.push(candidate);
26112
26150
  results.push(result);
26113
- if (options.entityCtx && candidate.entities.length > 0) {
26151
+ if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
26114
26152
  try {
26115
26153
  if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
26116
26154
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
@@ -26354,6 +26392,7 @@ function createAutoExtractor(options) {
26354
26392
  );
26355
26393
  stateFor(conversationId).watermark = window2[window2.length - 1].id;
26356
26394
  for (let i = 0; i < results.length; i++) {
26395
+ if (results[i].action === "suppressed") continue;
26357
26396
  options.onMemoryExtracted?.({
26358
26397
  candidate: candidates[i],
26359
26398
  result: results[i],
@@ -5759,7 +5759,7 @@ interface RecallResult {
5759
5759
  /** Diagnostic: total memories in the vault when fact lane was queried. */
5760
5760
  vaultSize?: number;
5761
5761
  }
5762
- type RetainAction = "create" | "merge" | "update" | "skip";
5762
+ type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed";
5763
5763
  type RetainSource = "manual" | "auto-extracted" | "capsule";
5764
5764
  /** Why the consolidator fell back to "create" instead of a real decision. */
5765
5765
  type ConsolidationFallbackReason = "llm_error" | "invalid_response";
@@ -5770,6 +5770,14 @@ interface RetainOptions {
5770
5770
  folderId?: string | null;
5771
5771
  /** When provided, applies merge-on-write logic instead of plain insert. */
5772
5772
  enableAutoMerge?: boolean;
5773
+ /**
5774
+ * When true, a would-be create is suppressed if it matches a soft-deleted
5775
+ * ("tombstoned") memory above the auto-merge threshold — so auto-extraction
5776
+ * can't silently resurrect a fact the user deleted. Off by default so manual
5777
+ * and other `retain()` callers are unaffected; auto-extraction opts in.
5778
+ * Returns `action: 'suppressed'` with the matched `tombstoneId`.
5779
+ */
5780
+ respectTombstones?: boolean;
5773
5781
  /** Cosine similarity threshold for auto-merge. Default: 0.85. */
5774
5782
  autoMergeThreshold?: number;
5775
5783
  /**
@@ -5819,7 +5827,12 @@ interface RetainResult {
5819
5827
  memoryId: string;
5820
5828
  /** When action is 'merge' or 'update', the prior memory's id. */
5821
5829
  targetId?: string;
5822
- /** Updated proof_count after this write. */
5830
+ /**
5831
+ * When action is 'suppressed', the id of the soft-deleted memory that blocked
5832
+ * re-creation. `memoryId` is set to the same id (no new memory was written).
5833
+ */
5834
+ tombstoneId?: string;
5835
+ /** Updated proof_count after this write. 0 when nothing was written (suppressed). */
5823
5836
  proofCount: number;
5824
5837
  }
5825
5838
 
@@ -5759,7 +5759,7 @@ interface RecallResult {
5759
5759
  /** Diagnostic: total memories in the vault when fact lane was queried. */
5760
5760
  vaultSize?: number;
5761
5761
  }
5762
- type RetainAction = "create" | "merge" | "update" | "skip";
5762
+ type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed";
5763
5763
  type RetainSource = "manual" | "auto-extracted" | "capsule";
5764
5764
  /** Why the consolidator fell back to "create" instead of a real decision. */
5765
5765
  type ConsolidationFallbackReason = "llm_error" | "invalid_response";
@@ -5770,6 +5770,14 @@ interface RetainOptions {
5770
5770
  folderId?: string | null;
5771
5771
  /** When provided, applies merge-on-write logic instead of plain insert. */
5772
5772
  enableAutoMerge?: boolean;
5773
+ /**
5774
+ * When true, a would-be create is suppressed if it matches a soft-deleted
5775
+ * ("tombstoned") memory above the auto-merge threshold — so auto-extraction
5776
+ * can't silently resurrect a fact the user deleted. Off by default so manual
5777
+ * and other `retain()` callers are unaffected; auto-extraction opts in.
5778
+ * Returns `action: 'suppressed'` with the matched `tombstoneId`.
5779
+ */
5780
+ respectTombstones?: boolean;
5773
5781
  /** Cosine similarity threshold for auto-merge. Default: 0.85. */
5774
5782
  autoMergeThreshold?: number;
5775
5783
  /**
@@ -5819,7 +5827,12 @@ interface RetainResult {
5819
5827
  memoryId: string;
5820
5828
  /** When action is 'merge' or 'update', the prior memory's id. */
5821
5829
  targetId?: string;
5822
- /** Updated proof_count after this write. */
5830
+ /**
5831
+ * When action is 'suppressed', the id of the soft-deleted memory that blocked
5832
+ * re-creation. `memoryId` is set to the same id (no new memory was written).
5833
+ */
5834
+ tombstoneId?: string;
5835
+ /** Updated proof_count after this write. 0 when nothing was written (suppressed). */
5823
5836
  proofCount: number;
5824
5837
  }
5825
5838
 
@@ -4722,6 +4722,15 @@ async function retain(content, ctx, options = {}) {
4722
4722
  }
4723
4723
  const embedding = await generateEmbedding(trimmed, ctx.embeddingOptions);
4724
4724
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
4725
+ if (options.respectTombstones) {
4726
+ const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
4727
+ threshold,
4728
+ folderId: options.folderId
4729
+ });
4730
+ if (tombstoneId) {
4731
+ return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
4732
+ }
4733
+ }
4725
4734
  const created = await createVaultMemoryOp(ctx.vaultCtx, {
4726
4735
  content: trimmed,
4727
4736
  scope: resolvedScope,
@@ -4751,6 +4760,32 @@ async function retain(content, ctx, options = {}) {
4751
4760
  function unionStrings(a, b) {
4752
4761
  return [.../* @__PURE__ */ new Set([...a, ...b])];
4753
4762
  }
4763
+ async function findTombstoneMatch(embedding, embeddingModel, scope, ctx, opts) {
4764
+ const rows = await getAllVaultMemoriesOp(ctx.vaultCtx, {
4765
+ includeDeleted: true,
4766
+ scopes: [scope],
4767
+ ...opts.folderId !== void 0 && { folderId: opts.folderId }
4768
+ });
4769
+ let bestId = null;
4770
+ let bestSim = opts.threshold;
4771
+ for (const row of rows) {
4772
+ if (!row.isDeleted || !row.embedding) continue;
4773
+ if (row.embeddingModel !== embeddingModel) continue;
4774
+ let vec;
4775
+ try {
4776
+ vec = JSON.parse(row.embedding);
4777
+ } catch {
4778
+ continue;
4779
+ }
4780
+ if (!Array.isArray(vec) || vec.length !== embedding.length) continue;
4781
+ const sim = cosineSimilarity(embedding, vec);
4782
+ if (sim >= bestSim) {
4783
+ bestSim = sim;
4784
+ bestId = row.uniqueId;
4785
+ }
4786
+ }
4787
+ return bestId;
4788
+ }
4754
4789
  function pickEventTimeUpdate(existing, incoming) {
4755
4790
  if (incoming === void 0 || incoming === null) return void 0;
4756
4791
  if (existing.eventTimeStart !== null) return void 0;
@@ -5024,6 +5059,8 @@ async function extractAndRetain(messages, retainCtx, options) {
5024
5059
  const result = await retain(candidate.content, retainCtx, {
5025
5060
  source: "auto-extracted",
5026
5061
  sourceChunkIds: candidate.sourceMessageIds,
5062
+ // Don't let extraction silently resurrect a fact the user deleted.
5063
+ respectTombstones: true,
5027
5064
  ...options.scope !== void 0 && { scope: options.scope },
5028
5065
  ...options.folderId !== void 0 && { folderId: options.folderId },
5029
5066
  ...consolidateOptions !== void 0 && { consolidateOptions },
@@ -5031,7 +5068,7 @@ async function extractAndRetain(messages, retainCtx, options) {
5031
5068
  });
5032
5069
  succeededCandidates.push(candidate);
5033
5070
  results.push(result);
5034
- if (options.entityCtx && candidate.entities.length > 0) {
5071
+ if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
5035
5072
  try {
5036
5073
  if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
5037
5074
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
@@ -5273,6 +5310,7 @@ function createAutoExtractor(options) {
5273
5310
  );
5274
5311
  stateFor(conversationId).watermark = window2[window2.length - 1].id;
5275
5312
  for (let i = 0; i < results.length; i++) {
5313
+ if (results[i].action === "suppressed") continue;
5276
5314
  options.onMemoryExtracted?.({
5277
5315
  candidate: candidates[i],
5278
5316
  result: results[i],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",