@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.
@@ -4946,11 +4946,17 @@ async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage,
4946
4946
  }
4947
4947
  return response.data.data.map((item) => item.embedding ?? []);
4948
4948
  }
4949
- async function generateEmbeddings(texts, options) {
4949
+ async function generateEmbeddings(texts, options, signal) {
4950
+ signal?.throwIfAborted();
4950
4951
  if (texts.length === 0) return [];
4951
4952
  const total = options.totalTimeoutMs;
4952
- if (total === void 0) return embedMany(texts, options, void 0);
4953
- return withDeadline((signal) => embedMany(texts, options, signal), total, "embedding batch");
4953
+ if (total === void 0) return embedMany(texts, options, signal);
4954
+ return withDeadline(
4955
+ (inner) => embedMany(texts, options, inner),
4956
+ total,
4957
+ "embedding batch",
4958
+ signal
4959
+ );
4954
4960
  }
4955
4961
  async function embedMany(texts, options, outer) {
4956
4962
  const { baseUrl = BASE_URL, model, batchSize, cache } = options;
@@ -10745,13 +10751,13 @@ function normalizeEntityName(name) {
10745
10751
  return name.trim().toLowerCase();
10746
10752
  }
10747
10753
  var TOPIC_SALIENCE_WEIGHTS = { primary: 1, secondary: 0.6, mention: 0.3 };
10748
- function topicWeight(topic) {
10749
- return topic.source === "user" ? 1 : topic.weight ?? 1;
10754
+ function topicWeight(topic2) {
10755
+ return topic2.source === "user" ? 1 : topic2.weight ?? 1;
10750
10756
  }
10751
10757
  function primaryTopic(topics) {
10752
10758
  let best = null;
10753
- for (const topic of topics ?? []) {
10754
- if (best === null || topicWeight(topic) > topicWeight(best)) best = topic;
10759
+ for (const topic2 of topics ?? []) {
10760
+ if (best === null || topicWeight(topic2) > topicWeight(best)) best = topic2;
10755
10761
  }
10756
10762
  return best;
10757
10763
  }
@@ -10889,7 +10895,7 @@ function resolveTopicsWrite(row, linked, inputs, source, mode) {
10889
10895
  }
10890
10896
  function weightsOf(stored2, inputs) {
10891
10897
  const out = /* @__PURE__ */ new Map();
10892
- for (const topic of stored2 ?? []) out.set(normalizeEntityName(topic.name), topic.weight);
10898
+ for (const topic2 of stored2 ?? []) out.set(normalizeEntityName(topic2.name), topic2.weight);
10893
10899
  for (const input of inputs) {
10894
10900
  const weight = parseTopicWeight(input.weight);
10895
10901
  if (weight === void 0) continue;
@@ -14035,6 +14041,20 @@ async function getAllVaultMemoryContentsOp(ctx, options) {
14035
14041
  async function countActiveVaultMemoriesOp(ctx) {
14036
14042
  return ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx)).fetchCount();
14037
14043
  }
14044
+ async function getVaultMemoryCountsOp(ctx) {
14045
+ const [live, archived, quarantined] = await Promise.all([
14046
+ countActiveVaultMemoriesOp(ctx),
14047
+ ctx.vaultMemoryCollection.query(
14048
+ ...baseVaultConditions(ctx, { includeArchived: true }),
14049
+ Q9.where("archived_at", Q9.notEq(null))
14050
+ ).fetchCount(),
14051
+ ctx.vaultMemoryCollection.query(
14052
+ ...baseVaultConditions(ctx, { includeQuarantined: true }),
14053
+ Q9.where("trust_tier", "quarantined")
14054
+ ).fetchCount()
14055
+ ]);
14056
+ return { live, archived, quarantined };
14057
+ }
14038
14058
  async function getActiveVaultMemoryIdsOp(ctx, ids) {
14039
14059
  if (ids.length === 0) return /* @__PURE__ */ new Set();
14040
14060
  const rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), Q9.where("id", Q9.oneOf(ids))).unsafeFetchRaw();
@@ -21190,7 +21210,8 @@ Each memory below is written as "<id>: <text>". Output strict JSON: {"memories":
21190
21210
  - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
21191
21211
  - "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
21192
21212
  No prose.`;
21193
- async function extractEntitiesForMemories(memories, options) {
21213
+ async function extractEntitiesForMemories(memories, options, signal) {
21214
+ signal?.throwIfAborted();
21194
21215
  const out = /* @__PURE__ */ new Map();
21195
21216
  if (memories.length === 0) return out;
21196
21217
  const redactor = resolvePiiRedactor(options.piiRedaction);
@@ -21203,6 +21224,7 @@ When a memory refers to one of these, reuse the EXACT existing name (same spelli
21203
21224
 
21204
21225
  ` : "";
21205
21226
  for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
21227
+ signal?.throwIfAborted();
21206
21228
  const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
21207
21229
  const start = Date.now();
21208
21230
  let attempts = 0;
@@ -21250,6 +21272,7 @@ List each memory's named entities.`,
21250
21272
  ...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
21251
21273
  ...options.backoffMs && { backoffMs: options.backoffMs }
21252
21274
  });
21275
+ signal?.throwIfAborted();
21253
21276
  if (parsed === null) {
21254
21277
  reportBatch(batch.length);
21255
21278
  getLogger().warn(
@@ -21484,6 +21507,7 @@ function createLocalMemoryStore(options) {
21484
21507
  backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
21485
21508
  };
21486
21509
  return {
21510
+ counts: () => getVaultMemoryCountsOp(vaultCtx),
21487
21511
  masksEmbeddingInput: options.embeddingOptions.maskInput !== void 0,
21488
21512
  list: (listOptions) => getAllVaultMemoriesOp(vaultCtx, listOptions),
21489
21513
  get: (id) => getVaultMemoryOp(vaultCtx, id),
@@ -21708,6 +21732,134 @@ async function decryptMemoryField(value, rings) {
21708
21732
  return decryptWith(value, rings);
21709
21733
  }
21710
21734
 
21735
+ // src/lib/memory/store/remoteTopics.ts
21736
+ function topic(value) {
21737
+ const t = value;
21738
+ 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")
21739
+ throw new Error("Invalid nearby topic");
21740
+ return t;
21741
+ }
21742
+ function createRemoteTopicPersistence(request) {
21743
+ return {
21744
+ topics: async () => {
21745
+ const out = [];
21746
+ let cursor;
21747
+ do {
21748
+ const page = await request(
21749
+ "/topics?limit=200" + (cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""),
21750
+ {}
21751
+ );
21752
+ if (!Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string")
21753
+ throw new Error("Invalid nearby topic page");
21754
+ out.push(...page.items.map(topic));
21755
+ cursor = page.next_cursor;
21756
+ } while (cursor);
21757
+ return out;
21758
+ },
21759
+ putTopic: async (value, expectedVersion) => {
21760
+ if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0)
21761
+ throw new Error("Expected topic version must be a nonnegative integer");
21762
+ return topic(
21763
+ await request("/topics", {
21764
+ method: "PUT",
21765
+ body: JSON.stringify({ topic: value, expected_version: expectedVersion })
21766
+ })
21767
+ );
21768
+ },
21769
+ importTopics: async (topics, signal) => {
21770
+ for (let i = 0; i < topics.length; i += 50) {
21771
+ signal?.throwIfAborted();
21772
+ await request(
21773
+ "/topics/import",
21774
+ {
21775
+ method: "POST",
21776
+ body: JSON.stringify({ topics: topics.slice(i, i + 50) })
21777
+ },
21778
+ signal
21779
+ );
21780
+ }
21781
+ signal?.throwIfAborted();
21782
+ }
21783
+ };
21784
+ }
21785
+ async function createRemoteTopic(persistence, name) {
21786
+ const canonical = normalizeEntityName(name);
21787
+ if (!canonical) return null;
21788
+ const existing = (await persistence.topics()).find((t) => t.name === canonical);
21789
+ if (existing && !existing.is_deleted) return canonical;
21790
+ try {
21791
+ await persistence.putTopic(
21792
+ { name: canonical, is_deleted: false, ...existing?.kind && { kind: existing.kind } },
21793
+ existing?.version ?? 0
21794
+ );
21795
+ } catch (error) {
21796
+ if (!(error instanceof RemoteMemoryError) || error.code !== "version_conflict") throw error;
21797
+ const current = (await persistence.topics()).find((t) => t.name === canonical);
21798
+ if (!current || current.is_deleted) throw error;
21799
+ }
21800
+ return canonical;
21801
+ }
21802
+
21803
+ // src/lib/memory/store/remoteWork.ts
21804
+ function createRemoteWorkPersistence(request) {
21805
+ return {
21806
+ claimWork: async (kind, processorVersion, token) => {
21807
+ const result = await request("/work/claim", {
21808
+ method: "POST",
21809
+ body: JSON.stringify({
21810
+ kind,
21811
+ processor_version: processorVersion,
21812
+ ...token && { token }
21813
+ })
21814
+ });
21815
+ if (!result || typeof result !== "object") throw new Error("Invalid nearby work lease");
21816
+ const lease = result.lease;
21817
+ if (lease === void 0) return null;
21818
+ 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)))
21819
+ throw new Error("Invalid nearby work lease");
21820
+ return lease;
21821
+ },
21822
+ releaseWork: async (kind, token) => {
21823
+ await request("/work/release", { method: "POST", body: JSON.stringify({ kind, token }) });
21824
+ }
21825
+ };
21826
+ }
21827
+ async function withRemoteWorkLease(persistence, kind, processorVersion, task) {
21828
+ const lease = await persistence.claimWork(kind, processorVersion);
21829
+ if (!lease) return null;
21830
+ const controller = new AbortController();
21831
+ const leaseLostError = new RemoteMemoryError("Maintenance lease lost", 409, "work_lease_expired");
21832
+ const cancelled = new Promise((_resolve, reject) => {
21833
+ controller.signal.addEventListener("abort", () => reject(leaseLostError), {
21834
+ once: true
21835
+ });
21836
+ });
21837
+ let renewing;
21838
+ const timer = setInterval(() => {
21839
+ if (renewing || controller.signal.aborted) return;
21840
+ renewing = persistence.claimWork(kind, processorVersion, lease.token).then((renewed) => {
21841
+ if (!renewed) controller.abort(leaseLostError);
21842
+ }).catch((error) => getLogger().warn("[memory/work] Lease renewal failed:", error)).finally(() => {
21843
+ renewing = void 0;
21844
+ });
21845
+ }, 3e4);
21846
+ try {
21847
+ const result = await Promise.race([task(lease.token, controller.signal), cancelled]);
21848
+ await renewing;
21849
+ return controller.signal.aborted ? null : result;
21850
+ } catch (error) {
21851
+ if (controller.signal.aborted || isRemoteWorkLeaseExpired(error)) return null;
21852
+ throw error;
21853
+ } finally {
21854
+ clearInterval(timer);
21855
+ await renewing;
21856
+ await persistence.releaseWork(kind, lease.token).catch((error) => getLogger().warn("[memory/work] Lease release failed:", error));
21857
+ }
21858
+ }
21859
+ function isRemoteWorkLeaseExpired(error) {
21860
+ return error instanceof RemoteMemoryError && error.code === "work_lease_expired";
21861
+ }
21862
+
21711
21863
  // src/lib/memory/store/remotePersistence.ts
21712
21864
  var RemoteMemoryError = class extends Error {
21713
21865
  constructor(message, status, code, retryAfterMs) {
@@ -22087,6 +22239,33 @@ async function createRemoteMemoryPersistence(options) {
22087
22239
  };
22088
22240
  };
22089
22241
  return {
22242
+ ...createRemoteTopicPersistence(request),
22243
+ ...createRemoteWorkPersistence(request),
22244
+ counts: async () => {
22245
+ const result = await request("/counts", {});
22246
+ if (!record(result) || ["live", "archived", "quarantined"].some(
22247
+ (key) => !Number.isSafeInteger(result[key]) || result[key] < 0
22248
+ ))
22249
+ throw new Error("Invalid nearby memory counts");
22250
+ return {
22251
+ live: result.live,
22252
+ archived: result.archived,
22253
+ quarantined: result.quarantined
22254
+ };
22255
+ },
22256
+ temporal: async (window2, limit = 100) => {
22257
+ if (!Number.isFinite(window2.start) || !Number.isFinite(window2.end) || window2.end <= window2.start || !Number.isInteger(limit) || limit < 1 || limit > 100)
22258
+ throw new Error("Temporal window must have end > start and limit must be 1\u2013100");
22259
+ const result = await request("/temporal", {
22260
+ method: "POST",
22261
+ body: JSON.stringify({ ...window2, limit })
22262
+ });
22263
+ if (!record(result) || !Array.isArray(result.items) || result.items.some(
22264
+ (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
22265
+ ))
22266
+ throw new Error("Invalid nearby temporal candidates");
22267
+ return result.items.map((item) => item.memory_id);
22268
+ },
22090
22269
  get: async (id, signal) => {
22091
22270
  try {
22092
22271
  return await decode(await request(memoryPath(id), {}, signal));
@@ -22135,7 +22314,7 @@ async function createRemoteMemoryPersistence(options) {
22135
22314
  write
22136
22315
  );
22137
22316
  },
22138
- putMany: async (writes, signal) => {
22317
+ putMany: async (writes, signal, leaseToken) => {
22139
22318
  if (writes.length < 1 || writes.length > 50 || new Set(writes.map((w) => w.memory.memory_id)).size !== writes.length)
22140
22319
  throw new Error("Batch must contain 1\u201350 distinct memory ids");
22141
22320
  const prepared = await Promise.all(
@@ -22145,7 +22324,11 @@ async function createRemoteMemoryPersistence(options) {
22145
22324
  "/memories/batch",
22146
22325
  {
22147
22326
  method: "POST",
22148
- body: JSON.stringify({ key_id: options.keyId, writes: prepared.map((w) => w.body) })
22327
+ body: JSON.stringify({
22328
+ key_id: options.keyId,
22329
+ writes: prepared.map((w) => w.body),
22330
+ ...leaseToken && { lease_token: leaseToken }
22331
+ })
22149
22332
  },
22150
22333
  signal
22151
22334
  );
@@ -22904,6 +23087,12 @@ async function migrateMemoriesToRemote(options) {
22904
23087
  let claimed = account.key_id === canonical.keyId;
22905
23088
  if (!active) await createDecaySweeper({ vaultCtx: options.vaultCtx }).runSweep();
22906
23089
  await backfillTopics(options.vaultCtx);
23090
+ const localTopics = options.topics ?? (options.vaultCtx.singleTenant === true ? (await options.vaultCtx.database.get("entity").query().unsafeFetchRaw()).map((t) => ({
23091
+ name: normalizeEntityName(typeof t.canonical_name === "string" ? t.canonical_name : ""),
23092
+ ...typeof t.kind === "string" && { kind: t.kind }
23093
+ })).filter((t) => t.name.length > 0) : []);
23094
+ if (localTopics.length)
23095
+ await createRemoteTopicPersistence(request).importTopics(localTopics, signal);
22907
23096
  const skipped = /* @__PURE__ */ new Map();
22908
23097
  const reembedded = /* @__PURE__ */ new Set();
22909
23098
  const unembedded = /* @__PURE__ */ new Set();
@@ -23030,7 +23219,6 @@ var MAX_QUERY_IDS = 1e3;
23030
23219
  var MAX_QUERY_TOPICS = 100;
23031
23220
  var MAX_QUERY_FILTER = 20;
23032
23221
  var GRAPH_LANE_ROWS = 256;
23033
- var TEMPORAL_LANE_ROWS = 256;
23034
23222
  var REPAIR_ROWS = 50;
23035
23223
  var REPAIR_SCAN = 2e3;
23036
23224
  var DECAY_TRANSITIONS_PER_SWEEP = 200;
@@ -23101,21 +23289,21 @@ function subscriptionKey({ memory, version }, options) {
23101
23289
  ]);
23102
23290
  }
23103
23291
  function topicInputs(topics, source) {
23104
- return topics.flatMap((topic) => {
23105
- const name = (typeof topic === "string" ? topic : topic.name).trim();
23106
- const kind = typeof topic === "string" ? void 0 : topic.kind;
23107
- const weight = typeof topic === "string" || source === "user" ? void 0 : parseTopicWeight(topic.weight);
23292
+ return topics.flatMap((topic2) => {
23293
+ const name = (typeof topic2 === "string" ? topic2 : topic2.name).trim();
23294
+ const kind = typeof topic2 === "string" ? void 0 : topic2.kind;
23295
+ const weight = typeof topic2 === "string" || source === "user" ? void 0 : parseTopicWeight(topic2.weight);
23108
23296
  return name ? [{ name, ...kind && { kind }, source, ...weight && { weight } }] : [];
23109
23297
  });
23110
23298
  }
23111
23299
  function mergeTopics(existing, added) {
23112
23300
  const byName = /* @__PURE__ */ new Map();
23113
- for (const topic of [...existing, ...added]) {
23114
- const key = normalizeEntityName(topic.name);
23301
+ for (const topic2 of [...existing, ...added]) {
23302
+ const key = normalizeEntityName(topic2.name);
23115
23303
  const kept = byName.get(key);
23116
- if (!kept) byName.set(key, topic);
23117
- else if (kept.weight !== void 0 && topic.weight !== void 0 && topic.weight > kept.weight)
23118
- byName.set(key, { ...kept, weight: topic.weight });
23304
+ if (!kept) byName.set(key, topic2);
23305
+ else if (kept.weight !== void 0 && topic2.weight !== void 0 && topic2.weight > kept.weight)
23306
+ byName.set(key, { ...kept, weight: topic2.weight });
23119
23307
  }
23120
23308
  return [...byName.values()];
23121
23309
  }
@@ -23126,7 +23314,7 @@ function sameTopics(stored2, next) {
23126
23314
  return next.every((t) => keys.has(key(t)));
23127
23315
  }
23128
23316
  function canonicalTopics(row) {
23129
- return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
23317
+ return new Set((parseTopics(row.topics) ?? []).map((topic2) => normalizeEntityName(topic2.name)));
23130
23318
  }
23131
23319
  function createRemoteMemoryStore(options) {
23132
23320
  const { persistence, embeddingOptions } = options;
@@ -23201,20 +23389,7 @@ function createRemoteMemoryStore(options) {
23201
23389
  const eventTimeRanking = async (query, now) => {
23202
23390
  const window2 = parseQueryTimeWindow(query, now);
23203
23391
  if (!window2) return [];
23204
- const rows = await pages(
23205
- metadata2,
23206
- { event_time_overlaps: { start: window2.start, end: window2.end } },
23207
- TEMPORAL_LANE_ROWS
23208
- );
23209
- return rows.map(({ memory }) => ({
23210
- memoryId: memory.memory_id,
23211
- score: scoreEventTimeOverlap(
23212
- memory.event_time_start ?? null,
23213
- memory.event_time_end ?? null,
23214
- memory.event_time_kind ?? null,
23215
- window2
23216
- )
23217
- })).filter((row) => row.score > 0).sort((a, b) => b.score - a.score).map((row) => row.memoryId);
23392
+ return persistence.temporal({ start: window2.start, end: window2.end });
23218
23393
  };
23219
23394
  const enumerateIds = async (read, ids, filters) => (await Promise.all(
23220
23395
  chunks2([...new Set(ids)], MAX_QUERY_IDS).map(
@@ -23265,22 +23440,28 @@ function createRemoteMemoryStore(options) {
23265
23440
  writeOptions?.signal?.throwIfAborted();
23266
23441
  const next = edit(structuredClone(record2.memory));
23267
23442
  if (!next) return null;
23268
- const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
23443
+ const saved = writeOptions?.leaseToken ? (await persistence.putMany(
23444
+ [{ memory: next, expectedVersion: record2 }],
23445
+ writeOptions.signal,
23446
+ writeOptions.leaseToken
23447
+ ))[0] : writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
23269
23448
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
23270
23449
  return saved;
23271
23450
  };
23272
23451
  const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
23273
23452
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
23274
- const mutateMany = async (ids, change, edit) => {
23453
+ const mutateMany = async (ids, change, edit, leaseToken, signal) => {
23275
23454
  const saved = /* @__PURE__ */ new Map();
23276
23455
  const failed = /* @__PURE__ */ new Map();
23277
23456
  for (const slice of chunks2([...new Set(ids)], MAX_BATCH)) {
23278
23457
  await serializedAll(slice, async () => {
23458
+ signal?.throwIfAborted();
23279
23459
  const writes = [];
23280
23460
  let read;
23281
23461
  try {
23282
- read = await readEveryState(slice);
23462
+ read = await readEveryState(slice, signal);
23283
23463
  } catch (error) {
23464
+ signal?.throwIfAborted();
23284
23465
  for (const id of slice) failed.set(id, error);
23285
23466
  return;
23286
23467
  }
@@ -23297,7 +23478,9 @@ function createRemoteMemoryStore(options) {
23297
23478
  if (writes.length === 0) return;
23298
23479
  try {
23299
23480
  const written = await persistence.putMany(
23300
- writes.map(({ before, memory }) => ({ memory, expectedVersion: before }))
23481
+ writes.map(({ before, memory }) => ({ memory, expectedVersion: before })),
23482
+ signal,
23483
+ leaseToken
23301
23484
  );
23302
23485
  const byChange = /* @__PURE__ */ new Map();
23303
23486
  written.forEach((record2, i) => {
@@ -23307,7 +23490,9 @@ function createRemoteMemoryStore(options) {
23307
23490
  });
23308
23491
  for (const [kind, changed] of byChange) notify(kind, changed);
23309
23492
  } catch (error) {
23310
- const perRow = error instanceof RemoteMemoryError && (error.code === "version_conflict" || error.status === 429);
23493
+ signal?.throwIfAborted();
23494
+ if (isRemoteWorkLeaseExpired(error)) throw error;
23495
+ const perRow = error instanceof RemoteMemoryError && (error.code === "version_conflict" || error.status === 429 && !leaseToken);
23311
23496
  for (const { before } of writes) {
23312
23497
  const id = before.memory.memory_id;
23313
23498
  if (!perRow) {
@@ -23315,9 +23500,16 @@ function createRemoteMemoryStore(options) {
23315
23500
  continue;
23316
23501
  }
23317
23502
  try {
23318
- const record2 = await mutateLocked(id, change, edit);
23503
+ const record2 = await mutateLocked(
23504
+ id,
23505
+ change,
23506
+ edit,
23507
+ leaseToken || signal ? { leaseToken, signal } : void 0
23508
+ );
23319
23509
  if (record2) saved.set(id, record2);
23320
23510
  } catch (rowError) {
23511
+ signal?.throwIfAborted();
23512
+ if (isRemoteWorkLeaseExpired(rowError)) throw rowError;
23321
23513
  failed.set(id, rowError);
23322
23514
  }
23323
23515
  }
@@ -23381,7 +23573,7 @@ function createRemoteMemoryStore(options) {
23381
23573
  return records.map((record2) => stored(record2.memory));
23382
23574
  };
23383
23575
  const asStored = (record2) => record2 ? stored(record2.memory) : null;
23384
- const knownKinds = async (topics) => {
23576
+ const knownKinds = async (topics, signal) => {
23385
23577
  const unkinded = [
23386
23578
  ...new Set(topics.filter((t) => !t.kind).map((t) => normalizeEntityName(t.name)))
23387
23579
  ];
@@ -23391,21 +23583,22 @@ function createRemoteMemoryStore(options) {
23391
23583
  topics: names,
23392
23584
  include_archived: true,
23393
23585
  include_quarantined: true,
23394
- include_superseded: true
23586
+ include_superseded: true,
23587
+ ...signal && { signal }
23395
23588
  })) {
23396
- for (const topic of parseTopics(memory.topics) ?? []) {
23397
- const name = normalizeEntityName(topic.name);
23398
- if (topic.kind && !kinds.has(name)) kinds.set(name, topic.kind);
23589
+ for (const topic2 of parseTopics(memory.topics) ?? []) {
23590
+ const name = normalizeEntityName(topic2.name);
23591
+ if (topic2.kind && !kinds.has(name)) kinds.set(name, topic2.kind);
23399
23592
  }
23400
23593
  }
23401
23594
  }
23402
- return (topic) => {
23403
- const kind = topic.kind ?? kinds.get(normalizeEntityName(topic.name));
23404
- return kind ? { ...topic, kind } : topic;
23595
+ return (topic2) => {
23596
+ const kind = topic2.kind ?? kinds.get(normalizeEntityName(topic2.name));
23597
+ return kind ? { ...topic2, kind } : topic2;
23405
23598
  };
23406
23599
  };
23407
23600
  const withKnownKinds = async (topics) => topics.map(await knownKinds(topics));
23408
- const readEveryState = async (ids) => {
23601
+ const readEveryState = async (ids, signal) => {
23409
23602
  const records = /* @__PURE__ */ new Map();
23410
23603
  const unreadable = /* @__PURE__ */ new Set();
23411
23604
  for (const memory_ids of chunks2(ids, MAX_QUERY_IDS)) {
@@ -23418,6 +23611,7 @@ function createRemoteMemoryStore(options) {
23418
23611
  include_quarantined: true,
23419
23612
  include_superseded: true,
23420
23613
  limit: 200,
23614
+ ...signal && { signal },
23421
23615
  ...cursor2 && { cursor: cursor2 }
23422
23616
  });
23423
23617
  warnFailed(page.failed);
@@ -23428,6 +23622,111 @@ function createRemoteMemoryStore(options) {
23428
23622
  }
23429
23623
  return { records, unreadable };
23430
23624
  };
23625
+ const extractTopicsUnlocked = async (memoryIds, extractOptions, leaseToken, signal) => {
23626
+ const start = Date.now();
23627
+ const extractedAt = extractOptions.now ?? start;
23628
+ const skippedIds = [];
23629
+ const skippedReasons = /* @__PURE__ */ new Map();
23630
+ const skip = (id, reason) => {
23631
+ if (skippedReasons.has(id)) return;
23632
+ skippedIds.push(id);
23633
+ skippedReasons.set(id, reason);
23634
+ };
23635
+ const ids = [...new Set(memoryIds)];
23636
+ const { records, unreadable } = await readEveryState(ids, signal);
23637
+ signal?.throwIfAborted();
23638
+ const inputs = [];
23639
+ for (const id of ids) {
23640
+ const memory = records.get(id)?.memory;
23641
+ if (unreadable.has(id)) skip(id, "unreadable");
23642
+ else if (!memory) skip(id, "not-found");
23643
+ else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
23644
+ else inputs.push({ id, content: memory.content });
23645
+ }
23646
+ const batches = [];
23647
+ const llmFailures = /* @__PURE__ */ new Map();
23648
+ const entitiesByMemory = await extractEntitiesForMemories(
23649
+ inputs,
23650
+ {
23651
+ ...extractOptions,
23652
+ onBatch: (batch) => {
23653
+ batches.push(batch);
23654
+ if (batch.failure) {
23655
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
23656
+ }
23657
+ notifyMemoryCallback(extractOptions.onBatch, batch);
23658
+ }
23659
+ },
23660
+ signal
23661
+ );
23662
+ signal?.throwIfAborted();
23663
+ const answered = /* @__PURE__ */ new Map();
23664
+ for (const input of inputs) {
23665
+ const entities = entitiesByMemory.get(input.id);
23666
+ if (entities === void 0) skip(input.id, "llm-unanswered");
23667
+ else
23668
+ answered.set(input.id, { content: input.content, topics: topicInputs(entities, "auto") });
23669
+ }
23670
+ const stampedIds = [];
23671
+ const fail = (id, reason) => {
23672
+ skip(id, reason);
23673
+ entitiesByMemory.delete(id);
23674
+ };
23675
+ try {
23676
+ const kinded = await knownKinds(
23677
+ [...answered.values()].flatMap((a) => a.topics),
23678
+ signal
23679
+ );
23680
+ signal?.throwIfAborted();
23681
+ const topics = new Map(
23682
+ [...answered].map(([id, a]) => [id, mergeTopics([], a.topics.map(kinded))])
23683
+ );
23684
+ const unchanged = (memory) => sameTopics(parseTopics(memory.topics), topics.get(memory.memory_id));
23685
+ const { saved, failed } = await mutateMany(
23686
+ [...answered.keys()],
23687
+ (before) => sameTopics(parseTopics(before.topics) ?? [], topics.get(before.memory_id)) ? "metadata" : "edit",
23688
+ (memory) => {
23689
+ if (memory.topics_user_managed || memory.content !== answered.get(memory.memory_id).content)
23690
+ return null;
23691
+ return {
23692
+ ...memory,
23693
+ ...!unchanged(memory) && {
23694
+ topics: serializeTopics(topics.get(memory.memory_id)),
23695
+ topics_updated_at: Date.now()
23696
+ },
23697
+ topics_extracted_at: Math.max(extractedAt, memory.updated_at),
23698
+ topics_extracted_version: TOPICS_EXTRACTION_VERSION
23699
+ };
23700
+ },
23701
+ leaseToken,
23702
+ signal
23703
+ );
23704
+ for (const id of answered.keys()) {
23705
+ if (saved.has(id)) stampedIds.push(id);
23706
+ else fail(id, failed.has(id) ? "link-failed" : "link-declined");
23707
+ }
23708
+ if (failed.size)
23709
+ getLogger().warn(
23710
+ "[memory/remote-store] Failed to write extracted topics:",
23711
+ failed.values().next().value
23712
+ );
23713
+ } catch (error) {
23714
+ signal?.throwIfAborted();
23715
+ if (isRemoteWorkLeaseExpired(error)) throw error;
23716
+ getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
23717
+ for (const id of answered.keys()) fail(id, "link-failed");
23718
+ }
23719
+ return {
23720
+ entitiesByMemory,
23721
+ stampedIds,
23722
+ skippedIds,
23723
+ skippedReasons,
23724
+ batchCount: batches.length,
23725
+ durationMs: Date.now() - start,
23726
+ batches,
23727
+ llmFailures
23728
+ };
23729
+ };
23431
23730
  const topicMaintenance = {
23432
23731
  getTopicBacklog: async (backlogOptions = {}) => {
23433
23732
  const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
@@ -23445,97 +23744,12 @@ function createRemoteMemoryStore(options) {
23445
23744
  topicsBackfill: []
23446
23745
  };
23447
23746
  },
23448
- extractTopics: async (memoryIds, extractOptions) => {
23449
- const start = Date.now();
23450
- const extractedAt = extractOptions.now ?? start;
23451
- const skippedIds = [];
23452
- const skippedReasons = /* @__PURE__ */ new Map();
23453
- const skip = (id, reason) => {
23454
- if (skippedReasons.has(id)) return;
23455
- skippedIds.push(id);
23456
- skippedReasons.set(id, reason);
23457
- };
23458
- const ids = [...new Set(memoryIds)];
23459
- const { records, unreadable } = await readEveryState(ids);
23460
- const inputs = [];
23461
- for (const id of ids) {
23462
- const memory = records.get(id)?.memory;
23463
- if (unreadable.has(id)) skip(id, "unreadable");
23464
- else if (!memory) skip(id, "not-found");
23465
- else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
23466
- else inputs.push({ id, content: memory.content });
23467
- }
23468
- const batches = [];
23469
- const llmFailures = /* @__PURE__ */ new Map();
23470
- const entitiesByMemory = await extractEntitiesForMemories(inputs, {
23471
- ...extractOptions,
23472
- onBatch: (batch) => {
23473
- batches.push(batch);
23474
- if (batch.failure) {
23475
- for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
23476
- }
23477
- notifyMemoryCallback(extractOptions.onBatch, batch);
23478
- }
23479
- });
23480
- const answered = /* @__PURE__ */ new Map();
23481
- for (const input of inputs) {
23482
- const entities = entitiesByMemory.get(input.id);
23483
- if (entities === void 0) skip(input.id, "llm-unanswered");
23484
- else
23485
- answered.set(input.id, { content: input.content, topics: topicInputs(entities, "auto") });
23486
- }
23487
- const stampedIds = [];
23488
- const fail = (id, reason) => {
23489
- skip(id, reason);
23490
- entitiesByMemory.delete(id);
23491
- };
23492
- try {
23493
- const kinded = await knownKinds([...answered.values()].flatMap((a) => a.topics));
23494
- const topics = new Map(
23495
- [...answered].map(([id, a]) => [id, mergeTopics([], a.topics.map(kinded))])
23496
- );
23497
- const unchanged = (memory) => sameTopics(parseTopics(memory.topics), topics.get(memory.memory_id));
23498
- const { saved, failed } = await mutateMany(
23499
- [...answered.keys()],
23500
- (before) => sameTopics(parseTopics(before.topics) ?? [], topics.get(before.memory_id)) ? "metadata" : "edit",
23501
- (memory) => {
23502
- if (memory.topics_user_managed || memory.content !== answered.get(memory.memory_id).content)
23503
- return null;
23504
- return {
23505
- ...memory,
23506
- ...!unchanged(memory) && {
23507
- topics: serializeTopics(topics.get(memory.memory_id)),
23508
- topics_updated_at: Date.now()
23509
- },
23510
- topics_extracted_at: Math.max(extractedAt, memory.updated_at),
23511
- topics_extracted_version: TOPICS_EXTRACTION_VERSION
23512
- };
23513
- }
23514
- );
23515
- for (const id of answered.keys()) {
23516
- if (saved.has(id)) stampedIds.push(id);
23517
- else fail(id, failed.has(id) ? "link-failed" : "link-declined");
23518
- }
23519
- if (failed.size)
23520
- getLogger().warn(
23521
- "[memory/remote-store] Failed to write extracted topics:",
23522
- failed.values().next().value
23523
- );
23524
- } catch (error) {
23525
- getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
23526
- for (const id of answered.keys()) fail(id, "link-failed");
23527
- }
23528
- return {
23529
- entitiesByMemory,
23530
- stampedIds,
23531
- skippedIds,
23532
- skippedReasons,
23533
- batchCount: batches.length,
23534
- durationMs: Date.now() - start,
23535
- batches,
23536
- llmFailures
23537
- };
23538
- },
23747
+ extractTopics: async (ids, extractOptions) => await withRemoteWorkLease(
23748
+ persistence,
23749
+ "topics",
23750
+ String(TOPICS_EXTRACTION_VERSION),
23751
+ (token, signal) => extractTopicsUnlocked(ids, extractOptions, token, signal)
23752
+ ) ?? { ...await extractTopicsUnlocked([], extractOptions), deferred: true },
23539
23753
  stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
23540
23754
  const ids = [...new Set(memoryIds)];
23541
23755
  const { saved, failed } = await mutateMany(
@@ -23632,14 +23846,29 @@ function createRemoteMemoryStore(options) {
23632
23846
  let ready = false;
23633
23847
  let cursor;
23634
23848
  let pending = /* @__PURE__ */ new Set();
23849
+ let topicFingerprint;
23635
23850
  let polling = false;
23636
23851
  const poll = () => {
23637
23852
  if (polling) return;
23638
23853
  polling = true;
23639
23854
  const current = generation;
23640
- changesSince(cursor, pending).then(({ records, resume, failed }) => {
23855
+ changesSince(cursor, pending).then(async ({ records, resume, failed }) => {
23856
+ let topicState;
23857
+ if ([...subscribers].some((s) => s.options.topics))
23858
+ try {
23859
+ topicState = JSON.stringify(
23860
+ (await persistence.topics()).map((t) => [t.name, t.version])
23861
+ );
23862
+ } catch (error) {
23863
+ getLogger().warn("[memory/remote-store] Topic subscription poll failed:", error);
23864
+ }
23641
23865
  if (current !== generation) return;
23642
23866
  const changed = /* @__PURE__ */ new Set();
23867
+ if (topicState !== void 0 && topicFingerprint !== void 0 && topicState !== topicFingerprint) {
23868
+ for (const subscriber of subscribers)
23869
+ if (subscriber.options.topics) changed.add(subscriber);
23870
+ }
23871
+ if (topicState !== void 0) topicFingerprint = topicState;
23643
23872
  for (const record2 of records) {
23644
23873
  const id = record2.memory.memory_id;
23645
23874
  const previous = seen.get(id);
@@ -23663,11 +23892,11 @@ function createRemoteMemoryStore(options) {
23663
23892
  };
23664
23893
  const unrepairable = /* @__PURE__ */ new Set();
23665
23894
  let repairCursor;
23666
- const repairEmbeddings = async ({
23895
+ const repairEmbeddingsUnlocked = async ({
23667
23896
  maxRows = REPAIR_ROWS,
23668
23897
  maxScan = REPAIR_SCAN,
23669
23898
  cursor: start = repairCursor
23670
- } = {}) => {
23899
+ } = {}, leaseToken, signal) => {
23671
23900
  const scannedRows = [];
23672
23901
  let cursor2 = start;
23673
23902
  do {
@@ -23676,6 +23905,7 @@ function createRemoteMemoryStore(options) {
23676
23905
  include_archived: true,
23677
23906
  include_quarantined: true,
23678
23907
  include_superseded: true,
23908
+ ...signal && { signal },
23679
23909
  limit: Math.min(200, Math.max(1, maxScan - scannedRows.length)),
23680
23910
  ...cursor2 && { cursor: cursor2 }
23681
23911
  });
@@ -23693,6 +23923,7 @@ function createRemoteMemoryStore(options) {
23693
23923
  skippedConflict: 0
23694
23924
  };
23695
23925
  const finish = () => {
23926
+ signal?.throwIfAborted();
23696
23927
  const settled = allStale.every(
23697
23928
  ({ memory }) => repaired.has(memory.memory_id) || unrepairable.has(memory.memory_id)
23698
23929
  );
@@ -23701,13 +23932,15 @@ function createRemoteMemoryStore(options) {
23701
23932
  };
23702
23933
  const stale = allStale.slice(0, maxRows).map(({ memory }) => memory.memory_id);
23703
23934
  if (stale.length === 0) return finish();
23704
- const { records, unreadable } = await readEveryState(stale);
23935
+ const { records, unreadable } = await readEveryState(stale, signal);
23936
+ signal?.throwIfAborted();
23705
23937
  result.failed += unreadable.size;
23706
23938
  for (const id of stale)
23707
23939
  if (unreadable.has(id) || !records.get(id)?.memory.content.trim()) unrepairable.add(id);
23708
23940
  const targets = stale.filter((id) => !unrepairable.has(id)).map((id) => records.get(id));
23709
23941
  if (targets.length === 0) return finish();
23710
- const vectors = await embedForRepair(targets);
23942
+ const vectors = await embedForRepair(targets, signal);
23943
+ signal?.throwIfAborted();
23711
23944
  if (!vectors) {
23712
23945
  result.failed += targets.length;
23713
23946
  return finish();
@@ -23719,15 +23952,28 @@ function createRemoteMemoryStore(options) {
23719
23952
  continue;
23720
23953
  }
23721
23954
  await serialized(record2.memory.memory_id, async () => {
23955
+ signal?.throwIfAborted();
23722
23956
  try {
23723
- const saved = await persistence.put(
23724
- { ...record2.memory, embedding: storedVector(vector), embedding_model: model },
23725
- record2
23957
+ const [saved] = await persistence.putMany(
23958
+ [
23959
+ {
23960
+ memory: {
23961
+ ...record2.memory,
23962
+ embedding: storedVector(vector),
23963
+ embedding_model: model
23964
+ },
23965
+ expectedVersion: record2
23966
+ }
23967
+ ],
23968
+ signal,
23969
+ leaseToken
23726
23970
  );
23727
23971
  notify("embedding", [saved]);
23728
23972
  repaired.add(record2.memory.memory_id);
23729
23973
  result.reembedded++;
23730
23974
  } catch (error) {
23975
+ signal?.throwIfAborted();
23976
+ if (isRemoteWorkLeaseExpired(error)) throw error;
23731
23977
  if (error instanceof RemoteMemoryError && error.code === "version_conflict")
23732
23978
  result.skippedConflict++;
23733
23979
  else result.failed++;
@@ -23736,19 +23982,28 @@ function createRemoteMemoryStore(options) {
23736
23982
  }
23737
23983
  return finish();
23738
23984
  };
23739
- const embedForRepair = async (targets) => {
23985
+ const repairEmbeddings = async (repairOptions = {}) => await withRemoteWorkLease(
23986
+ persistence,
23987
+ "embeddings",
23988
+ model,
23989
+ (token, signal) => repairEmbeddingsUnlocked(repairOptions, token, signal)
23990
+ ) ?? { scanned: 0, reembedded: 0, failed: 0, skippedConflict: 0, deferred: true };
23991
+ const embedForRepair = async (targets, signal) => {
23740
23992
  const texts = targets.map(({ memory }) => memory.content);
23741
23993
  try {
23742
- return await generateEmbeddings(texts, embeddingOptions);
23994
+ return await generateEmbeddings(texts, embeddingOptions, signal);
23743
23995
  } catch (error) {
23996
+ signal?.throwIfAborted();
23744
23997
  getLogger().warn("[memory/remote-store] Failed to embed memories for repair:", error);
23745
23998
  if (!rejectsInput(error)) return null;
23746
23999
  }
23747
24000
  const vectors = [];
23748
24001
  for (const [i, text14] of texts.entries()) {
24002
+ signal?.throwIfAborted();
23749
24003
  try {
23750
- [vectors[i]] = await generateEmbeddings([text14], embeddingOptions);
24004
+ [vectors[i]] = await generateEmbeddings([text14], embeddingOptions, signal);
23751
24005
  } catch (error) {
24006
+ signal?.throwIfAborted();
23752
24007
  if (!rejectsInput(error)) return null;
23753
24008
  unrepairable.add(targets[i].memory.memory_id);
23754
24009
  }
@@ -23756,6 +24011,26 @@ function createRemoteMemoryStore(options) {
23756
24011
  return vectors;
23757
24012
  };
23758
24013
  return {
24014
+ createTopic: async (name) => {
24015
+ const canonical = await createRemoteTopic(persistence, name);
24016
+ if (canonical) notify("edit", []);
24017
+ return canonical;
24018
+ },
24019
+ importTopics: async (topics) => {
24020
+ if (!topics.length) return;
24021
+ const existing = new Set((await persistence.topics()).map((topic2) => topic2.name));
24022
+ const missing = topics.flatMap((topic2) => {
24023
+ const name = normalizeEntityName(topic2.name);
24024
+ if (existing.has(name)) return [];
24025
+ existing.add(name);
24026
+ return [{ ...topic2, name }];
24027
+ });
24028
+ if (!missing.length) return;
24029
+ await persistence.importTopics(missing);
24030
+ for (const subscriber of [...subscribers])
24031
+ if (subscriber.options.topics) safely(subscriber.onChange);
24032
+ },
24033
+ counts: () => persistence.counts(),
23759
24034
  masksEmbeddingInput: embeddingOptions.maskInput !== void 0,
23760
24035
  topicMaintenance,
23761
24036
  repairEmbeddings,
@@ -23772,8 +24047,8 @@ function createRemoteMemoryStore(options) {
23772
24047
  include_quarantined: true,
23773
24048
  include_superseded: true
23774
24049
  })) {
23775
- for (const topic of parseTopics(memory.topics) ?? []) {
23776
- const name = normalizeEntityName(topic.name);
24050
+ for (const topic2 of parseTopics(memory.topics) ?? []) {
24051
+ const name = normalizeEntityName(topic2.name);
23777
24052
  if (!name) continue;
23778
24053
  const entry = topics.get(name) ?? {
23779
24054
  name,
@@ -23781,12 +24056,24 @@ function createRemoteMemoryStore(options) {
23781
24056
  memoryCount: 0,
23782
24057
  members: /* @__PURE__ */ new Set()
23783
24058
  };
23784
- entry.kind ?? (entry.kind = topic.kind ?? null);
24059
+ entry.kind ?? (entry.kind = topic2.kind ?? null);
23785
24060
  entry.members.add(memory.memory_id);
23786
24061
  topics.set(name, entry);
23787
24062
  }
23788
24063
  }
23789
- return [...topics.values()].map(({ members, ...topic }) => ({ ...topic, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
24064
+ for (const topic2 of await persistence.topics()) {
24065
+ if (topic2.is_deleted) continue;
24066
+ const current = topics.get(topic2.name);
24067
+ if (current) current.kind ?? (current.kind = topic2.kind ?? null);
24068
+ else
24069
+ topics.set(topic2.name, {
24070
+ name: topic2.name,
24071
+ kind: topic2.kind ?? null,
24072
+ memoryCount: 0,
24073
+ members: /* @__PURE__ */ new Set()
24074
+ });
24075
+ }
24076
+ return [...topics.values()].map(({ members, ...topic2 }) => ({ ...topic2, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
23790
24077
  },
23791
24078
  get: async (id) => asStored(await live(id)),
23792
24079
  listArchived: async () => (await enumerate({ archived_only: true, order: "archived", include_embeddings: true })).map(
@@ -23931,10 +24218,10 @@ function createRemoteMemoryStore(options) {
23931
24218
  if (!saved) return [];
23932
24219
  const now = /* @__PURE__ */ new Date();
23933
24220
  return mergeTopics([], added).map(
23934
- (topic) => ({
23935
- uniqueId: normalizeEntityName(topic.name),
23936
- canonicalName: normalizeEntityName(topic.name),
23937
- kind: topic.kind ?? null,
24221
+ (topic2) => ({
24222
+ uniqueId: normalizeEntityName(topic2.name),
24223
+ canonicalName: normalizeEntityName(topic2.name),
24224
+ kind: topic2.kind ?? null,
23938
24225
  createdAt: now,
23939
24226
  updatedAt: now
23940
24227
  })