@anuma/sdk 2.4.2 → 2.6.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.
@@ -4125,6 +4125,7 @@ function isOwnedByCtxUser(ctx, record) {
4125
4125
  function baseVaultConditions(ctx, options) {
4126
4126
  return [
4127
4127
  ...options?.includeDeleted ? [] : [import_watermelondb9.Q.where("is_deleted", false)],
4128
+ ...options?.includeSuperseded ? [] : [import_watermelondb9.Q.where("superseded_by", null)],
4128
4129
  ...ctx.userId !== void 0 ? [import_watermelondb9.Q.where("user_id", ctx.userId)] : [],
4129
4130
  ...options?.since ? [import_watermelondb9.Q.where("updated_at", import_watermelondb9.Q.gt(options.since.getTime()))] : []
4130
4131
  ];
@@ -4165,6 +4166,9 @@ function vaultMemoryToStoredRaw(memory) {
4165
4166
  eventTimeKind: memory.eventTimeKind ?? null,
4166
4167
  topicsUserManaged: memory.topicsUserManaged ?? false,
4167
4168
  topicsExtractedAt: memory.topicsExtractedAt ?? null,
4169
+ topicsExtractedVersion: memory.topicsExtractedVersion ?? null,
4170
+ supersededBy: memory.supersededBy ?? null,
4171
+ supersededAt: memory.supersededAt ?? null,
4168
4172
  createdAt: memory.createdAt,
4169
4173
  updatedAt: memory.updatedAt,
4170
4174
  isDeleted: memory.isDeleted
@@ -4210,6 +4214,59 @@ async function createVaultMemoryOp(ctx, opts) {
4210
4214
  });
4211
4215
  return vaultMemoryToStored(created, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
4212
4216
  }
4217
+ async function createSupersedingMemoryOp(ctx, opts, targetId) {
4218
+ if (!targetId) return { created: null, retired: false };
4219
+ const scope = opts.scope ?? "private";
4220
+ const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
4221
+ opts.content,
4222
+ ctx.walletAddress,
4223
+ ctx.signMessage,
4224
+ ctx.embeddedWalletSigner
4225
+ ) : opts.content;
4226
+ let createdRecord = null;
4227
+ await ctx.database.write(async () => {
4228
+ let target;
4229
+ try {
4230
+ target = await ctx.vaultMemoryCollection.find(targetId);
4231
+ } catch {
4232
+ return;
4233
+ }
4234
+ if (target.isDeleted || target.supersededBy || !isOwnedByCtxUser(ctx, target)) return;
4235
+ createdRecord = await ctx.vaultMemoryCollection.create((record) => {
4236
+ record._setRaw("content", encryptedContent);
4237
+ record._setRaw("scope", scope);
4238
+ record._setRaw("folder_id", opts.folderId ?? null);
4239
+ record._setRaw("user_id", ctx.userId ?? null);
4240
+ record._setRaw("is_deleted", false);
4241
+ if (opts.embedding !== void 0) {
4242
+ record._setRaw("embedding", opts.embedding);
4243
+ record._setRaw("embedding_model", opts.embeddingModel ?? null);
4244
+ }
4245
+ if (opts.sourceChunkIds !== void 0) {
4246
+ record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
4247
+ }
4248
+ record._setRaw("proof_count", opts.proofCount ?? 1);
4249
+ record._setRaw("source", opts.source ?? "manual");
4250
+ if (opts.eventTime) {
4251
+ record._setRaw("event_time_start", opts.eventTime.start ?? null);
4252
+ record._setRaw("event_time_end", opts.eventTime.end ?? null);
4253
+ record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
4254
+ }
4255
+ });
4256
+ await target.update((r) => {
4257
+ r._setRaw("superseded_by", createdRecord.id);
4258
+ r._setRaw("superseded_at", Date.now());
4259
+ });
4260
+ });
4261
+ if (!createdRecord) return { created: null, retired: false };
4262
+ const created = await vaultMemoryToStored(
4263
+ createdRecord,
4264
+ ctx.walletAddress,
4265
+ ctx.signMessage,
4266
+ ctx.embeddedWalletSigner
4267
+ );
4268
+ return { created, retired: true };
4269
+ }
4213
4270
  async function getMemoriesByEventTimeOp(ctx, windowStart, windowEnd) {
4214
4271
  const records = await ctx.vaultMemoryCollection.query(
4215
4272
  ...baseVaultConditions(ctx),
@@ -4349,6 +4406,9 @@ function vaultMemoryRawToStoredRaw(raw) {
4349
4406
  // SQLite stores booleans as 0/1, LokiJS as true/false — coerce both.
4350
4407
  topicsUserManaged: raw.topics_user_managed === true || raw.topics_user_managed === 1,
4351
4408
  topicsExtractedAt: raw.topics_extracted_at ?? null,
4409
+ topicsExtractedVersion: raw.topics_extracted_version ?? null,
4410
+ supersededBy: raw.superseded_by ?? null,
4411
+ supersededAt: raw.superseded_at ?? null,
4352
4412
  createdAt: new Date(raw.created_at),
4353
4413
  updatedAt: new Date(raw.updated_at),
4354
4414
  isDeleted: raw.is_deleted === true || raw.is_deleted === 1
@@ -4390,7 +4450,7 @@ async function getAllVaultMemoryContentsOp(ctx, options) {
4390
4450
  async function updateVaultMemoryOp(ctx, id, opts) {
4391
4451
  try {
4392
4452
  const probe = await ctx.vaultMemoryCollection.find(id);
4393
- if (probe.isDeleted || !isOwnedByCtxUser(ctx, probe)) return null;
4453
+ if (probe.isDeleted || probe.supersededBy || !isOwnedByCtxUser(ctx, probe)) return null;
4394
4454
  const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
4395
4455
  opts.content,
4396
4456
  ctx.walletAddress,
@@ -4401,7 +4461,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
4401
4461
  const record = probe;
4402
4462
  const originalUpdatedAt = record.updatedAt.getTime();
4403
4463
  await ctx.database.write(async () => {
4404
- if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
4464
+ if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record)) {
4405
4465
  stale = true;
4406
4466
  return;
4407
4467
  }
@@ -4479,6 +4539,40 @@ async function deleteVaultMemoryOp(ctx, id) {
4479
4539
  return false;
4480
4540
  }
4481
4541
  }
4542
+ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
4543
+ if (id === supersededById) return false;
4544
+ try {
4545
+ const record = await ctx.vaultMemoryCollection.find(id);
4546
+ if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record)) return false;
4547
+ let successor;
4548
+ try {
4549
+ successor = await ctx.vaultMemoryCollection.find(supersededById);
4550
+ } catch {
4551
+ return false;
4552
+ }
4553
+ if (successor.isDeleted || successor.supersededBy || !isOwnedByCtxUser(ctx, successor)) {
4554
+ return false;
4555
+ }
4556
+ let stale = false;
4557
+ await ctx.database.write(async () => {
4558
+ if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record)) {
4559
+ stale = true;
4560
+ return;
4561
+ }
4562
+ if (successor.isDeleted || successor.supersededBy || !isOwnedByCtxUser(ctx, successor)) {
4563
+ stale = true;
4564
+ return;
4565
+ }
4566
+ await record.update((r) => {
4567
+ r._setRaw("superseded_by", supersededById);
4568
+ r._setRaw("superseded_at", Date.now());
4569
+ });
4570
+ });
4571
+ return !stale;
4572
+ } catch {
4573
+ return false;
4574
+ }
4575
+ }
4482
4576
  async function getUnfiledVaultMemoriesOp(ctx) {
4483
4577
  const conditions = [
4484
4578
  import_watermelondb9.Q.where("folder_id", null),
@@ -4509,7 +4603,10 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
4509
4603
  for (const raw of rows) {
4510
4604
  const stamp = raw.topics_extracted_at ?? null;
4511
4605
  if (stamp !== null) {
4512
- if (raw.updated_at > stamp) stampedPending.push(raw);
4606
+ const version = raw.topics_extracted_version ?? 0;
4607
+ if (raw.updated_at > stamp || version < TOPICS_EXTRACTION_VERSION) {
4608
+ stampedPending.push(raw);
4609
+ }
4513
4610
  } else {
4514
4611
  unstamped.push(raw);
4515
4612
  }
@@ -4539,7 +4636,7 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
4539
4636
  );
4540
4637
  return { pending, linkedUnstamped };
4541
4638
  }
4542
- async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt) {
4639
+ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) {
4543
4640
  if (memoryIds.length === 0) return [];
4544
4641
  const uniqueIds = Array.from(new Set(memoryIds));
4545
4642
  const CHUNK = 500;
@@ -4562,6 +4659,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt) {
4562
4659
  prepared.push(
4563
4660
  record.prepareUpdate((r) => {
4564
4661
  r._setRaw("topics_extracted_at", extractedAt);
4662
+ r._setRaw("topics_extracted_version", version);
4565
4663
  r._setRaw("updated_at", originalUpdatedAt);
4566
4664
  })
4567
4665
  );
@@ -4614,13 +4712,14 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel)
4614
4712
  return false;
4615
4713
  }
4616
4714
  }
4617
- var import_watermelondb9;
4715
+ var import_watermelondb9, TOPICS_EXTRACTION_VERSION;
4618
4716
  var init_operations3 = __esm({
4619
4717
  "src/lib/db/memoryVault/operations.ts"() {
4620
4718
  "use strict";
4621
4719
  import_watermelondb9 = require("@nozbe/watermelondb");
4622
4720
  init_operations2();
4623
4721
  init_encryption2();
4722
+ TOPICS_EXTRACTION_VERSION = 1;
4624
4723
  }
4625
4724
  });
4626
4725
 
@@ -6896,7 +6995,9 @@ function validate2(parsed, newContent, validIds) {
6896
6995
  if (typeof parsed !== "object" || parsed === null) return null;
6897
6996
  const obj = parsed;
6898
6997
  const action = obj.action;
6899
- if (action !== "create" && action !== "update" && action !== "noop") return null;
6998
+ if (action !== "create" && action !== "update" && action !== "noop" && action !== "supersede") {
6999
+ return null;
7000
+ }
6900
7001
  if (action === "create") {
6901
7002
  const c2 = typeof obj.content === "string" ? obj.content.trim() : "";
6902
7003
  return { action: "create", content: c2.length > 0 ? c2 : newContent };
@@ -6908,7 +7009,7 @@ function validate2(parsed, newContent, validIds) {
6908
7009
  }
6909
7010
  const c = typeof obj.content === "string" ? obj.content.trim() : "";
6910
7011
  if (c.length === 0) return null;
6911
- return { action: "update", targetId, content: c };
7012
+ return { action, targetId, content: c };
6912
7013
  }
6913
7014
  var DEFAULT_MODEL2, DEFAULT_CONSOLIDATE_ATTEMPTS, DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS, SYSTEM_PROMPT2;
6914
7015
  var init_consolidate = __esm({
@@ -6924,10 +7025,11 @@ var init_consolidate = __esm({
6924
7025
 
6925
7026
  A "memory" is a self-contained natural-language fact about the user. Multiple memories about the same EXACT FACET (the same dimension of the same subject) should never coexist \u2014 they should be merged into one. Different facets on the same subject (e.g. user's dog's name vs user's dog's age) are SEPARATE memories.
6926
7027
 
6927
- Decide one of three actions:
7028
+ Decide one of four actions:
6928
7029
 
6929
7030
  - "create": the new memory describes a distinct fact not covered by any existing memory. Write it as new.
6930
- - "update": the new memory describes the SAME FACET as exactly one existing memory. Either it adds detail, corrects a value, or rephrases the same fact. Return targetId of that existing memory + the consolidated content (richest version of the two).
7031
+ - "update": the new memory describes the SAME FACET as exactly one existing memory and is COMPATIBLE with it \u2014 it adds detail, fixes an incomplete value, or rephrases the same still-true fact. Return targetId of that existing memory + the consolidated content (richest version of the two).
7032
+ - "supersede": the new memory REPLACES a standing fact whose value has CHANGED and is now INCOMPATIBLE with an existing memory \u2014 the old fact was true before but is false now (a state change). Return targetId of the STALE existing memory to retire + content = the NEW fact. The old memory is kept as history, not overwritten or deleted; do not re-state it.
6931
7033
  - "noop": the new memory is already fully captured by an existing memory \u2014 same facet, no new information. Skip the write.
6932
7034
 
6933
7035
  RULES (carry these strictly):
@@ -6935,18 +7037,21 @@ RULES (carry these strictly):
6935
7037
  1. ONE OBSERVATION PER DISTINCT FACET. If the new memory is about "user's aunt's twins" and an existing memory is also about "user's aunt's twins", they are the same facet \u2192 update, never create.
6936
7038
  2. MATCH BY FACET, NOT TOPIC. Two memories both mentioning "Zara" is not enough to merge \u2014 only merge if they describe the same property (e.g. both about "user's pending Zara boot exchange"). Two memories about Zara on different topics (e.g. "user shops at Zara" + "user returned a sweater to Zara last week") are separate facets.
6937
7039
  3. NO COMPUTATION. Do not sum counts, decrement quantities, or derive new facts from existing ones. If the new memory says "user spent $200 today" and an existing memory says "user spent $150 yesterday", these are TWO SEPARATE EVENTS \u2014 create.
6938
- 4. Events with different occurredAt dates are different memories even if the same activity ("user went to gym Monday" vs "user went to gym Friday" \u2014 separate events).
6939
- 5. When in doubt between create and update, choose create. Over-merging loses information; under-merging is recoverable.
7040
+ 4. EVENTS vs STANDING STATE.
7041
+ - Distinct EVENTS (episodic \u2014 something that happened on a date) are separate memories, even if the same activity: "user went to gym Monday" vs "user went to gym Friday" \u2192 create; keep both.
7042
+ - A STANDING ATTRIBUTE (an ongoing state: where the user lives/works, relationship status, current role) has ONE current value. When the new memory changes that value ("Lives in Portland" \u2192 "Lives in San Francisco"; "Works at Google" \u2192 "Works at Riverbend"; "Engaged" \u2192 "Broke up"), the old value is no longer true \u2192 supersede (retire the old, record the new). Do NOT create (that leaves a stale contradiction) and do NOT update (that erases the history that the old value was once true).
7043
+ 5. When in doubt between create and update, choose create \u2014 EXCEPT when the new memory directly contradicts the current value of a standing attribute in an existing memory; then choose supersede. Over-merging loses information; a live contradiction is worse than either.
6940
7044
 
6941
7045
  OUTPUT \u2014 strict JSON, no prose:
6942
7046
  {
6943
- "action": "create" | "update" | "noop",
6944
- "targetId": "<existing memory id, required for update/noop, omit for create>",
6945
- "content": "<consolidated content, required for create/update, omit for noop>"
7047
+ "action": "create" | "update" | "supersede" | "noop",
7048
+ "targetId": "<existing memory id \u2014 required for update/supersede/noop, omit for create>",
7049
+ "content": "<content \u2014 required for create/update/supersede, omit for noop>"
6946
7050
  }
6947
7051
 
6948
7052
  For "create": content is the new memory verbatim (or a slight refinement).
6949
7053
  For "update": content is the merged richest-version, \u226480 words.
7054
+ For "supersede": content is the NEW fact only (\u226480 words); targetId is the stale memory being retired.
6950
7055
  For "noop": no content (existing memory is already correct).`;
6951
7056
  }
6952
7057
  });
@@ -6978,6 +7083,7 @@ __export(index_exports, {
6978
7083
  StoredVaultFolderModel: () => VaultFolder,
6979
7084
  StoredVaultMemoryModel: () => VaultMemory,
6980
7085
  StreamExpiredError: () => StreamExpiredError,
7086
+ TOPICS_EXTRACTION_VERSION: () => TOPICS_EXTRACTION_VERSION,
6981
7087
  TOPIC_EXTRACTION_BATCH_SIZE: () => TOPIC_EXTRACTION_BATCH_SIZE,
6982
7088
  WalletPoller: () => WalletPoller,
6983
7089
  addConversationMemoriesOp: () => addConversationMemoriesOp,
@@ -7079,6 +7185,7 @@ __export(index_exports, {
7079
7185
  stampTopicsExtractedAtOp: () => stampTopicsExtractedAtOp,
7080
7186
  streamCancelPath: () => streamCancelPath,
7081
7187
  streamReplayPath: () => streamReplayPath,
7188
+ supersedeVaultMemoryOp: () => supersedeVaultMemoryOp,
7082
7189
  updateVaultFolderContextOp: () => updateVaultFolderContextOp,
7083
7190
  updateVaultFolderOp: () => updateVaultFolderOp,
7084
7191
  updateVaultMemoryEmbeddingOp: () => updateVaultMemoryEmbeddingOp,
@@ -12543,6 +12650,15 @@ __decorateClass([
12543
12650
  __decorateClass([
12544
12651
  (0, import_decorators3.field)("topics_extracted_at")
12545
12652
  ], VaultMemory.prototype, "topicsExtractedAt", 2);
12653
+ __decorateClass([
12654
+ (0, import_decorators3.field)("superseded_by")
12655
+ ], VaultMemory.prototype, "supersededBy", 2);
12656
+ __decorateClass([
12657
+ (0, import_decorators3.field)("superseded_at")
12658
+ ], VaultMemory.prototype, "supersededAt", 2);
12659
+ __decorateClass([
12660
+ (0, import_decorators3.field)("topics_extracted_version")
12661
+ ], VaultMemory.prototype, "topicsExtractedVersion", 2);
12546
12662
  __decorateClass([
12547
12663
  import_decorators3.readonly,
12548
12664
  (0, import_decorators3.date)("created_at")
@@ -12926,53 +13042,62 @@ async function retain(content, ctx, options = {}) {
12926
13042
  const enableAutoMerge = options.enableAutoMerge ?? true;
12927
13043
  const threshold = options.autoMergeThreshold ?? DEFAULT_AUTO_MERGE_THRESHOLD;
12928
13044
  const resolvedScope = options.scope ?? DEFAULT_SCOPE;
13045
+ let supersedeTargetId;
13046
+ let supersedeContent;
12929
13047
  if (enableAutoMerge) {
12930
13048
  if (options.consolidateOptions) {
12931
- const consolidationResult = await tryConsolidate(trimmed, ctx, options);
12932
- if (consolidationResult) return consolidationResult;
12933
- }
12934
- const matches = await searchVaultMemories(
12935
- trimmed,
12936
- ctx.vaultCtx,
12937
- ctx.embeddingOptions,
12938
- ctx.vaultCache,
12939
- {
12940
- limit: 1,
12941
- minSimilarity: threshold,
12942
- useFusion: false,
12943
- scopes: [resolvedScope],
12944
- ...options.folderId !== void 0 && { folderId: options.folderId }
12945
- }
12946
- );
12947
- if (matches.length > 0) {
12948
- const targetId = matches[0].uniqueId;
12949
- const existing = await getVaultMemoryOp(ctx.vaultCtx, targetId);
12950
- if (existing) {
12951
- const mergedSourceIds = unionStrings(
12952
- existing.sourceChunkIds ?? [],
12953
- options.sourceChunkIds ?? []
12954
- );
12955
- const eventTimeUpdate = pickEventTimeUpdate(existing, options.eventTime);
12956
- const updated = await updateVaultMemoryOp(ctx.vaultCtx, targetId, {
12957
- content: existing.content,
12958
- proofCountIncrement: 1,
12959
- sourceChunkIds: mergedSourceIds,
12960
- preserveUpdatedAt: true,
12961
- ...eventTimeUpdate && { eventTime: eventTimeUpdate }
12962
- });
12963
- if (updated) {
12964
- return {
12965
- action: "merge",
12966
- memoryId: targetId,
12967
- targetId,
12968
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
12969
- };
13049
+ const outcome = await tryConsolidate(trimmed, ctx, options);
13050
+ if (outcome) {
13051
+ if ("done" in outcome) return outcome.done;
13052
+ supersedeTargetId = outcome.supersede;
13053
+ supersedeContent = outcome.content;
13054
+ }
13055
+ }
13056
+ if (!supersedeTargetId) {
13057
+ const matches = await searchVaultMemories(
13058
+ trimmed,
13059
+ ctx.vaultCtx,
13060
+ ctx.embeddingOptions,
13061
+ ctx.vaultCache,
13062
+ {
13063
+ limit: 1,
13064
+ minSimilarity: threshold,
13065
+ useFusion: false,
13066
+ scopes: [resolvedScope],
13067
+ ...options.folderId !== void 0 && { folderId: options.folderId }
13068
+ }
13069
+ );
13070
+ if (matches.length > 0) {
13071
+ const targetId = matches[0].uniqueId;
13072
+ const existing = await getVaultMemoryOp(ctx.vaultCtx, targetId);
13073
+ if (existing && !existing.supersededBy) {
13074
+ const mergedSourceIds = unionStrings(
13075
+ existing.sourceChunkIds ?? [],
13076
+ options.sourceChunkIds ?? []
13077
+ );
13078
+ const eventTimeUpdate = pickEventTimeUpdate(existing, options.eventTime);
13079
+ const updated = await updateVaultMemoryOp(ctx.vaultCtx, targetId, {
13080
+ content: existing.content,
13081
+ proofCountIncrement: 1,
13082
+ sourceChunkIds: mergedSourceIds,
13083
+ preserveUpdatedAt: true,
13084
+ ...eventTimeUpdate && { eventTime: eventTimeUpdate }
13085
+ });
13086
+ if (updated) {
13087
+ return {
13088
+ action: "merge",
13089
+ memoryId: targetId,
13090
+ targetId,
13091
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
13092
+ };
13093
+ }
13094
+ await assertMergeTargetGoneOrThrow(ctx, targetId);
12970
13095
  }
12971
- await assertMergeTargetGoneOrThrow(ctx, targetId);
12972
13096
  }
12973
13097
  }
12974
13098
  }
12975
- const embedding = await generateEmbedding(trimmed, ctx.embeddingOptions);
13099
+ const contentToWrite = supersedeContent ?? trimmed;
13100
+ const embedding = await generateEmbedding(contentToWrite, ctx.embeddingOptions);
12976
13101
  const embeddingModel = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
12977
13102
  if (options.respectTombstones) {
12978
13103
  const tombstoneId = await findTombstoneMatch(embedding, embeddingModel, resolvedScope, ctx, {
@@ -12983,8 +13108,8 @@ async function retain(content, ctx, options = {}) {
12983
13108
  return { action: "suppressed", memoryId: tombstoneId, tombstoneId, proofCount: 0 };
12984
13109
  }
12985
13110
  }
12986
- const created = await createVaultMemoryOp(ctx.vaultCtx, {
12987
- content: trimmed,
13111
+ const createOpts = {
13112
+ content: contentToWrite,
12988
13113
  scope: resolvedScope,
12989
13114
  ...options.folderId !== void 0 && { folderId: options.folderId },
12990
13115
  embedding: JSON.stringify(embedding),
@@ -13001,7 +13126,24 @@ async function retain(content, ctx, options = {}) {
13001
13126
  kind: options.eventTime.kind
13002
13127
  }
13003
13128
  }
13004
- });
13129
+ };
13130
+ if (supersedeTargetId) {
13131
+ const { created: created2, retired } = await createSupersedingMemoryOp(
13132
+ ctx.vaultCtx,
13133
+ createOpts,
13134
+ supersedeTargetId
13135
+ );
13136
+ if (created2 && retired) {
13137
+ ctx.vaultCache.set(created2.uniqueId, Float32Array.from(embedding));
13138
+ return {
13139
+ action: "supersede",
13140
+ memoryId: created2.uniqueId,
13141
+ targetId: supersedeTargetId,
13142
+ proofCount: 1
13143
+ };
13144
+ }
13145
+ }
13146
+ const created = await createVaultMemoryOp(ctx.vaultCtx, createOpts);
13005
13147
  ctx.vaultCache.set(created.uniqueId, Float32Array.from(embedding));
13006
13148
  return {
13007
13149
  action: "create",
@@ -13071,9 +13213,14 @@ async function tryConsolidate(trimmed, ctx, options) {
13071
13213
  const { consolidateMemory: doConsolidate } = await Promise.resolve().then(() => (init_consolidate(), consolidate_exports));
13072
13214
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
13073
13215
  if (decision.action === "create") return null;
13216
+ if (decision.action === "supersede" && decision.targetId && decision.content) {
13217
+ const existing = await getVaultMemoryOp(ctx.vaultCtx, decision.targetId);
13218
+ if (!existing || existing.supersededBy) return null;
13219
+ return { supersede: decision.targetId, content: decision.content };
13220
+ }
13074
13221
  if (decision.action === "noop" && decision.targetId) {
13075
13222
  const existing = await getVaultMemoryOp(ctx.vaultCtx, decision.targetId);
13076
- if (!existing) return null;
13223
+ if (!existing || existing.supersededBy) return null;
13077
13224
  const mergedSourceIds = unionStrings(
13078
13225
  existing.sourceChunkIds ?? [],
13079
13226
  options.sourceChunkIds ?? []
@@ -13091,15 +13238,17 @@ async function tryConsolidate(trimmed, ctx, options) {
13091
13238
  return null;
13092
13239
  }
13093
13240
  return {
13094
- action: "merge",
13095
- memoryId: decision.targetId,
13096
- targetId: decision.targetId,
13097
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
13241
+ done: {
13242
+ action: "merge",
13243
+ memoryId: decision.targetId,
13244
+ targetId: decision.targetId,
13245
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
13246
+ }
13098
13247
  };
13099
13248
  }
13100
13249
  if (decision.action === "update" && decision.targetId && decision.content) {
13101
13250
  const existing = await getVaultMemoryOp(ctx.vaultCtx, decision.targetId);
13102
- if (!existing) return null;
13251
+ if (!existing || existing.supersededBy) return null;
13103
13252
  const mergedSourceIds = unionStrings(
13104
13253
  existing.sourceChunkIds ?? [],
13105
13254
  options.sourceChunkIds ?? []
@@ -13126,10 +13275,12 @@ async function tryConsolidate(trimmed, ctx, options) {
13126
13275
  }
13127
13276
  ctx.vaultCache.set(decision.targetId, Float32Array.from(newEmbedding));
13128
13277
  return {
13129
- action: "update",
13130
- memoryId: decision.targetId,
13131
- targetId: decision.targetId,
13132
- proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
13278
+ done: {
13279
+ action: "update",
13280
+ memoryId: decision.targetId,
13281
+ targetId: decision.targetId,
13282
+ proofCount: updated.proofCount ?? (existing.proofCount ?? 1) + 1
13283
+ }
13133
13284
  };
13134
13285
  }
13135
13286
  return null;
@@ -16624,7 +16775,7 @@ __decorateClass([
16624
16775
  ], VaultFolder.prototype, "context", 2);
16625
16776
 
16626
16777
  // src/lib/db/schema.ts
16627
- var SDK_SCHEMA_VERSION = 36;
16778
+ var SDK_SCHEMA_VERSION = 38;
16628
16779
  var sdkSchema = (0, import_watermelondb19.appSchema)({
16629
16780
  version: SDK_SCHEMA_VERSION,
16630
16781
  tables: [
@@ -16749,7 +16900,20 @@ var sdkSchema = (0, import_watermelondb19.appSchema)({
16749
16900
  // Unix ms of the last LLM topic-extraction pass over this memory's
16750
16901
  // content. Null = never extracted standalone (legacy rows with entity
16751
16902
  // links are grandfathered — see getMemoriesNeedingTopicExtractionOp).
16752
- { name: "topics_extracted_at", type: "number", isOptional: true }
16903
+ { name: "topics_extracted_at", type: "number", isOptional: true },
16904
+ // Write-time supersession (A2). When set, this fact was replaced by a
16905
+ // newer, incompatible-value fact (e.g. "Lives in Portland" superseded by
16906
+ // "Lives in SF"); `superseded_by` points at the newer memory's id and
16907
+ // `superseded_at` is the Unix ms it happened. Superseded rows stay in
16908
+ // the table (history / read-time fallback) but are excluded from recall
16909
+ // and dedup by default. Null = live (not superseded). Indexed so the
16910
+ // recall filter stays cheap.
16911
+ { name: "superseded_by", type: "string", isOptional: true, isIndexed: true },
16912
+ { name: "superseded_at", type: "number", isOptional: true },
16913
+ // The extraction-logic version this memory was last stamped under. Null
16914
+ // (pre-v38) is treated as version 0, so a bump of TOPICS_EXTRACTION_VERSION
16915
+ // re-extracts stale rows. See getMemoriesNeedingTopicExtractionOp.
16916
+ { name: "topics_extracted_version", type: "number", isOptional: true }
16753
16917
  ]
16754
16918
  }),
16755
16919
  // Entity table — canonical names extracted from auto-extraction (W5).
@@ -17360,6 +17524,34 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
17360
17524
  columns: [{ name: "topics_extracted_at", type: "number", isOptional: true }]
17361
17525
  })
17362
17526
  ]
17527
+ },
17528
+ // v36 -> v37: write-time supersession columns on memory_vault. Existing rows
17529
+ // keep both NULL (= live / not superseded), so no backfill — legacy
17530
+ // contradictory rows are handled by the read-time supersession fallback.
17531
+ {
17532
+ toVersion: 37,
17533
+ steps: [
17534
+ (0, import_migrations2.addColumns)({
17535
+ table: "memory_vault",
17536
+ columns: [
17537
+ { name: "superseded_by", type: "string", isOptional: true, isIndexed: true },
17538
+ { name: "superseded_at", type: "number", isOptional: true }
17539
+ ]
17540
+ })
17541
+ ]
17542
+ },
17543
+ // v37 -> v38: topics_extracted_version on memory_vault. Existing rows keep it
17544
+ // NULL, read as version 0 by getMemoriesNeedingTopicExtractionOp — so the
17545
+ // first sweep after a TOPICS_EXTRACTION_VERSION bump re-extracts them (drained
17546
+ // across sweeps by the worker's limit). See topicExtract.ts.
17547
+ {
17548
+ toVersion: 38,
17549
+ steps: [
17550
+ (0, import_migrations2.addColumns)({
17551
+ table: "memory_vault",
17552
+ columns: [{ name: "topics_extracted_version", type: "number", isOptional: true }]
17553
+ })
17554
+ ]
17363
17555
  }
17364
17556
  ]
17365
17557
  });
@@ -18724,6 +18916,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
18724
18916
  StoredVaultFolderModel,
18725
18917
  StoredVaultMemoryModel,
18726
18918
  StreamExpiredError,
18919
+ TOPICS_EXTRACTION_VERSION,
18727
18920
  TOPIC_EXTRACTION_BATCH_SIZE,
18728
18921
  WalletPoller,
18729
18922
  addConversationMemoriesOp,
@@ -18825,6 +19018,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
18825
19018
  stampTopicsExtractedAtOp,
18826
19019
  streamCancelPath,
18827
19020
  streamReplayPath,
19021
+ supersedeVaultMemoryOp,
18828
19022
  updateVaultFolderContextOp,
18829
19023
  updateVaultFolderOp,
18830
19024
  updateVaultMemoryEmbeddingOp,