@anuma/sdk 6.39.3 → 6.41.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.
@@ -157,6 +157,8 @@ interface StoredTopic {
157
157
  name: string;
158
158
  kind?: string;
159
159
  source: TopicSource;
160
+ /** How central the topic is to the memory, in (0, 1]; read it with {@link topicWeight}. */
161
+ weight?: number;
160
162
  }
161
163
 
162
164
  interface EntityOperationsContext {
@@ -1463,6 +1463,7 @@ __export(index_exports, {
1463
1463
  TOOL_RESULT_ORIGIN: () => TOOL_RESULT_ORIGIN,
1464
1464
  TOPICS_EXTRACTION_VERSION: () => TOPICS_EXTRACTION_VERSION,
1465
1465
  TOPIC_EXTRACTION_BATCH_SIZE: () => TOPIC_EXTRACTION_BATCH_SIZE,
1466
+ TOPIC_SALIENCE_WEIGHTS: () => TOPIC_SALIENCE_WEIGHTS,
1466
1467
  TREND_RECENT_WINDOW_DAYS: () => TREND_RECENT_WINDOW_DAYS,
1467
1468
  TREND_STALE_WINDOW_DAYS: () => TREND_STALE_WINDOW_DAYS,
1468
1469
  TextProcessor: () => TextProcessor,
@@ -1725,6 +1726,7 @@ __export(index_exports, {
1725
1726
  preEmbedVaultMemories: () => preEmbedVaultMemories,
1726
1727
  preprocessFiles: () => preprocessFiles,
1727
1728
  previewToolSelection: () => previewToolSelection,
1729
+ primaryTopic: () => primaryTopic,
1728
1730
  projectToStored: () => projectToStored,
1729
1731
  putAppFileOp: () => putAppFileOp,
1730
1732
  quantizeEmbedding: () => quantizeEmbedding,
@@ -1798,6 +1800,7 @@ __export(index_exports, {
1798
1800
  summarizeObservationTrends: () => summarizeObservationTrends,
1799
1801
  supersedeVaultMemoryOp: () => supersedeVaultMemoryOp,
1800
1802
  synthesizeProfile: () => synthesizeProfile,
1803
+ topicWeight: () => topicWeight,
1801
1804
  traverseGraphLane: () => traverseGraphLane,
1802
1805
  ttlForType: () => ttlForType,
1803
1806
  updateAttrs: () => updateAttrs,
@@ -12951,6 +12954,20 @@ var ENTITY_KINDS = [
12951
12954
  function normalizeEntityName(name) {
12952
12955
  return name.trim().toLowerCase();
12953
12956
  }
12957
+ var TOPIC_SALIENCE_WEIGHTS = { primary: 1, secondary: 0.6, mention: 0.3 };
12958
+ function topicWeight(topic) {
12959
+ return topic.source === "user" ? 1 : topic.weight ?? 1;
12960
+ }
12961
+ function primaryTopic(topics) {
12962
+ let best = null;
12963
+ for (const topic of topics ?? []) {
12964
+ if (best === null || topicWeight(topic) > topicWeight(best)) best = topic;
12965
+ }
12966
+ return best;
12967
+ }
12968
+ function parseTopicWeight(value) {
12969
+ return typeof value === "number" && value > 0 && value <= 1 ? value : void 0;
12970
+ }
12954
12971
  function serializeTopics(topics) {
12955
12972
  return JSON.stringify(topics);
12956
12973
  }
@@ -12966,12 +12983,14 @@ function parseTopics(value) {
12966
12983
  const out = [];
12967
12984
  for (const raw of parsed) {
12968
12985
  if (typeof raw !== "object" || raw === null) continue;
12969
- const { name, kind, source } = raw;
12986
+ const { name, kind, source, weight } = raw;
12970
12987
  if (typeof name !== "string" || name.trim().length === 0) continue;
12988
+ const parsedWeight = parseTopicWeight(weight);
12971
12989
  out.push({
12972
12990
  name,
12973
12991
  ...typeof kind === "string" && kind.length > 0 ? { kind } : {},
12974
- source: source === "user" ? "user" : "auto"
12992
+ source: source === "user" ? "user" : "auto",
12993
+ ...parsedWeight !== void 0 ? { weight: parsedWeight } : {}
12975
12994
  });
12976
12995
  }
12977
12996
  return out;
@@ -13058,21 +13077,47 @@ function displayNamesOf(entities) {
13058
13077
  }
13059
13078
  function topicsEqual(stored2, computed) {
13060
13079
  if (stored2 === null || stored2.length !== computed.length) return false;
13061
- const key = (t) => JSON.stringify([t.name, t.kind ?? null, t.source]);
13080
+ const key = (t) => JSON.stringify([t.name, t.kind ?? null, t.source, t.weight ?? null]);
13062
13081
  const storedKeys = stored2.map(key).sort();
13063
13082
  const computedKeys = computed.map(key).sort();
13064
13083
  return storedKeys.every((k, i) => k === computedKeys[i]);
13065
13084
  }
13066
- function resolveTopicsWrite(row, linked, displayNames, source) {
13085
+ function resolveTopicsWrite(row, linked, inputs, source, mode) {
13067
13086
  if (row === null || source === null) return null;
13068
- const topics = topicsForEntities(linked, displayNames, source);
13069
- if (topicsEqual(parseTopics(row.topics), topics)) return null;
13087
+ const stored2 = parseTopics(row.topics);
13088
+ const carried = mode === "merge" ? stored2 : null;
13089
+ const weights = source === "auto" ? weightsOf(carried, inputs) : null;
13090
+ const displayNames = displayNamesOf([...inputs, ...carried ?? []]);
13091
+ const topics = topicsForEntities(linked, displayNames, source, weights);
13092
+ if (topicsEqual(stored2, topics)) return null;
13070
13093
  return { row, topics };
13071
13094
  }
13072
- function topicsForEntities(entities, displayNames, source) {
13095
+ function weightsOf(stored2, inputs) {
13096
+ const out = /* @__PURE__ */ new Map();
13097
+ for (const topic of stored2 ?? []) out.set(normalizeEntityName(topic.name), topic.weight);
13098
+ for (const input of inputs) {
13099
+ const weight = parseTopicWeight(input.weight);
13100
+ if (weight === void 0) continue;
13101
+ const name = normalizeEntityName(input.name);
13102
+ if (!out.has(name)) {
13103
+ out.set(name, weight);
13104
+ continue;
13105
+ }
13106
+ const current = out.get(name);
13107
+ if (current !== void 0 && weight > current) out.set(name, weight);
13108
+ }
13109
+ return out;
13110
+ }
13111
+ function topicsForEntities(entities, displayNames, source, weights) {
13073
13112
  return entities.map((e) => {
13074
13113
  const name = displayNames.get(e.canonicalName) ?? e.canonicalName;
13075
- return { name, ...e.kind ? { kind: e.kind } : {}, source };
13114
+ const weight = weights?.get(e.canonicalName);
13115
+ return {
13116
+ name,
13117
+ ...e.kind ? { kind: e.kind } : {},
13118
+ source,
13119
+ ...weight !== void 0 ? { weight } : {}
13120
+ };
13076
13121
  });
13077
13122
  }
13078
13123
  function toEntityObjects(entityInputs) {
@@ -13080,7 +13125,7 @@ function toEntityObjects(entityInputs) {
13080
13125
  }
13081
13126
  async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
13082
13127
  const row = await findVaultRowInWrite(ctx, memoryId);
13083
- return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
13128
+ return resolveTopicsWrite(row, linked, toEntityObjects(inputs), source, "replace");
13084
13129
  }
13085
13130
  function prepareMemoryTopicsUpdate(write) {
13086
13131
  return prepareTopicsUpdate(write.row, write.topics, Date.now());
@@ -13118,8 +13163,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
13118
13163
  const topicsWrite = resolveTopicsWrite(
13119
13164
  vaultRow,
13120
13165
  [...carriedOver, ...entities],
13121
- displayNamesOf(normalized),
13122
- options?.topicsSource ?? "auto"
13166
+ normalized,
13167
+ options?.topicsSource ?? "auto",
13168
+ "merge"
13123
13169
  );
13124
13170
  if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
13125
13171
  return;
@@ -13185,8 +13231,9 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
13185
13231
  const topicsWrite = resolveTopicsWrite(
13186
13232
  vaultRow,
13187
13233
  entities,
13188
- displayNamesOf(normalized),
13189
- options.topicsSource
13234
+ normalized,
13235
+ options.topicsSource,
13236
+ "replace"
13190
13237
  );
13191
13238
  if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
13192
13239
  return;
@@ -20719,9 +20766,9 @@ For each durable fact, output:
20719
20766
  - type: one of identity | preference | relationship | plan | ongoing_context | constraint | other
20720
20767
  - confidence: 0.0-1.0; how sure you are this is durable AND true. Only include facts >= 0.7.
20721
20768
  - sourceMessageIds: which message IDs from the conversation contained the evidence
20722
- - entities: named entities mentioned, each as an object { "name": string, "kind": one of "person" | "organization" | "place" | "event" | "product" | "thing" | "concept" | "other" }. Optional \u2014 include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES} Examples:
20723
- - "started a new job at Hollowpoint Labs and I'm learning Haskell for it" \u2192 entities: [{ "name": "Hollowpoint Labs", "kind": "organization" }, { "name": "Haskell", "kind": "concept" }]
20724
- - "running the Berlin Half in May and I track workouts in Whoop" \u2192 entities: [{ "name": "Berlin Half", "kind": "event" }, { "name": "Whoop", "kind": "product" }]
20769
+ - entities: named entities mentioned, each as an object { "name": string, "kind": one of "person" | "organization" | "place" | "event" | "product" | "thing" | "concept" | "other", "salience": one of "primary" | "secondary" | "mention" }. Optional \u2014 include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES} Salience is how central the entity is to the fact: "primary" = the fact is ABOUT it (usually exactly one per fact), "secondary" = a meaningful part of it, "mention" = named in passing. Examples:
20770
+ - "started a new job at Hollowpoint Labs and I'm learning Haskell for it" \u2192 entities: [{ "name": "Hollowpoint Labs", "kind": "organization", "salience": "primary" }, { "name": "Haskell", "kind": "concept", "salience": "secondary" }]
20771
+ - "running the Berlin Half in May and I track workouts in Whoop" \u2192 entities: [{ "name": "Berlin Half", "kind": "event", "salience": "primary" }, { "name": "Whoop", "kind": "product", "salience": "mention" }]
20725
20772
  - eventTime: when the event in this fact occurs/occurred. Resolve relative phrases ("yesterday", "next week") against the current date given in the user message. Output one of:
20726
20773
  - { "kind": "point", "start": "YYYY-MM-DD" } for a single date ("met Sara on March 14, 2026")
20727
20774
  - { "kind": "range", "start": "YYYY-MM-DD", "end": "YYYY-MM-DD" } for a span ("Japan trip May 4\u201315, 2026")
@@ -20802,9 +20849,10 @@ function restoreCandidates(candidates, redactor, ownNames) {
20802
20849
  candidate: {
20803
20850
  ...c,
20804
20851
  content: content.text,
20805
- entities: c.entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
20806
- (e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
20807
- )
20852
+ entities: c.entities.flatMap((e) => {
20853
+ const restored = redactor.restoreForStorage(e.name);
20854
+ return restored.unresolved ? [] : [{ ...e, name: restored.text }];
20855
+ })
20808
20856
  },
20809
20857
  unresolved: content.unresolved
20810
20858
  };
@@ -21033,7 +21081,12 @@ function parseEntities(raw) {
21033
21081
  const name = typeof obj.name === "string" ? obj.name.trim() : "";
21034
21082
  if (name.length === 0 || isGenericEntityName(name)) continue;
21035
21083
  const kind = typeof obj.kind === "string" && ENTITY_KINDS.includes(obj.kind) ? obj.kind : void 0;
21036
- out.push(kind !== void 0 ? { name, kind } : { name });
21084
+ const weight = typeof obj.salience === "string" && Object.keys(TOPIC_SALIENCE_WEIGHTS).includes(obj.salience) ? TOPIC_SALIENCE_WEIGHTS[obj.salience] : void 0;
21085
+ out.push({
21086
+ name,
21087
+ ...kind !== void 0 ? { kind } : {},
21088
+ ...weight !== void 0 ? { weight } : {}
21089
+ });
21037
21090
  }
21038
21091
  return out;
21039
21092
  }
@@ -23013,7 +23066,13 @@ Include only NAMED entities, skip generic/common nouns. A topic must identify a
23013
23066
  - Prefer 0 entities over a weak one. A memory whose only candidate is a generic noun has NO entities.
23014
23067
  ${ENTITY_KIND_GUIDELINES}
23015
23068
 
23016
- Each memory below is written as "<id>: <text>". Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
23069
+ Give each entity a "salience" \u2014 how central it is to the memory:
23070
+ - "primary": the memory is ABOUT it. A memory with entities usually has exactly one primary; never more than two.
23071
+ - "secondary": a meaningful part of the memory, but not its subject.
23072
+ - "mention": named in passing; the memory would say much the same without it.
23073
+ Example: "Ran the Chicago Marathon with my sister Ana in 3:41" \u2192 Chicago Marathon primary, Ana secondary.
23074
+
23075
+ Each memory below is written as "<id>: <text>". Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string, "salience": "primary" | "secondary" | "mention"}]}]}
23017
23076
  - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
23018
23077
  - "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
23019
23078
  No prose.`;
@@ -23084,9 +23143,10 @@ List each memory's named entities.`,
23084
23143
  return out;
23085
23144
  }
23086
23145
  function restoreEntities(entities, redactor) {
23087
- return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
23088
- (e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
23089
- );
23146
+ return entities.flatMap((e) => {
23147
+ const restored = redactor.restoreForStorage(e.name);
23148
+ return restored.unresolved ? [] : [{ ...e, name: restored.text }];
23149
+ });
23090
23150
  }
23091
23151
  function parseTopicResponse(parsed, validIds) {
23092
23152
  if (typeof parsed !== "object" || parsed === null) return null;
@@ -23247,6 +23307,14 @@ function createLocalMemoryStore(options) {
23247
23307
  getLogger().warn("[memory/store] Failed to embed memory:", err);
23248
23308
  });
23249
23309
  };
23310
+ const maintenance = {
23311
+ createDecaySweeper: (sweeperOptions) => createDecaySweeper({ ...sweeperOptions, vaultCtx }),
23312
+ getTopicBacklog: (backlogOptions) => getMemoriesNeedingTopicExtractionOp(vaultCtx, backlogOptions),
23313
+ extractTopics: (memoryIds, extractOptions) => extractAndLinkEntitiesForMemoriesOp(vaultCtx, memoryIds, extractOptions),
23314
+ stampTopicsExtracted: (memoryIds, extractedAt, version) => stampTopicsExtractedAtOp(vaultCtx, memoryIds, extractedAt, version),
23315
+ relinkTopics: (memoryIds) => relinkMemoryTopicsOp(vaultCtx, memoryIds),
23316
+ backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
23317
+ };
23250
23318
  return {
23251
23319
  masksEmbeddingInput: options.embeddingOptions.maskInput !== void 0,
23252
23320
  list: (listOptions) => getAllVaultMemoriesOp(vaultCtx, listOptions),
@@ -23260,21 +23328,22 @@ function createLocalMemoryStore(options) {
23260
23328
  memoriesByTopics: (names) => getMemoriesByEntityNamesOp(entityCtx, names),
23261
23329
  topicsByMemories: (memoryIds) => getEntitiesByMemoryIdsOp(entityCtx, memoryIds),
23262
23330
  listTopics: async () => {
23263
- const links = await entityCtx.memoryEntityCollection.query(...linksOwnedBy).fetch();
23331
+ const links = await entityCtx.memoryEntityCollection.query(...linksOwnedBy).unsafeFetchRaw();
23264
23332
  const members = /* @__PURE__ */ new Map();
23265
23333
  for (const link of links) {
23266
- const entityId = String(link.entityId);
23334
+ const entityId = String(link.entity_id);
23267
23335
  const set = members.get(entityId) ?? /* @__PURE__ */ new Set();
23268
- set.add(String(link.memoryId));
23336
+ set.add(String(link.memory_id));
23269
23337
  members.set(entityId, set);
23270
23338
  }
23271
- const entities = userId === void 0 ? await entityCtx.entityCollection.query().fetch() : members.size ? await entityCtx.entityCollection.query(import_watermelondb32.Q.where("id", import_watermelondb32.Q.oneOf([...members.keys()]))).fetch() : [];
23339
+ const entities = userId === void 0 ? await entityCtx.entityCollection.query().unsafeFetchRaw() : members.size ? await entityCtx.entityCollection.query(import_watermelondb32.Q.where("id", import_watermelondb32.Q.oneOf([...members.keys()]))).unsafeFetchRaw() : [];
23272
23340
  const byName = /* @__PURE__ */ new Map();
23273
23341
  for (const entity of entities) {
23274
- const count2 = members.get(entity.id)?.size ?? 0;
23275
- const existing = byName.get(entity.canonicalName);
23276
- byName.set(entity.canonicalName, {
23277
- name: entity.canonicalName,
23342
+ const name = String(entity.canonical_name);
23343
+ const count2 = members.get(String(entity.id))?.size ?? 0;
23344
+ const existing = byName.get(name);
23345
+ byName.set(name, {
23346
+ name,
23278
23347
  kind: existing?.kind ?? entity.kind ?? null,
23279
23348
  memoryCount: (existing?.memoryCount ?? 0) + count2
23280
23349
  });
@@ -23365,14 +23434,8 @@ function createLocalMemoryStore(options) {
23365
23434
  }
23366
23435
  return () => subscriptions.forEach((s) => s.unsubscribe());
23367
23436
  },
23368
- maintenance: {
23369
- createDecaySweeper: (sweeperOptions) => createDecaySweeper({ ...sweeperOptions, vaultCtx }),
23370
- getTopicBacklog: (backlogOptions) => getMemoriesNeedingTopicExtractionOp(vaultCtx, backlogOptions),
23371
- extractTopics: (memoryIds, extractOptions) => extractAndLinkEntitiesForMemoriesOp(vaultCtx, memoryIds, extractOptions),
23372
- stampTopicsExtracted: (memoryIds, extractedAt, version) => stampTopicsExtractedAtOp(vaultCtx, memoryIds, extractedAt, version),
23373
- relinkTopics: (memoryIds) => relinkMemoryTopicsOp(vaultCtx, memoryIds),
23374
- backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
23375
- }
23437
+ maintenance,
23438
+ topicMaintenance: maintenance
23376
23439
  };
23377
23440
  }
23378
23441
 
@@ -24157,7 +24220,7 @@ function createRemoteMemoryPipeline(options) {
24157
24220
  const memory = {
24158
24221
  ...snapshot.memory,
24159
24222
  content: patch.content,
24160
- updated_at: patch.preserveUpdatedAt ? snapshot.memory.updated_at : Date.now()
24223
+ updated_at: patch.preserveUpdatedAt ? snapshot.memory.updated_at : Math.max(Date.now(), (snapshot.memory.topics_extracted_at ?? 0) + 1)
24161
24224
  };
24162
24225
  if (patch.scope !== void 0) memory.scope = patch.scope;
24163
24226
  if (patch.embedding === void 0 && patch.content !== snapshot.memory.content) {
@@ -24291,6 +24354,7 @@ function projection({ memory }) {
24291
24354
  };
24292
24355
  }
24293
24356
  function relevant(change, options) {
24357
+ if (change === "metadata") return false;
24294
24358
  return options.includeDeleted ? change === "membership" : change !== "embedding" || !!options.embeddings;
24295
24359
  }
24296
24360
  function safely(onChange) {
@@ -24371,17 +24435,32 @@ function topicInputs(topics, source) {
24371
24435
  return topics.flatMap((topic) => {
24372
24436
  const name = (typeof topic === "string" ? topic : topic.name).trim();
24373
24437
  const kind = typeof topic === "string" ? void 0 : topic.kind;
24374
- return name ? [{ name, ...kind && { kind }, source }] : [];
24438
+ const weight = typeof topic === "string" || source === "user" ? void 0 : parseTopicWeight(topic.weight);
24439
+ return name ? [{ name, ...kind && { kind }, source, ...weight && { weight } }] : [];
24375
24440
  });
24376
24441
  }
24377
24442
  function mergeTopics(existing, added) {
24378
24443
  const byName = /* @__PURE__ */ new Map();
24379
24444
  for (const topic of [...existing, ...added]) {
24380
24445
  const key = normalizeEntityName(topic.name);
24381
- if (!byName.has(key)) byName.set(key, topic);
24446
+ const kept = byName.get(key);
24447
+ if (!kept) byName.set(key, topic);
24448
+ else if (kept.weight !== void 0 && topic.weight !== void 0 && topic.weight > kept.weight)
24449
+ byName.set(key, { ...kept, weight: topic.weight });
24382
24450
  }
24383
24451
  return [...byName.values()];
24384
24452
  }
24453
+ function sameTopics(stored2, next) {
24454
+ const key = (t) => JSON.stringify([t.name, t.kind ?? null, t.source, t.weight ?? null]);
24455
+ if (stored2 === null || stored2.length !== next.length) return false;
24456
+ const keys = new Set(stored2.map(key));
24457
+ return next.every((t) => keys.has(key(t)));
24458
+ }
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
+ }
24385
24464
  function canonicalTopics(row) {
24386
24465
  return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
24387
24466
  }
@@ -24394,7 +24473,12 @@ function createRemoteMemoryStore(options) {
24394
24473
  const seen = /* @__PURE__ */ new Map();
24395
24474
  let timer;
24396
24475
  const notify = (change, records = []) => {
24397
- if (timer) for (const record2 of records) seen.set(record2.memory.memory_id, record2);
24476
+ if (timer)
24477
+ for (const record2 of records) {
24478
+ const previous = seen.get(record2.memory.memory_id);
24479
+ if ((previous?.version ?? 0) + 1 === record2.version)
24480
+ seen.set(record2.memory.memory_id, record2);
24481
+ }
24398
24482
  for (const subscriber of [...subscribers])
24399
24483
  if (relevant(change, subscriber.options)) safely(subscriber.onChange);
24400
24484
  };
@@ -24455,15 +24539,72 @@ function createRemoteMemoryStore(options) {
24455
24539
  const record2 = await persistence.get(id);
24456
24540
  return record2 && !record2.memory.is_deleted ? record2 : null;
24457
24541
  };
24458
- const mutate = async (id, change, edit) => serialized(id, async () => {
24542
+ const mutateLocked = async (id, change, edit) => {
24459
24543
  const record2 = await live(id);
24460
24544
  if (!record2) return null;
24461
24545
  const next = edit(structuredClone(record2.memory));
24462
24546
  if (!next) return null;
24463
24547
  const saved = await persistence.put(next, record2);
24464
- notify(change, [saved]);
24548
+ notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
24465
24549
  return saved;
24466
- });
24550
+ };
24551
+ const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
24552
+ const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
24553
+ const mutateMany = async (ids, change, edit) => {
24554
+ const saved = /* @__PURE__ */ new Map();
24555
+ const failed = /* @__PURE__ */ new Map();
24556
+ for (const slice of chunks([...new Set(ids)], MAX_BATCH)) {
24557
+ await serializedAll(slice, async () => {
24558
+ const writes = [];
24559
+ let read;
24560
+ try {
24561
+ read = await readEveryState(slice);
24562
+ } catch (error) {
24563
+ for (const id of slice) failed.set(id, error);
24564
+ return;
24565
+ }
24566
+ const { records, unreadable } = read;
24567
+ for (const id of slice) {
24568
+ const record2 = records.get(id);
24569
+ if (unreadable.has(id)) failed.set(id, new Error(`Memory ${id} could not be decrypted`));
24570
+ else if (!record2) failed.set(id, new Error(`Memory ${id} was not found`));
24571
+ else if (!record2.memory.is_deleted) {
24572
+ const memory = edit(structuredClone(record2.memory));
24573
+ if (memory) writes.push({ before: record2, memory });
24574
+ }
24575
+ }
24576
+ if (writes.length === 0) return;
24577
+ try {
24578
+ const written = await persistence.putMany(
24579
+ writes.map(({ before, memory }) => ({ memory, expectedVersion: before }))
24580
+ );
24581
+ const byChange = /* @__PURE__ */ new Map();
24582
+ written.forEach((record2, i) => {
24583
+ saved.set(record2.memory.memory_id, record2);
24584
+ const kind = change(writes[i].before.memory);
24585
+ byChange.set(kind, [...byChange.get(kind) ?? [], record2]);
24586
+ });
24587
+ for (const [kind, changed] of byChange) notify(kind, changed);
24588
+ } catch (error) {
24589
+ const perRow = error instanceof RemoteMemoryError && (error.code === "version_conflict" || error.status === 429);
24590
+ for (const { before } of writes) {
24591
+ const id = before.memory.memory_id;
24592
+ if (!perRow) {
24593
+ failed.set(id, error);
24594
+ continue;
24595
+ }
24596
+ try {
24597
+ const record2 = await mutateLocked(id, change, edit);
24598
+ if (record2) saved.set(id, record2);
24599
+ } catch (rowError) {
24600
+ failed.set(id, rowError);
24601
+ }
24602
+ }
24603
+ }
24604
+ });
24605
+ }
24606
+ return { saved, failed };
24607
+ };
24467
24608
  const embedInBackground = ({ memory: { memory_id: id, content } }) => {
24468
24609
  generateEmbedding(content, embeddingOptions).then(
24469
24610
  (vector) => serialized(id, async () => {
@@ -24519,11 +24660,10 @@ function createRemoteMemoryStore(options) {
24519
24660
  return records.map((record2) => stored(record2.memory));
24520
24661
  };
24521
24662
  const asStored = (record2) => record2 ? stored(record2.memory) : null;
24522
- const withKnownKinds = async (topics) => {
24663
+ const knownKinds = async (topics) => {
24523
24664
  const unkinded = [
24524
24665
  ...new Set(topics.filter((t) => !t.kind).map((t) => normalizeEntityName(t.name)))
24525
24666
  ];
24526
- if (unkinded.length === 0) return topics;
24527
24667
  const kinds = /* @__PURE__ */ new Map();
24528
24668
  for (const names of chunks(unkinded, MAX_QUERY_TOPICS)) {
24529
24669
  for (const { memory } of await pages(metadata, {
@@ -24538,10 +24678,136 @@ function createRemoteMemoryStore(options) {
24538
24678
  }
24539
24679
  }
24540
24680
  }
24541
- return topics.map((topic) => {
24681
+ return (topic) => {
24542
24682
  const kind = topic.kind ?? kinds.get(normalizeEntityName(topic.name));
24543
24683
  return kind ? { ...topic, kind } : topic;
24544
- });
24684
+ };
24685
+ };
24686
+ const withKnownKinds = async (topics) => topics.map(await knownKinds(topics));
24687
+ const readEveryState = async (ids) => {
24688
+ const records = /* @__PURE__ */ new Map();
24689
+ const unreadable = /* @__PURE__ */ new Set();
24690
+ for (const memory_ids of chunks(ids, MAX_QUERY_IDS)) {
24691
+ let cursor2;
24692
+ do {
24693
+ const page = await persistence.query({
24694
+ memory_ids,
24695
+ include_deleted: true,
24696
+ include_archived: true,
24697
+ include_quarantined: true,
24698
+ include_superseded: true,
24699
+ limit: 200,
24700
+ ...cursor2 && { cursor: cursor2 }
24701
+ });
24702
+ warnFailed(page.failed);
24703
+ for (const record2 of page.items) records.set(record2.memory.memory_id, record2);
24704
+ for (const failure of page.failed) unreadable.add(failure.memory_id);
24705
+ cursor2 = page.next_cursor;
24706
+ } while (cursor2);
24707
+ }
24708
+ return { records, unreadable };
24709
+ };
24710
+ const topicMaintenance = {
24711
+ 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
+ const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
24721
+ return {
24722
+ pending: [...stale, ...fresh].slice(0, limit).map((uniqueId) => ({ uniqueId })),
24723
+ linkedUnstamped: [],
24724
+ topicsToRelink: [],
24725
+ topicsBackfill: []
24726
+ };
24727
+ },
24728
+ extractTopics: async (memoryIds, extractOptions) => {
24729
+ const extractedAt = extractOptions.now ?? Date.now();
24730
+ const skippedIds = [];
24731
+ const skippedReasons = /* @__PURE__ */ new Map();
24732
+ const skip = (id, reason) => {
24733
+ if (skippedReasons.has(id)) return;
24734
+ skippedIds.push(id);
24735
+ skippedReasons.set(id, reason);
24736
+ };
24737
+ const ids = [...new Set(memoryIds)];
24738
+ const { records, unreadable } = await readEveryState(ids);
24739
+ const inputs = [];
24740
+ for (const id of ids) {
24741
+ const memory = records.get(id)?.memory;
24742
+ if (unreadable.has(id)) skip(id, "unreadable");
24743
+ else if (!memory) skip(id, "not-found");
24744
+ else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
24745
+ else inputs.push({ id, content: memory.content });
24746
+ }
24747
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, extractOptions);
24748
+ const answered = /* @__PURE__ */ new Map();
24749
+ for (const input of inputs) {
24750
+ const entities = entitiesByMemory.get(input.id);
24751
+ if (entities === void 0) skip(input.id, "llm-unanswered");
24752
+ else
24753
+ answered.set(input.id, { content: input.content, topics: topicInputs(entities, "auto") });
24754
+ }
24755
+ const stampedIds = [];
24756
+ const fail = (id, reason) => {
24757
+ skip(id, reason);
24758
+ entitiesByMemory.delete(id);
24759
+ };
24760
+ try {
24761
+ const kinded = await knownKinds([...answered.values()].flatMap((a) => a.topics));
24762
+ const topics = new Map(
24763
+ [...answered].map(([id, a]) => [id, mergeTopics([], a.topics.map(kinded))])
24764
+ );
24765
+ const unchanged = (memory) => sameTopics(parseTopics(memory.topics), topics.get(memory.memory_id));
24766
+ const { saved, failed } = await mutateMany(
24767
+ [...answered.keys()],
24768
+ (before) => sameTopics(parseTopics(before.topics) ?? [], topics.get(before.memory_id)) ? "metadata" : "edit",
24769
+ (memory) => {
24770
+ if (memory.topics_user_managed || memory.content !== answered.get(memory.memory_id).content)
24771
+ return null;
24772
+ return {
24773
+ ...memory,
24774
+ ...!unchanged(memory) && {
24775
+ topics: serializeTopics(topics.get(memory.memory_id)),
24776
+ topics_updated_at: Date.now()
24777
+ },
24778
+ topics_extracted_at: Math.max(extractedAt, memory.updated_at),
24779
+ topics_extracted_version: TOPICS_EXTRACTION_VERSION
24780
+ };
24781
+ }
24782
+ );
24783
+ for (const id of answered.keys()) {
24784
+ if (saved.has(id)) stampedIds.push(id);
24785
+ else fail(id, failed.has(id) ? "link-failed" : "link-declined");
24786
+ }
24787
+ if (failed.size)
24788
+ getLogger().warn(
24789
+ "[memory/remote-store] Failed to write extracted topics:",
24790
+ failed.values().next().value
24791
+ );
24792
+ } catch (error) {
24793
+ getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
24794
+ for (const id of answered.keys()) fail(id, "link-failed");
24795
+ }
24796
+ return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
24797
+ },
24798
+ stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
24799
+ const ids = [...new Set(memoryIds)];
24800
+ const { saved, failed } = await mutateMany(
24801
+ ids,
24802
+ () => "metadata",
24803
+ (memory) => memory.topics_user_managed ? null : { ...memory, topics_extracted_at: extractedAt, topics_extracted_version: version }
24804
+ );
24805
+ if (failed.size)
24806
+ getLogger().warn("[memory/remote-store] Failed to stamp topics:", [...failed.values()]);
24807
+ return ids.filter((id) => saved.has(id));
24808
+ },
24809
+ relinkTopics: () => Promise.resolve([]),
24810
+ backfillTopics: () => Promise.resolve([])
24545
24811
  };
24546
24812
  const everyState = {
24547
24813
  include_deleted: true,
@@ -24620,6 +24886,7 @@ function createRemoteMemoryStore(options) {
24620
24886
  };
24621
24887
  return {
24622
24888
  masksEmbeddingInput: embeddingOptions.maskInput !== void 0,
24889
+ topicMaintenance,
24623
24890
  list: async (listOptions = {}) => (await listRows(decrypted, listOptions, listFilters(listOptions), maxOf(listOptions))).map(
24624
24891
  (r) => stored(r.memory)
24625
24892
  ),
@@ -24704,7 +24971,7 @@ function createRemoteMemoryStore(options) {
24704
24971
  const kind = patch.kind !== void 0 ? patch.kind : memory.kind;
24705
24972
  assertValidKindLevel(kind, level);
24706
24973
  memory.content = patch.content;
24707
- memory.updated_at = Date.now();
24974
+ memory.updated_at = Math.max(Date.now(), (memory.topics_extracted_at ?? 0) + 1);
24708
24975
  if (patch.level !== void 0) {
24709
24976
  memory.level = level;
24710
24977
  memory.scope = scopeForLevel(level);
@@ -39041,6 +39308,7 @@ function useWalletBinding(options = {}) {
39041
39308
  TOOL_RESULT_ORIGIN,
39042
39309
  TOPICS_EXTRACTION_VERSION,
39043
39310
  TOPIC_EXTRACTION_BATCH_SIZE,
39311
+ TOPIC_SALIENCE_WEIGHTS,
39044
39312
  TREND_RECENT_WINDOW_DAYS,
39045
39313
  TREND_STALE_WINDOW_DAYS,
39046
39314
  TextProcessor,
@@ -39303,6 +39571,7 @@ function useWalletBinding(options = {}) {
39303
39571
  preEmbedVaultMemories,
39304
39572
  preprocessFiles,
39305
39573
  previewToolSelection,
39574
+ primaryTopic,
39306
39575
  projectToStored,
39307
39576
  putAppFileOp,
39308
39577
  quantizeEmbedding,
@@ -39376,6 +39645,7 @@ function useWalletBinding(options = {}) {
39376
39645
  summarizeObservationTrends,
39377
39646
  supersedeVaultMemoryOp,
39378
39647
  synthesizeProfile,
39648
+ topicWeight,
39379
39649
  traverseGraphLane,
39380
39650
  ttlForType,
39381
39651
  updateAttrs,