@anuma/sdk 8.2.0 → 9.0.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.
@@ -6757,11 +6757,17 @@ async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage,
6757
6757
  }
6758
6758
  return response.data.data.map((item) => item.embedding ?? []);
6759
6759
  }
6760
- async function generateEmbeddings(texts, options) {
6760
+ async function generateEmbeddings(texts, options, signal) {
6761
+ signal?.throwIfAborted();
6761
6762
  if (texts.length === 0) return [];
6762
6763
  const total = options.totalTimeoutMs;
6763
- if (total === void 0) return embedMany(texts, options, void 0);
6764
- return withDeadline((signal) => embedMany(texts, options, signal), total, "embedding batch");
6764
+ if (total === void 0) return embedMany(texts, options, signal);
6765
+ return withDeadline(
6766
+ (inner) => embedMany(texts, options, inner),
6767
+ total,
6768
+ "embedding batch",
6769
+ signal
6770
+ );
6765
6771
  }
6766
6772
  async function embedMany(texts, options, outer) {
6767
6773
  const { baseUrl = BASE_URL, model, batchSize, cache } = options;
@@ -12570,13 +12576,13 @@ function normalizeEntityName(name) {
12570
12576
  return name.trim().toLowerCase();
12571
12577
  }
12572
12578
  var TOPIC_SALIENCE_WEIGHTS = { primary: 1, secondary: 0.6, mention: 0.3 };
12573
- function topicWeight(topic) {
12574
- return topic.source === "user" ? 1 : topic.weight ?? 1;
12579
+ function topicWeight(topic2) {
12580
+ return topic2.source === "user" ? 1 : topic2.weight ?? 1;
12575
12581
  }
12576
12582
  function primaryTopic(topics) {
12577
12583
  let best = null;
12578
- for (const topic of topics ?? []) {
12579
- if (best === null || topicWeight(topic) > topicWeight(best)) best = topic;
12584
+ for (const topic2 of topics ?? []) {
12585
+ if (best === null || topicWeight(topic2) > topicWeight(best)) best = topic2;
12580
12586
  }
12581
12587
  return best;
12582
12588
  }
@@ -12714,7 +12720,7 @@ function resolveTopicsWrite(row, linked, inputs, source, mode) {
12714
12720
  }
12715
12721
  function weightsOf(stored2, inputs) {
12716
12722
  const out = /* @__PURE__ */ new Map();
12717
- for (const topic of stored2 ?? []) out.set(normalizeEntityName(topic.name), topic.weight);
12723
+ for (const topic2 of stored2 ?? []) out.set(normalizeEntityName(topic2.name), topic2.weight);
12718
12724
  for (const input of inputs) {
12719
12725
  const weight = parseTopicWeight(input.weight);
12720
12726
  if (weight === void 0) continue;
@@ -15860,6 +15866,20 @@ async function getAllVaultMemoryContentsOp(ctx, options) {
15860
15866
  async function countActiveVaultMemoriesOp(ctx) {
15861
15867
  return ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx)).fetchCount();
15862
15868
  }
15869
+ async function getVaultMemoryCountsOp(ctx) {
15870
+ const [live, archived, quarantined] = await Promise.all([
15871
+ countActiveVaultMemoriesOp(ctx),
15872
+ ctx.vaultMemoryCollection.query(
15873
+ ...baseVaultConditions(ctx, { includeArchived: true }),
15874
+ import_watermelondb24.Q.where("archived_at", import_watermelondb24.Q.notEq(null))
15875
+ ).fetchCount(),
15876
+ ctx.vaultMemoryCollection.query(
15877
+ ...baseVaultConditions(ctx, { includeQuarantined: true }),
15878
+ import_watermelondb24.Q.where("trust_tier", "quarantined")
15879
+ ).fetchCount()
15880
+ ]);
15881
+ return { live, archived, quarantined };
15882
+ }
15863
15883
  async function getActiveVaultMemoryIdsOp(ctx, ids) {
15864
15884
  if (ids.length === 0) return /* @__PURE__ */ new Set();
15865
15885
  const rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), import_watermelondb24.Q.where("id", import_watermelondb24.Q.oneOf(ids))).unsafeFetchRaw();
@@ -23063,7 +23083,8 @@ Each memory below is written as "<id>: <text>". Output strict JSON: {"memories":
23063
23083
  - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
23064
23084
  - "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
23065
23085
  No prose.`;
23066
- async function extractEntitiesForMemories(memories, options) {
23086
+ async function extractEntitiesForMemories(memories, options, signal) {
23087
+ signal?.throwIfAborted();
23067
23088
  const out = /* @__PURE__ */ new Map();
23068
23089
  if (memories.length === 0) return out;
23069
23090
  const redactor = resolvePiiRedactor(options.piiRedaction);
@@ -23076,6 +23097,7 @@ When a memory refers to one of these, reuse the EXACT existing name (same spelli
23076
23097
 
23077
23098
  ` : "";
23078
23099
  for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
23100
+ signal?.throwIfAborted();
23079
23101
  const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
23080
23102
  const start = Date.now();
23081
23103
  let attempts = 0;
@@ -23123,6 +23145,7 @@ List each memory's named entities.`,
23123
23145
  ...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
23124
23146
  ...options.backoffMs && { backoffMs: options.backoffMs }
23125
23147
  });
23148
+ signal?.throwIfAborted();
23126
23149
  if (parsed === null) {
23127
23150
  reportBatch(batch.length);
23128
23151
  getLogger().warn(
@@ -23357,6 +23380,7 @@ function createLocalMemoryStore(options) {
23357
23380
  backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
23358
23381
  };
23359
23382
  return {
23383
+ counts: () => getVaultMemoryCountsOp(vaultCtx),
23360
23384
  masksEmbeddingInput: options.embeddingOptions.maskInput !== void 0,
23361
23385
  list: (listOptions) => getAllVaultMemoriesOp(vaultCtx, listOptions),
23362
23386
  get: (id) => getVaultMemoryOp(vaultCtx, id),
@@ -23586,6 +23610,137 @@ init_logger();
23586
23610
 
23587
23611
  // src/lib/memory/store/remotePersistence.ts
23588
23612
  init_portalLlm();
23613
+
23614
+ // src/lib/memory/store/remoteTopics.ts
23615
+ function topic(value) {
23616
+ const t = value;
23617
+ if (!t || typeof t.name !== "string" || !t.name || !Number.isSafeInteger(t.version) || t.version < 1 || typeof t.is_deleted !== "boolean" || typeof t.server_updated_at !== "string" || t.kind !== void 0 && typeof t.kind !== "string")
23618
+ throw new Error("Invalid nearby topic");
23619
+ return t;
23620
+ }
23621
+ function createRemoteTopicPersistence(request) {
23622
+ return {
23623
+ topics: async () => {
23624
+ const out = [];
23625
+ let cursor;
23626
+ do {
23627
+ const page = await request(
23628
+ "/topics?limit=200" + (cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""),
23629
+ {}
23630
+ );
23631
+ if (!Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string")
23632
+ throw new Error("Invalid nearby topic page");
23633
+ out.push(...page.items.map(topic));
23634
+ cursor = page.next_cursor;
23635
+ } while (cursor);
23636
+ return out;
23637
+ },
23638
+ putTopic: async (value, expectedVersion) => {
23639
+ if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0)
23640
+ throw new Error("Expected topic version must be a nonnegative integer");
23641
+ return topic(
23642
+ await request("/topics", {
23643
+ method: "PUT",
23644
+ body: JSON.stringify({ topic: value, expected_version: expectedVersion })
23645
+ })
23646
+ );
23647
+ },
23648
+ importTopics: async (topics, signal) => {
23649
+ for (let i = 0; i < topics.length; i += 50) {
23650
+ signal?.throwIfAborted();
23651
+ await request(
23652
+ "/topics/import",
23653
+ {
23654
+ method: "POST",
23655
+ body: JSON.stringify({ topics: topics.slice(i, i + 50) })
23656
+ },
23657
+ signal
23658
+ );
23659
+ }
23660
+ signal?.throwIfAborted();
23661
+ }
23662
+ };
23663
+ }
23664
+ async function createRemoteTopic(persistence, name) {
23665
+ const canonical = normalizeEntityName(name);
23666
+ if (!canonical) return null;
23667
+ const existing = (await persistence.topics()).find((t) => t.name === canonical);
23668
+ if (existing && !existing.is_deleted) return canonical;
23669
+ try {
23670
+ await persistence.putTopic(
23671
+ { name: canonical, is_deleted: false, ...existing?.kind && { kind: existing.kind } },
23672
+ existing?.version ?? 0
23673
+ );
23674
+ } catch (error) {
23675
+ if (!(error instanceof RemoteMemoryError) || error.code !== "version_conflict") throw error;
23676
+ const current = (await persistence.topics()).find((t) => t.name === canonical);
23677
+ if (!current || current.is_deleted) throw error;
23678
+ }
23679
+ return canonical;
23680
+ }
23681
+
23682
+ // src/lib/memory/store/remoteWork.ts
23683
+ init_logger();
23684
+ function createRemoteWorkPersistence(request) {
23685
+ return {
23686
+ claimWork: async (kind, processorVersion, token) => {
23687
+ const result = await request("/work/claim", {
23688
+ method: "POST",
23689
+ body: JSON.stringify({
23690
+ kind,
23691
+ processor_version: processorVersion,
23692
+ ...token && { token }
23693
+ })
23694
+ });
23695
+ if (!result || typeof result !== "object") throw new Error("Invalid nearby work lease");
23696
+ const lease = result.lease;
23697
+ if (lease === void 0) return null;
23698
+ if (!lease || lease.kind !== kind || typeof lease.token !== "string" || !lease.token || lease.processor_version !== processorVersion || typeof lease.expires_at !== "string" || !Number.isFinite(Date.parse(lease.expires_at)))
23699
+ throw new Error("Invalid nearby work lease");
23700
+ return lease;
23701
+ },
23702
+ releaseWork: async (kind, token) => {
23703
+ await request("/work/release", { method: "POST", body: JSON.stringify({ kind, token }) });
23704
+ }
23705
+ };
23706
+ }
23707
+ async function withRemoteWorkLease(persistence, kind, processorVersion, task) {
23708
+ const lease = await persistence.claimWork(kind, processorVersion);
23709
+ if (!lease) return null;
23710
+ const controller = new AbortController();
23711
+ const leaseLostError = new RemoteMemoryError("Maintenance lease lost", 409, "work_lease_expired");
23712
+ const cancelled = new Promise((_resolve, reject) => {
23713
+ controller.signal.addEventListener("abort", () => reject(leaseLostError), {
23714
+ once: true
23715
+ });
23716
+ });
23717
+ let renewing;
23718
+ const timer = setInterval(() => {
23719
+ if (renewing || controller.signal.aborted) return;
23720
+ renewing = persistence.claimWork(kind, processorVersion, lease.token).then((renewed) => {
23721
+ if (!renewed) controller.abort(leaseLostError);
23722
+ }).catch((error) => getLogger().warn("[memory/work] Lease renewal failed:", error)).finally(() => {
23723
+ renewing = void 0;
23724
+ });
23725
+ }, 3e4);
23726
+ try {
23727
+ const result = await Promise.race([task(lease.token, controller.signal), cancelled]);
23728
+ await renewing;
23729
+ return controller.signal.aborted ? null : result;
23730
+ } catch (error) {
23731
+ if (controller.signal.aborted || isRemoteWorkLeaseExpired(error)) return null;
23732
+ throw error;
23733
+ } finally {
23734
+ clearInterval(timer);
23735
+ await renewing;
23736
+ await persistence.releaseWork(kind, lease.token).catch((error) => getLogger().warn("[memory/work] Lease release failed:", error));
23737
+ }
23738
+ }
23739
+ function isRemoteWorkLeaseExpired(error) {
23740
+ return error instanceof RemoteMemoryError && error.code === "work_lease_expired";
23741
+ }
23742
+
23743
+ // src/lib/memory/store/remotePersistence.ts
23589
23744
  var RemoteMemoryError = class extends Error {
23590
23745
  constructor(message, status, code, retryAfterMs) {
23591
23746
  super(message);
@@ -23964,6 +24119,33 @@ async function createRemoteMemoryPersistence(options) {
23964
24119
  };
23965
24120
  };
23966
24121
  return {
24122
+ ...createRemoteTopicPersistence(request),
24123
+ ...createRemoteWorkPersistence(request),
24124
+ counts: async () => {
24125
+ const result = await request("/counts", {});
24126
+ if (!record(result) || ["live", "archived", "quarantined"].some(
24127
+ (key) => !Number.isSafeInteger(result[key]) || result[key] < 0
24128
+ ))
24129
+ throw new Error("Invalid nearby memory counts");
24130
+ return {
24131
+ live: result.live,
24132
+ archived: result.archived,
24133
+ quarantined: result.quarantined
24134
+ };
24135
+ },
24136
+ temporal: async (window2, limit = 100) => {
24137
+ if (!Number.isFinite(window2.start) || !Number.isFinite(window2.end) || window2.end <= window2.start || !Number.isInteger(limit) || limit < 1 || limit > 100)
24138
+ throw new Error("Temporal window must have end > start and limit must be 1\u2013100");
24139
+ const result = await request("/temporal", {
24140
+ method: "POST",
24141
+ body: JSON.stringify({ ...window2, limit })
24142
+ });
24143
+ if (!record(result) || !Array.isArray(result.items) || result.items.some(
24144
+ (item) => !record(item) || typeof item.memory_id !== "string" || !item.memory_id || typeof item.score !== "number" || !Number.isFinite(item.score) || item.score <= 0 || item.score > 1
24145
+ ))
24146
+ throw new Error("Invalid nearby temporal candidates");
24147
+ return result.items.map((item) => item.memory_id);
24148
+ },
23967
24149
  get: async (id, signal) => {
23968
24150
  try {
23969
24151
  return await decode(await request(memoryPath(id), {}, signal));
@@ -24012,7 +24194,7 @@ async function createRemoteMemoryPersistence(options) {
24012
24194
  write
24013
24195
  );
24014
24196
  },
24015
- putMany: async (writes, signal) => {
24197
+ putMany: async (writes, signal, leaseToken) => {
24016
24198
  if (writes.length < 1 || writes.length > 50 || new Set(writes.map((w) => w.memory.memory_id)).size !== writes.length)
24017
24199
  throw new Error("Batch must contain 1\u201350 distinct memory ids");
24018
24200
  const prepared = await Promise.all(
@@ -24022,7 +24204,11 @@ async function createRemoteMemoryPersistence(options) {
24022
24204
  "/memories/batch",
24023
24205
  {
24024
24206
  method: "POST",
24025
- body: JSON.stringify({ key_id: options.keyId, writes: prepared.map((w) => w.body) })
24207
+ body: JSON.stringify({
24208
+ key_id: options.keyId,
24209
+ writes: prepared.map((w) => w.body),
24210
+ ...leaseToken && { lease_token: leaseToken }
24211
+ })
24026
24212
  },
24027
24213
  signal
24028
24214
  );
@@ -24782,6 +24968,12 @@ async function migrateMemoriesToRemote(options) {
24782
24968
  let claimed = account.key_id === canonical.keyId;
24783
24969
  if (!active) await createDecaySweeper({ vaultCtx: options.vaultCtx }).runSweep();
24784
24970
  await backfillTopics(options.vaultCtx);
24971
+ const localTopics = options.topics ?? (options.vaultCtx.singleTenant === true ? (await options.vaultCtx.database.get("entity").query().unsafeFetchRaw()).map((t) => ({
24972
+ name: normalizeEntityName(typeof t.canonical_name === "string" ? t.canonical_name : ""),
24973
+ ...typeof t.kind === "string" && { kind: t.kind }
24974
+ })).filter((t) => t.name.length > 0) : []);
24975
+ if (localTopics.length)
24976
+ await createRemoteTopicPersistence(request).importTopics(localTopics, signal);
24785
24977
  const skipped = /* @__PURE__ */ new Map();
24786
24978
  const reembedded = /* @__PURE__ */ new Set();
24787
24979
  const unembedded = /* @__PURE__ */ new Set();
@@ -24909,7 +25101,6 @@ var MAX_QUERY_IDS = 1e3;
24909
25101
  var MAX_QUERY_TOPICS = 100;
24910
25102
  var MAX_QUERY_FILTER = 20;
24911
25103
  var GRAPH_LANE_ROWS = 256;
24912
- var TEMPORAL_LANE_ROWS = 256;
24913
25104
  var REPAIR_ROWS = 50;
24914
25105
  var REPAIR_SCAN = 2e3;
24915
25106
  var DECAY_TRANSITIONS_PER_SWEEP = 200;
@@ -24980,21 +25171,21 @@ function subscriptionKey({ memory, version }, options) {
24980
25171
  ]);
24981
25172
  }
24982
25173
  function topicInputs(topics, source) {
24983
- return topics.flatMap((topic) => {
24984
- const name = (typeof topic === "string" ? topic : topic.name).trim();
24985
- const kind = typeof topic === "string" ? void 0 : topic.kind;
24986
- const weight = typeof topic === "string" || source === "user" ? void 0 : parseTopicWeight(topic.weight);
25174
+ return topics.flatMap((topic2) => {
25175
+ const name = (typeof topic2 === "string" ? topic2 : topic2.name).trim();
25176
+ const kind = typeof topic2 === "string" ? void 0 : topic2.kind;
25177
+ const weight = typeof topic2 === "string" || source === "user" ? void 0 : parseTopicWeight(topic2.weight);
24987
25178
  return name ? [{ name, ...kind && { kind }, source, ...weight && { weight } }] : [];
24988
25179
  });
24989
25180
  }
24990
25181
  function mergeTopics(existing, added) {
24991
25182
  const byName = /* @__PURE__ */ new Map();
24992
- for (const topic of [...existing, ...added]) {
24993
- const key = normalizeEntityName(topic.name);
25183
+ for (const topic2 of [...existing, ...added]) {
25184
+ const key = normalizeEntityName(topic2.name);
24994
25185
  const kept = byName.get(key);
24995
- if (!kept) byName.set(key, topic);
24996
- else if (kept.weight !== void 0 && topic.weight !== void 0 && topic.weight > kept.weight)
24997
- byName.set(key, { ...kept, weight: topic.weight });
25186
+ if (!kept) byName.set(key, topic2);
25187
+ else if (kept.weight !== void 0 && topic2.weight !== void 0 && topic2.weight > kept.weight)
25188
+ byName.set(key, { ...kept, weight: topic2.weight });
24998
25189
  }
24999
25190
  return [...byName.values()];
25000
25191
  }
@@ -25005,7 +25196,7 @@ function sameTopics(stored2, next) {
25005
25196
  return next.every((t) => keys.has(key(t)));
25006
25197
  }
25007
25198
  function canonicalTopics(row) {
25008
- return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
25199
+ return new Set((parseTopics(row.topics) ?? []).map((topic2) => normalizeEntityName(topic2.name)));
25009
25200
  }
25010
25201
  function createRemoteMemoryStore(options) {
25011
25202
  const { persistence, embeddingOptions } = options;
@@ -25080,20 +25271,7 @@ function createRemoteMemoryStore(options) {
25080
25271
  const eventTimeRanking = async (query, now) => {
25081
25272
  const window2 = parseQueryTimeWindow(query, now);
25082
25273
  if (!window2) return [];
25083
- const rows = await pages(
25084
- metadata2,
25085
- { event_time_overlaps: { start: window2.start, end: window2.end } },
25086
- TEMPORAL_LANE_ROWS
25087
- );
25088
- return rows.map(({ memory }) => ({
25089
- memoryId: memory.memory_id,
25090
- score: scoreEventTimeOverlap(
25091
- memory.event_time_start ?? null,
25092
- memory.event_time_end ?? null,
25093
- memory.event_time_kind ?? null,
25094
- window2
25095
- )
25096
- })).filter((row) => row.score > 0).sort((a, b) => b.score - a.score).map((row) => row.memoryId);
25274
+ return persistence.temporal({ start: window2.start, end: window2.end });
25097
25275
  };
25098
25276
  const enumerateIds = async (read, ids, filters) => (await Promise.all(
25099
25277
  chunks2([...new Set(ids)], MAX_QUERY_IDS).map(
@@ -25144,22 +25322,28 @@ function createRemoteMemoryStore(options) {
25144
25322
  writeOptions?.signal?.throwIfAborted();
25145
25323
  const next = edit(structuredClone(record2.memory));
25146
25324
  if (!next) return null;
25147
- const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
25325
+ const saved = writeOptions?.leaseToken ? (await persistence.putMany(
25326
+ [{ memory: next, expectedVersion: record2 }],
25327
+ writeOptions.signal,
25328
+ writeOptions.leaseToken
25329
+ ))[0] : writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
25148
25330
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
25149
25331
  return saved;
25150
25332
  };
25151
25333
  const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
25152
25334
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
25153
- const mutateMany = async (ids, change, edit) => {
25335
+ const mutateMany = async (ids, change, edit, leaseToken, signal) => {
25154
25336
  const saved = /* @__PURE__ */ new Map();
25155
25337
  const failed = /* @__PURE__ */ new Map();
25156
25338
  for (const slice of chunks2([...new Set(ids)], MAX_BATCH)) {
25157
25339
  await serializedAll(slice, async () => {
25340
+ signal?.throwIfAborted();
25158
25341
  const writes = [];
25159
25342
  let read;
25160
25343
  try {
25161
- read = await readEveryState(slice);
25344
+ read = await readEveryState(slice, signal);
25162
25345
  } catch (error) {
25346
+ signal?.throwIfAborted();
25163
25347
  for (const id of slice) failed.set(id, error);
25164
25348
  return;
25165
25349
  }
@@ -25176,7 +25360,9 @@ function createRemoteMemoryStore(options) {
25176
25360
  if (writes.length === 0) return;
25177
25361
  try {
25178
25362
  const written = await persistence.putMany(
25179
- writes.map(({ before, memory }) => ({ memory, expectedVersion: before }))
25363
+ writes.map(({ before, memory }) => ({ memory, expectedVersion: before })),
25364
+ signal,
25365
+ leaseToken
25180
25366
  );
25181
25367
  const byChange = /* @__PURE__ */ new Map();
25182
25368
  written.forEach((record2, i) => {
@@ -25186,7 +25372,9 @@ function createRemoteMemoryStore(options) {
25186
25372
  });
25187
25373
  for (const [kind, changed] of byChange) notify(kind, changed);
25188
25374
  } catch (error) {
25189
- const perRow = error instanceof RemoteMemoryError && (error.code === "version_conflict" || error.status === 429);
25375
+ signal?.throwIfAborted();
25376
+ if (isRemoteWorkLeaseExpired(error)) throw error;
25377
+ const perRow = error instanceof RemoteMemoryError && (error.code === "version_conflict" || error.status === 429 && !leaseToken);
25190
25378
  for (const { before } of writes) {
25191
25379
  const id = before.memory.memory_id;
25192
25380
  if (!perRow) {
@@ -25194,9 +25382,16 @@ function createRemoteMemoryStore(options) {
25194
25382
  continue;
25195
25383
  }
25196
25384
  try {
25197
- const record2 = await mutateLocked(id, change, edit);
25385
+ const record2 = await mutateLocked(
25386
+ id,
25387
+ change,
25388
+ edit,
25389
+ leaseToken || signal ? { leaseToken, signal } : void 0
25390
+ );
25198
25391
  if (record2) saved.set(id, record2);
25199
25392
  } catch (rowError) {
25393
+ signal?.throwIfAborted();
25394
+ if (isRemoteWorkLeaseExpired(rowError)) throw rowError;
25200
25395
  failed.set(id, rowError);
25201
25396
  }
25202
25397
  }
@@ -25260,7 +25455,7 @@ function createRemoteMemoryStore(options) {
25260
25455
  return records.map((record2) => stored(record2.memory));
25261
25456
  };
25262
25457
  const asStored = (record2) => record2 ? stored(record2.memory) : null;
25263
- const knownKinds = async (topics) => {
25458
+ const knownKinds = async (topics, signal) => {
25264
25459
  const unkinded = [
25265
25460
  ...new Set(topics.filter((t) => !t.kind).map((t) => normalizeEntityName(t.name)))
25266
25461
  ];
@@ -25270,21 +25465,22 @@ function createRemoteMemoryStore(options) {
25270
25465
  topics: names,
25271
25466
  include_archived: true,
25272
25467
  include_quarantined: true,
25273
- include_superseded: true
25468
+ include_superseded: true,
25469
+ ...signal && { signal }
25274
25470
  })) {
25275
- for (const topic of parseTopics(memory.topics) ?? []) {
25276
- const name = normalizeEntityName(topic.name);
25277
- if (topic.kind && !kinds.has(name)) kinds.set(name, topic.kind);
25471
+ for (const topic2 of parseTopics(memory.topics) ?? []) {
25472
+ const name = normalizeEntityName(topic2.name);
25473
+ if (topic2.kind && !kinds.has(name)) kinds.set(name, topic2.kind);
25278
25474
  }
25279
25475
  }
25280
25476
  }
25281
- return (topic) => {
25282
- const kind = topic.kind ?? kinds.get(normalizeEntityName(topic.name));
25283
- return kind ? { ...topic, kind } : topic;
25477
+ return (topic2) => {
25478
+ const kind = topic2.kind ?? kinds.get(normalizeEntityName(topic2.name));
25479
+ return kind ? { ...topic2, kind } : topic2;
25284
25480
  };
25285
25481
  };
25286
25482
  const withKnownKinds = async (topics) => topics.map(await knownKinds(topics));
25287
- const readEveryState = async (ids) => {
25483
+ const readEveryState = async (ids, signal) => {
25288
25484
  const records = /* @__PURE__ */ new Map();
25289
25485
  const unreadable = /* @__PURE__ */ new Set();
25290
25486
  for (const memory_ids of chunks2(ids, MAX_QUERY_IDS)) {
@@ -25297,6 +25493,7 @@ function createRemoteMemoryStore(options) {
25297
25493
  include_quarantined: true,
25298
25494
  include_superseded: true,
25299
25495
  limit: 200,
25496
+ ...signal && { signal },
25300
25497
  ...cursor2 && { cursor: cursor2 }
25301
25498
  });
25302
25499
  warnFailed(page.failed);
@@ -25307,6 +25504,111 @@ function createRemoteMemoryStore(options) {
25307
25504
  }
25308
25505
  return { records, unreadable };
25309
25506
  };
25507
+ const extractTopicsUnlocked = async (memoryIds, extractOptions, leaseToken, signal) => {
25508
+ const start = Date.now();
25509
+ const extractedAt = extractOptions.now ?? start;
25510
+ const skippedIds = [];
25511
+ const skippedReasons = /* @__PURE__ */ new Map();
25512
+ const skip = (id, reason) => {
25513
+ if (skippedReasons.has(id)) return;
25514
+ skippedIds.push(id);
25515
+ skippedReasons.set(id, reason);
25516
+ };
25517
+ const ids = [...new Set(memoryIds)];
25518
+ const { records, unreadable } = await readEveryState(ids, signal);
25519
+ signal?.throwIfAborted();
25520
+ const inputs = [];
25521
+ for (const id of ids) {
25522
+ const memory = records.get(id)?.memory;
25523
+ if (unreadable.has(id)) skip(id, "unreadable");
25524
+ else if (!memory) skip(id, "not-found");
25525
+ else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
25526
+ else inputs.push({ id, content: memory.content });
25527
+ }
25528
+ const batches = [];
25529
+ const llmFailures = /* @__PURE__ */ new Map();
25530
+ const entitiesByMemory = await extractEntitiesForMemories(
25531
+ inputs,
25532
+ {
25533
+ ...extractOptions,
25534
+ onBatch: (batch) => {
25535
+ batches.push(batch);
25536
+ if (batch.failure) {
25537
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
25538
+ }
25539
+ notifyMemoryCallback(extractOptions.onBatch, batch);
25540
+ }
25541
+ },
25542
+ signal
25543
+ );
25544
+ signal?.throwIfAborted();
25545
+ const answered = /* @__PURE__ */ new Map();
25546
+ for (const input of inputs) {
25547
+ const entities = entitiesByMemory.get(input.id);
25548
+ if (entities === void 0) skip(input.id, "llm-unanswered");
25549
+ else
25550
+ answered.set(input.id, { content: input.content, topics: topicInputs(entities, "auto") });
25551
+ }
25552
+ const stampedIds = [];
25553
+ const fail = (id, reason) => {
25554
+ skip(id, reason);
25555
+ entitiesByMemory.delete(id);
25556
+ };
25557
+ try {
25558
+ const kinded = await knownKinds(
25559
+ [...answered.values()].flatMap((a) => a.topics),
25560
+ signal
25561
+ );
25562
+ signal?.throwIfAborted();
25563
+ const topics = new Map(
25564
+ [...answered].map(([id, a]) => [id, mergeTopics([], a.topics.map(kinded))])
25565
+ );
25566
+ const unchanged = (memory) => sameTopics(parseTopics(memory.topics), topics.get(memory.memory_id));
25567
+ const { saved, failed } = await mutateMany(
25568
+ [...answered.keys()],
25569
+ (before) => sameTopics(parseTopics(before.topics) ?? [], topics.get(before.memory_id)) ? "metadata" : "edit",
25570
+ (memory) => {
25571
+ if (memory.topics_user_managed || memory.content !== answered.get(memory.memory_id).content)
25572
+ return null;
25573
+ return {
25574
+ ...memory,
25575
+ ...!unchanged(memory) && {
25576
+ topics: serializeTopics(topics.get(memory.memory_id)),
25577
+ topics_updated_at: Date.now()
25578
+ },
25579
+ topics_extracted_at: Math.max(extractedAt, memory.updated_at),
25580
+ topics_extracted_version: TOPICS_EXTRACTION_VERSION
25581
+ };
25582
+ },
25583
+ leaseToken,
25584
+ signal
25585
+ );
25586
+ for (const id of answered.keys()) {
25587
+ if (saved.has(id)) stampedIds.push(id);
25588
+ else fail(id, failed.has(id) ? "link-failed" : "link-declined");
25589
+ }
25590
+ if (failed.size)
25591
+ getLogger().warn(
25592
+ "[memory/remote-store] Failed to write extracted topics:",
25593
+ failed.values().next().value
25594
+ );
25595
+ } catch (error) {
25596
+ signal?.throwIfAborted();
25597
+ if (isRemoteWorkLeaseExpired(error)) throw error;
25598
+ getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
25599
+ for (const id of answered.keys()) fail(id, "link-failed");
25600
+ }
25601
+ return {
25602
+ entitiesByMemory,
25603
+ stampedIds,
25604
+ skippedIds,
25605
+ skippedReasons,
25606
+ batchCount: batches.length,
25607
+ durationMs: Date.now() - start,
25608
+ batches,
25609
+ llmFailures
25610
+ };
25611
+ };
25310
25612
  const topicMaintenance = {
25311
25613
  getTopicBacklog: async (backlogOptions = {}) => {
25312
25614
  const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
@@ -25324,97 +25626,12 @@ function createRemoteMemoryStore(options) {
25324
25626
  topicsBackfill: []
25325
25627
  };
25326
25628
  },
25327
- extractTopics: async (memoryIds, extractOptions) => {
25328
- const start = Date.now();
25329
- const extractedAt = extractOptions.now ?? start;
25330
- const skippedIds = [];
25331
- const skippedReasons = /* @__PURE__ */ new Map();
25332
- const skip = (id, reason) => {
25333
- if (skippedReasons.has(id)) return;
25334
- skippedIds.push(id);
25335
- skippedReasons.set(id, reason);
25336
- };
25337
- const ids = [...new Set(memoryIds)];
25338
- const { records, unreadable } = await readEveryState(ids);
25339
- const inputs = [];
25340
- for (const id of ids) {
25341
- const memory = records.get(id)?.memory;
25342
- if (unreadable.has(id)) skip(id, "unreadable");
25343
- else if (!memory) skip(id, "not-found");
25344
- else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
25345
- else inputs.push({ id, content: memory.content });
25346
- }
25347
- const batches = [];
25348
- const llmFailures = /* @__PURE__ */ new Map();
25349
- const entitiesByMemory = await extractEntitiesForMemories(inputs, {
25350
- ...extractOptions,
25351
- onBatch: (batch) => {
25352
- batches.push(batch);
25353
- if (batch.failure) {
25354
- for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
25355
- }
25356
- notifyMemoryCallback(extractOptions.onBatch, batch);
25357
- }
25358
- });
25359
- const answered = /* @__PURE__ */ new Map();
25360
- for (const input of inputs) {
25361
- const entities = entitiesByMemory.get(input.id);
25362
- if (entities === void 0) skip(input.id, "llm-unanswered");
25363
- else
25364
- answered.set(input.id, { content: input.content, topics: topicInputs(entities, "auto") });
25365
- }
25366
- const stampedIds = [];
25367
- const fail = (id, reason) => {
25368
- skip(id, reason);
25369
- entitiesByMemory.delete(id);
25370
- };
25371
- try {
25372
- const kinded = await knownKinds([...answered.values()].flatMap((a) => a.topics));
25373
- const topics = new Map(
25374
- [...answered].map(([id, a]) => [id, mergeTopics([], a.topics.map(kinded))])
25375
- );
25376
- const unchanged = (memory) => sameTopics(parseTopics(memory.topics), topics.get(memory.memory_id));
25377
- const { saved, failed } = await mutateMany(
25378
- [...answered.keys()],
25379
- (before) => sameTopics(parseTopics(before.topics) ?? [], topics.get(before.memory_id)) ? "metadata" : "edit",
25380
- (memory) => {
25381
- if (memory.topics_user_managed || memory.content !== answered.get(memory.memory_id).content)
25382
- return null;
25383
- return {
25384
- ...memory,
25385
- ...!unchanged(memory) && {
25386
- topics: serializeTopics(topics.get(memory.memory_id)),
25387
- topics_updated_at: Date.now()
25388
- },
25389
- topics_extracted_at: Math.max(extractedAt, memory.updated_at),
25390
- topics_extracted_version: TOPICS_EXTRACTION_VERSION
25391
- };
25392
- }
25393
- );
25394
- for (const id of answered.keys()) {
25395
- if (saved.has(id)) stampedIds.push(id);
25396
- else fail(id, failed.has(id) ? "link-failed" : "link-declined");
25397
- }
25398
- if (failed.size)
25399
- getLogger().warn(
25400
- "[memory/remote-store] Failed to write extracted topics:",
25401
- failed.values().next().value
25402
- );
25403
- } catch (error) {
25404
- getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
25405
- for (const id of answered.keys()) fail(id, "link-failed");
25406
- }
25407
- return {
25408
- entitiesByMemory,
25409
- stampedIds,
25410
- skippedIds,
25411
- skippedReasons,
25412
- batchCount: batches.length,
25413
- durationMs: Date.now() - start,
25414
- batches,
25415
- llmFailures
25416
- };
25417
- },
25629
+ extractTopics: async (ids, extractOptions) => await withRemoteWorkLease(
25630
+ persistence,
25631
+ "topics",
25632
+ String(TOPICS_EXTRACTION_VERSION),
25633
+ (token, signal) => extractTopicsUnlocked(ids, extractOptions, token, signal)
25634
+ ) ?? { ...await extractTopicsUnlocked([], extractOptions), deferred: true },
25418
25635
  stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
25419
25636
  const ids = [...new Set(memoryIds)];
25420
25637
  const { saved, failed } = await mutateMany(
@@ -25511,14 +25728,29 @@ function createRemoteMemoryStore(options) {
25511
25728
  let ready = false;
25512
25729
  let cursor;
25513
25730
  let pending = /* @__PURE__ */ new Set();
25731
+ let topicFingerprint;
25514
25732
  let polling = false;
25515
25733
  const poll = () => {
25516
25734
  if (polling) return;
25517
25735
  polling = true;
25518
25736
  const current = generation;
25519
- changesSince(cursor, pending).then(({ records, resume, failed }) => {
25737
+ changesSince(cursor, pending).then(async ({ records, resume, failed }) => {
25738
+ let topicState;
25739
+ if ([...subscribers].some((s) => s.options.topics))
25740
+ try {
25741
+ topicState = JSON.stringify(
25742
+ (await persistence.topics()).map((t) => [t.name, t.version])
25743
+ );
25744
+ } catch (error) {
25745
+ getLogger().warn("[memory/remote-store] Topic subscription poll failed:", error);
25746
+ }
25520
25747
  if (current !== generation) return;
25521
25748
  const changed = /* @__PURE__ */ new Set();
25749
+ if (topicState !== void 0 && topicFingerprint !== void 0 && topicState !== topicFingerprint) {
25750
+ for (const subscriber of subscribers)
25751
+ if (subscriber.options.topics) changed.add(subscriber);
25752
+ }
25753
+ if (topicState !== void 0) topicFingerprint = topicState;
25522
25754
  for (const record2 of records) {
25523
25755
  const id = record2.memory.memory_id;
25524
25756
  const previous = seen.get(id);
@@ -25542,11 +25774,11 @@ function createRemoteMemoryStore(options) {
25542
25774
  };
25543
25775
  const unrepairable = /* @__PURE__ */ new Set();
25544
25776
  let repairCursor;
25545
- const repairEmbeddings = async ({
25777
+ const repairEmbeddingsUnlocked = async ({
25546
25778
  maxRows = REPAIR_ROWS,
25547
25779
  maxScan = REPAIR_SCAN,
25548
25780
  cursor: start = repairCursor
25549
- } = {}) => {
25781
+ } = {}, leaseToken, signal) => {
25550
25782
  const scannedRows = [];
25551
25783
  let cursor2 = start;
25552
25784
  do {
@@ -25555,6 +25787,7 @@ function createRemoteMemoryStore(options) {
25555
25787
  include_archived: true,
25556
25788
  include_quarantined: true,
25557
25789
  include_superseded: true,
25790
+ ...signal && { signal },
25558
25791
  limit: Math.min(200, Math.max(1, maxScan - scannedRows.length)),
25559
25792
  ...cursor2 && { cursor: cursor2 }
25560
25793
  });
@@ -25572,6 +25805,7 @@ function createRemoteMemoryStore(options) {
25572
25805
  skippedConflict: 0
25573
25806
  };
25574
25807
  const finish = () => {
25808
+ signal?.throwIfAborted();
25575
25809
  const settled = allStale.every(
25576
25810
  ({ memory }) => repaired.has(memory.memory_id) || unrepairable.has(memory.memory_id)
25577
25811
  );
@@ -25580,13 +25814,15 @@ function createRemoteMemoryStore(options) {
25580
25814
  };
25581
25815
  const stale = allStale.slice(0, maxRows).map(({ memory }) => memory.memory_id);
25582
25816
  if (stale.length === 0) return finish();
25583
- const { records, unreadable } = await readEveryState(stale);
25817
+ const { records, unreadable } = await readEveryState(stale, signal);
25818
+ signal?.throwIfAborted();
25584
25819
  result.failed += unreadable.size;
25585
25820
  for (const id of stale)
25586
25821
  if (unreadable.has(id) || !records.get(id)?.memory.content.trim()) unrepairable.add(id);
25587
25822
  const targets = stale.filter((id) => !unrepairable.has(id)).map((id) => records.get(id));
25588
25823
  if (targets.length === 0) return finish();
25589
- const vectors = await embedForRepair(targets);
25824
+ const vectors = await embedForRepair(targets, signal);
25825
+ signal?.throwIfAborted();
25590
25826
  if (!vectors) {
25591
25827
  result.failed += targets.length;
25592
25828
  return finish();
@@ -25598,15 +25834,28 @@ function createRemoteMemoryStore(options) {
25598
25834
  continue;
25599
25835
  }
25600
25836
  await serialized(record2.memory.memory_id, async () => {
25837
+ signal?.throwIfAborted();
25601
25838
  try {
25602
- const saved = await persistence.put(
25603
- { ...record2.memory, embedding: storedVector(vector), embedding_model: model },
25604
- record2
25839
+ const [saved] = await persistence.putMany(
25840
+ [
25841
+ {
25842
+ memory: {
25843
+ ...record2.memory,
25844
+ embedding: storedVector(vector),
25845
+ embedding_model: model
25846
+ },
25847
+ expectedVersion: record2
25848
+ }
25849
+ ],
25850
+ signal,
25851
+ leaseToken
25605
25852
  );
25606
25853
  notify("embedding", [saved]);
25607
25854
  repaired.add(record2.memory.memory_id);
25608
25855
  result.reembedded++;
25609
25856
  } catch (error) {
25857
+ signal?.throwIfAborted();
25858
+ if (isRemoteWorkLeaseExpired(error)) throw error;
25610
25859
  if (error instanceof RemoteMemoryError && error.code === "version_conflict")
25611
25860
  result.skippedConflict++;
25612
25861
  else result.failed++;
@@ -25615,19 +25864,28 @@ function createRemoteMemoryStore(options) {
25615
25864
  }
25616
25865
  return finish();
25617
25866
  };
25618
- const embedForRepair = async (targets) => {
25867
+ const repairEmbeddings = async (repairOptions = {}) => await withRemoteWorkLease(
25868
+ persistence,
25869
+ "embeddings",
25870
+ model,
25871
+ (token, signal) => repairEmbeddingsUnlocked(repairOptions, token, signal)
25872
+ ) ?? { scanned: 0, reembedded: 0, failed: 0, skippedConflict: 0, deferred: true };
25873
+ const embedForRepair = async (targets, signal) => {
25619
25874
  const texts = targets.map(({ memory }) => memory.content);
25620
25875
  try {
25621
- return await generateEmbeddings(texts, embeddingOptions);
25876
+ return await generateEmbeddings(texts, embeddingOptions, signal);
25622
25877
  } catch (error) {
25878
+ signal?.throwIfAborted();
25623
25879
  getLogger().warn("[memory/remote-store] Failed to embed memories for repair:", error);
25624
25880
  if (!rejectsInput(error)) return null;
25625
25881
  }
25626
25882
  const vectors = [];
25627
25883
  for (const [i, text14] of texts.entries()) {
25884
+ signal?.throwIfAborted();
25628
25885
  try {
25629
- [vectors[i]] = await generateEmbeddings([text14], embeddingOptions);
25886
+ [vectors[i]] = await generateEmbeddings([text14], embeddingOptions, signal);
25630
25887
  } catch (error) {
25888
+ signal?.throwIfAborted();
25631
25889
  if (!rejectsInput(error)) return null;
25632
25890
  unrepairable.add(targets[i].memory.memory_id);
25633
25891
  }
@@ -25635,6 +25893,26 @@ function createRemoteMemoryStore(options) {
25635
25893
  return vectors;
25636
25894
  };
25637
25895
  return {
25896
+ createTopic: async (name) => {
25897
+ const canonical = await createRemoteTopic(persistence, name);
25898
+ if (canonical) notify("edit", []);
25899
+ return canonical;
25900
+ },
25901
+ importTopics: async (topics) => {
25902
+ if (!topics.length) return;
25903
+ const existing = new Set((await persistence.topics()).map((topic2) => topic2.name));
25904
+ const missing = topics.flatMap((topic2) => {
25905
+ const name = normalizeEntityName(topic2.name);
25906
+ if (existing.has(name)) return [];
25907
+ existing.add(name);
25908
+ return [{ ...topic2, name }];
25909
+ });
25910
+ if (!missing.length) return;
25911
+ await persistence.importTopics(missing);
25912
+ for (const subscriber of [...subscribers])
25913
+ if (subscriber.options.topics) safely(subscriber.onChange);
25914
+ },
25915
+ counts: () => persistence.counts(),
25638
25916
  masksEmbeddingInput: embeddingOptions.maskInput !== void 0,
25639
25917
  topicMaintenance,
25640
25918
  repairEmbeddings,
@@ -25651,8 +25929,8 @@ function createRemoteMemoryStore(options) {
25651
25929
  include_quarantined: true,
25652
25930
  include_superseded: true
25653
25931
  })) {
25654
- for (const topic of parseTopics(memory.topics) ?? []) {
25655
- const name = normalizeEntityName(topic.name);
25932
+ for (const topic2 of parseTopics(memory.topics) ?? []) {
25933
+ const name = normalizeEntityName(topic2.name);
25656
25934
  if (!name) continue;
25657
25935
  const entry = topics.get(name) ?? {
25658
25936
  name,
@@ -25660,12 +25938,24 @@ function createRemoteMemoryStore(options) {
25660
25938
  memoryCount: 0,
25661
25939
  members: /* @__PURE__ */ new Set()
25662
25940
  };
25663
- entry.kind ?? (entry.kind = topic.kind ?? null);
25941
+ entry.kind ?? (entry.kind = topic2.kind ?? null);
25664
25942
  entry.members.add(memory.memory_id);
25665
25943
  topics.set(name, entry);
25666
25944
  }
25667
25945
  }
25668
- return [...topics.values()].map(({ members, ...topic }) => ({ ...topic, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
25946
+ for (const topic2 of await persistence.topics()) {
25947
+ if (topic2.is_deleted) continue;
25948
+ const current = topics.get(topic2.name);
25949
+ if (current) current.kind ?? (current.kind = topic2.kind ?? null);
25950
+ else
25951
+ topics.set(topic2.name, {
25952
+ name: topic2.name,
25953
+ kind: topic2.kind ?? null,
25954
+ memoryCount: 0,
25955
+ members: /* @__PURE__ */ new Set()
25956
+ });
25957
+ }
25958
+ return [...topics.values()].map(({ members, ...topic2 }) => ({ ...topic2, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
25669
25959
  },
25670
25960
  get: async (id) => asStored(await live(id)),
25671
25961
  listArchived: async () => (await enumerate({ archived_only: true, order: "archived", include_embeddings: true })).map(
@@ -25810,10 +26100,10 @@ function createRemoteMemoryStore(options) {
25810
26100
  if (!saved) return [];
25811
26101
  const now = /* @__PURE__ */ new Date();
25812
26102
  return mergeTopics([], added).map(
25813
- (topic) => ({
25814
- uniqueId: normalizeEntityName(topic.name),
25815
- canonicalName: normalizeEntityName(topic.name),
25816
- kind: topic.kind ?? null,
26103
+ (topic2) => ({
26104
+ uniqueId: normalizeEntityName(topic2.name),
26105
+ canonicalName: normalizeEntityName(topic2.name),
26106
+ kind: topic2.kind ?? null,
25817
26107
  createdAt: now,
25818
26108
  updatedAt: now
25819
26109
  })