@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.
@@ -4325,11 +4325,17 @@ async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage,
4325
4325
  }
4326
4326
  return response.data.data.map((item) => item.embedding ?? []);
4327
4327
  }
4328
- async function generateEmbeddings(texts, options) {
4328
+ async function generateEmbeddings(texts, options, signal) {
4329
+ signal?.throwIfAborted();
4329
4330
  if (texts.length === 0) return [];
4330
4331
  const total = options.totalTimeoutMs;
4331
- if (total === void 0) return embedMany(texts, options, void 0);
4332
- return withDeadline((signal) => embedMany(texts, options, signal), total, "embedding batch");
4332
+ if (total === void 0) return embedMany(texts, options, signal);
4333
+ return withDeadline(
4334
+ (inner) => embedMany(texts, options, inner),
4335
+ total,
4336
+ "embedding batch",
4337
+ signal
4338
+ );
4333
4339
  }
4334
4340
  async function embedMany(texts, options, outer) {
4335
4341
  const { baseUrl = BASE_URL, model, batchSize, cache } = options;
@@ -9965,13 +9971,13 @@ function normalizeEntityName(name) {
9965
9971
  return name.trim().toLowerCase();
9966
9972
  }
9967
9973
  var TOPIC_SALIENCE_WEIGHTS = { primary: 1, secondary: 0.6, mention: 0.3 };
9968
- function topicWeight(topic) {
9969
- return topic.source === "user" ? 1 : topic.weight ?? 1;
9974
+ function topicWeight(topic2) {
9975
+ return topic2.source === "user" ? 1 : topic2.weight ?? 1;
9970
9976
  }
9971
9977
  function primaryTopic(topics) {
9972
9978
  let best = null;
9973
- for (const topic of topics ?? []) {
9974
- if (best === null || topicWeight(topic) > topicWeight(best)) best = topic;
9979
+ for (const topic2 of topics ?? []) {
9980
+ if (best === null || topicWeight(topic2) > topicWeight(best)) best = topic2;
9975
9981
  }
9976
9982
  return best;
9977
9983
  }
@@ -11540,7 +11546,7 @@ function resolveTopicsWrite(row, linked, inputs, source, mode) {
11540
11546
  }
11541
11547
  function weightsOf(stored2, inputs) {
11542
11548
  const out = /* @__PURE__ */ new Map();
11543
- for (const topic of stored2 ?? []) out.set(normalizeEntityName(topic.name), topic.weight);
11549
+ for (const topic2 of stored2 ?? []) out.set(normalizeEntityName(topic2.name), topic2.weight);
11544
11550
  for (const input of inputs) {
11545
11551
  const weight = parseTopicWeight(input.weight);
11546
11552
  if (weight === void 0) continue;
@@ -12423,6 +12429,20 @@ async function getAllVaultMemoryContentsOp(ctx, options) {
12423
12429
  async function countActiveVaultMemoriesOp(ctx) {
12424
12430
  return ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx)).fetchCount();
12425
12431
  }
12432
+ async function getVaultMemoryCountsOp(ctx) {
12433
+ const [live, archived, quarantined] = await Promise.all([
12434
+ countActiveVaultMemoriesOp(ctx),
12435
+ ctx.vaultMemoryCollection.query(
12436
+ ...baseVaultConditions(ctx, { includeArchived: true }),
12437
+ Q6.where("archived_at", Q6.notEq(null))
12438
+ ).fetchCount(),
12439
+ ctx.vaultMemoryCollection.query(
12440
+ ...baseVaultConditions(ctx, { includeQuarantined: true }),
12441
+ Q6.where("trust_tier", "quarantined")
12442
+ ).fetchCount()
12443
+ ]);
12444
+ return { live, archived, quarantined };
12445
+ }
12426
12446
  async function getActiveVaultMemoryIdsOp(ctx, ids) {
12427
12447
  if (ids.length === 0) return /* @__PURE__ */ new Set();
12428
12448
  const rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), Q6.where("id", Q6.oneOf(ids))).unsafeFetchRaw();
@@ -19085,7 +19105,8 @@ Each memory below is written as "<id>: <text>". Output strict JSON: {"memories":
19085
19105
  - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
19086
19106
  - "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
19087
19107
  No prose.`;
19088
- async function extractEntitiesForMemories(memories, options) {
19108
+ async function extractEntitiesForMemories(memories, options, signal) {
19109
+ signal?.throwIfAborted();
19089
19110
  const out = /* @__PURE__ */ new Map();
19090
19111
  if (memories.length === 0) return out;
19091
19112
  const redactor = resolvePiiRedactor(options.piiRedaction);
@@ -19098,6 +19119,7 @@ When a memory refers to one of these, reuse the EXACT existing name (same spelli
19098
19119
 
19099
19120
  ` : "";
19100
19121
  for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
19122
+ signal?.throwIfAborted();
19101
19123
  const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
19102
19124
  const start = Date.now();
19103
19125
  let attempts = 0;
@@ -19145,6 +19167,7 @@ List each memory's named entities.`,
19145
19167
  ...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
19146
19168
  ...options.backoffMs && { backoffMs: options.backoffMs }
19147
19169
  });
19170
+ signal?.throwIfAborted();
19148
19171
  if (parsed === null) {
19149
19172
  reportBatch(batch.length);
19150
19173
  getLogger().warn(
@@ -19379,6 +19402,7 @@ function createLocalMemoryStore(options) {
19379
19402
  backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
19380
19403
  };
19381
19404
  return {
19405
+ counts: () => getVaultMemoryCountsOp(vaultCtx),
19382
19406
  masksEmbeddingInput: options.embeddingOptions.maskInput !== void 0,
19383
19407
  list: (listOptions) => getAllVaultMemoriesOp(vaultCtx, listOptions),
19384
19408
  get: (id) => getVaultMemoryOp(vaultCtx, id),
@@ -19603,6 +19627,134 @@ async function decryptMemoryField(value, rings) {
19603
19627
  return decryptWith(value, rings);
19604
19628
  }
19605
19629
 
19630
+ // src/lib/memory/store/remoteTopics.ts
19631
+ function topic(value) {
19632
+ const t = value;
19633
+ 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")
19634
+ throw new Error("Invalid nearby topic");
19635
+ return t;
19636
+ }
19637
+ function createRemoteTopicPersistence(request) {
19638
+ return {
19639
+ topics: async () => {
19640
+ const out = [];
19641
+ let cursor;
19642
+ do {
19643
+ const page = await request(
19644
+ "/topics?limit=200" + (cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""),
19645
+ {}
19646
+ );
19647
+ if (!Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string")
19648
+ throw new Error("Invalid nearby topic page");
19649
+ out.push(...page.items.map(topic));
19650
+ cursor = page.next_cursor;
19651
+ } while (cursor);
19652
+ return out;
19653
+ },
19654
+ putTopic: async (value, expectedVersion) => {
19655
+ if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0)
19656
+ throw new Error("Expected topic version must be a nonnegative integer");
19657
+ return topic(
19658
+ await request("/topics", {
19659
+ method: "PUT",
19660
+ body: JSON.stringify({ topic: value, expected_version: expectedVersion })
19661
+ })
19662
+ );
19663
+ },
19664
+ importTopics: async (topics, signal) => {
19665
+ for (let i = 0; i < topics.length; i += 50) {
19666
+ signal?.throwIfAborted();
19667
+ await request(
19668
+ "/topics/import",
19669
+ {
19670
+ method: "POST",
19671
+ body: JSON.stringify({ topics: topics.slice(i, i + 50) })
19672
+ },
19673
+ signal
19674
+ );
19675
+ }
19676
+ signal?.throwIfAborted();
19677
+ }
19678
+ };
19679
+ }
19680
+ async function createRemoteTopic(persistence, name) {
19681
+ const canonical = normalizeEntityName(name);
19682
+ if (!canonical) return null;
19683
+ const existing = (await persistence.topics()).find((t) => t.name === canonical);
19684
+ if (existing && !existing.is_deleted) return canonical;
19685
+ try {
19686
+ await persistence.putTopic(
19687
+ { name: canonical, is_deleted: false, ...existing?.kind && { kind: existing.kind } },
19688
+ existing?.version ?? 0
19689
+ );
19690
+ } catch (error) {
19691
+ if (!(error instanceof RemoteMemoryError) || error.code !== "version_conflict") throw error;
19692
+ const current = (await persistence.topics()).find((t) => t.name === canonical);
19693
+ if (!current || current.is_deleted) throw error;
19694
+ }
19695
+ return canonical;
19696
+ }
19697
+
19698
+ // src/lib/memory/store/remoteWork.ts
19699
+ function createRemoteWorkPersistence(request) {
19700
+ return {
19701
+ claimWork: async (kind, processorVersion, token) => {
19702
+ const result = await request("/work/claim", {
19703
+ method: "POST",
19704
+ body: JSON.stringify({
19705
+ kind,
19706
+ processor_version: processorVersion,
19707
+ ...token && { token }
19708
+ })
19709
+ });
19710
+ if (!result || typeof result !== "object") throw new Error("Invalid nearby work lease");
19711
+ const lease = result.lease;
19712
+ if (lease === void 0) return null;
19713
+ 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)))
19714
+ throw new Error("Invalid nearby work lease");
19715
+ return lease;
19716
+ },
19717
+ releaseWork: async (kind, token) => {
19718
+ await request("/work/release", { method: "POST", body: JSON.stringify({ kind, token }) });
19719
+ }
19720
+ };
19721
+ }
19722
+ async function withRemoteWorkLease(persistence, kind, processorVersion, task) {
19723
+ const lease = await persistence.claimWork(kind, processorVersion);
19724
+ if (!lease) return null;
19725
+ const controller = new AbortController();
19726
+ const leaseLostError = new RemoteMemoryError("Maintenance lease lost", 409, "work_lease_expired");
19727
+ const cancelled = new Promise((_resolve, reject) => {
19728
+ controller.signal.addEventListener("abort", () => reject(leaseLostError), {
19729
+ once: true
19730
+ });
19731
+ });
19732
+ let renewing;
19733
+ const timer = setInterval(() => {
19734
+ if (renewing || controller.signal.aborted) return;
19735
+ renewing = persistence.claimWork(kind, processorVersion, lease.token).then((renewed) => {
19736
+ if (!renewed) controller.abort(leaseLostError);
19737
+ }).catch((error) => getLogger().warn("[memory/work] Lease renewal failed:", error)).finally(() => {
19738
+ renewing = void 0;
19739
+ });
19740
+ }, 3e4);
19741
+ try {
19742
+ const result = await Promise.race([task(lease.token, controller.signal), cancelled]);
19743
+ await renewing;
19744
+ return controller.signal.aborted ? null : result;
19745
+ } catch (error) {
19746
+ if (controller.signal.aborted || isRemoteWorkLeaseExpired(error)) return null;
19747
+ throw error;
19748
+ } finally {
19749
+ clearInterval(timer);
19750
+ await renewing;
19751
+ await persistence.releaseWork(kind, lease.token).catch((error) => getLogger().warn("[memory/work] Lease release failed:", error));
19752
+ }
19753
+ }
19754
+ function isRemoteWorkLeaseExpired(error) {
19755
+ return error instanceof RemoteMemoryError && error.code === "work_lease_expired";
19756
+ }
19757
+
19606
19758
  // src/lib/memory/store/remotePersistence.ts
19607
19759
  var RemoteMemoryError = class extends Error {
19608
19760
  constructor(message, status, code, retryAfterMs) {
@@ -19982,6 +20134,33 @@ async function createRemoteMemoryPersistence(options) {
19982
20134
  };
19983
20135
  };
19984
20136
  return {
20137
+ ...createRemoteTopicPersistence(request),
20138
+ ...createRemoteWorkPersistence(request),
20139
+ counts: async () => {
20140
+ const result = await request("/counts", {});
20141
+ if (!record(result) || ["live", "archived", "quarantined"].some(
20142
+ (key) => !Number.isSafeInteger(result[key]) || result[key] < 0
20143
+ ))
20144
+ throw new Error("Invalid nearby memory counts");
20145
+ return {
20146
+ live: result.live,
20147
+ archived: result.archived,
20148
+ quarantined: result.quarantined
20149
+ };
20150
+ },
20151
+ temporal: async (window2, limit = 100) => {
20152
+ if (!Number.isFinite(window2.start) || !Number.isFinite(window2.end) || window2.end <= window2.start || !Number.isInteger(limit) || limit < 1 || limit > 100)
20153
+ throw new Error("Temporal window must have end > start and limit must be 1\u2013100");
20154
+ const result = await request("/temporal", {
20155
+ method: "POST",
20156
+ body: JSON.stringify({ ...window2, limit })
20157
+ });
20158
+ if (!record(result) || !Array.isArray(result.items) || result.items.some(
20159
+ (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
20160
+ ))
20161
+ throw new Error("Invalid nearby temporal candidates");
20162
+ return result.items.map((item) => item.memory_id);
20163
+ },
19985
20164
  get: async (id, signal) => {
19986
20165
  try {
19987
20166
  return await decode(await request(memoryPath(id), {}, signal));
@@ -20030,7 +20209,7 @@ async function createRemoteMemoryPersistence(options) {
20030
20209
  write
20031
20210
  );
20032
20211
  },
20033
- putMany: async (writes, signal) => {
20212
+ putMany: async (writes, signal, leaseToken) => {
20034
20213
  if (writes.length < 1 || writes.length > 50 || new Set(writes.map((w) => w.memory.memory_id)).size !== writes.length)
20035
20214
  throw new Error("Batch must contain 1\u201350 distinct memory ids");
20036
20215
  const prepared = await Promise.all(
@@ -20040,7 +20219,11 @@ async function createRemoteMemoryPersistence(options) {
20040
20219
  "/memories/batch",
20041
20220
  {
20042
20221
  method: "POST",
20043
- body: JSON.stringify({ key_id: options.keyId, writes: prepared.map((w) => w.body) })
20222
+ body: JSON.stringify({
20223
+ key_id: options.keyId,
20224
+ writes: prepared.map((w) => w.body),
20225
+ ...leaseToken && { lease_token: leaseToken }
20226
+ })
20044
20227
  },
20045
20228
  signal
20046
20229
  );
@@ -20799,6 +20982,12 @@ async function migrateMemoriesToRemote(options) {
20799
20982
  let claimed = account.key_id === canonical.keyId;
20800
20983
  if (!active) await createDecaySweeper({ vaultCtx: options.vaultCtx }).runSweep();
20801
20984
  await backfillTopics(options.vaultCtx);
20985
+ const localTopics = options.topics ?? (options.vaultCtx.singleTenant === true ? (await options.vaultCtx.database.get("entity").query().unsafeFetchRaw()).map((t) => ({
20986
+ name: normalizeEntityName(typeof t.canonical_name === "string" ? t.canonical_name : ""),
20987
+ ...typeof t.kind === "string" && { kind: t.kind }
20988
+ })).filter((t) => t.name.length > 0) : []);
20989
+ if (localTopics.length)
20990
+ await createRemoteTopicPersistence(request).importTopics(localTopics, signal);
20802
20991
  const skipped = /* @__PURE__ */ new Map();
20803
20992
  const reembedded = /* @__PURE__ */ new Set();
20804
20993
  const unembedded = /* @__PURE__ */ new Set();
@@ -20925,7 +21114,6 @@ var MAX_QUERY_IDS = 1e3;
20925
21114
  var MAX_QUERY_TOPICS = 100;
20926
21115
  var MAX_QUERY_FILTER = 20;
20927
21116
  var GRAPH_LANE_ROWS = 256;
20928
- var TEMPORAL_LANE_ROWS = 256;
20929
21117
  var REPAIR_ROWS = 50;
20930
21118
  var REPAIR_SCAN = 2e3;
20931
21119
  var DECAY_TRANSITIONS_PER_SWEEP = 200;
@@ -20996,21 +21184,21 @@ function subscriptionKey({ memory, version }, options) {
20996
21184
  ]);
20997
21185
  }
20998
21186
  function topicInputs(topics, source) {
20999
- return topics.flatMap((topic) => {
21000
- const name = (typeof topic === "string" ? topic : topic.name).trim();
21001
- const kind = typeof topic === "string" ? void 0 : topic.kind;
21002
- const weight = typeof topic === "string" || source === "user" ? void 0 : parseTopicWeight(topic.weight);
21187
+ return topics.flatMap((topic2) => {
21188
+ const name = (typeof topic2 === "string" ? topic2 : topic2.name).trim();
21189
+ const kind = typeof topic2 === "string" ? void 0 : topic2.kind;
21190
+ const weight = typeof topic2 === "string" || source === "user" ? void 0 : parseTopicWeight(topic2.weight);
21003
21191
  return name ? [{ name, ...kind && { kind }, source, ...weight && { weight } }] : [];
21004
21192
  });
21005
21193
  }
21006
21194
  function mergeTopics(existing, added) {
21007
21195
  const byName = /* @__PURE__ */ new Map();
21008
- for (const topic of [...existing, ...added]) {
21009
- const key = normalizeEntityName(topic.name);
21196
+ for (const topic2 of [...existing, ...added]) {
21197
+ const key = normalizeEntityName(topic2.name);
21010
21198
  const kept = byName.get(key);
21011
- if (!kept) byName.set(key, topic);
21012
- else if (kept.weight !== void 0 && topic.weight !== void 0 && topic.weight > kept.weight)
21013
- byName.set(key, { ...kept, weight: topic.weight });
21199
+ if (!kept) byName.set(key, topic2);
21200
+ else if (kept.weight !== void 0 && topic2.weight !== void 0 && topic2.weight > kept.weight)
21201
+ byName.set(key, { ...kept, weight: topic2.weight });
21014
21202
  }
21015
21203
  return [...byName.values()];
21016
21204
  }
@@ -21021,7 +21209,7 @@ function sameTopics(stored2, next) {
21021
21209
  return next.every((t) => keys.has(key(t)));
21022
21210
  }
21023
21211
  function canonicalTopics(row) {
21024
- return new Set((parseTopics(row.topics) ?? []).map((topic) => normalizeEntityName(topic.name)));
21212
+ return new Set((parseTopics(row.topics) ?? []).map((topic2) => normalizeEntityName(topic2.name)));
21025
21213
  }
21026
21214
  function createRemoteMemoryStore(options) {
21027
21215
  const { persistence, embeddingOptions } = options;
@@ -21096,20 +21284,7 @@ function createRemoteMemoryStore(options) {
21096
21284
  const eventTimeRanking = async (query, now) => {
21097
21285
  const window2 = parseQueryTimeWindow(query, now);
21098
21286
  if (!window2) return [];
21099
- const rows = await pages(
21100
- metadata2,
21101
- { event_time_overlaps: { start: window2.start, end: window2.end } },
21102
- TEMPORAL_LANE_ROWS
21103
- );
21104
- return rows.map(({ memory }) => ({
21105
- memoryId: memory.memory_id,
21106
- score: scoreEventTimeOverlap(
21107
- memory.event_time_start ?? null,
21108
- memory.event_time_end ?? null,
21109
- memory.event_time_kind ?? null,
21110
- window2
21111
- )
21112
- })).filter((row) => row.score > 0).sort((a, b) => b.score - a.score).map((row) => row.memoryId);
21287
+ return persistence.temporal({ start: window2.start, end: window2.end });
21113
21288
  };
21114
21289
  const enumerateIds = async (read, ids, filters) => (await Promise.all(
21115
21290
  chunks2([...new Set(ids)], MAX_QUERY_IDS).map(
@@ -21160,22 +21335,28 @@ function createRemoteMemoryStore(options) {
21160
21335
  writeOptions?.signal?.throwIfAborted();
21161
21336
  const next = edit(structuredClone(record2.memory));
21162
21337
  if (!next) return null;
21163
- const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
21338
+ const saved = writeOptions?.leaseToken ? (await persistence.putMany(
21339
+ [{ memory: next, expectedVersion: record2 }],
21340
+ writeOptions.signal,
21341
+ writeOptions.leaseToken
21342
+ ))[0] : writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
21164
21343
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
21165
21344
  return saved;
21166
21345
  };
21167
21346
  const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
21168
21347
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
21169
- const mutateMany = async (ids, change, edit) => {
21348
+ const mutateMany = async (ids, change, edit, leaseToken, signal) => {
21170
21349
  const saved = /* @__PURE__ */ new Map();
21171
21350
  const failed = /* @__PURE__ */ new Map();
21172
21351
  for (const slice of chunks2([...new Set(ids)], MAX_BATCH)) {
21173
21352
  await serializedAll(slice, async () => {
21353
+ signal?.throwIfAborted();
21174
21354
  const writes = [];
21175
21355
  let read;
21176
21356
  try {
21177
- read = await readEveryState(slice);
21357
+ read = await readEveryState(slice, signal);
21178
21358
  } catch (error) {
21359
+ signal?.throwIfAborted();
21179
21360
  for (const id of slice) failed.set(id, error);
21180
21361
  return;
21181
21362
  }
@@ -21192,7 +21373,9 @@ function createRemoteMemoryStore(options) {
21192
21373
  if (writes.length === 0) return;
21193
21374
  try {
21194
21375
  const written = await persistence.putMany(
21195
- writes.map(({ before, memory }) => ({ memory, expectedVersion: before }))
21376
+ writes.map(({ before, memory }) => ({ memory, expectedVersion: before })),
21377
+ signal,
21378
+ leaseToken
21196
21379
  );
21197
21380
  const byChange = /* @__PURE__ */ new Map();
21198
21381
  written.forEach((record2, i) => {
@@ -21202,7 +21385,9 @@ function createRemoteMemoryStore(options) {
21202
21385
  });
21203
21386
  for (const [kind, changed] of byChange) notify(kind, changed);
21204
21387
  } catch (error) {
21205
- const perRow = error instanceof RemoteMemoryError && (error.code === "version_conflict" || error.status === 429);
21388
+ signal?.throwIfAborted();
21389
+ if (isRemoteWorkLeaseExpired(error)) throw error;
21390
+ const perRow = error instanceof RemoteMemoryError && (error.code === "version_conflict" || error.status === 429 && !leaseToken);
21206
21391
  for (const { before } of writes) {
21207
21392
  const id = before.memory.memory_id;
21208
21393
  if (!perRow) {
@@ -21210,9 +21395,16 @@ function createRemoteMemoryStore(options) {
21210
21395
  continue;
21211
21396
  }
21212
21397
  try {
21213
- const record2 = await mutateLocked(id, change, edit);
21398
+ const record2 = await mutateLocked(
21399
+ id,
21400
+ change,
21401
+ edit,
21402
+ leaseToken || signal ? { leaseToken, signal } : void 0
21403
+ );
21214
21404
  if (record2) saved.set(id, record2);
21215
21405
  } catch (rowError) {
21406
+ signal?.throwIfAborted();
21407
+ if (isRemoteWorkLeaseExpired(rowError)) throw rowError;
21216
21408
  failed.set(id, rowError);
21217
21409
  }
21218
21410
  }
@@ -21276,7 +21468,7 @@ function createRemoteMemoryStore(options) {
21276
21468
  return records.map((record2) => stored(record2.memory));
21277
21469
  };
21278
21470
  const asStored = (record2) => record2 ? stored(record2.memory) : null;
21279
- const knownKinds = async (topics) => {
21471
+ const knownKinds = async (topics, signal) => {
21280
21472
  const unkinded = [
21281
21473
  ...new Set(topics.filter((t) => !t.kind).map((t) => normalizeEntityName(t.name)))
21282
21474
  ];
@@ -21286,21 +21478,22 @@ function createRemoteMemoryStore(options) {
21286
21478
  topics: names,
21287
21479
  include_archived: true,
21288
21480
  include_quarantined: true,
21289
- include_superseded: true
21481
+ include_superseded: true,
21482
+ ...signal && { signal }
21290
21483
  })) {
21291
- for (const topic of parseTopics(memory.topics) ?? []) {
21292
- const name = normalizeEntityName(topic.name);
21293
- if (topic.kind && !kinds.has(name)) kinds.set(name, topic.kind);
21484
+ for (const topic2 of parseTopics(memory.topics) ?? []) {
21485
+ const name = normalizeEntityName(topic2.name);
21486
+ if (topic2.kind && !kinds.has(name)) kinds.set(name, topic2.kind);
21294
21487
  }
21295
21488
  }
21296
21489
  }
21297
- return (topic) => {
21298
- const kind = topic.kind ?? kinds.get(normalizeEntityName(topic.name));
21299
- return kind ? { ...topic, kind } : topic;
21490
+ return (topic2) => {
21491
+ const kind = topic2.kind ?? kinds.get(normalizeEntityName(topic2.name));
21492
+ return kind ? { ...topic2, kind } : topic2;
21300
21493
  };
21301
21494
  };
21302
21495
  const withKnownKinds = async (topics) => topics.map(await knownKinds(topics));
21303
- const readEveryState = async (ids) => {
21496
+ const readEveryState = async (ids, signal) => {
21304
21497
  const records = /* @__PURE__ */ new Map();
21305
21498
  const unreadable = /* @__PURE__ */ new Set();
21306
21499
  for (const memory_ids of chunks2(ids, MAX_QUERY_IDS)) {
@@ -21313,6 +21506,7 @@ function createRemoteMemoryStore(options) {
21313
21506
  include_quarantined: true,
21314
21507
  include_superseded: true,
21315
21508
  limit: 200,
21509
+ ...signal && { signal },
21316
21510
  ...cursor2 && { cursor: cursor2 }
21317
21511
  });
21318
21512
  warnFailed(page.failed);
@@ -21323,6 +21517,111 @@ function createRemoteMemoryStore(options) {
21323
21517
  }
21324
21518
  return { records, unreadable };
21325
21519
  };
21520
+ const extractTopicsUnlocked = async (memoryIds, extractOptions, leaseToken, signal) => {
21521
+ const start = Date.now();
21522
+ const extractedAt = extractOptions.now ?? start;
21523
+ const skippedIds = [];
21524
+ const skippedReasons = /* @__PURE__ */ new Map();
21525
+ const skip = (id, reason) => {
21526
+ if (skippedReasons.has(id)) return;
21527
+ skippedIds.push(id);
21528
+ skippedReasons.set(id, reason);
21529
+ };
21530
+ const ids = [...new Set(memoryIds)];
21531
+ const { records, unreadable } = await readEveryState(ids, signal);
21532
+ signal?.throwIfAborted();
21533
+ const inputs = [];
21534
+ for (const id of ids) {
21535
+ const memory = records.get(id)?.memory;
21536
+ if (unreadable.has(id)) skip(id, "unreadable");
21537
+ else if (!memory) skip(id, "not-found");
21538
+ else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
21539
+ else inputs.push({ id, content: memory.content });
21540
+ }
21541
+ const batches = [];
21542
+ const llmFailures = /* @__PURE__ */ new Map();
21543
+ const entitiesByMemory = await extractEntitiesForMemories(
21544
+ inputs,
21545
+ {
21546
+ ...extractOptions,
21547
+ onBatch: (batch) => {
21548
+ batches.push(batch);
21549
+ if (batch.failure) {
21550
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
21551
+ }
21552
+ notifyMemoryCallback(extractOptions.onBatch, batch);
21553
+ }
21554
+ },
21555
+ signal
21556
+ );
21557
+ signal?.throwIfAborted();
21558
+ const answered = /* @__PURE__ */ new Map();
21559
+ for (const input of inputs) {
21560
+ const entities = entitiesByMemory.get(input.id);
21561
+ if (entities === void 0) skip(input.id, "llm-unanswered");
21562
+ else
21563
+ answered.set(input.id, { content: input.content, topics: topicInputs(entities, "auto") });
21564
+ }
21565
+ const stampedIds = [];
21566
+ const fail = (id, reason) => {
21567
+ skip(id, reason);
21568
+ entitiesByMemory.delete(id);
21569
+ };
21570
+ try {
21571
+ const kinded = await knownKinds(
21572
+ [...answered.values()].flatMap((a) => a.topics),
21573
+ signal
21574
+ );
21575
+ signal?.throwIfAborted();
21576
+ const topics = new Map(
21577
+ [...answered].map(([id, a]) => [id, mergeTopics([], a.topics.map(kinded))])
21578
+ );
21579
+ const unchanged = (memory) => sameTopics(parseTopics(memory.topics), topics.get(memory.memory_id));
21580
+ const { saved, failed } = await mutateMany(
21581
+ [...answered.keys()],
21582
+ (before) => sameTopics(parseTopics(before.topics) ?? [], topics.get(before.memory_id)) ? "metadata" : "edit",
21583
+ (memory) => {
21584
+ if (memory.topics_user_managed || memory.content !== answered.get(memory.memory_id).content)
21585
+ return null;
21586
+ return {
21587
+ ...memory,
21588
+ ...!unchanged(memory) && {
21589
+ topics: serializeTopics(topics.get(memory.memory_id)),
21590
+ topics_updated_at: Date.now()
21591
+ },
21592
+ topics_extracted_at: Math.max(extractedAt, memory.updated_at),
21593
+ topics_extracted_version: TOPICS_EXTRACTION_VERSION
21594
+ };
21595
+ },
21596
+ leaseToken,
21597
+ signal
21598
+ );
21599
+ for (const id of answered.keys()) {
21600
+ if (saved.has(id)) stampedIds.push(id);
21601
+ else fail(id, failed.has(id) ? "link-failed" : "link-declined");
21602
+ }
21603
+ if (failed.size)
21604
+ getLogger().warn(
21605
+ "[memory/remote-store] Failed to write extracted topics:",
21606
+ failed.values().next().value
21607
+ );
21608
+ } catch (error) {
21609
+ signal?.throwIfAborted();
21610
+ if (isRemoteWorkLeaseExpired(error)) throw error;
21611
+ getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
21612
+ for (const id of answered.keys()) fail(id, "link-failed");
21613
+ }
21614
+ return {
21615
+ entitiesByMemory,
21616
+ stampedIds,
21617
+ skippedIds,
21618
+ skippedReasons,
21619
+ batchCount: batches.length,
21620
+ durationMs: Date.now() - start,
21621
+ batches,
21622
+ llmFailures
21623
+ };
21624
+ };
21326
21625
  const topicMaintenance = {
21327
21626
  getTopicBacklog: async (backlogOptions = {}) => {
21328
21627
  const limit = backlogOptions.limit !== void 0 && backlogOptions.limit > 0 ? backlogOptions.limit : Infinity;
@@ -21340,97 +21639,12 @@ function createRemoteMemoryStore(options) {
21340
21639
  topicsBackfill: []
21341
21640
  };
21342
21641
  },
21343
- extractTopics: async (memoryIds, extractOptions) => {
21344
- const start = Date.now();
21345
- const extractedAt = extractOptions.now ?? start;
21346
- const skippedIds = [];
21347
- const skippedReasons = /* @__PURE__ */ new Map();
21348
- const skip = (id, reason) => {
21349
- if (skippedReasons.has(id)) return;
21350
- skippedIds.push(id);
21351
- skippedReasons.set(id, reason);
21352
- };
21353
- const ids = [...new Set(memoryIds)];
21354
- const { records, unreadable } = await readEveryState(ids);
21355
- const inputs = [];
21356
- for (const id of ids) {
21357
- const memory = records.get(id)?.memory;
21358
- if (unreadable.has(id)) skip(id, "unreadable");
21359
- else if (!memory) skip(id, "not-found");
21360
- else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
21361
- else inputs.push({ id, content: memory.content });
21362
- }
21363
- const batches = [];
21364
- const llmFailures = /* @__PURE__ */ new Map();
21365
- const entitiesByMemory = await extractEntitiesForMemories(inputs, {
21366
- ...extractOptions,
21367
- onBatch: (batch) => {
21368
- batches.push(batch);
21369
- if (batch.failure) {
21370
- for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
21371
- }
21372
- notifyMemoryCallback(extractOptions.onBatch, batch);
21373
- }
21374
- });
21375
- const answered = /* @__PURE__ */ new Map();
21376
- for (const input of inputs) {
21377
- const entities = entitiesByMemory.get(input.id);
21378
- if (entities === void 0) skip(input.id, "llm-unanswered");
21379
- else
21380
- answered.set(input.id, { content: input.content, topics: topicInputs(entities, "auto") });
21381
- }
21382
- const stampedIds = [];
21383
- const fail = (id, reason) => {
21384
- skip(id, reason);
21385
- entitiesByMemory.delete(id);
21386
- };
21387
- try {
21388
- const kinded = await knownKinds([...answered.values()].flatMap((a) => a.topics));
21389
- const topics = new Map(
21390
- [...answered].map(([id, a]) => [id, mergeTopics([], a.topics.map(kinded))])
21391
- );
21392
- const unchanged = (memory) => sameTopics(parseTopics(memory.topics), topics.get(memory.memory_id));
21393
- const { saved, failed } = await mutateMany(
21394
- [...answered.keys()],
21395
- (before) => sameTopics(parseTopics(before.topics) ?? [], topics.get(before.memory_id)) ? "metadata" : "edit",
21396
- (memory) => {
21397
- if (memory.topics_user_managed || memory.content !== answered.get(memory.memory_id).content)
21398
- return null;
21399
- return {
21400
- ...memory,
21401
- ...!unchanged(memory) && {
21402
- topics: serializeTopics(topics.get(memory.memory_id)),
21403
- topics_updated_at: Date.now()
21404
- },
21405
- topics_extracted_at: Math.max(extractedAt, memory.updated_at),
21406
- topics_extracted_version: TOPICS_EXTRACTION_VERSION
21407
- };
21408
- }
21409
- );
21410
- for (const id of answered.keys()) {
21411
- if (saved.has(id)) stampedIds.push(id);
21412
- else fail(id, failed.has(id) ? "link-failed" : "link-declined");
21413
- }
21414
- if (failed.size)
21415
- getLogger().warn(
21416
- "[memory/remote-store] Failed to write extracted topics:",
21417
- failed.values().next().value
21418
- );
21419
- } catch (error) {
21420
- getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
21421
- for (const id of answered.keys()) fail(id, "link-failed");
21422
- }
21423
- return {
21424
- entitiesByMemory,
21425
- stampedIds,
21426
- skippedIds,
21427
- skippedReasons,
21428
- batchCount: batches.length,
21429
- durationMs: Date.now() - start,
21430
- batches,
21431
- llmFailures
21432
- };
21433
- },
21642
+ extractTopics: async (ids, extractOptions) => await withRemoteWorkLease(
21643
+ persistence,
21644
+ "topics",
21645
+ String(TOPICS_EXTRACTION_VERSION),
21646
+ (token, signal) => extractTopicsUnlocked(ids, extractOptions, token, signal)
21647
+ ) ?? { ...await extractTopicsUnlocked([], extractOptions), deferred: true },
21434
21648
  stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
21435
21649
  const ids = [...new Set(memoryIds)];
21436
21650
  const { saved, failed } = await mutateMany(
@@ -21527,14 +21741,29 @@ function createRemoteMemoryStore(options) {
21527
21741
  let ready = false;
21528
21742
  let cursor;
21529
21743
  let pending = /* @__PURE__ */ new Set();
21744
+ let topicFingerprint;
21530
21745
  let polling = false;
21531
21746
  const poll = () => {
21532
21747
  if (polling) return;
21533
21748
  polling = true;
21534
21749
  const current = generation;
21535
- changesSince(cursor, pending).then(({ records, resume, failed }) => {
21750
+ changesSince(cursor, pending).then(async ({ records, resume, failed }) => {
21751
+ let topicState;
21752
+ if ([...subscribers].some((s) => s.options.topics))
21753
+ try {
21754
+ topicState = JSON.stringify(
21755
+ (await persistence.topics()).map((t) => [t.name, t.version])
21756
+ );
21757
+ } catch (error) {
21758
+ getLogger().warn("[memory/remote-store] Topic subscription poll failed:", error);
21759
+ }
21536
21760
  if (current !== generation) return;
21537
21761
  const changed = /* @__PURE__ */ new Set();
21762
+ if (topicState !== void 0 && topicFingerprint !== void 0 && topicState !== topicFingerprint) {
21763
+ for (const subscriber of subscribers)
21764
+ if (subscriber.options.topics) changed.add(subscriber);
21765
+ }
21766
+ if (topicState !== void 0) topicFingerprint = topicState;
21538
21767
  for (const record2 of records) {
21539
21768
  const id = record2.memory.memory_id;
21540
21769
  const previous = seen.get(id);
@@ -21558,11 +21787,11 @@ function createRemoteMemoryStore(options) {
21558
21787
  };
21559
21788
  const unrepairable = /* @__PURE__ */ new Set();
21560
21789
  let repairCursor;
21561
- const repairEmbeddings = async ({
21790
+ const repairEmbeddingsUnlocked = async ({
21562
21791
  maxRows = REPAIR_ROWS,
21563
21792
  maxScan = REPAIR_SCAN,
21564
21793
  cursor: start = repairCursor
21565
- } = {}) => {
21794
+ } = {}, leaseToken, signal) => {
21566
21795
  const scannedRows = [];
21567
21796
  let cursor2 = start;
21568
21797
  do {
@@ -21571,6 +21800,7 @@ function createRemoteMemoryStore(options) {
21571
21800
  include_archived: true,
21572
21801
  include_quarantined: true,
21573
21802
  include_superseded: true,
21803
+ ...signal && { signal },
21574
21804
  limit: Math.min(200, Math.max(1, maxScan - scannedRows.length)),
21575
21805
  ...cursor2 && { cursor: cursor2 }
21576
21806
  });
@@ -21588,6 +21818,7 @@ function createRemoteMemoryStore(options) {
21588
21818
  skippedConflict: 0
21589
21819
  };
21590
21820
  const finish = () => {
21821
+ signal?.throwIfAborted();
21591
21822
  const settled = allStale.every(
21592
21823
  ({ memory }) => repaired.has(memory.memory_id) || unrepairable.has(memory.memory_id)
21593
21824
  );
@@ -21596,13 +21827,15 @@ function createRemoteMemoryStore(options) {
21596
21827
  };
21597
21828
  const stale = allStale.slice(0, maxRows).map(({ memory }) => memory.memory_id);
21598
21829
  if (stale.length === 0) return finish();
21599
- const { records, unreadable } = await readEveryState(stale);
21830
+ const { records, unreadable } = await readEveryState(stale, signal);
21831
+ signal?.throwIfAborted();
21600
21832
  result.failed += unreadable.size;
21601
21833
  for (const id of stale)
21602
21834
  if (unreadable.has(id) || !records.get(id)?.memory.content.trim()) unrepairable.add(id);
21603
21835
  const targets = stale.filter((id) => !unrepairable.has(id)).map((id) => records.get(id));
21604
21836
  if (targets.length === 0) return finish();
21605
- const vectors = await embedForRepair(targets);
21837
+ const vectors = await embedForRepair(targets, signal);
21838
+ signal?.throwIfAborted();
21606
21839
  if (!vectors) {
21607
21840
  result.failed += targets.length;
21608
21841
  return finish();
@@ -21614,15 +21847,28 @@ function createRemoteMemoryStore(options) {
21614
21847
  continue;
21615
21848
  }
21616
21849
  await serialized(record2.memory.memory_id, async () => {
21850
+ signal?.throwIfAborted();
21617
21851
  try {
21618
- const saved = await persistence.put(
21619
- { ...record2.memory, embedding: storedVector(vector), embedding_model: model },
21620
- record2
21852
+ const [saved] = await persistence.putMany(
21853
+ [
21854
+ {
21855
+ memory: {
21856
+ ...record2.memory,
21857
+ embedding: storedVector(vector),
21858
+ embedding_model: model
21859
+ },
21860
+ expectedVersion: record2
21861
+ }
21862
+ ],
21863
+ signal,
21864
+ leaseToken
21621
21865
  );
21622
21866
  notify("embedding", [saved]);
21623
21867
  repaired.add(record2.memory.memory_id);
21624
21868
  result.reembedded++;
21625
21869
  } catch (error) {
21870
+ signal?.throwIfAborted();
21871
+ if (isRemoteWorkLeaseExpired(error)) throw error;
21626
21872
  if (error instanceof RemoteMemoryError && error.code === "version_conflict")
21627
21873
  result.skippedConflict++;
21628
21874
  else result.failed++;
@@ -21631,19 +21877,28 @@ function createRemoteMemoryStore(options) {
21631
21877
  }
21632
21878
  return finish();
21633
21879
  };
21634
- const embedForRepair = async (targets) => {
21880
+ const repairEmbeddings = async (repairOptions = {}) => await withRemoteWorkLease(
21881
+ persistence,
21882
+ "embeddings",
21883
+ model,
21884
+ (token, signal) => repairEmbeddingsUnlocked(repairOptions, token, signal)
21885
+ ) ?? { scanned: 0, reembedded: 0, failed: 0, skippedConflict: 0, deferred: true };
21886
+ const embedForRepair = async (targets, signal) => {
21635
21887
  const texts = targets.map(({ memory }) => memory.content);
21636
21888
  try {
21637
- return await generateEmbeddings(texts, embeddingOptions);
21889
+ return await generateEmbeddings(texts, embeddingOptions, signal);
21638
21890
  } catch (error) {
21891
+ signal?.throwIfAborted();
21639
21892
  getLogger().warn("[memory/remote-store] Failed to embed memories for repair:", error);
21640
21893
  if (!rejectsInput(error)) return null;
21641
21894
  }
21642
21895
  const vectors = [];
21643
21896
  for (const [i, text14] of texts.entries()) {
21897
+ signal?.throwIfAborted();
21644
21898
  try {
21645
- [vectors[i]] = await generateEmbeddings([text14], embeddingOptions);
21899
+ [vectors[i]] = await generateEmbeddings([text14], embeddingOptions, signal);
21646
21900
  } catch (error) {
21901
+ signal?.throwIfAborted();
21647
21902
  if (!rejectsInput(error)) return null;
21648
21903
  unrepairable.add(targets[i].memory.memory_id);
21649
21904
  }
@@ -21651,6 +21906,26 @@ function createRemoteMemoryStore(options) {
21651
21906
  return vectors;
21652
21907
  };
21653
21908
  return {
21909
+ createTopic: async (name) => {
21910
+ const canonical = await createRemoteTopic(persistence, name);
21911
+ if (canonical) notify("edit", []);
21912
+ return canonical;
21913
+ },
21914
+ importTopics: async (topics) => {
21915
+ if (!topics.length) return;
21916
+ const existing = new Set((await persistence.topics()).map((topic2) => topic2.name));
21917
+ const missing = topics.flatMap((topic2) => {
21918
+ const name = normalizeEntityName(topic2.name);
21919
+ if (existing.has(name)) return [];
21920
+ existing.add(name);
21921
+ return [{ ...topic2, name }];
21922
+ });
21923
+ if (!missing.length) return;
21924
+ await persistence.importTopics(missing);
21925
+ for (const subscriber of [...subscribers])
21926
+ if (subscriber.options.topics) safely(subscriber.onChange);
21927
+ },
21928
+ counts: () => persistence.counts(),
21654
21929
  masksEmbeddingInput: embeddingOptions.maskInput !== void 0,
21655
21930
  topicMaintenance,
21656
21931
  repairEmbeddings,
@@ -21667,8 +21942,8 @@ function createRemoteMemoryStore(options) {
21667
21942
  include_quarantined: true,
21668
21943
  include_superseded: true
21669
21944
  })) {
21670
- for (const topic of parseTopics(memory.topics) ?? []) {
21671
- const name = normalizeEntityName(topic.name);
21945
+ for (const topic2 of parseTopics(memory.topics) ?? []) {
21946
+ const name = normalizeEntityName(topic2.name);
21672
21947
  if (!name) continue;
21673
21948
  const entry = topics.get(name) ?? {
21674
21949
  name,
@@ -21676,12 +21951,24 @@ function createRemoteMemoryStore(options) {
21676
21951
  memoryCount: 0,
21677
21952
  members: /* @__PURE__ */ new Set()
21678
21953
  };
21679
- entry.kind ?? (entry.kind = topic.kind ?? null);
21954
+ entry.kind ?? (entry.kind = topic2.kind ?? null);
21680
21955
  entry.members.add(memory.memory_id);
21681
21956
  topics.set(name, entry);
21682
21957
  }
21683
21958
  }
21684
- return [...topics.values()].map(({ members, ...topic }) => ({ ...topic, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
21959
+ for (const topic2 of await persistence.topics()) {
21960
+ if (topic2.is_deleted) continue;
21961
+ const current = topics.get(topic2.name);
21962
+ if (current) current.kind ?? (current.kind = topic2.kind ?? null);
21963
+ else
21964
+ topics.set(topic2.name, {
21965
+ name: topic2.name,
21966
+ kind: topic2.kind ?? null,
21967
+ memoryCount: 0,
21968
+ members: /* @__PURE__ */ new Set()
21969
+ });
21970
+ }
21971
+ return [...topics.values()].map(({ members, ...topic2 }) => ({ ...topic2, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
21685
21972
  },
21686
21973
  get: async (id) => asStored(await live(id)),
21687
21974
  listArchived: async () => (await enumerate({ archived_only: true, order: "archived", include_embeddings: true })).map(
@@ -21826,10 +22113,10 @@ function createRemoteMemoryStore(options) {
21826
22113
  if (!saved) return [];
21827
22114
  const now = /* @__PURE__ */ new Date();
21828
22115
  return mergeTopics([], added).map(
21829
- (topic) => ({
21830
- uniqueId: normalizeEntityName(topic.name),
21831
- canonicalName: normalizeEntityName(topic.name),
21832
- kind: topic.kind ?? null,
22116
+ (topic2) => ({
22117
+ uniqueId: normalizeEntityName(topic2.name),
22118
+ canonicalName: normalizeEntityName(topic2.name),
22119
+ kind: topic2.kind ?? null,
21833
22120
  createdAt: now,
21834
22121
  updatedAt: now
21835
22122
  })