@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.
@@ -11098,6 +11098,20 @@ var ENTITY_KINDS = [
11098
11098
  function normalizeEntityName(name) {
11099
11099
  return name.trim().toLowerCase();
11100
11100
  }
11101
+ var TOPIC_SALIENCE_WEIGHTS = { primary: 1, secondary: 0.6, mention: 0.3 };
11102
+ function topicWeight(topic) {
11103
+ return topic.source === "user" ? 1 : topic.weight ?? 1;
11104
+ }
11105
+ function primaryTopic(topics) {
11106
+ let best = null;
11107
+ for (const topic of topics ?? []) {
11108
+ if (best === null || topicWeight(topic) > topicWeight(best)) best = topic;
11109
+ }
11110
+ return best;
11111
+ }
11112
+ function parseTopicWeight(value) {
11113
+ return typeof value === "number" && value > 0 && value <= 1 ? value : void 0;
11114
+ }
11101
11115
  function serializeTopics(topics) {
11102
11116
  return JSON.stringify(topics);
11103
11117
  }
@@ -11113,12 +11127,14 @@ function parseTopics(value) {
11113
11127
  const out = [];
11114
11128
  for (const raw of parsed) {
11115
11129
  if (typeof raw !== "object" || raw === null) continue;
11116
- const { name, kind, source } = raw;
11130
+ const { name, kind, source, weight } = raw;
11117
11131
  if (typeof name !== "string" || name.trim().length === 0) continue;
11132
+ const parsedWeight = parseTopicWeight(weight);
11118
11133
  out.push({
11119
11134
  name,
11120
11135
  ...typeof kind === "string" && kind.length > 0 ? { kind } : {},
11121
- source: source === "user" ? "user" : "auto"
11136
+ source: source === "user" ? "user" : "auto",
11137
+ ...parsedWeight !== void 0 ? { weight: parsedWeight } : {}
11122
11138
  });
11123
11139
  }
11124
11140
  return out;
@@ -11205,21 +11221,47 @@ function displayNamesOf(entities) {
11205
11221
  }
11206
11222
  function topicsEqual(stored2, computed) {
11207
11223
  if (stored2 === null || stored2.length !== computed.length) return false;
11208
- const key = (t) => JSON.stringify([t.name, t.kind ?? null, t.source]);
11224
+ const key = (t) => JSON.stringify([t.name, t.kind ?? null, t.source, t.weight ?? null]);
11209
11225
  const storedKeys = stored2.map(key).sort();
11210
11226
  const computedKeys = computed.map(key).sort();
11211
11227
  return storedKeys.every((k, i) => k === computedKeys[i]);
11212
11228
  }
11213
- function resolveTopicsWrite(row, linked, displayNames, source) {
11229
+ function resolveTopicsWrite(row, linked, inputs, source, mode) {
11214
11230
  if (row === null || source === null) return null;
11215
- const topics = topicsForEntities(linked, displayNames, source);
11216
- if (topicsEqual(parseTopics(row.topics), topics)) return null;
11231
+ const stored2 = parseTopics(row.topics);
11232
+ const carried = mode === "merge" ? stored2 : null;
11233
+ const weights = source === "auto" ? weightsOf(carried, inputs) : null;
11234
+ const displayNames = displayNamesOf([...inputs, ...carried ?? []]);
11235
+ const topics = topicsForEntities(linked, displayNames, source, weights);
11236
+ if (topicsEqual(stored2, topics)) return null;
11217
11237
  return { row, topics };
11218
11238
  }
11219
- function topicsForEntities(entities, displayNames, source) {
11239
+ function weightsOf(stored2, inputs) {
11240
+ const out = /* @__PURE__ */ new Map();
11241
+ for (const topic of stored2 ?? []) out.set(normalizeEntityName(topic.name), topic.weight);
11242
+ for (const input of inputs) {
11243
+ const weight = parseTopicWeight(input.weight);
11244
+ if (weight === void 0) continue;
11245
+ const name = normalizeEntityName(input.name);
11246
+ if (!out.has(name)) {
11247
+ out.set(name, weight);
11248
+ continue;
11249
+ }
11250
+ const current = out.get(name);
11251
+ if (current !== void 0 && weight > current) out.set(name, weight);
11252
+ }
11253
+ return out;
11254
+ }
11255
+ function topicsForEntities(entities, displayNames, source, weights) {
11220
11256
  return entities.map((e) => {
11221
11257
  const name = displayNames.get(e.canonicalName) ?? e.canonicalName;
11222
- return { name, ...e.kind ? { kind: e.kind } : {}, source };
11258
+ const weight = weights?.get(e.canonicalName);
11259
+ return {
11260
+ name,
11261
+ ...e.kind ? { kind: e.kind } : {},
11262
+ source,
11263
+ ...weight !== void 0 ? { weight } : {}
11264
+ };
11223
11265
  });
11224
11266
  }
11225
11267
  function toEntityObjects(entityInputs) {
@@ -11227,7 +11269,7 @@ function toEntityObjects(entityInputs) {
11227
11269
  }
11228
11270
  async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
11229
11271
  const row = await findVaultRowInWrite(ctx, memoryId);
11230
- return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
11272
+ return resolveTopicsWrite(row, linked, toEntityObjects(inputs), source, "replace");
11231
11273
  }
11232
11274
  function prepareMemoryTopicsUpdate(write) {
11233
11275
  return prepareTopicsUpdate(write.row, write.topics, Date.now());
@@ -11265,8 +11307,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
11265
11307
  const topicsWrite = resolveTopicsWrite(
11266
11308
  vaultRow,
11267
11309
  [...carriedOver, ...entities],
11268
- displayNamesOf(normalized),
11269
- options?.topicsSource ?? "auto"
11310
+ normalized,
11311
+ options?.topicsSource ?? "auto",
11312
+ "merge"
11270
11313
  );
11271
11314
  if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
11272
11315
  return;
@@ -11332,8 +11375,9 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
11332
11375
  const topicsWrite = resolveTopicsWrite(
11333
11376
  vaultRow,
11334
11377
  entities,
11335
- displayNamesOf(normalized),
11336
- options.topicsSource
11378
+ normalized,
11379
+ options.topicsSource,
11380
+ "replace"
11337
11381
  );
11338
11382
  if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
11339
11383
  return;
@@ -18839,9 +18883,9 @@ For each durable fact, output:
18839
18883
  - type: one of identity | preference | relationship | plan | ongoing_context | constraint | other
18840
18884
  - confidence: 0.0-1.0; how sure you are this is durable AND true. Only include facts >= 0.7.
18841
18885
  - sourceMessageIds: which message IDs from the conversation contained the evidence
18842
- - 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:
18843
- - "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" }]
18844
- - "running the Berlin Half in May and I track workouts in Whoop" \u2192 entities: [{ "name": "Berlin Half", "kind": "event" }, { "name": "Whoop", "kind": "product" }]
18886
+ - 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:
18887
+ - "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" }]
18888
+ - "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" }]
18845
18889
  - 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:
18846
18890
  - { "kind": "point", "start": "YYYY-MM-DD" } for a single date ("met Sara on March 14, 2026")
18847
18891
  - { "kind": "range", "start": "YYYY-MM-DD", "end": "YYYY-MM-DD" } for a span ("Japan trip May 4\u201315, 2026")
@@ -18922,9 +18966,10 @@ function restoreCandidates(candidates, redactor, ownNames) {
18922
18966
  candidate: {
18923
18967
  ...c,
18924
18968
  content: content.text,
18925
- entities: c.entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
18926
- (e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
18927
- )
18969
+ entities: c.entities.flatMap((e) => {
18970
+ const restored = redactor.restoreForStorage(e.name);
18971
+ return restored.unresolved ? [] : [{ ...e, name: restored.text }];
18972
+ })
18928
18973
  },
18929
18974
  unresolved: content.unresolved
18930
18975
  };
@@ -19153,7 +19198,12 @@ function parseEntities(raw) {
19153
19198
  const name = typeof obj.name === "string" ? obj.name.trim() : "";
19154
19199
  if (name.length === 0 || isGenericEntityName(name)) continue;
19155
19200
  const kind = typeof obj.kind === "string" && ENTITY_KINDS.includes(obj.kind) ? obj.kind : void 0;
19156
- out.push(kind !== void 0 ? { name, kind } : { name });
19201
+ const weight = typeof obj.salience === "string" && Object.keys(TOPIC_SALIENCE_WEIGHTS).includes(obj.salience) ? TOPIC_SALIENCE_WEIGHTS[obj.salience] : void 0;
19202
+ out.push({
19203
+ name,
19204
+ ...kind !== void 0 ? { kind } : {},
19205
+ ...weight !== void 0 ? { weight } : {}
19206
+ });
19157
19207
  }
19158
19208
  return out;
19159
19209
  }
@@ -21112,7 +21162,13 @@ Include only NAMED entities, skip generic/common nouns. A topic must identify a
21112
21162
  - Prefer 0 entities over a weak one. A memory whose only candidate is a generic noun has NO entities.
21113
21163
  ${ENTITY_KIND_GUIDELINES}
21114
21164
 
21115
- Each memory below is written as "<id>: <text>". Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
21165
+ Give each entity a "salience" \u2014 how central it is to the memory:
21166
+ - "primary": the memory is ABOUT it. A memory with entities usually has exactly one primary; never more than two.
21167
+ - "secondary": a meaningful part of the memory, but not its subject.
21168
+ - "mention": named in passing; the memory would say much the same without it.
21169
+ Example: "Ran the Chicago Marathon with my sister Ana in 3:41" \u2192 Chicago Marathon primary, Ana secondary.
21170
+
21171
+ Each memory below is written as "<id>: <text>". Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string, "salience": "primary" | "secondary" | "mention"}]}]}
21116
21172
  - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
21117
21173
  - "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
21118
21174
  No prose.`;
@@ -21183,9 +21239,10 @@ List each memory's named entities.`,
21183
21239
  return out;
21184
21240
  }
21185
21241
  function restoreEntities(entities, redactor) {
21186
- return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
21187
- (e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
21188
- );
21242
+ return entities.flatMap((e) => {
21243
+ const restored = redactor.restoreForStorage(e.name);
21244
+ return restored.unresolved ? [] : [{ ...e, name: restored.text }];
21245
+ });
21189
21246
  }
21190
21247
  function parseTopicResponse(parsed, validIds) {
21191
21248
  if (typeof parsed !== "object" || parsed === null) return null;
@@ -21346,6 +21403,14 @@ function createLocalMemoryStore(options) {
21346
21403
  getLogger().warn("[memory/store] Failed to embed memory:", err);
21347
21404
  });
21348
21405
  };
21406
+ const maintenance = {
21407
+ createDecaySweeper: (sweeperOptions) => createDecaySweeper({ ...sweeperOptions, vaultCtx }),
21408
+ getTopicBacklog: (backlogOptions) => getMemoriesNeedingTopicExtractionOp(vaultCtx, backlogOptions),
21409
+ extractTopics: (memoryIds, extractOptions) => extractAndLinkEntitiesForMemoriesOp(vaultCtx, memoryIds, extractOptions),
21410
+ stampTopicsExtracted: (memoryIds, extractedAt, version) => stampTopicsExtractedAtOp(vaultCtx, memoryIds, extractedAt, version),
21411
+ relinkTopics: (memoryIds) => relinkMemoryTopicsOp(vaultCtx, memoryIds),
21412
+ backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
21413
+ };
21349
21414
  return {
21350
21415
  masksEmbeddingInput: options.embeddingOptions.maskInput !== void 0,
21351
21416
  list: (listOptions) => getAllVaultMemoriesOp(vaultCtx, listOptions),
@@ -21359,21 +21424,22 @@ function createLocalMemoryStore(options) {
21359
21424
  memoriesByTopics: (names) => getMemoriesByEntityNamesOp(entityCtx, names),
21360
21425
  topicsByMemories: (memoryIds) => getEntitiesByMemoryIdsOp(entityCtx, memoryIds),
21361
21426
  listTopics: async () => {
21362
- const links = await entityCtx.memoryEntityCollection.query(...linksOwnedBy).fetch();
21427
+ const links = await entityCtx.memoryEntityCollection.query(...linksOwnedBy).unsafeFetchRaw();
21363
21428
  const members = /* @__PURE__ */ new Map();
21364
21429
  for (const link of links) {
21365
- const entityId = String(link.entityId);
21430
+ const entityId = String(link.entity_id);
21366
21431
  const set = members.get(entityId) ?? /* @__PURE__ */ new Set();
21367
- set.add(String(link.memoryId));
21432
+ set.add(String(link.memory_id));
21368
21433
  members.set(entityId, set);
21369
21434
  }
21370
- const entities = userId === void 0 ? await entityCtx.entityCollection.query().fetch() : members.size ? await entityCtx.entityCollection.query(Q15.where("id", Q15.oneOf([...members.keys()]))).fetch() : [];
21435
+ const entities = userId === void 0 ? await entityCtx.entityCollection.query().unsafeFetchRaw() : members.size ? await entityCtx.entityCollection.query(Q15.where("id", Q15.oneOf([...members.keys()]))).unsafeFetchRaw() : [];
21371
21436
  const byName = /* @__PURE__ */ new Map();
21372
21437
  for (const entity of entities) {
21373
- const count2 = members.get(entity.id)?.size ?? 0;
21374
- const existing = byName.get(entity.canonicalName);
21375
- byName.set(entity.canonicalName, {
21376
- name: entity.canonicalName,
21438
+ const name = String(entity.canonical_name);
21439
+ const count2 = members.get(String(entity.id))?.size ?? 0;
21440
+ const existing = byName.get(name);
21441
+ byName.set(name, {
21442
+ name,
21377
21443
  kind: existing?.kind ?? entity.kind ?? null,
21378
21444
  memoryCount: (existing?.memoryCount ?? 0) + count2
21379
21445
  });
@@ -21464,14 +21530,8 @@ function createLocalMemoryStore(options) {
21464
21530
  }
21465
21531
  return () => subscriptions.forEach((s) => s.unsubscribe());
21466
21532
  },
21467
- maintenance: {
21468
- createDecaySweeper: (sweeperOptions) => createDecaySweeper({ ...sweeperOptions, vaultCtx }),
21469
- getTopicBacklog: (backlogOptions) => getMemoriesNeedingTopicExtractionOp(vaultCtx, backlogOptions),
21470
- extractTopics: (memoryIds, extractOptions) => extractAndLinkEntitiesForMemoriesOp(vaultCtx, memoryIds, extractOptions),
21471
- stampTopicsExtracted: (memoryIds, extractedAt, version) => stampTopicsExtractedAtOp(vaultCtx, memoryIds, extractedAt, version),
21472
- relinkTopics: (memoryIds) => relinkMemoryTopicsOp(vaultCtx, memoryIds),
21473
- backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
21474
- }
21533
+ maintenance,
21534
+ topicMaintenance: maintenance
21475
21535
  };
21476
21536
  }
21477
21537
 
@@ -22255,7 +22315,7 @@ function createRemoteMemoryPipeline(options) {
22255
22315
  const memory = {
22256
22316
  ...snapshot.memory,
22257
22317
  content: patch.content,
22258
- updated_at: patch.preserveUpdatedAt ? snapshot.memory.updated_at : Date.now()
22318
+ updated_at: patch.preserveUpdatedAt ? snapshot.memory.updated_at : Math.max(Date.now(), (snapshot.memory.topics_extracted_at ?? 0) + 1)
22259
22319
  };
22260
22320
  if (patch.scope !== void 0) memory.scope = patch.scope;
22261
22321
  if (patch.embedding === void 0 && patch.content !== snapshot.memory.content) {
@@ -22388,6 +22448,7 @@ function projection({ memory }) {
22388
22448
  };
22389
22449
  }
22390
22450
  function relevant(change, options) {
22451
+ if (change === "metadata") return false;
22391
22452
  return options.includeDeleted ? change === "membership" : change !== "embedding" || !!options.embeddings;
22392
22453
  }
22393
22454
  function safely(onChange) {
@@ -22468,17 +22529,32 @@ function topicInputs(topics, source) {
22468
22529
  return topics.flatMap((topic) => {
22469
22530
  const name = (typeof topic === "string" ? topic : topic.name).trim();
22470
22531
  const kind = typeof topic === "string" ? void 0 : topic.kind;
22471
- return name ? [{ name, ...kind && { kind }, source }] : [];
22532
+ const weight = typeof topic === "string" || source === "user" ? void 0 : parseTopicWeight(topic.weight);
22533
+ return name ? [{ name, ...kind && { kind }, source, ...weight && { weight } }] : [];
22472
22534
  });
22473
22535
  }
22474
22536
  function mergeTopics(existing, added) {
22475
22537
  const byName = /* @__PURE__ */ new Map();
22476
22538
  for (const topic of [...existing, ...added]) {
22477
22539
  const key = normalizeEntityName(topic.name);
22478
- if (!byName.has(key)) byName.set(key, topic);
22540
+ const kept = byName.get(key);
22541
+ if (!kept) byName.set(key, topic);
22542
+ else if (kept.weight !== void 0 && topic.weight !== void 0 && topic.weight > kept.weight)
22543
+ byName.set(key, { ...kept, weight: topic.weight });
22479
22544
  }
22480
22545
  return [...byName.values()];
22481
22546
  }
22547
+ function sameTopics(stored2, next) {
22548
+ const key = (t) => JSON.stringify([t.name, t.kind ?? null, t.source, t.weight ?? null]);
22549
+ if (stored2 === null || stored2.length !== next.length) return false;
22550
+ const keys = new Set(stored2.map(key));
22551
+ return next.every((t) => keys.has(key(t)));
22552
+ }
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
+ }
22482
22558
  function canonicalTopics(row) {
22483
22559
  return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
22484
22560
  }
@@ -22491,7 +22567,12 @@ function createRemoteMemoryStore(options) {
22491
22567
  const seen = /* @__PURE__ */ new Map();
22492
22568
  let timer;
22493
22569
  const notify = (change, records = []) => {
22494
- if (timer) for (const record2 of records) seen.set(record2.memory.memory_id, record2);
22570
+ if (timer)
22571
+ for (const record2 of records) {
22572
+ const previous = seen.get(record2.memory.memory_id);
22573
+ if ((previous?.version ?? 0) + 1 === record2.version)
22574
+ seen.set(record2.memory.memory_id, record2);
22575
+ }
22495
22576
  for (const subscriber of [...subscribers])
22496
22577
  if (relevant(change, subscriber.options)) safely(subscriber.onChange);
22497
22578
  };
@@ -22552,15 +22633,72 @@ function createRemoteMemoryStore(options) {
22552
22633
  const record2 = await persistence.get(id);
22553
22634
  return record2 && !record2.memory.is_deleted ? record2 : null;
22554
22635
  };
22555
- const mutate = async (id, change, edit) => serialized(id, async () => {
22636
+ const mutateLocked = async (id, change, edit) => {
22556
22637
  const record2 = await live(id);
22557
22638
  if (!record2) return null;
22558
22639
  const next = edit(structuredClone(record2.memory));
22559
22640
  if (!next) return null;
22560
22641
  const saved = await persistence.put(next, record2);
22561
- notify(change, [saved]);
22642
+ notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
22562
22643
  return saved;
22563
- });
22644
+ };
22645
+ const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
22646
+ const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
22647
+ const mutateMany = async (ids, change, edit) => {
22648
+ const saved = /* @__PURE__ */ new Map();
22649
+ const failed = /* @__PURE__ */ new Map();
22650
+ for (const slice of chunks([...new Set(ids)], MAX_BATCH)) {
22651
+ await serializedAll(slice, async () => {
22652
+ const writes = [];
22653
+ let read;
22654
+ try {
22655
+ read = await readEveryState(slice);
22656
+ } catch (error) {
22657
+ for (const id of slice) failed.set(id, error);
22658
+ return;
22659
+ }
22660
+ const { records, unreadable } = read;
22661
+ for (const id of slice) {
22662
+ const record2 = records.get(id);
22663
+ if (unreadable.has(id)) failed.set(id, new Error(`Memory ${id} could not be decrypted`));
22664
+ else if (!record2) failed.set(id, new Error(`Memory ${id} was not found`));
22665
+ else if (!record2.memory.is_deleted) {
22666
+ const memory = edit(structuredClone(record2.memory));
22667
+ if (memory) writes.push({ before: record2, memory });
22668
+ }
22669
+ }
22670
+ if (writes.length === 0) return;
22671
+ try {
22672
+ const written = await persistence.putMany(
22673
+ writes.map(({ before, memory }) => ({ memory, expectedVersion: before }))
22674
+ );
22675
+ const byChange = /* @__PURE__ */ new Map();
22676
+ written.forEach((record2, i) => {
22677
+ saved.set(record2.memory.memory_id, record2);
22678
+ const kind = change(writes[i].before.memory);
22679
+ byChange.set(kind, [...byChange.get(kind) ?? [], record2]);
22680
+ });
22681
+ for (const [kind, changed] of byChange) notify(kind, changed);
22682
+ } catch (error) {
22683
+ const perRow = error instanceof RemoteMemoryError && (error.code === "version_conflict" || error.status === 429);
22684
+ for (const { before } of writes) {
22685
+ const id = before.memory.memory_id;
22686
+ if (!perRow) {
22687
+ failed.set(id, error);
22688
+ continue;
22689
+ }
22690
+ try {
22691
+ const record2 = await mutateLocked(id, change, edit);
22692
+ if (record2) saved.set(id, record2);
22693
+ } catch (rowError) {
22694
+ failed.set(id, rowError);
22695
+ }
22696
+ }
22697
+ }
22698
+ });
22699
+ }
22700
+ return { saved, failed };
22701
+ };
22564
22702
  const embedInBackground = ({ memory: { memory_id: id, content } }) => {
22565
22703
  generateEmbedding(content, embeddingOptions).then(
22566
22704
  (vector) => serialized(id, async () => {
@@ -22616,11 +22754,10 @@ function createRemoteMemoryStore(options) {
22616
22754
  return records.map((record2) => stored(record2.memory));
22617
22755
  };
22618
22756
  const asStored = (record2) => record2 ? stored(record2.memory) : null;
22619
- const withKnownKinds = async (topics) => {
22757
+ const knownKinds = async (topics) => {
22620
22758
  const unkinded = [
22621
22759
  ...new Set(topics.filter((t) => !t.kind).map((t) => normalizeEntityName(t.name)))
22622
22760
  ];
22623
- if (unkinded.length === 0) return topics;
22624
22761
  const kinds = /* @__PURE__ */ new Map();
22625
22762
  for (const names of chunks(unkinded, MAX_QUERY_TOPICS)) {
22626
22763
  for (const { memory } of await pages(metadata, {
@@ -22635,10 +22772,136 @@ function createRemoteMemoryStore(options) {
22635
22772
  }
22636
22773
  }
22637
22774
  }
22638
- return topics.map((topic) => {
22775
+ return (topic) => {
22639
22776
  const kind = topic.kind ?? kinds.get(normalizeEntityName(topic.name));
22640
22777
  return kind ? { ...topic, kind } : topic;
22641
- });
22778
+ };
22779
+ };
22780
+ const withKnownKinds = async (topics) => topics.map(await knownKinds(topics));
22781
+ const readEveryState = async (ids) => {
22782
+ const records = /* @__PURE__ */ new Map();
22783
+ const unreadable = /* @__PURE__ */ new Set();
22784
+ for (const memory_ids of chunks(ids, MAX_QUERY_IDS)) {
22785
+ let cursor2;
22786
+ do {
22787
+ const page = await persistence.query({
22788
+ memory_ids,
22789
+ include_deleted: true,
22790
+ include_archived: true,
22791
+ include_quarantined: true,
22792
+ include_superseded: true,
22793
+ limit: 200,
22794
+ ...cursor2 && { cursor: cursor2 }
22795
+ });
22796
+ warnFailed(page.failed);
22797
+ for (const record2 of page.items) records.set(record2.memory.memory_id, record2);
22798
+ for (const failure of page.failed) unreadable.add(failure.memory_id);
22799
+ cursor2 = page.next_cursor;
22800
+ } while (cursor2);
22801
+ }
22802
+ return { records, unreadable };
22803
+ };
22804
+ const topicMaintenance = {
22805
+ 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
+ const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
22815
+ return {
22816
+ pending: [...stale, ...fresh].slice(0, limit).map((uniqueId) => ({ uniqueId })),
22817
+ linkedUnstamped: [],
22818
+ topicsToRelink: [],
22819
+ topicsBackfill: []
22820
+ };
22821
+ },
22822
+ extractTopics: async (memoryIds, extractOptions) => {
22823
+ const extractedAt = extractOptions.now ?? Date.now();
22824
+ const skippedIds = [];
22825
+ const skippedReasons = /* @__PURE__ */ new Map();
22826
+ const skip = (id, reason) => {
22827
+ if (skippedReasons.has(id)) return;
22828
+ skippedIds.push(id);
22829
+ skippedReasons.set(id, reason);
22830
+ };
22831
+ const ids = [...new Set(memoryIds)];
22832
+ const { records, unreadable } = await readEveryState(ids);
22833
+ const inputs = [];
22834
+ for (const id of ids) {
22835
+ const memory = records.get(id)?.memory;
22836
+ if (unreadable.has(id)) skip(id, "unreadable");
22837
+ else if (!memory) skip(id, "not-found");
22838
+ else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
22839
+ else inputs.push({ id, content: memory.content });
22840
+ }
22841
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, extractOptions);
22842
+ const answered = /* @__PURE__ */ new Map();
22843
+ for (const input of inputs) {
22844
+ const entities = entitiesByMemory.get(input.id);
22845
+ if (entities === void 0) skip(input.id, "llm-unanswered");
22846
+ else
22847
+ answered.set(input.id, { content: input.content, topics: topicInputs(entities, "auto") });
22848
+ }
22849
+ const stampedIds = [];
22850
+ const fail = (id, reason) => {
22851
+ skip(id, reason);
22852
+ entitiesByMemory.delete(id);
22853
+ };
22854
+ try {
22855
+ const kinded = await knownKinds([...answered.values()].flatMap((a) => a.topics));
22856
+ const topics = new Map(
22857
+ [...answered].map(([id, a]) => [id, mergeTopics([], a.topics.map(kinded))])
22858
+ );
22859
+ const unchanged = (memory) => sameTopics(parseTopics(memory.topics), topics.get(memory.memory_id));
22860
+ const { saved, failed } = await mutateMany(
22861
+ [...answered.keys()],
22862
+ (before) => sameTopics(parseTopics(before.topics) ?? [], topics.get(before.memory_id)) ? "metadata" : "edit",
22863
+ (memory) => {
22864
+ if (memory.topics_user_managed || memory.content !== answered.get(memory.memory_id).content)
22865
+ return null;
22866
+ return {
22867
+ ...memory,
22868
+ ...!unchanged(memory) && {
22869
+ topics: serializeTopics(topics.get(memory.memory_id)),
22870
+ topics_updated_at: Date.now()
22871
+ },
22872
+ topics_extracted_at: Math.max(extractedAt, memory.updated_at),
22873
+ topics_extracted_version: TOPICS_EXTRACTION_VERSION
22874
+ };
22875
+ }
22876
+ );
22877
+ for (const id of answered.keys()) {
22878
+ if (saved.has(id)) stampedIds.push(id);
22879
+ else fail(id, failed.has(id) ? "link-failed" : "link-declined");
22880
+ }
22881
+ if (failed.size)
22882
+ getLogger().warn(
22883
+ "[memory/remote-store] Failed to write extracted topics:",
22884
+ failed.values().next().value
22885
+ );
22886
+ } catch (error) {
22887
+ getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
22888
+ for (const id of answered.keys()) fail(id, "link-failed");
22889
+ }
22890
+ return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
22891
+ },
22892
+ stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
22893
+ const ids = [...new Set(memoryIds)];
22894
+ const { saved, failed } = await mutateMany(
22895
+ ids,
22896
+ () => "metadata",
22897
+ (memory) => memory.topics_user_managed ? null : { ...memory, topics_extracted_at: extractedAt, topics_extracted_version: version }
22898
+ );
22899
+ if (failed.size)
22900
+ getLogger().warn("[memory/remote-store] Failed to stamp topics:", [...failed.values()]);
22901
+ return ids.filter((id) => saved.has(id));
22902
+ },
22903
+ relinkTopics: () => Promise.resolve([]),
22904
+ backfillTopics: () => Promise.resolve([])
22642
22905
  };
22643
22906
  const everyState = {
22644
22907
  include_deleted: true,
@@ -22717,6 +22980,7 @@ function createRemoteMemoryStore(options) {
22717
22980
  };
22718
22981
  return {
22719
22982
  masksEmbeddingInput: embeddingOptions.maskInput !== void 0,
22983
+ topicMaintenance,
22720
22984
  list: async (listOptions = {}) => (await listRows(decrypted, listOptions, listFilters(listOptions), maxOf(listOptions))).map(
22721
22985
  (r) => stored(r.memory)
22722
22986
  ),
@@ -22801,7 +23065,7 @@ function createRemoteMemoryStore(options) {
22801
23065
  const kind = patch.kind !== void 0 ? patch.kind : memory.kind;
22802
23066
  assertValidKindLevel(kind, level);
22803
23067
  memory.content = patch.content;
22804
- memory.updated_at = Date.now();
23068
+ memory.updated_at = Math.max(Date.now(), (memory.topics_extracted_at ?? 0) + 1);
22805
23069
  if (patch.level !== void 0) {
22806
23070
  memory.level = level;
22807
23071
  memory.scope = scopeForLevel(level);
@@ -37137,6 +37401,7 @@ export {
37137
37401
  TOOL_RESULT_ORIGIN,
37138
37402
  TOPICS_EXTRACTION_VERSION,
37139
37403
  TOPIC_EXTRACTION_BATCH_SIZE,
37404
+ TOPIC_SALIENCE_WEIGHTS,
37140
37405
  TREND_RECENT_WINDOW_DAYS,
37141
37406
  TREND_STALE_WINDOW_DAYS,
37142
37407
  TextProcessor,
@@ -37399,6 +37664,7 @@ export {
37399
37664
  preEmbedVaultMemories,
37400
37665
  preprocessFiles,
37401
37666
  previewToolSelection,
37667
+ primaryTopic,
37402
37668
  projectToStored,
37403
37669
  putAppFileOp,
37404
37670
  quantizeEmbedding,
@@ -37472,6 +37738,7 @@ export {
37472
37738
  summarizeObservationTrends,
37473
37739
  supersedeVaultMemoryOp,
37474
37740
  synthesizeProfile,
37741
+ topicWeight,
37475
37742
  traverseGraphLane,
37476
37743
  ttlForType,
37477
37744
  updateAttrs,