@anuma/sdk 6.41.0 → 6.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20196,6 +20196,8 @@ var queryFields = [
20196
20196
  "memory_ids",
20197
20197
  "topics",
20198
20198
  "updated_after",
20199
+ "topics_due",
20200
+ "topics_extracted",
20199
20201
  "order",
20200
20202
  "cursor",
20201
20203
  "limit",
@@ -21083,11 +21085,6 @@ function sameTopics(stored2, next) {
21083
21085
  const keys = new Set(stored2.map(key));
21084
21086
  return next.every((t) => keys.has(key(t)));
21085
21087
  }
21086
- function extractionDue(memory) {
21087
- const stamp = memory.topics_extracted_at;
21088
- if (stamp === void 0 || stamp === null) return "fresh";
21089
- return memory.updated_at > stamp || (memory.topics_extracted_version ?? 0) < TOPICS_EXTRACTION_VERSION ? "stale" : null;
21090
- }
21091
21088
  function canonicalTopics(row) {
21092
21089
  return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
21093
21090
  }
@@ -21336,17 +21333,16 @@ function createRemoteMemoryStore(options) {
21336
21333
  };
21337
21334
  const topicMaintenance = {
21338
21335
  getTopicBacklog: async (backlogOptions = {}) => {
21339
- const stale = [];
21340
- const fresh = [];
21341
- for (const { memory } of await pages(metadata, { order: "created" })) {
21342
- if (memory.topics_user_managed) continue;
21343
- const due = extractionDue(memory);
21344
- if (due === "stale") stale.push(memory.memory_id);
21345
- else if (due === "fresh") fresh.push(memory.memory_id);
21346
- }
21347
21336
  const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
21337
+ const due = (extracted2, max) => pages(
21338
+ metadata,
21339
+ { order: "created", topics_due: TOPICS_EXTRACTION_VERSION, topics_extracted: extracted2 },
21340
+ max
21341
+ );
21342
+ const stale = await due(true, limit);
21343
+ const fresh = stale.length < limit ? await due(false, limit - stale.length) : [];
21348
21344
  return {
21349
- pending: [...stale, ...fresh].slice(0, limit).map((uniqueId) => ({ uniqueId })),
21345
+ pending: [...stale, ...fresh].map(({ memory }) => ({ uniqueId: memory.memory_id })),
21350
21346
  linkedUnstamped: [],
21351
21347
  topicsToRelink: [],
21352
21348
  topicsBackfill: []
@@ -21843,12 +21839,19 @@ var FACET_STRUCTURED_RESPONSE_HINT = {
21843
21839
  interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
21844
21840
  };
21845
21841
  async function synthesizeProfile(ctx, options = {}) {
21846
- if (!ctx.vaultCtx) {
21847
- throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
21842
+ const { store } = options;
21843
+ const vaultCtx = ctx.vaultCtx;
21844
+ if (!store && !vaultCtx) {
21845
+ throw new Error(
21846
+ "synthesizeProfile requires options.store or ctx.vaultCtx (vault-backed facts)."
21847
+ );
21848
21848
  }
21849
- if (!ctx.vaultCache) {
21850
- throw new Error("synthesizeProfile requires ctx.vaultCache (semantic fact recall).");
21849
+ if (!store && !ctx.vaultCache) {
21850
+ throw new Error(
21851
+ "synthesizeProfile requires options.store or ctx.vaultCache (semantic fact recall)."
21852
+ );
21851
21853
  }
21854
+ const recallCtx = store ? { embeddingOptions: ctx.embeddingOptions, factSource: store.factSource } : ctx;
21852
21855
  const facets = options.facets ?? DEFAULT_PROFILE_FACETS;
21853
21856
  const scopes = options.scopes ?? DEFAULT_SCOPES;
21854
21857
  const config = {
@@ -21859,11 +21862,8 @@ async function synthesizeProfile(ctx, options = {}) {
21859
21862
  reviewedMemoryIdsSignature: reviewedMemoryIdsSignature(options.reviewedMemoryIds)
21860
21863
  };
21861
21864
  const previous = options.previous && options.previous.version === PROFILE_DOC_VERSION && configMatches(options.previous.config, config) ? options.previous : void 0;
21862
- const memories = await getAllVaultMemoriesOp(ctx.vaultCtx, {
21863
- scopes,
21864
- includeDeleted: true,
21865
- includeSuperseded: true
21866
- });
21865
+ const listOptions = { scopes, includeDeleted: true, includeSuperseded: true };
21866
+ const [memories, projections] = store ? await Promise.all([store.list(listOptions), store.listProjections(listOptions)]) : [await getAllVaultMemoriesOp(vaultCtx, listOptions), []];
21867
21867
  const watermark = computeVaultWatermark(memories);
21868
21868
  const observationTrends = summarizeObservationTrends(
21869
21869
  memories.filter((m) => !m.isDeleted && m.supersededBy === null).map((m) => ({
@@ -21873,11 +21873,14 @@ async function synthesizeProfile(ctx, options = {}) {
21873
21873
  }))
21874
21874
  );
21875
21875
  const presentIds = new Set(memories.map((m) => m.uniqueId));
21876
+ const unreadableMemoryIds = projections.map((p) => p.uniqueId).filter((id) => !presentIds.has(id)).sort();
21877
+ const previousUnreadable = new Set(previous?.unreadableMemoryIds ?? []);
21878
+ const sameUnreadable = unreadableMemoryIds.length === previousUnreadable.size && unreadableMemoryIds.every((id) => previousUnreadable.has(id));
21876
21879
  const hasStaleSections = previous?.sections.some((s) => s.stale);
21877
21880
  const citesMissingFact = previous?.sections.some(
21878
21881
  (s) => s.sourceMemoryIds.some((id) => !presentIds.has(id))
21879
21882
  );
21880
- if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact) {
21883
+ if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact && sameUnreadable) {
21881
21884
  const sameOrder = previous.sections.length === facets.length && facets.every((f, i) => previous.sections[i]?.key === f.key);
21882
21885
  if (sameOrder) {
21883
21886
  if (trendsEqual(previous.observationTrends, observationTrends)) return previous;
@@ -21890,12 +21893,13 @@ async function synthesizeProfile(ctx, options = {}) {
21890
21893
  };
21891
21894
  }
21892
21895
  const staleKeys = await computeStaleFacetKeys(
21893
- ctx,
21896
+ recallCtx,
21894
21897
  memories,
21895
21898
  facets,
21896
21899
  previous,
21897
21900
  watermark,
21898
- options.reviewedMemoryIds
21901
+ options.reviewedMemoryIds,
21902
+ previousUnreadable
21899
21903
  );
21900
21904
  const memoriesById = new Map(memories.map((memory) => [memory.uniqueId, memory]));
21901
21905
  const fallbackPriors = /* @__PURE__ */ new Map();
@@ -21915,7 +21919,7 @@ async function synthesizeProfile(ctx, options = {}) {
21915
21919
  if (prior && !staleKeys.has(facet.key)) {
21916
21920
  return prior;
21917
21921
  }
21918
- return synthesizeFacet(facet, ctx, options, fallbackPriors.get(facet.key));
21922
+ return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
21919
21923
  })
21920
21924
  );
21921
21925
  const sections = settled.map((r, i) => {
@@ -21930,12 +21934,18 @@ async function synthesizeProfile(ctx, options = {}) {
21930
21934
  );
21931
21935
  return fallbackSection(facet, fallbackPriors.get(facet.key));
21932
21936
  });
21937
+ if (!sameUnreadable && unreadableMemoryIds.length > 0) {
21938
+ getLogger().warn("[memory/synthesizeProfile] some memories could not be read", {
21939
+ unreadable: unreadableMemoryIds.length
21940
+ });
21941
+ }
21933
21942
  return {
21934
21943
  version: PROFILE_DOC_VERSION,
21935
21944
  sections,
21936
21945
  vaultWatermark: watermark,
21937
21946
  config,
21938
21947
  observationTrends,
21948
+ ...unreadableMemoryIds.length > 0 && { unreadableMemoryIds },
21939
21949
  generatedAt: Date.now()
21940
21950
  };
21941
21951
  }
@@ -21969,11 +21979,13 @@ function computeVaultWatermark(memories) {
21969
21979
  }
21970
21980
  return max;
21971
21981
  }
21972
- async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds) {
21982
+ async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds, previouslyUnreadable) {
21973
21983
  const allKeys = new Set(facets.map((f) => f.key));
21974
21984
  if (previous && watermark < previous.vaultWatermark) return allKeys;
21975
21985
  if (!previous) return allKeys;
21976
- const changed = memories.filter((m) => changeTime(m) > previous.vaultWatermark);
21986
+ const changed = memories.filter(
21987
+ (m) => changeTime(m) > previous.vaultWatermark || previouslyUnreadable.has(m.uniqueId)
21988
+ );
21977
21989
  const changedIds = new Set(changed.map((m) => m.uniqueId));
21978
21990
  const presentIds = new Set(memories.map((m) => m.uniqueId));
21979
21991
  const stale = /* @__PURE__ */ new Set();
@@ -22047,7 +22059,12 @@ async function attributeFacts(ctx, candidates, facets) {
22047
22059
  for (const fv of factVectors) {
22048
22060
  let matchedAny = false;
22049
22061
  for (let i = 0; i < facets.length; i++) {
22050
- if (cosineSimilarity(fv, queryVectors[i]) >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
22062
+ const query = queryVectors[i];
22063
+ const similarity = cosineSimilarity(
22064
+ fv,
22065
+ fv.length < query.length ? query.slice(0, fv.length) : query
22066
+ );
22067
+ if (similarity >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
22051
22068
  keys.add(facets[i].key);
22052
22069
  matchedAny = true;
22053
22070
  }
@@ -9590,6 +9590,8 @@ interface RemoteMemoryQueryOptions extends RemoteMemoryReadFilters {
9590
9590
  memory_ids?: string[];
9591
9591
  topics?: string[];
9592
9592
  updated_after?: number;
9593
+ topics_due?: number;
9594
+ topics_extracted?: boolean;
9593
9595
  order?: "created" | "updated" | "archived" | "changed";
9594
9596
  cursor?: string;
9595
9597
  limit?: number;
@@ -9821,6 +9823,12 @@ interface ProfileDoc {
9821
9823
  * without another LLM pass. Recomputed every synthesis (not delta-cached).
9822
9824
  */
9823
9825
  observationTrends: Record<ObservationTrend, number>;
9826
+ /**
9827
+ * Sorted ids the store listed but could not decrypt at synthesis time.
9828
+ * Absent when every memory was readable. A later call regenerates only when
9829
+ * this set changes, and attributes a memory once it becomes readable.
9830
+ */
9831
+ unreadableMemoryIds?: string[];
9824
9832
  /** Unix ms this doc was produced. */
9825
9833
  generatedAt: number;
9826
9834
  }
@@ -9874,6 +9882,12 @@ interface SynthesizeProfileOptions extends PortalLlmAuth {
9874
9882
  * never accidentally disable the gate by finding it empty.
9875
9883
  */
9876
9884
  reviewedMemoryIds?: readonly string[];
9885
+ /**
9886
+ * Read facts and recall through this store instead of `ctx.vaultCtx` and
9887
+ * `ctx.vaultCache`, so a remote store works. Only `ctx.embeddingOptions` is
9888
+ * then read from `ctx`.
9889
+ */
9890
+ store?: Pick<MemoryStore, "list" | "listProjections" | "factSource">;
9877
9891
  }
9878
9892
  /**
9879
9893
  * Synthesize a shareable {@link ProfileDoc} from the user's vault, on-device.
@@ -9590,6 +9590,8 @@ interface RemoteMemoryQueryOptions extends RemoteMemoryReadFilters {
9590
9590
  memory_ids?: string[];
9591
9591
  topics?: string[];
9592
9592
  updated_after?: number;
9593
+ topics_due?: number;
9594
+ topics_extracted?: boolean;
9593
9595
  order?: "created" | "updated" | "archived" | "changed";
9594
9596
  cursor?: string;
9595
9597
  limit?: number;
@@ -9821,6 +9823,12 @@ interface ProfileDoc {
9821
9823
  * without another LLM pass. Recomputed every synthesis (not delta-cached).
9822
9824
  */
9823
9825
  observationTrends: Record<ObservationTrend, number>;
9826
+ /**
9827
+ * Sorted ids the store listed but could not decrypt at synthesis time.
9828
+ * Absent when every memory was readable. A later call regenerates only when
9829
+ * this set changes, and attributes a memory once it becomes readable.
9830
+ */
9831
+ unreadableMemoryIds?: string[];
9824
9832
  /** Unix ms this doc was produced. */
9825
9833
  generatedAt: number;
9826
9834
  }
@@ -9874,6 +9882,12 @@ interface SynthesizeProfileOptions extends PortalLlmAuth {
9874
9882
  * never accidentally disable the gate by finding it empty.
9875
9883
  */
9876
9884
  reviewedMemoryIds?: readonly string[];
9885
+ /**
9886
+ * Read facts and recall through this store instead of `ctx.vaultCtx` and
9887
+ * `ctx.vaultCache`, so a remote store works. Only `ctx.embeddingOptions` is
9888
+ * then read from `ctx`.
9889
+ */
9890
+ store?: Pick<MemoryStore, "list" | "listProjections" | "factSource">;
9877
9891
  }
9878
9892
  /**
9879
9893
  * Synthesize a shareable {@link ProfileDoc} from the user's vault, on-device.
@@ -18563,6 +18563,8 @@ var queryFields = [
18563
18563
  "memory_ids",
18564
18564
  "topics",
18565
18565
  "updated_after",
18566
+ "topics_due",
18567
+ "topics_extracted",
18566
18568
  "order",
18567
18569
  "cursor",
18568
18570
  "limit",
@@ -19448,11 +19450,6 @@ function sameTopics(stored2, next) {
19448
19450
  const keys = new Set(stored2.map(key));
19449
19451
  return next.every((t) => keys.has(key(t)));
19450
19452
  }
19451
- function extractionDue(memory) {
19452
- const stamp = memory.topics_extracted_at;
19453
- if (stamp === void 0 || stamp === null) return "fresh";
19454
- return memory.updated_at > stamp || (memory.topics_extracted_version ?? 0) < TOPICS_EXTRACTION_VERSION ? "stale" : null;
19455
- }
19456
19453
  function canonicalTopics(row) {
19457
19454
  return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
19458
19455
  }
@@ -19701,17 +19698,16 @@ function createRemoteMemoryStore(options) {
19701
19698
  };
19702
19699
  const topicMaintenance = {
19703
19700
  getTopicBacklog: async (backlogOptions = {}) => {
19704
- const stale = [];
19705
- const fresh = [];
19706
- for (const { memory } of await pages(metadata, { order: "created" })) {
19707
- if (memory.topics_user_managed) continue;
19708
- const due = extractionDue(memory);
19709
- if (due === "stale") stale.push(memory.memory_id);
19710
- else if (due === "fresh") fresh.push(memory.memory_id);
19711
- }
19712
19701
  const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
19702
+ const due = (extracted2, max) => pages(
19703
+ metadata,
19704
+ { order: "created", topics_due: TOPICS_EXTRACTION_VERSION, topics_extracted: extracted2 },
19705
+ max
19706
+ );
19707
+ const stale = await due(true, limit);
19708
+ const fresh = stale.length < limit ? await due(false, limit - stale.length) : [];
19713
19709
  return {
19714
- pending: [...stale, ...fresh].slice(0, limit).map((uniqueId) => ({ uniqueId })),
19710
+ pending: [...stale, ...fresh].map(({ memory }) => ({ uniqueId: memory.memory_id })),
19715
19711
  linkedUnstamped: [],
19716
19712
  topicsToRelink: [],
19717
19713
  topicsBackfill: []
@@ -20206,12 +20202,19 @@ var FACET_STRUCTURED_RESPONSE_HINT = {
20206
20202
  interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
20207
20203
  };
20208
20204
  async function synthesizeProfile(ctx, options = {}) {
20209
- if (!ctx.vaultCtx) {
20210
- throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
20205
+ const { store } = options;
20206
+ const vaultCtx = ctx.vaultCtx;
20207
+ if (!store && !vaultCtx) {
20208
+ throw new Error(
20209
+ "synthesizeProfile requires options.store or ctx.vaultCtx (vault-backed facts)."
20210
+ );
20211
20211
  }
20212
- if (!ctx.vaultCache) {
20213
- throw new Error("synthesizeProfile requires ctx.vaultCache (semantic fact recall).");
20212
+ if (!store && !ctx.vaultCache) {
20213
+ throw new Error(
20214
+ "synthesizeProfile requires options.store or ctx.vaultCache (semantic fact recall)."
20215
+ );
20214
20216
  }
20217
+ const recallCtx = store ? { embeddingOptions: ctx.embeddingOptions, factSource: store.factSource } : ctx;
20215
20218
  const facets = options.facets ?? DEFAULT_PROFILE_FACETS;
20216
20219
  const scopes = options.scopes ?? DEFAULT_SCOPES;
20217
20220
  const config = {
@@ -20222,11 +20225,8 @@ async function synthesizeProfile(ctx, options = {}) {
20222
20225
  reviewedMemoryIdsSignature: reviewedMemoryIdsSignature(options.reviewedMemoryIds)
20223
20226
  };
20224
20227
  const previous = options.previous && options.previous.version === PROFILE_DOC_VERSION && configMatches(options.previous.config, config) ? options.previous : void 0;
20225
- const memories = await getAllVaultMemoriesOp(ctx.vaultCtx, {
20226
- scopes,
20227
- includeDeleted: true,
20228
- includeSuperseded: true
20229
- });
20228
+ const listOptions = { scopes, includeDeleted: true, includeSuperseded: true };
20229
+ const [memories, projections] = store ? await Promise.all([store.list(listOptions), store.listProjections(listOptions)]) : [await getAllVaultMemoriesOp(vaultCtx, listOptions), []];
20230
20230
  const watermark = computeVaultWatermark(memories);
20231
20231
  const observationTrends = summarizeObservationTrends(
20232
20232
  memories.filter((m) => !m.isDeleted && m.supersededBy === null).map((m) => ({
@@ -20236,11 +20236,14 @@ async function synthesizeProfile(ctx, options = {}) {
20236
20236
  }))
20237
20237
  );
20238
20238
  const presentIds = new Set(memories.map((m) => m.uniqueId));
20239
+ const unreadableMemoryIds = projections.map((p) => p.uniqueId).filter((id) => !presentIds.has(id)).sort();
20240
+ const previousUnreadable = new Set(previous?.unreadableMemoryIds ?? []);
20241
+ const sameUnreadable = unreadableMemoryIds.length === previousUnreadable.size && unreadableMemoryIds.every((id) => previousUnreadable.has(id));
20239
20242
  const hasStaleSections = previous?.sections.some((s) => s.stale);
20240
20243
  const citesMissingFact = previous?.sections.some(
20241
20244
  (s) => s.sourceMemoryIds.some((id) => !presentIds.has(id))
20242
20245
  );
20243
- if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact) {
20246
+ if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact && sameUnreadable) {
20244
20247
  const sameOrder = previous.sections.length === facets.length && facets.every((f, i) => previous.sections[i]?.key === f.key);
20245
20248
  if (sameOrder) {
20246
20249
  if (trendsEqual(previous.observationTrends, observationTrends)) return previous;
@@ -20253,12 +20256,13 @@ async function synthesizeProfile(ctx, options = {}) {
20253
20256
  };
20254
20257
  }
20255
20258
  const staleKeys = await computeStaleFacetKeys(
20256
- ctx,
20259
+ recallCtx,
20257
20260
  memories,
20258
20261
  facets,
20259
20262
  previous,
20260
20263
  watermark,
20261
- options.reviewedMemoryIds
20264
+ options.reviewedMemoryIds,
20265
+ previousUnreadable
20262
20266
  );
20263
20267
  const memoriesById = new Map(memories.map((memory) => [memory.uniqueId, memory]));
20264
20268
  const fallbackPriors = /* @__PURE__ */ new Map();
@@ -20278,7 +20282,7 @@ async function synthesizeProfile(ctx, options = {}) {
20278
20282
  if (prior && !staleKeys.has(facet.key)) {
20279
20283
  return prior;
20280
20284
  }
20281
- return synthesizeFacet(facet, ctx, options, fallbackPriors.get(facet.key));
20285
+ return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
20282
20286
  })
20283
20287
  );
20284
20288
  const sections = settled.map((r, i) => {
@@ -20293,12 +20297,18 @@ async function synthesizeProfile(ctx, options = {}) {
20293
20297
  );
20294
20298
  return fallbackSection(facet, fallbackPriors.get(facet.key));
20295
20299
  });
20300
+ if (!sameUnreadable && unreadableMemoryIds.length > 0) {
20301
+ getLogger().warn("[memory/synthesizeProfile] some memories could not be read", {
20302
+ unreadable: unreadableMemoryIds.length
20303
+ });
20304
+ }
20296
20305
  return {
20297
20306
  version: PROFILE_DOC_VERSION,
20298
20307
  sections,
20299
20308
  vaultWatermark: watermark,
20300
20309
  config,
20301
20310
  observationTrends,
20311
+ ...unreadableMemoryIds.length > 0 && { unreadableMemoryIds },
20302
20312
  generatedAt: Date.now()
20303
20313
  };
20304
20314
  }
@@ -20332,11 +20342,13 @@ function computeVaultWatermark(memories) {
20332
20342
  }
20333
20343
  return max;
20334
20344
  }
20335
- async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds) {
20345
+ async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds, previouslyUnreadable) {
20336
20346
  const allKeys = new Set(facets.map((f) => f.key));
20337
20347
  if (previous && watermark < previous.vaultWatermark) return allKeys;
20338
20348
  if (!previous) return allKeys;
20339
- const changed = memories.filter((m) => changeTime(m) > previous.vaultWatermark);
20349
+ const changed = memories.filter(
20350
+ (m) => changeTime(m) > previous.vaultWatermark || previouslyUnreadable.has(m.uniqueId)
20351
+ );
20340
20352
  const changedIds = new Set(changed.map((m) => m.uniqueId));
20341
20353
  const presentIds = new Set(memories.map((m) => m.uniqueId));
20342
20354
  const stale = /* @__PURE__ */ new Set();
@@ -20410,7 +20422,12 @@ async function attributeFacts(ctx, candidates, facets) {
20410
20422
  for (const fv of factVectors) {
20411
20423
  let matchedAny = false;
20412
20424
  for (let i = 0; i < facets.length; i++) {
20413
- if (cosineSimilarity(fv, queryVectors[i]) >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
20425
+ const query = queryVectors[i];
20426
+ const similarity = cosineSimilarity(
20427
+ fv,
20428
+ fv.length < query.length ? query.slice(0, fv.length) : query
20429
+ );
20430
+ if (similarity >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
20414
20431
  keys.add(facets[i].key);
20415
20432
  matchedAny = true;
20416
20433
  }
@@ -23569,6 +23569,8 @@ var queryFields = [
23569
23569
  "memory_ids",
23570
23570
  "topics",
23571
23571
  "updated_after",
23572
+ "topics_due",
23573
+ "topics_extracted",
23572
23574
  "order",
23573
23575
  "cursor",
23574
23576
  "limit",
@@ -24456,11 +24458,6 @@ function sameTopics(stored2, next) {
24456
24458
  const keys = new Set(stored2.map(key));
24457
24459
  return next.every((t) => keys.has(key(t)));
24458
24460
  }
24459
- function extractionDue(memory) {
24460
- const stamp = memory.topics_extracted_at;
24461
- if (stamp === void 0 || stamp === null) return "fresh";
24462
- return memory.updated_at > stamp || (memory.topics_extracted_version ?? 0) < TOPICS_EXTRACTION_VERSION ? "stale" : null;
24463
- }
24464
24461
  function canonicalTopics(row) {
24465
24462
  return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
24466
24463
  }
@@ -24709,17 +24706,16 @@ function createRemoteMemoryStore(options) {
24709
24706
  };
24710
24707
  const topicMaintenance = {
24711
24708
  getTopicBacklog: async (backlogOptions = {}) => {
24712
- const stale = [];
24713
- const fresh = [];
24714
- for (const { memory } of await pages(metadata, { order: "created" })) {
24715
- if (memory.topics_user_managed) continue;
24716
- const due = extractionDue(memory);
24717
- if (due === "stale") stale.push(memory.memory_id);
24718
- else if (due === "fresh") fresh.push(memory.memory_id);
24719
- }
24720
24709
  const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
24710
+ const due = (extracted2, max) => pages(
24711
+ metadata,
24712
+ { order: "created", topics_due: TOPICS_EXTRACTION_VERSION, topics_extracted: extracted2 },
24713
+ max
24714
+ );
24715
+ const stale = await due(true, limit);
24716
+ const fresh = stale.length < limit ? await due(false, limit - stale.length) : [];
24721
24717
  return {
24722
- pending: [...stale, ...fresh].slice(0, limit).map((uniqueId) => ({ uniqueId })),
24718
+ pending: [...stale, ...fresh].map(({ memory }) => ({ uniqueId: memory.memory_id })),
24723
24719
  linkedUnstamped: [],
24724
24720
  topicsToRelink: [],
24725
24721
  topicsBackfill: []
@@ -25216,12 +25212,19 @@ var FACET_STRUCTURED_RESPONSE_HINT = {
25216
25212
  interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
25217
25213
  };
25218
25214
  async function synthesizeProfile(ctx, options = {}) {
25219
- if (!ctx.vaultCtx) {
25220
- throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
25215
+ const { store } = options;
25216
+ const vaultCtx = ctx.vaultCtx;
25217
+ if (!store && !vaultCtx) {
25218
+ throw new Error(
25219
+ "synthesizeProfile requires options.store or ctx.vaultCtx (vault-backed facts)."
25220
+ );
25221
25221
  }
25222
- if (!ctx.vaultCache) {
25223
- throw new Error("synthesizeProfile requires ctx.vaultCache (semantic fact recall).");
25222
+ if (!store && !ctx.vaultCache) {
25223
+ throw new Error(
25224
+ "synthesizeProfile requires options.store or ctx.vaultCache (semantic fact recall)."
25225
+ );
25224
25226
  }
25227
+ const recallCtx = store ? { embeddingOptions: ctx.embeddingOptions, factSource: store.factSource } : ctx;
25225
25228
  const facets = options.facets ?? DEFAULT_PROFILE_FACETS;
25226
25229
  const scopes = options.scopes ?? DEFAULT_SCOPES;
25227
25230
  const config = {
@@ -25232,11 +25235,8 @@ async function synthesizeProfile(ctx, options = {}) {
25232
25235
  reviewedMemoryIdsSignature: reviewedMemoryIdsSignature(options.reviewedMemoryIds)
25233
25236
  };
25234
25237
  const previous = options.previous && options.previous.version === PROFILE_DOC_VERSION && configMatches(options.previous.config, config) ? options.previous : void 0;
25235
- const memories = await getAllVaultMemoriesOp(ctx.vaultCtx, {
25236
- scopes,
25237
- includeDeleted: true,
25238
- includeSuperseded: true
25239
- });
25238
+ const listOptions = { scopes, includeDeleted: true, includeSuperseded: true };
25239
+ const [memories, projections] = store ? await Promise.all([store.list(listOptions), store.listProjections(listOptions)]) : [await getAllVaultMemoriesOp(vaultCtx, listOptions), []];
25240
25240
  const watermark = computeVaultWatermark(memories);
25241
25241
  const observationTrends = summarizeObservationTrends(
25242
25242
  memories.filter((m) => !m.isDeleted && m.supersededBy === null).map((m) => ({
@@ -25246,11 +25246,14 @@ async function synthesizeProfile(ctx, options = {}) {
25246
25246
  }))
25247
25247
  );
25248
25248
  const presentIds = new Set(memories.map((m) => m.uniqueId));
25249
+ const unreadableMemoryIds = projections.map((p) => p.uniqueId).filter((id) => !presentIds.has(id)).sort();
25250
+ const previousUnreadable = new Set(previous?.unreadableMemoryIds ?? []);
25251
+ const sameUnreadable = unreadableMemoryIds.length === previousUnreadable.size && unreadableMemoryIds.every((id) => previousUnreadable.has(id));
25249
25252
  const hasStaleSections = previous?.sections.some((s) => s.stale);
25250
25253
  const citesMissingFact = previous?.sections.some(
25251
25254
  (s) => s.sourceMemoryIds.some((id) => !presentIds.has(id))
25252
25255
  );
25253
- if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact) {
25256
+ if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact && sameUnreadable) {
25254
25257
  const sameOrder = previous.sections.length === facets.length && facets.every((f, i) => previous.sections[i]?.key === f.key);
25255
25258
  if (sameOrder) {
25256
25259
  if (trendsEqual(previous.observationTrends, observationTrends)) return previous;
@@ -25263,12 +25266,13 @@ async function synthesizeProfile(ctx, options = {}) {
25263
25266
  };
25264
25267
  }
25265
25268
  const staleKeys = await computeStaleFacetKeys(
25266
- ctx,
25269
+ recallCtx,
25267
25270
  memories,
25268
25271
  facets,
25269
25272
  previous,
25270
25273
  watermark,
25271
- options.reviewedMemoryIds
25274
+ options.reviewedMemoryIds,
25275
+ previousUnreadable
25272
25276
  );
25273
25277
  const memoriesById = new Map(memories.map((memory) => [memory.uniqueId, memory]));
25274
25278
  const fallbackPriors = /* @__PURE__ */ new Map();
@@ -25288,7 +25292,7 @@ async function synthesizeProfile(ctx, options = {}) {
25288
25292
  if (prior && !staleKeys.has(facet.key)) {
25289
25293
  return prior;
25290
25294
  }
25291
- return synthesizeFacet(facet, ctx, options, fallbackPriors.get(facet.key));
25295
+ return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
25292
25296
  })
25293
25297
  );
25294
25298
  const sections = settled.map((r, i) => {
@@ -25303,12 +25307,18 @@ async function synthesizeProfile(ctx, options = {}) {
25303
25307
  );
25304
25308
  return fallbackSection(facet, fallbackPriors.get(facet.key));
25305
25309
  });
25310
+ if (!sameUnreadable && unreadableMemoryIds.length > 0) {
25311
+ getLogger().warn("[memory/synthesizeProfile] some memories could not be read", {
25312
+ unreadable: unreadableMemoryIds.length
25313
+ });
25314
+ }
25306
25315
  return {
25307
25316
  version: PROFILE_DOC_VERSION,
25308
25317
  sections,
25309
25318
  vaultWatermark: watermark,
25310
25319
  config,
25311
25320
  observationTrends,
25321
+ ...unreadableMemoryIds.length > 0 && { unreadableMemoryIds },
25312
25322
  generatedAt: Date.now()
25313
25323
  };
25314
25324
  }
@@ -25342,11 +25352,13 @@ function computeVaultWatermark(memories) {
25342
25352
  }
25343
25353
  return max;
25344
25354
  }
25345
- async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds) {
25355
+ async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds, previouslyUnreadable) {
25346
25356
  const allKeys = new Set(facets.map((f) => f.key));
25347
25357
  if (previous && watermark < previous.vaultWatermark) return allKeys;
25348
25358
  if (!previous) return allKeys;
25349
- const changed = memories.filter((m) => changeTime(m) > previous.vaultWatermark);
25359
+ const changed = memories.filter(
25360
+ (m) => changeTime(m) > previous.vaultWatermark || previouslyUnreadable.has(m.uniqueId)
25361
+ );
25350
25362
  const changedIds = new Set(changed.map((m) => m.uniqueId));
25351
25363
  const presentIds = new Set(memories.map((m) => m.uniqueId));
25352
25364
  const stale = /* @__PURE__ */ new Set();
@@ -25420,7 +25432,12 @@ async function attributeFacts(ctx, candidates, facets) {
25420
25432
  for (const fv of factVectors) {
25421
25433
  let matchedAny = false;
25422
25434
  for (let i = 0; i < facets.length; i++) {
25423
- if (cosineSimilarity(fv, queryVectors[i]) >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
25435
+ const query = queryVectors[i];
25436
+ const similarity = cosineSimilarity(
25437
+ fv,
25438
+ fv.length < query.length ? query.slice(0, fv.length) : query
25439
+ );
25440
+ if (similarity >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
25424
25441
  keys.add(facets[i].key);
25425
25442
  matchedAny = true;
25426
25443
  }
@@ -11826,6 +11826,8 @@ interface RemoteMemoryQueryOptions extends RemoteMemoryReadFilters {
11826
11826
  memory_ids?: string[];
11827
11827
  topics?: string[];
11828
11828
  updated_after?: number;
11829
+ topics_due?: number;
11830
+ topics_extracted?: boolean;
11829
11831
  order?: "created" | "updated" | "archived" | "changed";
11830
11832
  cursor?: string;
11831
11833
  limit?: number;
@@ -12057,6 +12059,12 @@ interface ProfileDoc {
12057
12059
  * without another LLM pass. Recomputed every synthesis (not delta-cached).
12058
12060
  */
12059
12061
  observationTrends: Record<ObservationTrend, number>;
12062
+ /**
12063
+ * Sorted ids the store listed but could not decrypt at synthesis time.
12064
+ * Absent when every memory was readable. A later call regenerates only when
12065
+ * this set changes, and attributes a memory once it becomes readable.
12066
+ */
12067
+ unreadableMemoryIds?: string[];
12060
12068
  /** Unix ms this doc was produced. */
12061
12069
  generatedAt: number;
12062
12070
  }
@@ -12110,6 +12118,12 @@ interface SynthesizeProfileOptions extends PortalLlmAuth {
12110
12118
  * never accidentally disable the gate by finding it empty.
12111
12119
  */
12112
12120
  reviewedMemoryIds?: readonly string[];
12121
+ /**
12122
+ * Read facts and recall through this store instead of `ctx.vaultCtx` and
12123
+ * `ctx.vaultCache`, so a remote store works. Only `ctx.embeddingOptions` is
12124
+ * then read from `ctx`.
12125
+ */
12126
+ store?: Pick<MemoryStore, "list" | "listProjections" | "factSource">;
12113
12127
  }
12114
12128
  /**
12115
12129
  * Synthesize a shareable {@link ProfileDoc} from the user's vault, on-device.
@@ -11826,6 +11826,8 @@ interface RemoteMemoryQueryOptions extends RemoteMemoryReadFilters {
11826
11826
  memory_ids?: string[];
11827
11827
  topics?: string[];
11828
11828
  updated_after?: number;
11829
+ topics_due?: number;
11830
+ topics_extracted?: boolean;
11829
11831
  order?: "created" | "updated" | "archived" | "changed";
11830
11832
  cursor?: string;
11831
11833
  limit?: number;
@@ -12057,6 +12059,12 @@ interface ProfileDoc {
12057
12059
  * without another LLM pass. Recomputed every synthesis (not delta-cached).
12058
12060
  */
12059
12061
  observationTrends: Record<ObservationTrend, number>;
12062
+ /**
12063
+ * Sorted ids the store listed but could not decrypt at synthesis time.
12064
+ * Absent when every memory was readable. A later call regenerates only when
12065
+ * this set changes, and attributes a memory once it becomes readable.
12066
+ */
12067
+ unreadableMemoryIds?: string[];
12060
12068
  /** Unix ms this doc was produced. */
12061
12069
  generatedAt: number;
12062
12070
  }
@@ -12110,6 +12118,12 @@ interface SynthesizeProfileOptions extends PortalLlmAuth {
12110
12118
  * never accidentally disable the gate by finding it empty.
12111
12119
  */
12112
12120
  reviewedMemoryIds?: readonly string[];
12121
+ /**
12122
+ * Read facts and recall through this store instead of `ctx.vaultCtx` and
12123
+ * `ctx.vaultCache`, so a remote store works. Only `ctx.embeddingOptions` is
12124
+ * then read from `ctx`.
12125
+ */
12126
+ store?: Pick<MemoryStore, "list" | "listProjections" | "factSource">;
12113
12127
  }
12114
12128
  /**
12115
12129
  * Synthesize a shareable {@link ProfileDoc} from the user's vault, on-device.
@@ -21665,6 +21665,8 @@ var queryFields = [
21665
21665
  "memory_ids",
21666
21666
  "topics",
21667
21667
  "updated_after",
21668
+ "topics_due",
21669
+ "topics_extracted",
21668
21670
  "order",
21669
21671
  "cursor",
21670
21672
  "limit",
@@ -22550,11 +22552,6 @@ function sameTopics(stored2, next) {
22550
22552
  const keys = new Set(stored2.map(key));
22551
22553
  return next.every((t) => keys.has(key(t)));
22552
22554
  }
22553
- function extractionDue(memory) {
22554
- const stamp = memory.topics_extracted_at;
22555
- if (stamp === void 0 || stamp === null) return "fresh";
22556
- return memory.updated_at > stamp || (memory.topics_extracted_version ?? 0) < TOPICS_EXTRACTION_VERSION ? "stale" : null;
22557
- }
22558
22555
  function canonicalTopics(row) {
22559
22556
  return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
22560
22557
  }
@@ -22803,17 +22800,16 @@ function createRemoteMemoryStore(options) {
22803
22800
  };
22804
22801
  const topicMaintenance = {
22805
22802
  getTopicBacklog: async (backlogOptions = {}) => {
22806
- const stale = [];
22807
- const fresh = [];
22808
- for (const { memory } of await pages(metadata, { order: "created" })) {
22809
- if (memory.topics_user_managed) continue;
22810
- const due = extractionDue(memory);
22811
- if (due === "stale") stale.push(memory.memory_id);
22812
- else if (due === "fresh") fresh.push(memory.memory_id);
22813
- }
22814
22803
  const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
22804
+ const due = (extracted2, max) => pages(
22805
+ metadata,
22806
+ { order: "created", topics_due: TOPICS_EXTRACTION_VERSION, topics_extracted: extracted2 },
22807
+ max
22808
+ );
22809
+ const stale = await due(true, limit);
22810
+ const fresh = stale.length < limit ? await due(false, limit - stale.length) : [];
22815
22811
  return {
22816
- pending: [...stale, ...fresh].slice(0, limit).map((uniqueId) => ({ uniqueId })),
22812
+ pending: [...stale, ...fresh].map(({ memory }) => ({ uniqueId: memory.memory_id })),
22817
22813
  linkedUnstamped: [],
22818
22814
  topicsToRelink: [],
22819
22815
  topicsBackfill: []
@@ -23308,12 +23304,19 @@ var FACET_STRUCTURED_RESPONSE_HINT = {
23308
23304
  interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
23309
23305
  };
23310
23306
  async function synthesizeProfile(ctx, options = {}) {
23311
- if (!ctx.vaultCtx) {
23312
- throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
23307
+ const { store } = options;
23308
+ const vaultCtx = ctx.vaultCtx;
23309
+ if (!store && !vaultCtx) {
23310
+ throw new Error(
23311
+ "synthesizeProfile requires options.store or ctx.vaultCtx (vault-backed facts)."
23312
+ );
23313
23313
  }
23314
- if (!ctx.vaultCache) {
23315
- throw new Error("synthesizeProfile requires ctx.vaultCache (semantic fact recall).");
23314
+ if (!store && !ctx.vaultCache) {
23315
+ throw new Error(
23316
+ "synthesizeProfile requires options.store or ctx.vaultCache (semantic fact recall)."
23317
+ );
23316
23318
  }
23319
+ const recallCtx = store ? { embeddingOptions: ctx.embeddingOptions, factSource: store.factSource } : ctx;
23317
23320
  const facets = options.facets ?? DEFAULT_PROFILE_FACETS;
23318
23321
  const scopes = options.scopes ?? DEFAULT_SCOPES;
23319
23322
  const config = {
@@ -23324,11 +23327,8 @@ async function synthesizeProfile(ctx, options = {}) {
23324
23327
  reviewedMemoryIdsSignature: reviewedMemoryIdsSignature(options.reviewedMemoryIds)
23325
23328
  };
23326
23329
  const previous = options.previous && options.previous.version === PROFILE_DOC_VERSION && configMatches(options.previous.config, config) ? options.previous : void 0;
23327
- const memories = await getAllVaultMemoriesOp(ctx.vaultCtx, {
23328
- scopes,
23329
- includeDeleted: true,
23330
- includeSuperseded: true
23331
- });
23330
+ const listOptions = { scopes, includeDeleted: true, includeSuperseded: true };
23331
+ const [memories, projections] = store ? await Promise.all([store.list(listOptions), store.listProjections(listOptions)]) : [await getAllVaultMemoriesOp(vaultCtx, listOptions), []];
23332
23332
  const watermark = computeVaultWatermark(memories);
23333
23333
  const observationTrends = summarizeObservationTrends(
23334
23334
  memories.filter((m) => !m.isDeleted && m.supersededBy === null).map((m) => ({
@@ -23338,11 +23338,14 @@ async function synthesizeProfile(ctx, options = {}) {
23338
23338
  }))
23339
23339
  );
23340
23340
  const presentIds = new Set(memories.map((m) => m.uniqueId));
23341
+ const unreadableMemoryIds = projections.map((p) => p.uniqueId).filter((id) => !presentIds.has(id)).sort();
23342
+ const previousUnreadable = new Set(previous?.unreadableMemoryIds ?? []);
23343
+ const sameUnreadable = unreadableMemoryIds.length === previousUnreadable.size && unreadableMemoryIds.every((id) => previousUnreadable.has(id));
23341
23344
  const hasStaleSections = previous?.sections.some((s) => s.stale);
23342
23345
  const citesMissingFact = previous?.sections.some(
23343
23346
  (s) => s.sourceMemoryIds.some((id) => !presentIds.has(id))
23344
23347
  );
23345
- if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact) {
23348
+ if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact && sameUnreadable) {
23346
23349
  const sameOrder = previous.sections.length === facets.length && facets.every((f, i) => previous.sections[i]?.key === f.key);
23347
23350
  if (sameOrder) {
23348
23351
  if (trendsEqual(previous.observationTrends, observationTrends)) return previous;
@@ -23355,12 +23358,13 @@ async function synthesizeProfile(ctx, options = {}) {
23355
23358
  };
23356
23359
  }
23357
23360
  const staleKeys = await computeStaleFacetKeys(
23358
- ctx,
23361
+ recallCtx,
23359
23362
  memories,
23360
23363
  facets,
23361
23364
  previous,
23362
23365
  watermark,
23363
- options.reviewedMemoryIds
23366
+ options.reviewedMemoryIds,
23367
+ previousUnreadable
23364
23368
  );
23365
23369
  const memoriesById = new Map(memories.map((memory) => [memory.uniqueId, memory]));
23366
23370
  const fallbackPriors = /* @__PURE__ */ new Map();
@@ -23380,7 +23384,7 @@ async function synthesizeProfile(ctx, options = {}) {
23380
23384
  if (prior && !staleKeys.has(facet.key)) {
23381
23385
  return prior;
23382
23386
  }
23383
- return synthesizeFacet(facet, ctx, options, fallbackPriors.get(facet.key));
23387
+ return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
23384
23388
  })
23385
23389
  );
23386
23390
  const sections = settled.map((r, i) => {
@@ -23395,12 +23399,18 @@ async function synthesizeProfile(ctx, options = {}) {
23395
23399
  );
23396
23400
  return fallbackSection(facet, fallbackPriors.get(facet.key));
23397
23401
  });
23402
+ if (!sameUnreadable && unreadableMemoryIds.length > 0) {
23403
+ getLogger().warn("[memory/synthesizeProfile] some memories could not be read", {
23404
+ unreadable: unreadableMemoryIds.length
23405
+ });
23406
+ }
23398
23407
  return {
23399
23408
  version: PROFILE_DOC_VERSION,
23400
23409
  sections,
23401
23410
  vaultWatermark: watermark,
23402
23411
  config,
23403
23412
  observationTrends,
23413
+ ...unreadableMemoryIds.length > 0 && { unreadableMemoryIds },
23404
23414
  generatedAt: Date.now()
23405
23415
  };
23406
23416
  }
@@ -23434,11 +23444,13 @@ function computeVaultWatermark(memories) {
23434
23444
  }
23435
23445
  return max;
23436
23446
  }
23437
- async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds) {
23447
+ async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds, previouslyUnreadable) {
23438
23448
  const allKeys = new Set(facets.map((f) => f.key));
23439
23449
  if (previous && watermark < previous.vaultWatermark) return allKeys;
23440
23450
  if (!previous) return allKeys;
23441
- const changed = memories.filter((m) => changeTime(m) > previous.vaultWatermark);
23451
+ const changed = memories.filter(
23452
+ (m) => changeTime(m) > previous.vaultWatermark || previouslyUnreadable.has(m.uniqueId)
23453
+ );
23442
23454
  const changedIds = new Set(changed.map((m) => m.uniqueId));
23443
23455
  const presentIds = new Set(memories.map((m) => m.uniqueId));
23444
23456
  const stale = /* @__PURE__ */ new Set();
@@ -23512,7 +23524,12 @@ async function attributeFacts(ctx, candidates, facets) {
23512
23524
  for (const fv of factVectors) {
23513
23525
  let matchedAny = false;
23514
23526
  for (let i = 0; i < facets.length; i++) {
23515
- if (cosineSimilarity(fv, queryVectors[i]) >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
23527
+ const query = queryVectors[i];
23528
+ const similarity = cosineSimilarity(
23529
+ fv,
23530
+ fv.length < query.length ? query.slice(0, fv.length) : query
23531
+ );
23532
+ if (similarity >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
23516
23533
  keys.add(facets[i].key);
23517
23534
  matchedAny = true;
23518
23535
  }
@@ -35411,12 +35411,19 @@ var FACET_STRUCTURED_RESPONSE_HINT = {
35411
35411
  interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
35412
35412
  };
35413
35413
  async function synthesizeProfile(ctx, options = {}) {
35414
- if (!ctx.vaultCtx) {
35415
- throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
35414
+ const { store } = options;
35415
+ const vaultCtx = ctx.vaultCtx;
35416
+ if (!store && !vaultCtx) {
35417
+ throw new Error(
35418
+ "synthesizeProfile requires options.store or ctx.vaultCtx (vault-backed facts)."
35419
+ );
35416
35420
  }
35417
- if (!ctx.vaultCache) {
35418
- throw new Error("synthesizeProfile requires ctx.vaultCache (semantic fact recall).");
35421
+ if (!store && !ctx.vaultCache) {
35422
+ throw new Error(
35423
+ "synthesizeProfile requires options.store or ctx.vaultCache (semantic fact recall)."
35424
+ );
35419
35425
  }
35426
+ const recallCtx = store ? { embeddingOptions: ctx.embeddingOptions, factSource: store.factSource } : ctx;
35420
35427
  const facets = options.facets ?? DEFAULT_PROFILE_FACETS;
35421
35428
  const scopes = options.scopes ?? DEFAULT_SCOPES;
35422
35429
  const config = {
@@ -35427,11 +35434,8 @@ async function synthesizeProfile(ctx, options = {}) {
35427
35434
  reviewedMemoryIdsSignature: reviewedMemoryIdsSignature(options.reviewedMemoryIds)
35428
35435
  };
35429
35436
  const previous = options.previous && options.previous.version === PROFILE_DOC_VERSION && configMatches(options.previous.config, config) ? options.previous : void 0;
35430
- const memories = await getAllVaultMemoriesOp(ctx.vaultCtx, {
35431
- scopes,
35432
- includeDeleted: true,
35433
- includeSuperseded: true
35434
- });
35437
+ const listOptions = { scopes, includeDeleted: true, includeSuperseded: true };
35438
+ const [memories, projections] = store ? await Promise.all([store.list(listOptions), store.listProjections(listOptions)]) : [await getAllVaultMemoriesOp(vaultCtx, listOptions), []];
35435
35439
  const watermark = computeVaultWatermark(memories);
35436
35440
  const observationTrends = summarizeObservationTrends(
35437
35441
  memories.filter((m) => !m.isDeleted && m.supersededBy === null).map((m) => ({
@@ -35441,11 +35445,14 @@ async function synthesizeProfile(ctx, options = {}) {
35441
35445
  }))
35442
35446
  );
35443
35447
  const presentIds = new Set(memories.map((m) => m.uniqueId));
35448
+ const unreadableMemoryIds = projections.map((p) => p.uniqueId).filter((id) => !presentIds.has(id)).sort();
35449
+ const previousUnreadable = new Set(previous?.unreadableMemoryIds ?? []);
35450
+ const sameUnreadable = unreadableMemoryIds.length === previousUnreadable.size && unreadableMemoryIds.every((id) => previousUnreadable.has(id));
35444
35451
  const hasStaleSections = previous?.sections.some((s) => s.stale);
35445
35452
  const citesMissingFact = previous?.sections.some(
35446
35453
  (s) => s.sourceMemoryIds.some((id) => !presentIds.has(id))
35447
35454
  );
35448
- if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact) {
35455
+ if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact && sameUnreadable) {
35449
35456
  const sameOrder = previous.sections.length === facets.length && facets.every((f, i) => previous.sections[i]?.key === f.key);
35450
35457
  if (sameOrder) {
35451
35458
  if (trendsEqual(previous.observationTrends, observationTrends)) return previous;
@@ -35458,12 +35465,13 @@ async function synthesizeProfile(ctx, options = {}) {
35458
35465
  };
35459
35466
  }
35460
35467
  const staleKeys = await computeStaleFacetKeys(
35461
- ctx,
35468
+ recallCtx,
35462
35469
  memories,
35463
35470
  facets,
35464
35471
  previous,
35465
35472
  watermark,
35466
- options.reviewedMemoryIds
35473
+ options.reviewedMemoryIds,
35474
+ previousUnreadable
35467
35475
  );
35468
35476
  const memoriesById = new Map(memories.map((memory) => [memory.uniqueId, memory]));
35469
35477
  const fallbackPriors = /* @__PURE__ */ new Map();
@@ -35483,7 +35491,7 @@ async function synthesizeProfile(ctx, options = {}) {
35483
35491
  if (prior && !staleKeys.has(facet.key)) {
35484
35492
  return prior;
35485
35493
  }
35486
- return synthesizeFacet(facet, ctx, options, fallbackPriors.get(facet.key));
35494
+ return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
35487
35495
  })
35488
35496
  );
35489
35497
  const sections = settled.map((r, i) => {
@@ -35498,12 +35506,18 @@ async function synthesizeProfile(ctx, options = {}) {
35498
35506
  );
35499
35507
  return fallbackSection(facet, fallbackPriors.get(facet.key));
35500
35508
  });
35509
+ if (!sameUnreadable && unreadableMemoryIds.length > 0) {
35510
+ getLogger().warn("[memory/synthesizeProfile] some memories could not be read", {
35511
+ unreadable: unreadableMemoryIds.length
35512
+ });
35513
+ }
35501
35514
  return {
35502
35515
  version: PROFILE_DOC_VERSION,
35503
35516
  sections,
35504
35517
  vaultWatermark: watermark,
35505
35518
  config,
35506
35519
  observationTrends,
35520
+ ...unreadableMemoryIds.length > 0 && { unreadableMemoryIds },
35507
35521
  generatedAt: Date.now()
35508
35522
  };
35509
35523
  }
@@ -35537,11 +35551,13 @@ function computeVaultWatermark(memories) {
35537
35551
  }
35538
35552
  return max;
35539
35553
  }
35540
- async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds) {
35554
+ async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds, previouslyUnreadable) {
35541
35555
  const allKeys = new Set(facets.map((f) => f.key));
35542
35556
  if (previous && watermark < previous.vaultWatermark) return allKeys;
35543
35557
  if (!previous) return allKeys;
35544
- const changed = memories.filter((m) => changeTime(m) > previous.vaultWatermark);
35558
+ const changed = memories.filter(
35559
+ (m) => changeTime(m) > previous.vaultWatermark || previouslyUnreadable.has(m.uniqueId)
35560
+ );
35545
35561
  const changedIds = new Set(changed.map((m) => m.uniqueId));
35546
35562
  const presentIds = new Set(memories.map((m) => m.uniqueId));
35547
35563
  const stale = /* @__PURE__ */ new Set();
@@ -35615,7 +35631,12 @@ async function attributeFacts(ctx, candidates, facets) {
35615
35631
  for (const fv of factVectors) {
35616
35632
  let matchedAny = false;
35617
35633
  for (let i = 0; i < facets.length; i++) {
35618
- if (cosineSimilarity(fv, queryVectors[i]) >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
35634
+ const query = queryVectors[i];
35635
+ const similarity = cosineSimilarity(
35636
+ fv,
35637
+ fv.length < query.length ? query.slice(0, fv.length) : query
35638
+ );
35639
+ if (similarity >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
35619
35640
  keys.add(facets[i].key);
35620
35641
  matchedAny = true;
35621
35642
  }
@@ -9766,6 +9766,12 @@ interface ProfileDoc {
9766
9766
  * without another LLM pass. Recomputed every synthesis (not delta-cached).
9767
9767
  */
9768
9768
  observationTrends: Record<ObservationTrend, number>;
9769
+ /**
9770
+ * Sorted ids the store listed but could not decrypt at synthesis time.
9771
+ * Absent when every memory was readable. A later call regenerates only when
9772
+ * this set changes, and attributes a memory once it becomes readable.
9773
+ */
9774
+ unreadableMemoryIds?: string[];
9769
9775
  /** Unix ms this doc was produced. */
9770
9776
  generatedAt: number;
9771
9777
  }
@@ -9819,6 +9825,12 @@ interface SynthesizeProfileOptions extends PortalLlmAuth {
9819
9825
  * never accidentally disable the gate by finding it empty.
9820
9826
  */
9821
9827
  reviewedMemoryIds?: readonly string[];
9828
+ /**
9829
+ * Read facts and recall through this store instead of `ctx.vaultCtx` and
9830
+ * `ctx.vaultCache`, so a remote store works. Only `ctx.embeddingOptions` is
9831
+ * then read from `ctx`.
9832
+ */
9833
+ store?: Pick<MemoryStore, "list" | "listProjections" | "factSource">;
9822
9834
  }
9823
9835
  /**
9824
9836
  * Synthesize a shareable {@link ProfileDoc} from the user's vault, on-device.
@@ -9766,6 +9766,12 @@ interface ProfileDoc {
9766
9766
  * without another LLM pass. Recomputed every synthesis (not delta-cached).
9767
9767
  */
9768
9768
  observationTrends: Record<ObservationTrend, number>;
9769
+ /**
9770
+ * Sorted ids the store listed but could not decrypt at synthesis time.
9771
+ * Absent when every memory was readable. A later call regenerates only when
9772
+ * this set changes, and attributes a memory once it becomes readable.
9773
+ */
9774
+ unreadableMemoryIds?: string[];
9769
9775
  /** Unix ms this doc was produced. */
9770
9776
  generatedAt: number;
9771
9777
  }
@@ -9819,6 +9825,12 @@ interface SynthesizeProfileOptions extends PortalLlmAuth {
9819
9825
  * never accidentally disable the gate by finding it empty.
9820
9826
  */
9821
9827
  reviewedMemoryIds?: readonly string[];
9828
+ /**
9829
+ * Read facts and recall through this store instead of `ctx.vaultCtx` and
9830
+ * `ctx.vaultCache`, so a remote store works. Only `ctx.embeddingOptions` is
9831
+ * then read from `ctx`.
9832
+ */
9833
+ store?: Pick<MemoryStore, "list" | "listProjections" | "factSource">;
9822
9834
  }
9823
9835
  /**
9824
9836
  * Synthesize a shareable {@link ProfileDoc} from the user's vault, on-device.
@@ -33686,12 +33686,19 @@ var FACET_STRUCTURED_RESPONSE_HINT = {
33686
33686
  interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
33687
33687
  };
33688
33688
  async function synthesizeProfile(ctx, options = {}) {
33689
- if (!ctx.vaultCtx) {
33690
- throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
33689
+ const { store } = options;
33690
+ const vaultCtx = ctx.vaultCtx;
33691
+ if (!store && !vaultCtx) {
33692
+ throw new Error(
33693
+ "synthesizeProfile requires options.store or ctx.vaultCtx (vault-backed facts)."
33694
+ );
33691
33695
  }
33692
- if (!ctx.vaultCache) {
33693
- throw new Error("synthesizeProfile requires ctx.vaultCache (semantic fact recall).");
33696
+ if (!store && !ctx.vaultCache) {
33697
+ throw new Error(
33698
+ "synthesizeProfile requires options.store or ctx.vaultCache (semantic fact recall)."
33699
+ );
33694
33700
  }
33701
+ const recallCtx = store ? { embeddingOptions: ctx.embeddingOptions, factSource: store.factSource } : ctx;
33695
33702
  const facets = options.facets ?? DEFAULT_PROFILE_FACETS;
33696
33703
  const scopes = options.scopes ?? DEFAULT_SCOPES;
33697
33704
  const config = {
@@ -33702,11 +33709,8 @@ async function synthesizeProfile(ctx, options = {}) {
33702
33709
  reviewedMemoryIdsSignature: reviewedMemoryIdsSignature(options.reviewedMemoryIds)
33703
33710
  };
33704
33711
  const previous = options.previous && options.previous.version === PROFILE_DOC_VERSION && configMatches(options.previous.config, config) ? options.previous : void 0;
33705
- const memories = await getAllVaultMemoriesOp(ctx.vaultCtx, {
33706
- scopes,
33707
- includeDeleted: true,
33708
- includeSuperseded: true
33709
- });
33712
+ const listOptions = { scopes, includeDeleted: true, includeSuperseded: true };
33713
+ const [memories, projections] = store ? await Promise.all([store.list(listOptions), store.listProjections(listOptions)]) : [await getAllVaultMemoriesOp(vaultCtx, listOptions), []];
33710
33714
  const watermark = computeVaultWatermark(memories);
33711
33715
  const observationTrends = summarizeObservationTrends(
33712
33716
  memories.filter((m) => !m.isDeleted && m.supersededBy === null).map((m) => ({
@@ -33716,11 +33720,14 @@ async function synthesizeProfile(ctx, options = {}) {
33716
33720
  }))
33717
33721
  );
33718
33722
  const presentIds = new Set(memories.map((m) => m.uniqueId));
33723
+ const unreadableMemoryIds = projections.map((p) => p.uniqueId).filter((id) => !presentIds.has(id)).sort();
33724
+ const previousUnreadable = new Set(previous?.unreadableMemoryIds ?? []);
33725
+ const sameUnreadable = unreadableMemoryIds.length === previousUnreadable.size && unreadableMemoryIds.every((id) => previousUnreadable.has(id));
33719
33726
  const hasStaleSections = previous?.sections.some((s) => s.stale);
33720
33727
  const citesMissingFact = previous?.sections.some(
33721
33728
  (s) => s.sourceMemoryIds.some((id) => !presentIds.has(id))
33722
33729
  );
33723
- if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact) {
33730
+ if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact && sameUnreadable) {
33724
33731
  const sameOrder = previous.sections.length === facets.length && facets.every((f, i) => previous.sections[i]?.key === f.key);
33725
33732
  if (sameOrder) {
33726
33733
  if (trendsEqual(previous.observationTrends, observationTrends)) return previous;
@@ -33733,12 +33740,13 @@ async function synthesizeProfile(ctx, options = {}) {
33733
33740
  };
33734
33741
  }
33735
33742
  const staleKeys = await computeStaleFacetKeys(
33736
- ctx,
33743
+ recallCtx,
33737
33744
  memories,
33738
33745
  facets,
33739
33746
  previous,
33740
33747
  watermark,
33741
- options.reviewedMemoryIds
33748
+ options.reviewedMemoryIds,
33749
+ previousUnreadable
33742
33750
  );
33743
33751
  const memoriesById = new Map(memories.map((memory) => [memory.uniqueId, memory]));
33744
33752
  const fallbackPriors = /* @__PURE__ */ new Map();
@@ -33758,7 +33766,7 @@ async function synthesizeProfile(ctx, options = {}) {
33758
33766
  if (prior && !staleKeys.has(facet.key)) {
33759
33767
  return prior;
33760
33768
  }
33761
- return synthesizeFacet(facet, ctx, options, fallbackPriors.get(facet.key));
33769
+ return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
33762
33770
  })
33763
33771
  );
33764
33772
  const sections = settled.map((r, i) => {
@@ -33773,12 +33781,18 @@ async function synthesizeProfile(ctx, options = {}) {
33773
33781
  );
33774
33782
  return fallbackSection(facet, fallbackPriors.get(facet.key));
33775
33783
  });
33784
+ if (!sameUnreadable && unreadableMemoryIds.length > 0) {
33785
+ getLogger().warn("[memory/synthesizeProfile] some memories could not be read", {
33786
+ unreadable: unreadableMemoryIds.length
33787
+ });
33788
+ }
33776
33789
  return {
33777
33790
  version: PROFILE_DOC_VERSION,
33778
33791
  sections,
33779
33792
  vaultWatermark: watermark,
33780
33793
  config,
33781
33794
  observationTrends,
33795
+ ...unreadableMemoryIds.length > 0 && { unreadableMemoryIds },
33782
33796
  generatedAt: Date.now()
33783
33797
  };
33784
33798
  }
@@ -33812,11 +33826,13 @@ function computeVaultWatermark(memories) {
33812
33826
  }
33813
33827
  return max;
33814
33828
  }
33815
- async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds) {
33829
+ async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark, reviewedMemoryIds, previouslyUnreadable) {
33816
33830
  const allKeys = new Set(facets.map((f) => f.key));
33817
33831
  if (previous && watermark < previous.vaultWatermark) return allKeys;
33818
33832
  if (!previous) return allKeys;
33819
- const changed = memories.filter((m) => changeTime(m) > previous.vaultWatermark);
33833
+ const changed = memories.filter(
33834
+ (m) => changeTime(m) > previous.vaultWatermark || previouslyUnreadable.has(m.uniqueId)
33835
+ );
33820
33836
  const changedIds = new Set(changed.map((m) => m.uniqueId));
33821
33837
  const presentIds = new Set(memories.map((m) => m.uniqueId));
33822
33838
  const stale = /* @__PURE__ */ new Set();
@@ -33890,7 +33906,12 @@ async function attributeFacts(ctx, candidates, facets) {
33890
33906
  for (const fv of factVectors) {
33891
33907
  let matchedAny = false;
33892
33908
  for (let i = 0; i < facets.length; i++) {
33893
- if (cosineSimilarity(fv, queryVectors[i]) >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
33909
+ const query = queryVectors[i];
33910
+ const similarity = cosineSimilarity(
33911
+ fv,
33912
+ fv.length < query.length ? query.slice(0, fv.length) : query
33913
+ );
33914
+ if (similarity >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
33894
33915
  keys.add(facets[i].key);
33895
33916
  matchedAny = true;
33896
33917
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "6.41.0",
3
+ "version": "6.42.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",