@anuma/sdk 4.0.1 → 4.1.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.
@@ -2740,6 +2740,30 @@ async function updateVaultMemoryOp(ctx, id, opts) {
2740
2740
  return null;
2741
2741
  }
2742
2742
  }
2743
+ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
2744
+ let record;
2745
+ try {
2746
+ record = await ctx.vaultMemoryCollection.find(memoryId);
2747
+ } catch {
2748
+ return false;
2749
+ }
2750
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
2751
+ const originalUpdatedAt = record.updatedAt.getTime();
2752
+ let cleared = false;
2753
+ await ctx.database.write(async () => {
2754
+ if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
2755
+ cleared = true;
2756
+ await record.update((r) => {
2757
+ r._setRaw("topics_user_managed", false);
2758
+ r._setRaw("topics_extracted_version", null);
2759
+ if (record.topicsExtractedAt === null) {
2760
+ r._setRaw("topics_extracted_at", originalUpdatedAt);
2761
+ }
2762
+ r._setRaw("updated_at", originalUpdatedAt);
2763
+ });
2764
+ });
2765
+ return cleared;
2766
+ }
2743
2767
  async function setMemoryVisibilityOp(ctx, id, opts) {
2744
2768
  let record;
2745
2769
  try {
@@ -2901,11 +2925,13 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
2901
2925
  entityCtx,
2902
2926
  rows.map((r) => r.id)
2903
2927
  );
2928
+ const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
2904
2929
  const pendingRaw = [];
2905
2930
  const stampedPendingRaw = [];
2906
2931
  const linkedUnstampedAll = [];
2907
2932
  const topicsToRelinkAll = [];
2908
2933
  const topicsBackfillAll = [];
2934
+ const emptyCurationToClear = [];
2909
2935
  for (const raw of rows) {
2910
2936
  const id = raw.id;
2911
2937
  const topics = parseTopics(raw.topics);
@@ -2915,8 +2941,12 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
2915
2941
  continue;
2916
2942
  }
2917
2943
  if (raw.topics_user_managed) {
2918
- if (topics === null && linked !== void 0) topicsBackfillAll.push(id);
2919
- continue;
2944
+ if (topics !== null || linked !== void 0) {
2945
+ if (topics === null) topicsBackfillAll.push(id);
2946
+ continue;
2947
+ }
2948
+ if (cap !== void 0 && emptyCurationToClear.length >= cap) continue;
2949
+ emptyCurationToClear.push(id);
2920
2950
  }
2921
2951
  const stamp = raw.topics_extracted_at ?? null;
2922
2952
  let isPending = false;
@@ -2932,11 +2962,21 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
2932
2962
  pendingRaw.push(raw);
2933
2963
  isPending = true;
2934
2964
  }
2965
+ if (!isPending && linked === void 0 && topics === null) {
2966
+ pendingRaw.push(raw);
2967
+ isPending = true;
2968
+ }
2935
2969
  if (!isPending && topics === null && linked !== void 0) {
2936
2970
  topicsBackfillAll.push(id);
2937
2971
  }
2938
2972
  }
2939
- const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
2973
+ for (const id of emptyCurationToClear) {
2974
+ try {
2975
+ await clearMemoryTopicsOverrideOp(ctx, id, { unlessTopicsRecorded: true });
2976
+ } catch (err) {
2977
+ getLogger().warn("[memory/topics] repair clear failed", err);
2978
+ }
2979
+ }
2940
2980
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
2941
2981
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
2942
2982
  const pending = await mapInBatches(
@@ -6119,7 +6159,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6119
6159
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
6120
6160
  );
6121
6161
  }
6122
- const { recall: recall2 } = await import("./recall-O65PGPIQ.mjs");
6162
+ const { recall: recall2 } = await import("./recall-OCQOA7M3.mjs");
6123
6163
  let recallDegraded = [];
6124
6164
  const result = await recall2(
6125
6165
  query,
@@ -4980,6 +4980,30 @@ async function updateVaultMemoryOp(ctx, id, opts) {
4980
4980
  return null;
4981
4981
  }
4982
4982
  }
4983
+ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
4984
+ let record;
4985
+ try {
4986
+ record = await ctx.vaultMemoryCollection.find(memoryId);
4987
+ } catch {
4988
+ return false;
4989
+ }
4990
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
4991
+ const originalUpdatedAt = record.updatedAt.getTime();
4992
+ let cleared = false;
4993
+ await ctx.database.write(async () => {
4994
+ if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
4995
+ cleared = true;
4996
+ await record.update((r) => {
4997
+ r._setRaw("topics_user_managed", false);
4998
+ r._setRaw("topics_extracted_version", null);
4999
+ if (record.topicsExtractedAt === null) {
5000
+ r._setRaw("topics_extracted_at", originalUpdatedAt);
5001
+ }
5002
+ r._setRaw("updated_at", originalUpdatedAt);
5003
+ });
5004
+ });
5005
+ return cleared;
5006
+ }
4983
5007
  async function setMemoryVisibilityOp(ctx, id, opts) {
4984
5008
  let record;
4985
5009
  try {
@@ -5140,11 +5164,13 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
5140
5164
  entityCtx,
5141
5165
  rows.map((r) => r.id)
5142
5166
  );
5167
+ const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
5143
5168
  const pendingRaw = [];
5144
5169
  const stampedPendingRaw = [];
5145
5170
  const linkedUnstampedAll = [];
5146
5171
  const topicsToRelinkAll = [];
5147
5172
  const topicsBackfillAll = [];
5173
+ const emptyCurationToClear = [];
5148
5174
  for (const raw of rows) {
5149
5175
  const id = raw.id;
5150
5176
  const topics = parseTopics(raw.topics);
@@ -5154,8 +5180,12 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
5154
5180
  continue;
5155
5181
  }
5156
5182
  if (raw.topics_user_managed) {
5157
- if (topics === null && linked !== void 0) topicsBackfillAll.push(id);
5158
- continue;
5183
+ if (topics !== null || linked !== void 0) {
5184
+ if (topics === null) topicsBackfillAll.push(id);
5185
+ continue;
5186
+ }
5187
+ if (cap !== void 0 && emptyCurationToClear.length >= cap) continue;
5188
+ emptyCurationToClear.push(id);
5159
5189
  }
5160
5190
  const stamp = raw.topics_extracted_at ?? null;
5161
5191
  let isPending = false;
@@ -5171,11 +5201,21 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
5171
5201
  pendingRaw.push(raw);
5172
5202
  isPending = true;
5173
5203
  }
5204
+ if (!isPending && linked === void 0 && topics === null) {
5205
+ pendingRaw.push(raw);
5206
+ isPending = true;
5207
+ }
5174
5208
  if (!isPending && topics === null && linked !== void 0) {
5175
5209
  topicsBackfillAll.push(id);
5176
5210
  }
5177
5211
  }
5178
- const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
5212
+ for (const id of emptyCurationToClear) {
5213
+ try {
5214
+ await clearMemoryTopicsOverrideOp(ctx, id, { unlessTopicsRecorded: true });
5215
+ } catch (err) {
5216
+ getLogger().warn("[memory/topics] repair clear failed", err);
5217
+ }
5218
+ }
5179
5219
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
5180
5220
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
5181
5221
  const pending = await mapInBatches(
@@ -6083,6 +6083,12 @@ declare function getUnfiledVaultMemoriesOp(ctx: VaultMemoryOperationsContext): P
6083
6083
  * older version (including pre-v37 rows, read as version 0) is then re-extracted
6084
6084
  * by the next sweep, so topic-quality improvements propagate across the existing
6085
6085
  * vault. The worker's `limit` drains that re-extraction across sweeps.
6086
+ *
6087
+ * Bumping this is a WHOLE-VAULT re-extraction: the gate is unconditional across
6088
+ * every stamped row, and `stampTopicsExtractedAtOp` stamps this version on every
6089
+ * healthy row, so a bump sends each user's entire extracted vault back to the
6090
+ * LLM. Never reach for it to repair a subset — see the pre-v42-restore repair in
6091
+ * {@link getMemoriesNeedingTopicExtractionOp}, which targets the damaged rows.
6086
6092
  */
6087
6093
  declare const TOPICS_EXTRACTION_VERSION = 3;
6088
6094
  /**
@@ -6095,9 +6101,10 @@ interface MemoriesNeedingTopicExtraction {
6095
6101
  * Memories to run LLM topic extraction on (decrypted): never-extracted rows
6096
6102
  * with no entity links, plus stamped rows edited since their last pass
6097
6103
  * (`updated_at` > `topics_extracted_at`) or extracted under an older
6098
- * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}. Edited /
6099
- * stale-version rows come first (they get priority under `limit`), each group
6100
- * newest-created first.
6104
+ * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}, plus the
6105
+ * pre-v42-restore repair: a stamped row left with neither links nor a `topics`
6106
+ * record, which no no-LLM bucket can reach. Edited / stale-version rows come
6107
+ * first (they get priority under `limit`), each group newest-created first.
6101
6108
  */
6102
6109
  pending: StoredVaultMemory[];
6103
6110
  /**
@@ -6149,6 +6156,11 @@ interface MemoriesNeedingTopicExtraction {
6149
6156
  * between the record and the index and never re-derive them. That's why the
6150
6157
  * ownership filter lives in the partition below rather than in the query: a
6151
6158
  * restored curated memory is exactly the row whose index must be rebuilt.
6159
+ *
6160
+ * NOT purely a read: a curated row with no `topics` record AND no usable link is
6161
+ * a contradiction only a pre-v42 restore produces, and this is the one place
6162
+ * that can see all three facts at once, so it clears the flag there (capped by
6163
+ * `limit`) before returning. See the branch for why that's safe.
6152
6164
  */
6153
6165
  declare function getMemoriesNeedingTopicExtractionOp(ctx: VaultMemoryOperationsContext, options?: {
6154
6166
  limit?: number;
@@ -6083,6 +6083,12 @@ declare function getUnfiledVaultMemoriesOp(ctx: VaultMemoryOperationsContext): P
6083
6083
  * older version (including pre-v37 rows, read as version 0) is then re-extracted
6084
6084
  * by the next sweep, so topic-quality improvements propagate across the existing
6085
6085
  * vault. The worker's `limit` drains that re-extraction across sweeps.
6086
+ *
6087
+ * Bumping this is a WHOLE-VAULT re-extraction: the gate is unconditional across
6088
+ * every stamped row, and `stampTopicsExtractedAtOp` stamps this version on every
6089
+ * healthy row, so a bump sends each user's entire extracted vault back to the
6090
+ * LLM. Never reach for it to repair a subset — see the pre-v42-restore repair in
6091
+ * {@link getMemoriesNeedingTopicExtractionOp}, which targets the damaged rows.
6086
6092
  */
6087
6093
  declare const TOPICS_EXTRACTION_VERSION = 3;
6088
6094
  /**
@@ -6095,9 +6101,10 @@ interface MemoriesNeedingTopicExtraction {
6095
6101
  * Memories to run LLM topic extraction on (decrypted): never-extracted rows
6096
6102
  * with no entity links, plus stamped rows edited since their last pass
6097
6103
  * (`updated_at` > `topics_extracted_at`) or extracted under an older
6098
- * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}. Edited /
6099
- * stale-version rows come first (they get priority under `limit`), each group
6100
- * newest-created first.
6104
+ * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}, plus the
6105
+ * pre-v42-restore repair: a stamped row left with neither links nor a `topics`
6106
+ * record, which no no-LLM bucket can reach. Edited / stale-version rows come
6107
+ * first (they get priority under `limit`), each group newest-created first.
6101
6108
  */
6102
6109
  pending: StoredVaultMemory[];
6103
6110
  /**
@@ -6149,6 +6156,11 @@ interface MemoriesNeedingTopicExtraction {
6149
6156
  * between the record and the index and never re-derive them. That's why the
6150
6157
  * ownership filter lives in the partition below rather than in the query: a
6151
6158
  * restored curated memory is exactly the row whose index must be rebuilt.
6159
+ *
6160
+ * NOT purely a read: a curated row with no `topics` record AND no usable link is
6161
+ * a contradiction only a pre-v42 restore produces, and this is the one place
6162
+ * that can see all three facts at once, so it clears the flag there (capped by
6163
+ * `limit`) before returning. See the branch for why that's safe.
6152
6164
  */
6153
6165
  declare function getMemoriesNeedingTopicExtractionOp(ctx: VaultMemoryOperationsContext, options?: {
6154
6166
  limit?: number;
@@ -116,7 +116,7 @@ import {
116
116
  upsertMessageOp,
117
117
  useEncryption,
118
118
  vaultMemoryToStored
119
- } from "./chunk-BOAKGY4R.mjs";
119
+ } from "./chunk-VBO6KSNQ.mjs";
120
120
  import {
121
121
  PII_PATTERNS,
122
122
  PiiRedactor,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-BOAKGY4R.mjs";
3
+ } from "./chunk-VBO6KSNQ.mjs";
4
4
  import "./chunk-ILAWDQDE.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {
@@ -2927,7 +2927,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
2927
2927
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return null;
2928
2928
  let stale = false;
2929
2929
  const originalUpdatedAt = record.updatedAt.getTime();
2930
- await ctx.database.write(async () => {
2930
+ await ctx.database.write(async (writer) => {
2931
2931
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
2932
2932
  stale = true;
2933
2933
  return;
@@ -2936,30 +2936,23 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
2936
2936
  r._setRaw("topics_user_managed", true);
2937
2937
  r._setRaw("updated_at", originalUpdatedAt);
2938
2938
  });
2939
+ const linked = entities.length > 0 ? await writer.callWriter(
2940
+ () => linkMemoryEntitiesOp(entityCtx, memoryId, entities, { topicsSource: "user" })
2941
+ ) : [];
2942
+ const keep = new Set(linked.map((e) => e.uniqueId));
2943
+ const existing = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", memoryId)).fetch();
2944
+ const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
2945
+ if (staleLinks.length === 0 && entities.length > 0) return;
2946
+ const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, memoryId, linked, entities, "user");
2947
+ await ctx.database.batch(
2948
+ ...staleLinks.map((l) => l.prepareDestroyPermanently()),
2949
+ ...topicsOp ? [topicsOp] : []
2950
+ );
2939
2951
  });
2940
2952
  if (stale) return null;
2941
- const linked = entities.length > 0 ? await linkMemoryEntitiesOp(entityCtx, memoryId, entities, { topicsSource: "user" }) : [];
2942
- const keep = new Set(linked.map((e) => e.uniqueId));
2943
- const existing = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", memoryId)).fetch();
2944
- const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
2945
- if (staleLinks.length > 0 || entities.length === 0) {
2946
- await ctx.database.write(async () => {
2947
- const topicsOp = await prepareMemoryTopicsUpdate(
2948
- entityCtx,
2949
- memoryId,
2950
- linked,
2951
- entities,
2952
- "user"
2953
- );
2954
- await ctx.database.batch(
2955
- ...staleLinks.map((l) => l.prepareDestroyPermanently()),
2956
- ...topicsOp ? [topicsOp] : []
2957
- );
2958
- });
2959
- }
2960
2953
  return vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
2961
2954
  }
2962
- async function clearMemoryTopicsOverrideOp(ctx, memoryId) {
2955
+ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
2963
2956
  let record;
2964
2957
  try {
2965
2958
  record = await ctx.vaultMemoryCollection.find(memoryId);
@@ -2968,7 +2961,10 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId) {
2968
2961
  }
2969
2962
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
2970
2963
  const originalUpdatedAt = record.updatedAt.getTime();
2964
+ let cleared = false;
2971
2965
  await ctx.database.write(async () => {
2966
+ if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
2967
+ cleared = true;
2972
2968
  await record.update((r) => {
2973
2969
  r._setRaw("topics_user_managed", false);
2974
2970
  r._setRaw("topics_extracted_version", null);
@@ -2978,7 +2974,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId) {
2978
2974
  r._setRaw("updated_at", originalUpdatedAt);
2979
2975
  });
2980
2976
  });
2981
- return true;
2977
+ return cleared;
2982
2978
  }
2983
2979
  async function setMemoryVisibilityOp(ctx, id, opts) {
2984
2980
  let record;
@@ -3141,11 +3137,13 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
3141
3137
  entityCtx,
3142
3138
  rows.map((r) => r.id)
3143
3139
  );
3140
+ const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
3144
3141
  const pendingRaw = [];
3145
3142
  const stampedPendingRaw = [];
3146
3143
  const linkedUnstampedAll = [];
3147
3144
  const topicsToRelinkAll = [];
3148
3145
  const topicsBackfillAll = [];
3146
+ const emptyCurationToClear = [];
3149
3147
  for (const raw of rows) {
3150
3148
  const id = raw.id;
3151
3149
  const topics = parseTopics(raw.topics);
@@ -3155,8 +3153,12 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
3155
3153
  continue;
3156
3154
  }
3157
3155
  if (raw.topics_user_managed) {
3158
- if (topics === null && linked !== void 0) topicsBackfillAll.push(id);
3159
- continue;
3156
+ if (topics !== null || linked !== void 0) {
3157
+ if (topics === null) topicsBackfillAll.push(id);
3158
+ continue;
3159
+ }
3160
+ if (cap !== void 0 && emptyCurationToClear.length >= cap) continue;
3161
+ emptyCurationToClear.push(id);
3160
3162
  }
3161
3163
  const stamp = raw.topics_extracted_at ?? null;
3162
3164
  let isPending = false;
@@ -3172,11 +3174,21 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
3172
3174
  pendingRaw.push(raw);
3173
3175
  isPending = true;
3174
3176
  }
3177
+ if (!isPending && linked === void 0 && topics === null) {
3178
+ pendingRaw.push(raw);
3179
+ isPending = true;
3180
+ }
3175
3181
  if (!isPending && topics === null && linked !== void 0) {
3176
3182
  topicsBackfillAll.push(id);
3177
3183
  }
3178
3184
  }
3179
- const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
3185
+ for (const id of emptyCurationToClear) {
3186
+ try {
3187
+ await clearMemoryTopicsOverrideOp(ctx, id, { unlessTopicsRecorded: true });
3188
+ } catch (err) {
3189
+ getLogger().warn("[memory/topics] repair clear failed", err);
3190
+ }
3191
+ }
3180
3192
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
3181
3193
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
3182
3194
  const pending = await mapInBatches(
@@ -6430,7 +6442,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6430
6442
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
6431
6443
  );
6432
6444
  }
6433
- const { recall: recall2 } = await import("./recall-R3VX2UST.mjs");
6445
+ const { recall: recall2 } = await import("./recall-WXCZHXHZ.mjs");
6434
6446
  let recallDegraded = [];
6435
6447
  const result = await recall2(
6436
6448
  query,
@@ -5238,7 +5238,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
5238
5238
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return null;
5239
5239
  let stale = false;
5240
5240
  const originalUpdatedAt = record.updatedAt.getTime();
5241
- await ctx.database.write(async () => {
5241
+ await ctx.database.write(async (writer) => {
5242
5242
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
5243
5243
  stale = true;
5244
5244
  return;
@@ -5247,30 +5247,23 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
5247
5247
  r._setRaw("topics_user_managed", true);
5248
5248
  r._setRaw("updated_at", originalUpdatedAt);
5249
5249
  });
5250
+ const linked = entities.length > 0 ? await writer.callWriter(
5251
+ () => linkMemoryEntitiesOp(entityCtx, memoryId, entities, { topicsSource: "user" })
5252
+ ) : [];
5253
+ const keep = new Set(linked.map((e) => e.uniqueId));
5254
+ const existing = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", memoryId)).fetch();
5255
+ const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
5256
+ if (staleLinks.length === 0 && entities.length > 0) return;
5257
+ const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, memoryId, linked, entities, "user");
5258
+ await ctx.database.batch(
5259
+ ...staleLinks.map((l) => l.prepareDestroyPermanently()),
5260
+ ...topicsOp ? [topicsOp] : []
5261
+ );
5250
5262
  });
5251
5263
  if (stale) return null;
5252
- const linked = entities.length > 0 ? await linkMemoryEntitiesOp(entityCtx, memoryId, entities, { topicsSource: "user" }) : [];
5253
- const keep = new Set(linked.map((e) => e.uniqueId));
5254
- const existing = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", memoryId)).fetch();
5255
- const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
5256
- if (staleLinks.length > 0 || entities.length === 0) {
5257
- await ctx.database.write(async () => {
5258
- const topicsOp = await prepareMemoryTopicsUpdate(
5259
- entityCtx,
5260
- memoryId,
5261
- linked,
5262
- entities,
5263
- "user"
5264
- );
5265
- await ctx.database.batch(
5266
- ...staleLinks.map((l) => l.prepareDestroyPermanently()),
5267
- ...topicsOp ? [topicsOp] : []
5268
- );
5269
- });
5270
- }
5271
5264
  return vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
5272
5265
  }
5273
- async function clearMemoryTopicsOverrideOp(ctx, memoryId) {
5266
+ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
5274
5267
  let record;
5275
5268
  try {
5276
5269
  record = await ctx.vaultMemoryCollection.find(memoryId);
@@ -5279,7 +5272,10 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId) {
5279
5272
  }
5280
5273
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
5281
5274
  const originalUpdatedAt = record.updatedAt.getTime();
5275
+ let cleared = false;
5282
5276
  await ctx.database.write(async () => {
5277
+ if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
5278
+ cleared = true;
5283
5279
  await record.update((r) => {
5284
5280
  r._setRaw("topics_user_managed", false);
5285
5281
  r._setRaw("topics_extracted_version", null);
@@ -5289,7 +5285,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId) {
5289
5285
  r._setRaw("updated_at", originalUpdatedAt);
5290
5286
  });
5291
5287
  });
5292
- return true;
5288
+ return cleared;
5293
5289
  }
5294
5290
  async function setMemoryVisibilityOp(ctx, id, opts) {
5295
5291
  let record;
@@ -5451,11 +5447,13 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
5451
5447
  entityCtx,
5452
5448
  rows.map((r) => r.id)
5453
5449
  );
5450
+ const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
5454
5451
  const pendingRaw = [];
5455
5452
  const stampedPendingRaw = [];
5456
5453
  const linkedUnstampedAll = [];
5457
5454
  const topicsToRelinkAll = [];
5458
5455
  const topicsBackfillAll = [];
5456
+ const emptyCurationToClear = [];
5459
5457
  for (const raw of rows) {
5460
5458
  const id = raw.id;
5461
5459
  const topics = parseTopics(raw.topics);
@@ -5465,8 +5463,12 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
5465
5463
  continue;
5466
5464
  }
5467
5465
  if (raw.topics_user_managed) {
5468
- if (topics === null && linked !== void 0) topicsBackfillAll.push(id);
5469
- continue;
5466
+ if (topics !== null || linked !== void 0) {
5467
+ if (topics === null) topicsBackfillAll.push(id);
5468
+ continue;
5469
+ }
5470
+ if (cap !== void 0 && emptyCurationToClear.length >= cap) continue;
5471
+ emptyCurationToClear.push(id);
5470
5472
  }
5471
5473
  const stamp = raw.topics_extracted_at ?? null;
5472
5474
  let isPending = false;
@@ -5482,11 +5484,21 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
5482
5484
  pendingRaw.push(raw);
5483
5485
  isPending = true;
5484
5486
  }
5487
+ if (!isPending && linked === void 0 && topics === null) {
5488
+ pendingRaw.push(raw);
5489
+ isPending = true;
5490
+ }
5485
5491
  if (!isPending && topics === null && linked !== void 0) {
5486
5492
  topicsBackfillAll.push(id);
5487
5493
  }
5488
5494
  }
5489
- const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
5495
+ for (const id of emptyCurationToClear) {
5496
+ try {
5497
+ await clearMemoryTopicsOverrideOp(ctx, id, { unlessTopicsRecorded: true });
5498
+ } catch (err) {
5499
+ getLogger().warn("[memory/topics] repair clear failed", err);
5500
+ }
5501
+ }
5490
5502
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
5491
5503
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
5492
5504
  const pending = await mapInBatches(
@@ -7457,8 +7457,18 @@ declare function setMemoryEntitiesOp(ctx: VaultMemoryOperationsContext, memoryId
7457
7457
  * see the schema note. This op's version-invalidation trick is the reason the
7458
7458
  * earlier plan to exclude them from sync was dropped, and it's the last piece
7459
7459
  * that has to move before they can go.
7460
+ *
7461
+ * `options.unlessTopicsRecorded` declines the reset when the row already has a
7462
+ * `topics` record, re-checked INSIDE the serialized writer. Only the repair path
7463
+ * in {@link getMemoriesNeedingTopicExtractionOp} passes it: that path clears the
7464
+ * flag off rows whose curation is provably empty, and a `setMemoryEntitiesOp`
7465
+ * committing in the gap would have written a real record the autotagger must not
7466
+ * be handed. The user-facing reset leaves it off — resetting a memory that HAS
7467
+ * curated topics is the whole point there.
7460
7468
  */
7461
- declare function clearMemoryTopicsOverrideOp(ctx: VaultMemoryOperationsContext, memoryId: string): Promise<boolean>;
7469
+ declare function clearMemoryTopicsOverrideOp(ctx: VaultMemoryOperationsContext, memoryId: string, options?: {
7470
+ unlessTopicsRecorded?: boolean;
7471
+ }): Promise<boolean>;
7462
7472
  /**
7463
7473
  * Set a memory's People Nearby visibility (and optionally its twin opt-in).
7464
7474
  *
@@ -7500,6 +7510,12 @@ declare function getUnfiledVaultMemoriesOp(ctx: VaultMemoryOperationsContext): P
7500
7510
  * older version (including pre-v37 rows, read as version 0) is then re-extracted
7501
7511
  * by the next sweep, so topic-quality improvements propagate across the existing
7502
7512
  * vault. The worker's `limit` drains that re-extraction across sweeps.
7513
+ *
7514
+ * Bumping this is a WHOLE-VAULT re-extraction: the gate is unconditional across
7515
+ * every stamped row, and `stampTopicsExtractedAtOp` stamps this version on every
7516
+ * healthy row, so a bump sends each user's entire extracted vault back to the
7517
+ * LLM. Never reach for it to repair a subset — see the pre-v42-restore repair in
7518
+ * {@link getMemoriesNeedingTopicExtractionOp}, which targets the damaged rows.
7503
7519
  */
7504
7520
  declare const TOPICS_EXTRACTION_VERSION = 3;
7505
7521
  /**
@@ -7512,9 +7528,10 @@ interface MemoriesNeedingTopicExtraction {
7512
7528
  * Memories to run LLM topic extraction on (decrypted): never-extracted rows
7513
7529
  * with no entity links, plus stamped rows edited since their last pass
7514
7530
  * (`updated_at` > `topics_extracted_at`) or extracted under an older
7515
- * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}. Edited /
7516
- * stale-version rows come first (they get priority under `limit`), each group
7517
- * newest-created first.
7531
+ * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}, plus the
7532
+ * pre-v42-restore repair: a stamped row left with neither links nor a `topics`
7533
+ * record, which no no-LLM bucket can reach. Edited / stale-version rows come
7534
+ * first (they get priority under `limit`), each group newest-created first.
7518
7535
  */
7519
7536
  pending: StoredVaultMemory[];
7520
7537
  /**
@@ -7566,6 +7583,11 @@ interface MemoriesNeedingTopicExtraction {
7566
7583
  * between the record and the index and never re-derive them. That's why the
7567
7584
  * ownership filter lives in the partition below rather than in the query: a
7568
7585
  * restored curated memory is exactly the row whose index must be rebuilt.
7586
+ *
7587
+ * NOT purely a read: a curated row with no `topics` record AND no usable link is
7588
+ * a contradiction only a pre-v42 restore produces, and this is the one place
7589
+ * that can see all three facts at once, so it clears the flag there (capped by
7590
+ * `limit`) before returning. See the branch for why that's safe.
7569
7591
  */
7570
7592
  declare function getMemoriesNeedingTopicExtractionOp(ctx: VaultMemoryOperationsContext, options?: {
7571
7593
  limit?: number;
@@ -7457,8 +7457,18 @@ declare function setMemoryEntitiesOp(ctx: VaultMemoryOperationsContext, memoryId
7457
7457
  * see the schema note. This op's version-invalidation trick is the reason the
7458
7458
  * earlier plan to exclude them from sync was dropped, and it's the last piece
7459
7459
  * that has to move before they can go.
7460
+ *
7461
+ * `options.unlessTopicsRecorded` declines the reset when the row already has a
7462
+ * `topics` record, re-checked INSIDE the serialized writer. Only the repair path
7463
+ * in {@link getMemoriesNeedingTopicExtractionOp} passes it: that path clears the
7464
+ * flag off rows whose curation is provably empty, and a `setMemoryEntitiesOp`
7465
+ * committing in the gap would have written a real record the autotagger must not
7466
+ * be handed. The user-facing reset leaves it off — resetting a memory that HAS
7467
+ * curated topics is the whole point there.
7460
7468
  */
7461
- declare function clearMemoryTopicsOverrideOp(ctx: VaultMemoryOperationsContext, memoryId: string): Promise<boolean>;
7469
+ declare function clearMemoryTopicsOverrideOp(ctx: VaultMemoryOperationsContext, memoryId: string, options?: {
7470
+ unlessTopicsRecorded?: boolean;
7471
+ }): Promise<boolean>;
7462
7472
  /**
7463
7473
  * Set a memory's People Nearby visibility (and optionally its twin opt-in).
7464
7474
  *
@@ -7500,6 +7510,12 @@ declare function getUnfiledVaultMemoriesOp(ctx: VaultMemoryOperationsContext): P
7500
7510
  * older version (including pre-v37 rows, read as version 0) is then re-extracted
7501
7511
  * by the next sweep, so topic-quality improvements propagate across the existing
7502
7512
  * vault. The worker's `limit` drains that re-extraction across sweeps.
7513
+ *
7514
+ * Bumping this is a WHOLE-VAULT re-extraction: the gate is unconditional across
7515
+ * every stamped row, and `stampTopicsExtractedAtOp` stamps this version on every
7516
+ * healthy row, so a bump sends each user's entire extracted vault back to the
7517
+ * LLM. Never reach for it to repair a subset — see the pre-v42-restore repair in
7518
+ * {@link getMemoriesNeedingTopicExtractionOp}, which targets the damaged rows.
7503
7519
  */
7504
7520
  declare const TOPICS_EXTRACTION_VERSION = 3;
7505
7521
  /**
@@ -7512,9 +7528,10 @@ interface MemoriesNeedingTopicExtraction {
7512
7528
  * Memories to run LLM topic extraction on (decrypted): never-extracted rows
7513
7529
  * with no entity links, plus stamped rows edited since their last pass
7514
7530
  * (`updated_at` > `topics_extracted_at`) or extracted under an older
7515
- * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}. Edited /
7516
- * stale-version rows come first (they get priority under `limit`), each group
7517
- * newest-created first.
7531
+ * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}, plus the
7532
+ * pre-v42-restore repair: a stamped row left with neither links nor a `topics`
7533
+ * record, which no no-LLM bucket can reach. Edited / stale-version rows come
7534
+ * first (they get priority under `limit`), each group newest-created first.
7518
7535
  */
7519
7536
  pending: StoredVaultMemory[];
7520
7537
  /**
@@ -7566,6 +7583,11 @@ interface MemoriesNeedingTopicExtraction {
7566
7583
  * between the record and the index and never re-derive them. That's why the
7567
7584
  * ownership filter lives in the partition below rather than in the query: a
7568
7585
  * restored curated memory is exactly the row whose index must be rebuilt.
7586
+ *
7587
+ * NOT purely a read: a curated row with no `topics` record AND no usable link is
7588
+ * a contradiction only a pre-v42 restore produces, and this is the one place
7589
+ * that can see all three facts at once, so it clears the flag there (capped by
7590
+ * `limit`) before returning. See the branch for why that's safe.
7569
7591
  */
7570
7592
  declare function getMemoriesNeedingTopicExtractionOp(ctx: VaultMemoryOperationsContext, options?: {
7571
7593
  limit?: number;
@@ -161,7 +161,7 @@ import {
161
161
  updateVaultMemoryOp,
162
162
  useEncryption,
163
163
  vaultMemoryToStored
164
- } from "./chunk-BAJL46NX.mjs";
164
+ } from "./chunk-BS7C4CUW.mjs";
165
165
  import {
166
166
  PiiRedactor,
167
167
  createStreamingDeAnonymizer,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-BAJL46NX.mjs";
3
+ } from "./chunk-BS7C4CUW.mjs";
4
4
  import "./chunk-ILAWDQDE.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {
@@ -16942,6 +16942,30 @@ async function updateVaultMemoryOp(ctx, id, opts) {
16942
16942
  return null;
16943
16943
  }
16944
16944
  }
16945
+ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
16946
+ let record;
16947
+ try {
16948
+ record = await ctx.vaultMemoryCollection.find(memoryId);
16949
+ } catch {
16950
+ return false;
16951
+ }
16952
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
16953
+ const originalUpdatedAt = record.updatedAt.getTime();
16954
+ let cleared = false;
16955
+ await ctx.database.write(async () => {
16956
+ if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
16957
+ cleared = true;
16958
+ await record.update((r) => {
16959
+ r._setRaw("topics_user_managed", false);
16960
+ r._setRaw("topics_extracted_version", null);
16961
+ if (record.topicsExtractedAt === null) {
16962
+ r._setRaw("topics_extracted_at", originalUpdatedAt);
16963
+ }
16964
+ r._setRaw("updated_at", originalUpdatedAt);
16965
+ });
16966
+ });
16967
+ return cleared;
16968
+ }
16945
16969
  async function setMemoryVisibilityOp(ctx, id, opts) {
16946
16970
  let record;
16947
16971
  try {
@@ -17103,11 +17127,13 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
17103
17127
  entityCtx,
17104
17128
  rows.map((r) => r.id)
17105
17129
  );
17130
+ const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
17106
17131
  const pendingRaw = [];
17107
17132
  const stampedPendingRaw = [];
17108
17133
  const linkedUnstampedAll = [];
17109
17134
  const topicsToRelinkAll = [];
17110
17135
  const topicsBackfillAll = [];
17136
+ const emptyCurationToClear = [];
17111
17137
  for (const raw of rows) {
17112
17138
  const id = raw.id;
17113
17139
  const topics = parseTopics(raw.topics);
@@ -17117,8 +17143,12 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
17117
17143
  continue;
17118
17144
  }
17119
17145
  if (raw.topics_user_managed) {
17120
- if (topics === null && linked !== void 0) topicsBackfillAll.push(id);
17121
- continue;
17146
+ if (topics !== null || linked !== void 0) {
17147
+ if (topics === null) topicsBackfillAll.push(id);
17148
+ continue;
17149
+ }
17150
+ if (cap !== void 0 && emptyCurationToClear.length >= cap) continue;
17151
+ emptyCurationToClear.push(id);
17122
17152
  }
17123
17153
  const stamp = raw.topics_extracted_at ?? null;
17124
17154
  let isPending = false;
@@ -17134,11 +17164,21 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
17134
17164
  pendingRaw.push(raw);
17135
17165
  isPending = true;
17136
17166
  }
17167
+ if (!isPending && linked === void 0 && topics === null) {
17168
+ pendingRaw.push(raw);
17169
+ isPending = true;
17170
+ }
17137
17171
  if (!isPending && topics === null && linked !== void 0) {
17138
17172
  topicsBackfillAll.push(id);
17139
17173
  }
17140
17174
  }
17141
- const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
17175
+ for (const id of emptyCurationToClear) {
17176
+ try {
17177
+ await clearMemoryTopicsOverrideOp(ctx, id, { unlessTopicsRecorded: true });
17178
+ } catch (err) {
17179
+ getLogger().warn("[memory/topics] repair clear failed", err);
17180
+ }
17181
+ }
17142
17182
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
17143
17183
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
17144
17184
  const pending = await mapInBatches(
@@ -20310,7 +20350,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
20310
20350
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
20311
20351
  );
20312
20352
  }
20313
- const { recall: recall2 } = await import("./recall-YZWIV6IH.mjs");
20353
+ const { recall: recall2 } = await import("./recall-QTVT7HCZ.mjs");
20314
20354
  let recallDegraded = [];
20315
20355
  const result = await recall2(
20316
20356
  query,
@@ -17747,6 +17747,30 @@ async function updateVaultMemoryOp(ctx, id, opts) {
17747
17747
  return null;
17748
17748
  }
17749
17749
  }
17750
+ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
17751
+ let record;
17752
+ try {
17753
+ record = await ctx.vaultMemoryCollection.find(memoryId);
17754
+ } catch {
17755
+ return false;
17756
+ }
17757
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
17758
+ const originalUpdatedAt = record.updatedAt.getTime();
17759
+ let cleared = false;
17760
+ await ctx.database.write(async () => {
17761
+ if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
17762
+ cleared = true;
17763
+ await record.update((r) => {
17764
+ r._setRaw("topics_user_managed", false);
17765
+ r._setRaw("topics_extracted_version", null);
17766
+ if (record.topicsExtractedAt === null) {
17767
+ r._setRaw("topics_extracted_at", originalUpdatedAt);
17768
+ }
17769
+ r._setRaw("updated_at", originalUpdatedAt);
17770
+ });
17771
+ });
17772
+ return cleared;
17773
+ }
17750
17774
  async function setMemoryVisibilityOp(ctx, id, opts) {
17751
17775
  let record;
17752
17776
  try {
@@ -17907,11 +17931,13 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
17907
17931
  entityCtx,
17908
17932
  rows.map((r) => r.id)
17909
17933
  );
17934
+ const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
17910
17935
  const pendingRaw = [];
17911
17936
  const stampedPendingRaw = [];
17912
17937
  const linkedUnstampedAll = [];
17913
17938
  const topicsToRelinkAll = [];
17914
17939
  const topicsBackfillAll = [];
17940
+ const emptyCurationToClear = [];
17915
17941
  for (const raw of rows) {
17916
17942
  const id = raw.id;
17917
17943
  const topics = parseTopics(raw.topics);
@@ -17921,8 +17947,12 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
17921
17947
  continue;
17922
17948
  }
17923
17949
  if (raw.topics_user_managed) {
17924
- if (topics === null && linked !== void 0) topicsBackfillAll.push(id);
17925
- continue;
17950
+ if (topics !== null || linked !== void 0) {
17951
+ if (topics === null) topicsBackfillAll.push(id);
17952
+ continue;
17953
+ }
17954
+ if (cap !== void 0 && emptyCurationToClear.length >= cap) continue;
17955
+ emptyCurationToClear.push(id);
17926
17956
  }
17927
17957
  const stamp = raw.topics_extracted_at ?? null;
17928
17958
  let isPending = false;
@@ -17938,11 +17968,21 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
17938
17968
  pendingRaw.push(raw);
17939
17969
  isPending = true;
17940
17970
  }
17971
+ if (!isPending && linked === void 0 && topics === null) {
17972
+ pendingRaw.push(raw);
17973
+ isPending = true;
17974
+ }
17941
17975
  if (!isPending && topics === null && linked !== void 0) {
17942
17976
  topicsBackfillAll.push(id);
17943
17977
  }
17944
17978
  }
17945
- const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
17979
+ for (const id of emptyCurationToClear) {
17980
+ try {
17981
+ await clearMemoryTopicsOverrideOp(ctx, id, { unlessTopicsRecorded: true });
17982
+ } catch (err) {
17983
+ getLogger().warn("[memory/topics] repair clear failed", err);
17984
+ }
17985
+ }
17946
17986
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
17947
17987
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
17948
17988
  const pending = await mapInBatches(
@@ -6025,6 +6025,12 @@ declare function getUnfiledVaultMemoriesOp(ctx: VaultMemoryOperationsContext): P
6025
6025
  * older version (including pre-v37 rows, read as version 0) is then re-extracted
6026
6026
  * by the next sweep, so topic-quality improvements propagate across the existing
6027
6027
  * vault. The worker's `limit` drains that re-extraction across sweeps.
6028
+ *
6029
+ * Bumping this is a WHOLE-VAULT re-extraction: the gate is unconditional across
6030
+ * every stamped row, and `stampTopicsExtractedAtOp` stamps this version on every
6031
+ * healthy row, so a bump sends each user's entire extracted vault back to the
6032
+ * LLM. Never reach for it to repair a subset — see the pre-v42-restore repair in
6033
+ * {@link getMemoriesNeedingTopicExtractionOp}, which targets the damaged rows.
6028
6034
  */
6029
6035
  declare const TOPICS_EXTRACTION_VERSION = 3;
6030
6036
  /**
@@ -6037,9 +6043,10 @@ interface MemoriesNeedingTopicExtraction {
6037
6043
  * Memories to run LLM topic extraction on (decrypted): never-extracted rows
6038
6044
  * with no entity links, plus stamped rows edited since their last pass
6039
6045
  * (`updated_at` > `topics_extracted_at`) or extracted under an older
6040
- * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}. Edited /
6041
- * stale-version rows come first (they get priority under `limit`), each group
6042
- * newest-created first.
6046
+ * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}, plus the
6047
+ * pre-v42-restore repair: a stamped row left with neither links nor a `topics`
6048
+ * record, which no no-LLM bucket can reach. Edited / stale-version rows come
6049
+ * first (they get priority under `limit`), each group newest-created first.
6043
6050
  */
6044
6051
  pending: StoredVaultMemory[];
6045
6052
  /**
@@ -6091,6 +6098,11 @@ interface MemoriesNeedingTopicExtraction {
6091
6098
  * between the record and the index and never re-derive them. That's why the
6092
6099
  * ownership filter lives in the partition below rather than in the query: a
6093
6100
  * restored curated memory is exactly the row whose index must be rebuilt.
6101
+ *
6102
+ * NOT purely a read: a curated row with no `topics` record AND no usable link is
6103
+ * a contradiction only a pre-v42 restore produces, and this is the one place
6104
+ * that can see all three facts at once, so it clears the flag there (capped by
6105
+ * `limit`) before returning. See the branch for why that's safe.
6094
6106
  */
6095
6107
  declare function getMemoriesNeedingTopicExtractionOp(ctx: VaultMemoryOperationsContext, options?: {
6096
6108
  limit?: number;
@@ -6025,6 +6025,12 @@ declare function getUnfiledVaultMemoriesOp(ctx: VaultMemoryOperationsContext): P
6025
6025
  * older version (including pre-v37 rows, read as version 0) is then re-extracted
6026
6026
  * by the next sweep, so topic-quality improvements propagate across the existing
6027
6027
  * vault. The worker's `limit` drains that re-extraction across sweeps.
6028
+ *
6029
+ * Bumping this is a WHOLE-VAULT re-extraction: the gate is unconditional across
6030
+ * every stamped row, and `stampTopicsExtractedAtOp` stamps this version on every
6031
+ * healthy row, so a bump sends each user's entire extracted vault back to the
6032
+ * LLM. Never reach for it to repair a subset — see the pre-v42-restore repair in
6033
+ * {@link getMemoriesNeedingTopicExtractionOp}, which targets the damaged rows.
6028
6034
  */
6029
6035
  declare const TOPICS_EXTRACTION_VERSION = 3;
6030
6036
  /**
@@ -6037,9 +6043,10 @@ interface MemoriesNeedingTopicExtraction {
6037
6043
  * Memories to run LLM topic extraction on (decrypted): never-extracted rows
6038
6044
  * with no entity links, plus stamped rows edited since their last pass
6039
6045
  * (`updated_at` > `topics_extracted_at`) or extracted under an older
6040
- * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}. Edited /
6041
- * stale-version rows come first (they get priority under `limit`), each group
6042
- * newest-created first.
6046
+ * `topics_extracted_version` than {@link TOPICS_EXTRACTION_VERSION}, plus the
6047
+ * pre-v42-restore repair: a stamped row left with neither links nor a `topics`
6048
+ * record, which no no-LLM bucket can reach. Edited / stale-version rows come
6049
+ * first (they get priority under `limit`), each group newest-created first.
6043
6050
  */
6044
6051
  pending: StoredVaultMemory[];
6045
6052
  /**
@@ -6091,6 +6098,11 @@ interface MemoriesNeedingTopicExtraction {
6091
6098
  * between the record and the index and never re-derive them. That's why the
6092
6099
  * ownership filter lives in the partition below rather than in the query: a
6093
6100
  * restored curated memory is exactly the row whose index must be rebuilt.
6101
+ *
6102
+ * NOT purely a read: a curated row with no `topics` record AND no usable link is
6103
+ * a contradiction only a pre-v42 restore produces, and this is the one place
6104
+ * that can see all three facts at once, so it clears the flag there (capped by
6105
+ * `limit`) before returning. See the branch for why that's safe.
6094
6106
  */
6095
6107
  declare function getMemoriesNeedingTopicExtractionOp(ctx: VaultMemoryOperationsContext, options?: {
6096
6108
  limit?: number;
@@ -133,7 +133,7 @@ import {
133
133
  updateVaultMemoryOp,
134
134
  upsertMessageOp,
135
135
  vaultMemoryToStored
136
- } from "./chunk-WZE6ZVKE.mjs";
136
+ } from "./chunk-WADHFDMA.mjs";
137
137
  import {
138
138
  createStreamingDeAnonymizer,
139
139
  notifyConsolidationFallback,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-WZE6ZVKE.mjs";
3
+ } from "./chunk-WADHFDMA.mjs";
4
4
  import "./chunk-OBDRKQQI.mjs";
5
5
  import "./chunk-3RPGXXPZ.mjs";
6
6
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",