@anuma/sdk 9.1.0 → 9.1.1

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.
package/dist/index.mjs CHANGED
@@ -1623,11 +1623,12 @@ function parseRetryAfterMs(header) {
1623
1623
 
1624
1624
  // src/lib/memory/store/remotePersistence.ts
1625
1625
  var RemoteMemoryError = class extends Error {
1626
- constructor(message, status, code, retryAfterMs) {
1626
+ constructor(message, status, code, retryAfterMs, rateLimitBucket) {
1627
1627
  super(message);
1628
1628
  this.status = status;
1629
1629
  this.code = code;
1630
1630
  this.retryAfterMs = retryAfterMs;
1631
+ this.rateLimitBucket = rateLimitBucket;
1631
1632
  this.name = "RemoteMemoryError";
1632
1633
  }
1633
1634
  };
@@ -1646,6 +1647,14 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
1646
1647
  throw new Error("timeoutMs must be a positive number");
1647
1648
  const root = base.href.replace(/\/$/, "") + apiPath;
1648
1649
  const fetcher = options.fetch ?? globalThis.fetch;
1650
+ const cooldowns = /* @__PURE__ */ new Map();
1651
+ const memoryBudget = (path, method) => {
1652
+ if (method === "PUT")
1653
+ return path === "/topics" ? "private-memory-import" : "private-memory-write";
1654
+ if (method === "POST" && ["/import", "/memories/batch", "/topics/import"].includes(path))
1655
+ return "private-memory-import";
1656
+ return "private-memory-read";
1657
+ };
1649
1658
  const request = async (path, init = {}, signal) => {
1650
1659
  const controller = new AbortController();
1651
1660
  let timedOut = false;
@@ -1669,6 +1678,25 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
1669
1678
  const token = await Promise.race([options.getToken(), aborted]);
1670
1679
  if (controller.signal.aborted) await aborted;
1671
1680
  if (!token) throw new RemoteMemoryError("Authentication required", 401);
1681
+ const pathname = path.split("?")[0];
1682
+ const route = `${init.method ?? "GET"} ${pathname}`;
1683
+ const budgets = ["*", route];
1684
+ if (apiPath === "/api/private-memories")
1685
+ budgets.push("private-memory-ip", memoryBudget(pathname, init.method ?? "GET"));
1686
+ for (const bucket of budgets) {
1687
+ const cooldown = cooldowns.get(bucket);
1688
+ if (!cooldown) continue;
1689
+ const remaining = cooldown.until - Date.now();
1690
+ if (remaining > 0)
1691
+ throw new RemoteMemoryError(
1692
+ cooldown.error.message,
1693
+ 429,
1694
+ cooldown.error.code,
1695
+ remaining,
1696
+ cooldown.error.rateLimitBucket
1697
+ );
1698
+ cooldowns.delete(bucket);
1699
+ }
1672
1700
  const response = await fetcher(root + path, {
1673
1701
  ...init,
1674
1702
  headers: {
@@ -1681,12 +1709,18 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
1681
1709
  if (!response.ok) {
1682
1710
  const body = await response.json().catch(() => null);
1683
1711
  const retryAfter = parseRetryAfterMs(response.headers.get("Retry-After"));
1684
- throw new RemoteMemoryError(
1712
+ const error = new RemoteMemoryError(
1685
1713
  record(body) && typeof body.detail === "string" ? body.detail : `Nearby request failed (${response.status})`,
1686
1714
  response.status,
1687
1715
  record(body) && typeof body.code === "string" ? body.code : void 0,
1688
- retryAfter ?? void 0
1716
+ retryAfter ?? void 0,
1717
+ record(body) && typeof body.rate_limit_bucket === "string" ? body.rate_limit_bucket : void 0
1689
1718
  );
1719
+ if (response.status === 429 && retryAfter !== null && retryAfter > 0 && (!error.code || error.code === "rate_limited")) {
1720
+ const bucket = error.rateLimitBucket && budgets.includes(error.rateLimitBucket) ? error.rateLimitBucket : record(body) && (body.limit_scope === "account" || body.limit_scope === "ip") && !error.rateLimitBucket ? "*" : route;
1721
+ cooldowns.set(bucket, { until: Date.now() + retryAfter, error });
1722
+ }
1723
+ throw error;
1690
1724
  }
1691
1725
  return await response.json();
1692
1726
  } catch (error) {
@@ -12118,11 +12118,12 @@ function isRemoteWorkLeaseExpired(error) {
12118
12118
 
12119
12119
  // src/lib/memory/store/remotePersistence.ts
12120
12120
  var RemoteMemoryError = class extends Error {
12121
- constructor(message, status, code, retryAfterMs) {
12121
+ constructor(message, status, code, retryAfterMs, rateLimitBucket) {
12122
12122
  super(message);
12123
12123
  this.status = status;
12124
12124
  this.code = code;
12125
12125
  this.retryAfterMs = retryAfterMs;
12126
+ this.rateLimitBucket = rateLimitBucket;
12126
12127
  this.name = "RemoteMemoryError";
12127
12128
  }
12128
12129
  };
@@ -12217,6 +12218,14 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
12217
12218
  throw new Error("timeoutMs must be a positive number");
12218
12219
  const root = base.href.replace(/\/$/, "") + apiPath;
12219
12220
  const fetcher = options.fetch ?? globalThis.fetch;
12221
+ const cooldowns = /* @__PURE__ */ new Map();
12222
+ const memoryBudget = (path, method) => {
12223
+ if (method === "PUT")
12224
+ return path === "/topics" ? "private-memory-import" : "private-memory-write";
12225
+ if (method === "POST" && ["/import", "/memories/batch", "/topics/import"].includes(path))
12226
+ return "private-memory-import";
12227
+ return "private-memory-read";
12228
+ };
12220
12229
  const request = async (path, init = {}, signal) => {
12221
12230
  const controller = new AbortController();
12222
12231
  let timedOut = false;
@@ -12240,6 +12249,25 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
12240
12249
  const token = await Promise.race([options.getToken(), aborted2]);
12241
12250
  if (controller.signal.aborted) await aborted2;
12242
12251
  if (!token) throw new RemoteMemoryError("Authentication required", 401);
12252
+ const pathname = path.split("?")[0];
12253
+ const route = `${init.method ?? "GET"} ${pathname}`;
12254
+ const budgets = ["*", route];
12255
+ if (apiPath === "/api/private-memories")
12256
+ budgets.push("private-memory-ip", memoryBudget(pathname, init.method ?? "GET"));
12257
+ for (const bucket of budgets) {
12258
+ const cooldown = cooldowns.get(bucket);
12259
+ if (!cooldown) continue;
12260
+ const remaining = cooldown.until - Date.now();
12261
+ if (remaining > 0)
12262
+ throw new RemoteMemoryError(
12263
+ cooldown.error.message,
12264
+ 429,
12265
+ cooldown.error.code,
12266
+ remaining,
12267
+ cooldown.error.rateLimitBucket
12268
+ );
12269
+ cooldowns.delete(bucket);
12270
+ }
12243
12271
  const response = await fetcher(root + path, {
12244
12272
  ...init,
12245
12273
  headers: {
@@ -12252,12 +12280,18 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
12252
12280
  if (!response.ok) {
12253
12281
  const body = await response.json().catch(() => null);
12254
12282
  const retryAfter = parseRetryAfterMs(response.headers.get("Retry-After"));
12255
- throw new RemoteMemoryError(
12283
+ const error = new RemoteMemoryError(
12256
12284
  record(body) && typeof body.detail === "string" ? body.detail : `Nearby request failed (${response.status})`,
12257
12285
  response.status,
12258
12286
  record(body) && typeof body.code === "string" ? body.code : void 0,
12259
- retryAfter ?? void 0
12287
+ retryAfter ?? void 0,
12288
+ record(body) && typeof body.rate_limit_bucket === "string" ? body.rate_limit_bucket : void 0
12260
12289
  );
12290
+ if (response.status === 429 && retryAfter !== null && retryAfter > 0 && (!error.code || error.code === "rate_limited")) {
12291
+ const bucket = error.rateLimitBucket && budgets.includes(error.rateLimitBucket) ? error.rateLimitBucket : record(body) && (body.limit_scope === "account" || body.limit_scope === "ip") && !error.rateLimitBucket ? "*" : route;
12292
+ cooldowns.set(bucket, { until: Date.now() + retryAfter, error });
12293
+ }
12294
+ throw error;
12261
12295
  }
12262
12296
  return await response.json();
12263
12297
  } catch (error) {
@@ -12497,6 +12531,7 @@ async function createRemoteMemoryPersistence(options) {
12497
12531
  return {
12498
12532
  ...createRemoteTopicPersistence(request),
12499
12533
  ...createRemoteWorkPersistence(request),
12534
+ supportsRealtimeChanges: account.realtime_changes === true,
12500
12535
  counts: async () => {
12501
12536
  const result = await request("/counts", {});
12502
12537
  if (!record(result) || ["live", "archived", "quarantined"].some(
@@ -25334,6 +25369,9 @@ var RemoteMemoryPartialCreateError = class extends Error {
25334
25369
  function rejectsInput(error) {
25335
25370
  return error instanceof EmbeddingHttpError && error.status !== void 0 && error.status >= 400 && error.status < 500 && error.status !== 429 && !isFatalEmbeddingError(error);
25336
25371
  }
25372
+ function rejectsMemoryWrite(error) {
25373
+ return error instanceof RemoteMemoryError && (error.status === 400 || error.status === 422) && (error.code === void 0 || error.code === "unknown_field");
25374
+ }
25337
25375
  function warnFailed(failed) {
25338
25376
  if (failed.length)
25339
25377
  getLogger().warn(
@@ -25421,16 +25459,47 @@ function createRemoteMemoryStore(options) {
25421
25459
  const { persistence, embeddingOptions } = options;
25422
25460
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
25423
25461
  const pollIntervalMs = options.pollIntervalMs ?? 6e4;
25462
+ const activity = options.activity ?? {
25463
+ isActive: () => (typeof document === "undefined" || document.visibilityState !== "hidden") && (typeof navigator === "undefined" || navigator.onLine !== false),
25464
+ subscribe: (onChange) => {
25465
+ if (typeof window === "undefined" || typeof document === "undefined") return () => {
25466
+ };
25467
+ document.addEventListener("visibilitychange", onChange);
25468
+ window.addEventListener("online", onChange);
25469
+ window.addEventListener("offline", onChange);
25470
+ return () => {
25471
+ document.removeEventListener("visibilitychange", onChange);
25472
+ window.removeEventListener("online", onChange);
25473
+ window.removeEventListener("offline", onChange);
25474
+ };
25475
+ }
25476
+ };
25424
25477
  const pipeline = createRemoteMemoryPipeline({
25425
25478
  ...options,
25426
25479
  graphRanking: options.graphRanking ?? ((query, _traverse, recallOptions) => topicGraphRanking(query, recallOptions)),
25427
25480
  temporalRanking: options.temporalRanking ?? ((query, now) => eventTimeRanking(query, now))
25428
25481
  });
25429
25482
  const subscribers = /* @__PURE__ */ new Set();
25483
+ const retainTopics = () => pollIntervalMs > 0 && [...subscribers].some((subscriber) => subscriber.options.topics);
25430
25484
  const seen = /* @__PURE__ */ new Map();
25485
+ const reads = /* @__PURE__ */ new Map();
25486
+ const metadataReads = /* @__PURE__ */ new Map();
25487
+ let topicSnapshot;
25488
+ let topicCatalog;
25489
+ const invalidateReads = () => {
25490
+ reads.clear();
25491
+ metadataReads.clear();
25492
+ topicSnapshot = void 0;
25493
+ topicCatalog = void 0;
25494
+ };
25431
25495
  let timer;
25496
+ let scheduledAt;
25497
+ let stopActivity;
25498
+ let stopChanges;
25499
+ let changesConnected = false;
25432
25500
  const notify = (change, records = []) => {
25433
- if (timer)
25501
+ invalidateReads();
25502
+ if (subscribers.size)
25434
25503
  for (const record2 of records) {
25435
25504
  const previous = seen.get(record2.memory.memory_id);
25436
25505
  if ((previous?.version ?? 0) + 1 === record2.version)
@@ -25454,8 +25523,37 @@ function createRemoteMemoryStore(options) {
25454
25523
  } while (cursor2 && records.length < max);
25455
25524
  return records.slice(0, max);
25456
25525
  };
25457
- const decrypted = (options2) => persistence.query(options2);
25458
- const metadata3 = (options2) => persistence.queryMetadata(options2);
25526
+ const shareRead = (inflight, options2, read) => {
25527
+ if (options2.signal) return read();
25528
+ const key = JSON.stringify(Object.entries(options2).sort(([a], [b]) => a.localeCompare(b)));
25529
+ const active = inflight.get(key);
25530
+ if (active) return active;
25531
+ const pending2 = read();
25532
+ inflight.set(key, pending2);
25533
+ const clear = () => {
25534
+ if (inflight.get(key) === pending2) inflight.delete(key);
25535
+ };
25536
+ void pending2.then(clear, clear);
25537
+ return pending2;
25538
+ };
25539
+ const decrypted = (options2) => shareRead(reads, options2, () => persistence.query(options2));
25540
+ const metadata3 = (options2) => shareRead(
25541
+ metadataReads,
25542
+ options2,
25543
+ () => persistence.queryMetadata(options2)
25544
+ );
25545
+ const readTopics = () => {
25546
+ if (topicCatalog) return topicCatalog;
25547
+ const pending2 = persistence.topics();
25548
+ topicCatalog = pending2;
25549
+ const clear = () => {
25550
+ if (topicCatalog === pending2) topicCatalog = void 0;
25551
+ };
25552
+ void pending2.then(() => {
25553
+ if (!retainTopics()) clear();
25554
+ }, clear);
25555
+ return pending2;
25556
+ };
25459
25557
  const enumerate = (filters, max = Infinity) => pages(decrypted, filters, max);
25460
25558
  const memoriesByTopics = async (names) => {
25461
25559
  const wanted = new Set(names.map(normalizeEntityName).filter(Boolean));
@@ -25909,11 +26007,11 @@ function createRemoteMemoryStore(options) {
25909
26007
  };
25910
26008
  const changesSince = async (cursor2, pending2) => {
25911
26009
  const records = [];
25912
- const failed = /* @__PURE__ */ new Set();
26010
+ const failed = /* @__PURE__ */ new Map();
25913
26011
  const collect = (page) => {
25914
26012
  warnFailed(page.failed);
25915
26013
  records.push(...page.items);
25916
- for (const failure of page.failed) failed.add(failure.memory_id);
26014
+ for (const failure of page.failed) failed.set(failure.memory_id, failure.version);
25917
26015
  };
25918
26016
  let resume = cursor2;
25919
26017
  let next;
@@ -25947,25 +26045,72 @@ function createRemoteMemoryStore(options) {
25947
26045
  let ready = false;
25948
26046
  let cursor;
25949
26047
  let pending = /* @__PURE__ */ new Set();
26048
+ let failedVersions = /* @__PURE__ */ new Map();
25950
26049
  let topicFingerprint;
25951
26050
  let polling = false;
26051
+ let pollRequested = false;
26052
+ let failures = 0;
26053
+ let retryAt = 0;
26054
+ const backoff = (error) => {
26055
+ failures++;
26056
+ retryAt = Date.now() + Math.max(
26057
+ error instanceof RemoteMemoryError ? error.retryAfterMs ?? 0 : 0,
26058
+ Math.min(pollIntervalMs * 2 ** Math.min(failures - 1, 10), 15 * 6e4)
26059
+ );
26060
+ };
26061
+ const schedulePoll = (delay, keepEarlier = false) => {
26062
+ const at = Math.max(Date.now() + delay, retryAt);
26063
+ if (keepEarlier && timer && scheduledAt !== void 0 && scheduledAt <= at) return;
26064
+ if (timer) clearTimeout(timer);
26065
+ timer = void 0;
26066
+ scheduledAt = void 0;
26067
+ if (!subscribers.size || pollIntervalMs <= 0 || !activity.isActive()) return;
26068
+ scheduledAt = at;
26069
+ timer = setTimeout(poll, Math.max(0, at - Date.now()));
26070
+ };
25952
26071
  const poll = () => {
25953
- if (polling) return;
26072
+ if (timer) clearTimeout(timer);
26073
+ timer = void 0;
26074
+ scheduledAt = void 0;
26075
+ if (!subscribers.size || pollIntervalMs <= 0 || !activity.isActive()) return;
26076
+ if (Date.now() < retryAt) {
26077
+ schedulePoll(0);
26078
+ return;
26079
+ }
26080
+ if (polling) {
26081
+ pollRequested = true;
26082
+ return;
26083
+ }
25954
26084
  polling = true;
26085
+ pollRequested = false;
25955
26086
  const current = generation;
25956
26087
  changesSince(cursor, pending).then(async ({ records, resume, failed }) => {
25957
26088
  let topicState;
26089
+ let topicFailed = false;
25958
26090
  if ([...subscribers].some((s) => s.options.topics))
25959
26091
  try {
25960
- topicState = JSON.stringify(
25961
- (await persistence.topics()).map((t) => [t.name, t.version])
25962
- );
26092
+ topicCatalog = void 0;
26093
+ topicState = JSON.stringify((await readTopics()).map((t) => [t.name, t.version]));
25963
26094
  } catch (error) {
26095
+ topicFailed = true;
26096
+ if (current === generation) backoff(error);
25964
26097
  getLogger().warn("[memory/remote-store] Topic subscription poll failed:", error);
25965
26098
  }
25966
26099
  if (current !== generation) return;
26100
+ if (!topicFailed) {
26101
+ failures = 0;
26102
+ retryAt = 0;
26103
+ }
26104
+ const failedChanged = [...failed].some(
26105
+ ([id2, version]) => failedVersions.get(id2) !== version
26106
+ );
26107
+ if (records.length || failedChanged || topicState !== void 0 && topicState !== topicFingerprint) {
26108
+ reads.clear();
26109
+ metadataReads.clear();
26110
+ topicSnapshot = void 0;
26111
+ }
25967
26112
  const changed = /* @__PURE__ */ new Set();
25968
- if (topicState !== void 0 && topicFingerprint !== void 0 && topicState !== topicFingerprint) {
26113
+ if (ready && failedChanged || topicState !== void 0 && topicFingerprint !== void 0 && topicState !== topicFingerprint) {
25969
26114
  for (const subscriber of subscribers)
25970
26115
  if (subscriber.options.topics) changed.add(subscriber);
25971
26116
  }
@@ -25982,15 +26127,40 @@ function createRemoteMemoryStore(options) {
25982
26127
  }
25983
26128
  ready = true;
25984
26129
  cursor = resume;
25985
- pending = failed;
26130
+ pending = new Set(failed.keys());
26131
+ failedVersions = failed;
25986
26132
  for (const subscriber of changed)
25987
26133
  if (subscribers.has(subscriber)) safely(subscriber.onChange);
25988
26134
  }).catch((error) => {
26135
+ if (current === generation) backoff(error);
25989
26136
  getLogger().warn("[memory/remote-store] Subscription poll failed:", error);
25990
26137
  }).finally(() => {
25991
26138
  polling = false;
26139
+ schedulePoll(
26140
+ current !== generation ? 0 : pollRequested ? 200 : changesConnected ? Math.max(pollIntervalMs, 5 * 6e4) : pollIntervalMs
26141
+ );
25992
26142
  });
25993
26143
  };
26144
+ const refreshActivity = () => {
26145
+ if (timer) clearTimeout(timer);
26146
+ timer = void 0;
26147
+ if (!activity.isActive()) {
26148
+ stopChanges?.();
26149
+ stopChanges = void 0;
26150
+ changesConnected = false;
26151
+ return;
26152
+ }
26153
+ if (!stopChanges && persistence.supportsRealtimeChanges && options.subscribeChanges)
26154
+ stopChanges = options.subscribeChanges((connected) => {
26155
+ changesConnected = connected;
26156
+ if (polling) {
26157
+ pollRequested = true;
26158
+ return;
26159
+ }
26160
+ schedulePoll(connected ? 200 : 0, true);
26161
+ });
26162
+ poll();
26163
+ };
25994
26164
  const unrepairable = /* @__PURE__ */ new Set();
25995
26165
  let repairCursor;
25996
26166
  const repairEmbeddingsUnlocked = async ({
@@ -26046,40 +26216,78 @@ function createRemoteMemoryStore(options) {
26046
26216
  result.failed += targets.length;
26047
26217
  return finish();
26048
26218
  }
26049
- for (const [i, record2] of targets.entries()) {
26219
+ const writes = targets.flatMap((record2, i) => {
26050
26220
  const vector = vectors[i];
26051
26221
  if (!vector) {
26052
26222
  result.failed++;
26053
- continue;
26223
+ return [];
26054
26224
  }
26055
- await serialized(record2.memory.memory_id, async () => {
26056
- signal?.throwIfAborted();
26057
- try {
26058
- const [saved] = await persistence.putMany(
26059
- [
26060
- {
26061
- memory: {
26062
- ...record2.memory,
26063
- embedding: storedVector(vector),
26064
- embedding_model: model
26065
- },
26066
- expectedVersion: record2
26067
- }
26068
- ],
26069
- signal,
26070
- leaseToken
26071
- );
26072
- notify("embedding", [saved]);
26073
- repaired.add(record2.memory.memory_id);
26074
- result.reembedded++;
26075
- } catch (error) {
26225
+ try {
26226
+ return [
26227
+ {
26228
+ memory: {
26229
+ ...record2.memory,
26230
+ embedding: storedVector(vector),
26231
+ embedding_model: model
26232
+ },
26233
+ expectedVersion: record2
26234
+ }
26235
+ ];
26236
+ } catch {
26237
+ result.failed++;
26238
+ return [];
26239
+ }
26240
+ });
26241
+ for (const batch of chunks2(writes, MAX_BATCH)) {
26242
+ const continueRepair = await serializedAll(
26243
+ batch.map(({ memory }) => memory.memory_id),
26244
+ async () => {
26076
26245
  signal?.throwIfAborted();
26077
- if (isRemoteWorkLeaseExpired(error)) throw error;
26078
- if (error instanceof RemoteMemoryError && error.code === "version_conflict")
26079
- result.skippedConflict++;
26080
- else result.failed++;
26246
+ const commit = async (slice) => {
26247
+ const saved = await persistence.putMany(slice, signal, leaseToken);
26248
+ for (const record2 of saved) repaired.add(record2.memory.memory_id);
26249
+ result.reembedded += saved.length;
26250
+ notify("embedding", saved);
26251
+ };
26252
+ try {
26253
+ await commit(batch);
26254
+ } catch (error) {
26255
+ signal?.throwIfAborted();
26256
+ if (isRemoteWorkLeaseExpired(error)) throw error;
26257
+ if (!(error instanceof RemoteMemoryError && error.code === "version_conflict") && !rejectsMemoryWrite(error)) {
26258
+ result.failed += batch.length;
26259
+ return false;
26260
+ }
26261
+ if (batch.length === 1) {
26262
+ if (rejectsMemoryWrite(error)) {
26263
+ unrepairable.add(batch[0].memory.memory_id);
26264
+ result.failed++;
26265
+ } else result.skippedConflict++;
26266
+ return true;
26267
+ }
26268
+ for (const [index, write] of batch.entries()) {
26269
+ signal?.throwIfAborted();
26270
+ try {
26271
+ await commit([write]);
26272
+ } catch (error2) {
26273
+ signal?.throwIfAborted();
26274
+ if (isRemoteWorkLeaseExpired(error2)) throw error2;
26275
+ if (error2 instanceof RemoteMemoryError && error2.code === "version_conflict")
26276
+ result.skippedConflict++;
26277
+ else if (rejectsMemoryWrite(error2)) {
26278
+ unrepairable.add(write.memory.memory_id);
26279
+ result.failed++;
26280
+ } else {
26281
+ result.failed += batch.length - index;
26282
+ return false;
26283
+ }
26284
+ }
26285
+ }
26286
+ }
26287
+ return true;
26081
26288
  }
26082
- });
26289
+ );
26290
+ if (!continueRepair) break;
26083
26291
  }
26084
26292
  return finish();
26085
26293
  };
@@ -26119,7 +26327,7 @@ function createRemoteMemoryStore(options) {
26119
26327
  },
26120
26328
  importTopics: async (topics) => {
26121
26329
  if (!topics.length) return;
26122
- const existing = new Set((await persistence.topics()).map((topic2) => topic2.name));
26330
+ const existing = new Set((await readTopics()).map((topic2) => topic2.name));
26123
26331
  const missing = topics.flatMap((topic2) => {
26124
26332
  const name = normalizeEntityName(topic2.name);
26125
26333
  if (existing.has(name)) return [];
@@ -26128,6 +26336,7 @@ function createRemoteMemoryStore(options) {
26128
26336
  });
26129
26337
  if (!missing.length) return;
26130
26338
  await persistence.importTopics(missing);
26339
+ invalidateReads();
26131
26340
  for (const subscriber of [...subscribers])
26132
26341
  if (subscriber.options.topics) safely(subscriber.onChange);
26133
26342
  },
@@ -26141,40 +26350,51 @@ function createRemoteMemoryStore(options) {
26141
26350
  listProjections: async (listOptions = {}) => (await listRows(metadata3, listOptions, listFilters(listOptions), maxOf(listOptions))).map(
26142
26351
  projection
26143
26352
  ),
26144
- listTopics: async () => {
26145
- const topics = /* @__PURE__ */ new Map();
26146
- for (const { memory } of await pages(metadata3, {
26147
- include_archived: true,
26148
- include_quarantined: true,
26149
- include_superseded: true
26150
- })) {
26151
- for (const topic2 of parseTopics(memory.topics) ?? []) {
26152
- const name = normalizeEntityName(topic2.name);
26153
- if (!name) continue;
26154
- const entry = topics.get(name) ?? {
26155
- name,
26156
- kind: null,
26157
- memoryCount: 0,
26158
- members: /* @__PURE__ */ new Set()
26159
- };
26160
- entry.kind ?? (entry.kind = topic2.kind ?? null);
26161
- entry.members.add(memory.memory_id);
26162
- topics.set(name, entry);
26163
- }
26164
- }
26165
- for (const topic2 of await persistence.topics()) {
26166
- if (topic2.is_deleted) continue;
26167
- const current = topics.get(topic2.name);
26168
- if (current) current.kind ?? (current.kind = topic2.kind ?? null);
26169
- else
26170
- topics.set(topic2.name, {
26171
- name: topic2.name,
26172
- kind: topic2.kind ?? null,
26173
- memoryCount: 0,
26174
- members: /* @__PURE__ */ new Set()
26175
- });
26176
- }
26177
- return [...topics.values()].map(({ members, ...topic2 }) => ({ ...topic2, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
26353
+ listTopics: () => {
26354
+ if (topicSnapshot) return topicSnapshot.then((topics) => structuredClone(topics));
26355
+ const snapshot = (async () => {
26356
+ const topics = /* @__PURE__ */ new Map();
26357
+ for (const { memory } of await pages(metadata3, {
26358
+ include_archived: true,
26359
+ include_quarantined: true,
26360
+ include_superseded: true
26361
+ })) {
26362
+ for (const topic2 of parseTopics(memory.topics) ?? []) {
26363
+ const name = normalizeEntityName(topic2.name);
26364
+ if (!name) continue;
26365
+ const entry = topics.get(name) ?? {
26366
+ name,
26367
+ kind: null,
26368
+ memoryCount: 0,
26369
+ members: /* @__PURE__ */ new Set()
26370
+ };
26371
+ entry.kind ?? (entry.kind = topic2.kind ?? null);
26372
+ entry.members.add(memory.memory_id);
26373
+ topics.set(name, entry);
26374
+ }
26375
+ }
26376
+ for (const topic2 of await readTopics()) {
26377
+ if (topic2.is_deleted) continue;
26378
+ const current = topics.get(topic2.name);
26379
+ if (current) current.kind ?? (current.kind = topic2.kind ?? null);
26380
+ else
26381
+ topics.set(topic2.name, {
26382
+ name: topic2.name,
26383
+ kind: topic2.kind ?? null,
26384
+ memoryCount: 0,
26385
+ members: /* @__PURE__ */ new Set()
26386
+ });
26387
+ }
26388
+ return [...topics.values()].map(({ members, ...topic2 }) => ({ ...topic2, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
26389
+ })();
26390
+ topicSnapshot = snapshot;
26391
+ const clear = () => {
26392
+ if (topicSnapshot === snapshot) topicSnapshot = void 0;
26393
+ };
26394
+ void snapshot.then(() => {
26395
+ if (!retainTopics()) clear();
26396
+ }, clear);
26397
+ return snapshot.then((topics) => structuredClone(topics));
26178
26398
  },
26179
26399
  get: async (id2) => asStored(await live(id2)),
26180
26400
  listArchived: async () => (await enumerate({ archived_only: true, order: "archived", include_embeddings: true })).map(
@@ -26355,20 +26575,23 @@ function createRemoteMemoryStore(options) {
26355
26575
  subscribe: (onChange, subscribeOptions = {}) => {
26356
26576
  const subscriber = { onChange, options: subscribeOptions };
26357
26577
  subscribers.add(subscriber);
26358
- if (pollIntervalMs > 0 && !timer) {
26359
- timer = setInterval(poll, pollIntervalMs);
26360
- poll();
26578
+ if (pollIntervalMs > 0 && subscribers.size === 1) {
26579
+ stopActivity = activity.subscribe(refreshActivity);
26580
+ refreshActivity();
26361
26581
  }
26362
26582
  return () => {
26363
26583
  subscribers.delete(subscriber);
26364
- if (subscribers.size || !timer) return;
26365
- clearInterval(timer);
26584
+ if (!retainTopics()) invalidateReads();
26585
+ if (subscribers.size) return;
26586
+ if (timer) clearTimeout(timer);
26366
26587
  timer = void 0;
26588
+ stopActivity?.();
26589
+ stopActivity = void 0;
26590
+ stopChanges?.();
26591
+ stopChanges = void 0;
26592
+ changesConnected = false;
26367
26593
  generation++;
26368
- seen.clear();
26369
- ready = false;
26370
- cursor = void 0;
26371
- pending = /* @__PURE__ */ new Set();
26594
+ invalidateReads();
26372
26595
  };
26373
26596
  }
26374
26597
  };
@@ -11607,7 +11607,8 @@ declare class RemoteMemoryError extends Error {
11607
11607
  readonly status: number;
11608
11608
  readonly code?: string | undefined;
11609
11609
  readonly retryAfterMs?: number | undefined;
11610
- constructor(message: string, status: number, code?: string | undefined, retryAfterMs?: number | undefined);
11610
+ readonly rateLimitBucket?: string | undefined;
11611
+ constructor(message: string, status: number, code?: string | undefined, retryAfterMs?: number | undefined, rateLimitBucket?: string | undefined);
11611
11612
  }
11612
11613
  /**
11613
11614
  * Remote persistence foundation for MemoryStore, not yet its recall/retain implementation.
@@ -11616,6 +11617,7 @@ declare class RemoteMemoryError extends Error {
11616
11617
  * Migration must have activated the account under the canonical key before construction.
11617
11618
  */
11618
11619
  interface RemoteMemoryPersistence extends RemoteTopicPersistence, RemoteMemoryWorkPersistence {
11620
+ supportsRealtimeChanges?: boolean;
11619
11621
  counts(): Promise<MemoryCounts>;
11620
11622
  temporal(window: {
11621
11623
  start: number;
@@ -11747,6 +11749,12 @@ interface RemoteMemoryStoreOptions {
11747
11749
  graphRanking?: RecallFactSource["graphRanking"];
11748
11750
  temporalRanking?: RecallFactSource["temporalRanking"];
11749
11751
  pollIntervalMs?: number;
11752
+ activity?: {
11753
+ isActive: () => boolean;
11754
+ subscribe: (onChange: () => void) => () => void;
11755
+ };
11756
+ /** Report connection state on subscribe/reconnect and true on each account memory change. */
11757
+ subscribeChanges?: (onChange: (connected: boolean) => void) => () => void;
11750
11758
  }
11751
11759
  declare class RemoteMemoryPartialCreateError extends Error {
11752
11760
  readonly created: StoredVaultMemory[];