@anuma/sdk 9.1.4 → 9.1.6

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.
@@ -875,7 +875,8 @@ async function callPortalJsonCompletion(req) {
875
875
  }
876
876
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
877
877
  const startedAt = Date.now();
878
- const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
878
+ const deadlineAt = req.totalTimeoutMs === void 0 ? void 0 : startedAt + req.totalTimeoutMs;
879
+ const overBudget = () => deadlineAt !== void 0 && Date.now() >= deadlineAt;
879
880
  let lastFailure;
880
881
  let reinforce = false;
881
882
  const giveUp = () => {
@@ -888,17 +889,11 @@ async function callPortalJsonCompletion(req) {
888
889
  lastFailure = { reason: "time-budget-exhausted", attempts: attempt - 1 };
889
890
  break;
890
891
  }
891
- const attemptReq = req.totalTimeoutMs !== void 0 ? {
892
- ...req,
893
- timeoutMs: Math.max(
894
- 1,
895
- Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
896
- ),
897
- ...reinforce && { reinforceJsonContract: true }
898
- } : reinforce ? { ...req, reinforceJsonContract: true } : req;
892
+ const attemptReq = reinforce ? { ...req, reinforceJsonContract: true } : req;
899
893
  const outcome2 = await attemptPortalJson(
900
894
  { ...attemptReq, transport },
901
- endpoint
895
+ endpoint,
896
+ deadlineAt
902
897
  );
903
898
  if (outcome2.kind === "ok") {
904
899
  req.onAttempt?.({ attempt, ok: true });
@@ -938,13 +933,38 @@ async function callPortalJsonCompletion(req) {
938
933
  }
939
934
  return giveUp();
940
935
  }
941
- async function attemptPortalJson(req, endpoint) {
936
+ async function attemptPortalJson(req, endpoint, deadlineAt) {
937
+ const controller = new AbortController();
938
+ if (deadlineAt === void 0 || deadlineAt - Date.now() > MAX_TIMER_DELAY_MS) {
939
+ return requestPortalJson(req, endpoint, controller);
940
+ }
941
+ const remainingMs = deadlineAt - Date.now();
942
+ if (remainingMs <= 0) return TIME_BUDGET_EXHAUSTED;
943
+ let timer;
944
+ try {
945
+ const outcome2 = await Promise.race([
946
+ new Promise((resolve) => {
947
+ timer = setTimeout(() => {
948
+ resolve(TIME_BUDGET_EXHAUSTED);
949
+ controller.abort();
950
+ }, remainingMs);
951
+ }),
952
+ requestPortalJson(req, endpoint, controller, deadlineAt)
953
+ ]);
954
+ return Date.now() >= deadlineAt ? TIME_BUDGET_EXHAUSTED : outcome2;
955
+ } finally {
956
+ clearTimeout(timer);
957
+ }
958
+ }
959
+ async function requestPortalJson(req, endpoint, controller, deadlineAt) {
942
960
  const baseUrl = req.baseUrl ?? defaultBaseUrl();
943
961
  const fetchImpl = req.fetchFn ?? fetch;
944
- const timeoutMs = req.timeoutMs ?? 6e4;
945
962
  const authHeaders = await resolvePortalAuthHeaders(req, req.tag);
963
+ const remainingMs = deadlineAt === void 0 ? Infinity : deadlineAt - Date.now();
964
+ if (controller.signal.aborted || remainingMs <= 0) return TIME_BUDGET_EXHAUSTED;
946
965
  if (authHeaders === null)
947
966
  return { kind: "terminal", code: "auth-unavailable", reason: "auth unavailable (no token)" };
967
+ const timeoutMs = Math.min(req.timeoutMs ?? 6e4, remainingMs);
948
968
  const usesPrefill = req.transport !== "responses" && req.model.startsWith("anthropic/");
949
969
  const prefill = usesPrefill ? "{" : "";
950
970
  const messages = [
@@ -954,7 +974,6 @@ async function attemptPortalJson(req, endpoint) {
954
974
  ];
955
975
  if (prefill) messages.push({ role: "assistant", content: prefill });
956
976
  const supportsJsonObjectFormat = supportsResponseFormat(req.model);
957
- const controller = new AbortController();
958
977
  const timer = setTimeout(() => controller.abort(), timeoutMs);
959
978
  const requestBody = req.transport === "responses" ? {
960
979
  model: req.model,
@@ -1139,7 +1158,7 @@ function extractResponsesContent(body) {
1139
1158
  const text14 = root.output.filter((item) => asRecord(item)?.type === "message").flatMap((item) => Array.isArray(item.content) ? item.content : []).map((part) => asRecord(part)?.text).filter((t) => typeof t === "string").join("");
1140
1159
  return text14;
1141
1160
  }
1142
- var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RESPONSES_ONLY_MODEL_PREFIXES, CHAT_TO_RESPONSES_FIELDS, FOREIGN_ENDPOINT_SUFFIX, RETRYABLE_HTTP;
1161
+ var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RESPONSES_ONLY_MODEL_PREFIXES, CHAT_TO_RESPONSES_FIELDS, FOREIGN_ENDPOINT_SUFFIX, TIME_BUDGET_EXHAUSTED, MAX_TIMER_DELAY_MS, RETRYABLE_HTTP;
1143
1162
  var init_portalLlm = __esm({
1144
1163
  "src/lib/memory/portalLlm.ts"() {
1145
1164
  "use strict";
@@ -1159,6 +1178,12 @@ var init_portalLlm = __esm({
1159
1178
  chat: "/responses",
1160
1179
  responses: "/chat/completions"
1161
1180
  };
1181
+ TIME_BUDGET_EXHAUSTED = {
1182
+ kind: "terminal",
1183
+ code: "time-budget-exhausted",
1184
+ reason: "time budget exhausted"
1185
+ };
1186
+ MAX_TIMER_DELAY_MS = 2147483647;
1162
1187
  RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
1163
1188
  }
1164
1189
  });
@@ -2338,15 +2363,16 @@ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, sig
2338
2363
  if (!parsed) {
2339
2364
  return false;
2340
2365
  }
2366
+ const startEpoch = sessionEpoch;
2341
2367
  const cached = refreshCandidatesByAddress.get(walletAddress);
2342
2368
  if (cached) {
2343
- return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
2369
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached, startEpoch);
2344
2370
  }
2345
2371
  let derivePromise = pendingCandidateDerives.get(walletAddress);
2346
2372
  if (!derivePromise) {
2347
2373
  derivePromise = (async () => {
2348
2374
  const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
2349
- if (candidates2) {
2375
+ if (candidates2 && sessionEpoch === startEpoch) {
2350
2376
  refreshCandidatesByAddress.set(walletAddress, candidates2);
2351
2377
  }
2352
2378
  return candidates2;
@@ -2358,10 +2384,10 @@ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, sig
2358
2384
  pendingCandidateDerives.set(walletAddress, derivePromise);
2359
2385
  }
2360
2386
  const candidates = await derivePromise;
2361
- if (!candidates) {
2387
+ if (!candidates || sessionEpoch !== startEpoch) {
2362
2388
  return false;
2363
2389
  }
2364
- return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
2390
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates, startEpoch);
2365
2391
  }
2366
2392
  function parsePrefixedCiphertext(probeCiphertext) {
2367
2393
  const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
@@ -2404,7 +2430,7 @@ async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
2404
2430
  }
2405
2431
  return { legacy: legacyKey, current: currentKey };
2406
2432
  }
2407
- async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
2433
+ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates, startEpoch) {
2408
2434
  const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
2409
2435
  const keyBytes = hexToBytes(candidateHex);
2410
2436
  const candidateCryptoKey = await crypto.subtle.importKey(
@@ -2422,6 +2448,9 @@ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidat
2422
2448
  );
2423
2449
  return false;
2424
2450
  }
2451
+ if (sessionEpoch !== startEpoch) {
2452
+ return false;
2453
+ }
2425
2454
  setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
2426
2455
  cryptoKeyCache.delete(`${walletAddress}:v2`);
2427
2456
  cryptoKeyCache.delete(`${walletAddress}:v3`);
@@ -16784,6 +16813,16 @@ async function updateVaultMemoryOp(ctx, id2, opts) {
16784
16813
  const probe = await ctx.vaultMemoryCollection.find(id2);
16785
16814
  if (probe.isDeleted || probe.supersededBy || !isOwnedByCtxUser(ctx, probe)) return null;
16786
16815
  if (opts.freeFormOnly && isKinded(probe)) return null;
16816
+ const readStoredContent = probe._getRaw("content");
16817
+ if (opts.expectedContent !== void 0) {
16818
+ const current = await vaultMemoryToStored(
16819
+ probe,
16820
+ ctx.walletAddress,
16821
+ ctx.signMessage,
16822
+ ctx.embeddedWalletSigner
16823
+ );
16824
+ if (current.content !== opts.expectedContent) return null;
16825
+ }
16787
16826
  const levelAfter = (current) => {
16788
16827
  if (opts.level !== void 0) return opts.level;
16789
16828
  if (opts.scope === void 0) return resolveMemoryLevel(current.level, current.scope);
@@ -16791,7 +16830,7 @@ async function updateVaultMemoryOp(ctx, id2, opts) {
16791
16830
  };
16792
16831
  assertValidKindLevel(opts.kind !== void 0 ? opts.kind : probe.kind, levelAfter(probe));
16793
16832
  const encryptedKindValue = await encryptKindValue(ctx, opts.kindValue);
16794
- const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
16833
+ const encryptedContent = opts.content !== void 0 && ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
16795
16834
  opts.content,
16796
16835
  ctx.walletAddress,
16797
16836
  ctx.signMessage,
@@ -16799,13 +16838,13 @@ async function updateVaultMemoryOp(ctx, id2, opts) {
16799
16838
  ) : opts.content;
16800
16839
  let stale = false;
16801
16840
  const record2 = probe;
16802
- const originalUpdatedAt = record2.updatedAt.getTime();
16803
16841
  await ctx.database.write(async () => {
16804
16842
  await assertCanWrite2(ctx);
16805
- if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
16843
+ if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2) || opts.expectedContent !== void 0 && record2._getRaw("content") !== readStoredContent) {
16806
16844
  stale = true;
16807
16845
  return;
16808
16846
  }
16847
+ const originalUpdatedAt = record2.updatedAt.getTime();
16809
16848
  const nextKind = opts.kind !== void 0 ? opts.kind : record2.kind;
16810
16849
  const nextLevel = levelAfter(record2);
16811
16850
  assertValidKindLevel(nextKind, nextLevel);
@@ -16821,7 +16860,7 @@ async function updateVaultMemoryOp(ctx, id2, opts) {
16821
16860
  replayedObservation = opts.observationSourceIds.every((id3) => observedSources.includes(id3));
16822
16861
  }
16823
16862
  await record2.update((r) => {
16824
- r._setRaw("content", encryptedContent);
16863
+ if (encryptedContent !== void 0) r._setRaw("content", encryptedContent);
16825
16864
  if (opts.level !== void 0) {
16826
16865
  r._setRaw("level", nextLevel);
16827
16866
  r._setRaw("scope", scopeForLevel(nextLevel));
@@ -17589,7 +17628,7 @@ function classifyDecay(m, now, policy) {
17589
17628
  if (m.source === "manual" || m.source === SOURCE_PHOTO) return "keep";
17590
17629
  if (isTimeSensitiveType(m.factType) && m.eventTimeEnd !== null) {
17591
17630
  if (m.eventTimeEnd < now - resolved.pastEventGraceMs) return "archive";
17592
- if (m.eventTimeEnd >= now) return "keep";
17631
+ return "keep";
17593
17632
  }
17594
17633
  if (now - lastActivityAt(m) > ttlForType(m.factType, resolved)) return "archive";
17595
17634
  return "keep";
@@ -19571,6 +19610,104 @@ function validate(parsed, originalQuery) {
19571
19610
  return { mode, subQueries };
19572
19611
  }
19573
19612
 
19613
+ // src/lib/memoryVault/vaultPreparation.ts
19614
+ function createVaultPreparation() {
19615
+ return { corpora: /* @__PURE__ */ new WeakMap(), rows: /* @__PURE__ */ new WeakMap() };
19616
+ }
19617
+ var pendingRows = /* @__PURE__ */ new WeakMap();
19618
+ function loadVaultCorpus(ctx, options, preparation) {
19619
+ if (!preparation) return getAllVaultMemoriesOp(ctx, options);
19620
+ const corpusCache = preparation.corpora;
19621
+ let pending = corpusCache.get(ctx);
19622
+ if (!pending) {
19623
+ pending = /* @__PURE__ */ new Map();
19624
+ corpusCache.set(ctx, pending);
19625
+ }
19626
+ const key = JSON.stringify(options ?? {});
19627
+ let request = pending.get(key);
19628
+ if (!request) {
19629
+ const entries = pending;
19630
+ request = getAllVaultMemoriesOp(ctx, options).catch((error) => {
19631
+ entries.delete(key);
19632
+ throw error;
19633
+ });
19634
+ pending.set(key, request);
19635
+ }
19636
+ return request;
19637
+ }
19638
+ function rowRequestsFor(registry2, ctx, maskInput) {
19639
+ let byMask = registry2.get(ctx);
19640
+ if (!byMask) {
19641
+ byMask = /* @__PURE__ */ new Map();
19642
+ registry2.set(ctx, byMask);
19643
+ }
19644
+ let requests = byMask.get(maskInput);
19645
+ if (!requests) {
19646
+ requests = /* @__PURE__ */ new Map();
19647
+ byMask.set(maskInput, requests);
19648
+ }
19649
+ return requests;
19650
+ }
19651
+ function embedVaultRows(ctx, options, rows, preparation) {
19652
+ const pending = rowRequestsFor(pendingRows, ctx, options.maskInput);
19653
+ const prepared = preparation && rowRequestsFor(preparation.rows, ctx, options.maskInput);
19654
+ const model = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
19655
+ const keys = rows.map(
19656
+ (row) => JSON.stringify([
19657
+ row.uniqueId,
19658
+ row.updatedAt?.getTime(),
19659
+ row.content,
19660
+ model,
19661
+ options.baseUrl,
19662
+ options.timeoutMs,
19663
+ options.tokenTimeoutMs,
19664
+ options.totalTimeoutMs
19665
+ ])
19666
+ );
19667
+ const missing = /* @__PURE__ */ new Map();
19668
+ rows.forEach((row, index) => {
19669
+ if (!pending.has(keys[index]) && !prepared?.has(keys[index])) missing.set(keys[index], row);
19670
+ });
19671
+ if (missing.size > 0) {
19672
+ const entries = pending;
19673
+ const batch = Promise.resolve().then(
19674
+ () => generateEmbeddings(
19675
+ [...missing.values()].map((row) => row.content),
19676
+ options
19677
+ )
19678
+ );
19679
+ [...missing].forEach(([key, row], index) => {
19680
+ const request = batch.then((vectors) => {
19681
+ const vector = vectors[index];
19682
+ updateVaultMemoryEmbeddingOp(ctx, row.uniqueId, JSON.stringify(vector), model, {
19683
+ content: row.content,
19684
+ updatedAt: row.updatedAt?.getTime()
19685
+ }).catch(() => {
19686
+ });
19687
+ return vector;
19688
+ }).finally(() => {
19689
+ entries.delete(key);
19690
+ if (entries.size === 0) pendingRows.get(ctx)?.delete(options.maskInput);
19691
+ });
19692
+ pending.set(key, request);
19693
+ });
19694
+ }
19695
+ return Promise.all(
19696
+ keys.map((key) => {
19697
+ const cached = prepared?.get(key);
19698
+ if (cached) return cached;
19699
+ const request = pending.get(key);
19700
+ if (!prepared) return request;
19701
+ const retained = request.catch((error) => {
19702
+ prepared.delete(key);
19703
+ throw error;
19704
+ });
19705
+ prepared.set(key, retained);
19706
+ return retained;
19707
+ })
19708
+ );
19709
+ }
19710
+
19574
19711
  // src/lib/memoryVault/vectorVersion.ts
19575
19712
  var vectorRowVersion = /* @__PURE__ */ new WeakMap();
19576
19713
  function contentFingerprint(content) {
@@ -20133,11 +20270,11 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
20133
20270
  return combined.slice(0, limit);
20134
20271
  }
20135
20272
  async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options) {
20136
- const memories = await getAllVaultMemoriesOp(vaultCtx, options);
20273
+ const memories = await loadVaultCorpus(vaultCtx, options);
20137
20274
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
20138
20275
  const uncachedTexts = [];
20139
20276
  const uncachedKeys = [];
20140
- const uncachedIds = [];
20277
+ const uncachedRows = [];
20141
20278
  const uncachedVersions = [];
20142
20279
  for (const m of memories) {
20143
20280
  const content = m.content;
@@ -20156,12 +20293,12 @@ async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options)
20156
20293
  }
20157
20294
  uncachedTexts.push(m.content);
20158
20295
  uncachedKeys.push(m.uniqueId);
20159
- uncachedIds.push(m.uniqueId);
20296
+ uncachedRows.push(m);
20160
20297
  uncachedVersions.push(m.updatedAt);
20161
20298
  }
20162
20299
  }
20163
20300
  if (uncachedTexts.length > 0) {
20164
- const embeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
20301
+ const embeddings = await embedVaultRows(vaultCtx, embeddingOptions, uncachedRows);
20165
20302
  for (let i = 0; i < uncachedKeys.length; i++) {
20166
20303
  cacheRowVector(
20167
20304
  cache,
@@ -20170,13 +20307,6 @@ async function preEmbedVaultMemories(vaultCtx, embeddingOptions, cache, options)
20170
20307
  uncachedVersions[i],
20171
20308
  uncachedTexts[i]
20172
20309
  );
20173
- updateVaultMemoryEmbeddingOp(
20174
- vaultCtx,
20175
- uncachedIds[i],
20176
- JSON.stringify(embeddings[i]),
20177
- currentModel
20178
- ).catch(() => {
20179
- });
20180
20310
  }
20181
20311
  }
20182
20312
  }
@@ -20318,10 +20448,7 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
20318
20448
  let laneVecs;
20319
20449
  if (opts.embedStats) opts.embedStats.rowsEmbedded += laneRows.length;
20320
20450
  try {
20321
- laneVecs = await generateEmbeddings(
20322
- laneRows.map((m) => m.content),
20323
- embeddingOptions
20324
- );
20451
+ laneVecs = await embedVaultRows(vaultCtx, embeddingOptions, laneRows);
20325
20452
  } catch (err) {
20326
20453
  laneEmbedFailed = true;
20327
20454
  getLogger().warn(
@@ -20335,13 +20462,6 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
20335
20462
  cacheRowVector(cache, m.uniqueId, vec, m.updatedAt, m.content);
20336
20463
  resolvedThisCall.add(m.uniqueId);
20337
20464
  vectored.push({ uniqueId: m.uniqueId, embedding: vec, updatedAt: m.updatedAt });
20338
- updateVaultMemoryEmbeddingOp(
20339
- vaultCtx,
20340
- m.uniqueId,
20341
- JSON.stringify(vecs[i]),
20342
- currentModel
20343
- ).catch(() => {
20344
- });
20345
20465
  });
20346
20466
  }
20347
20467
  }
@@ -20533,9 +20653,10 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
20533
20653
  };
20534
20654
  }
20535
20655
  } else {
20536
- const loaded = await getAllVaultMemoriesOp(
20656
+ const loaded = await loadVaultCorpus(
20537
20657
  vaultCtx,
20538
- Object.keys(queryOpts).length > 0 ? queryOpts : void 0
20658
+ Object.keys(queryOpts).length > 0 ? queryOpts : void 0,
20659
+ searchOptions?.vaultPreparation
20539
20660
  );
20540
20661
  vaultSize = loaded.length;
20541
20662
  rowsDecrypted = loaded.length;
@@ -20608,7 +20729,12 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
20608
20729
  let newEmbeddings;
20609
20730
  embedStats.rowsEmbedded += uncachedTexts.length;
20610
20731
  try {
20611
- newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
20732
+ newEmbeddings = await embedVaultRows(
20733
+ vaultCtx,
20734
+ embeddingOptions,
20735
+ uncachedIndices.map((index) => memories[index]),
20736
+ searchOptions?.vaultPreparation
20737
+ );
20612
20738
  } catch (err) {
20613
20739
  embeddingFailure = true;
20614
20740
  getLogger().warn(
@@ -20624,13 +20750,6 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
20624
20750
  memories[uncachedIndices[j]].updatedAt,
20625
20751
  memories[uncachedIndices[j]].content
20626
20752
  );
20627
- updateVaultMemoryEmbeddingOp(
20628
- vaultCtx,
20629
- memories[uncachedIndices[j]].uniqueId,
20630
- JSON.stringify(newEmbeddings[j]),
20631
- currentModel
20632
- ).catch(() => {
20633
- });
20634
20753
  }
20635
20754
  }
20636
20755
  }
@@ -21035,7 +21154,6 @@ async function retainWithPersistence(content, ctx, options = {}) {
21035
21154
  const factTypeUpdate = pickFactTypeUpdate(existing, options.factType);
21036
21155
  const resurrect = resurrectFields(existing);
21037
21156
  const updated = await ctx.persistence.update(targetId, {
21038
- content: existing.content,
21039
21157
  freeFormOnly: true,
21040
21158
  proofCountIncrement: 1,
21041
21159
  observationSourceIds: options.sourceChunkIds,
@@ -21274,7 +21392,6 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
21274
21392
  const factTypeUpdate = pickFactTypeUpdate(existing, options.factType);
21275
21393
  const resurrect = resurrectFields(existing);
21276
21394
  const updated = await ctx.persistence.update(decision.targetId, {
21277
- content: existing.content,
21278
21395
  freeFormOnly: true,
21279
21396
  proofCountIncrement: 1,
21280
21397
  observationSourceIds: options.sourceChunkIds,
@@ -21302,6 +21419,10 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
21302
21419
  };
21303
21420
  }
21304
21421
  if (decision.action === "update" && decision.targetId && decision.content) {
21422
+ const expectedContent = candidates.find(
21423
+ (candidate) => candidate.id === decision.targetId
21424
+ )?.content;
21425
+ if (expectedContent === void 0) return null;
21305
21426
  const existing = await ctx.persistence.get(decision.targetId);
21306
21427
  if (!existing || existing.supersededBy) {
21307
21428
  return abandonToRace(
@@ -21309,6 +21430,9 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
21309
21430
  `update: target ${decision.targetId} ${existing ? "superseded" : "deleted"} before the write`
21310
21431
  );
21311
21432
  }
21433
+ if (existing.content !== expectedContent) {
21434
+ return abandonToRace(options, `update: target ${decision.targetId} edited before the write`);
21435
+ }
21312
21436
  const mergedSourceIds = unionStrings(
21313
21437
  existing.sourceChunkIds ?? [],
21314
21438
  options.sourceChunkIds ?? []
@@ -21321,6 +21445,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
21321
21445
  const resurrect = resurrectFields(existing);
21322
21446
  const updated = await ctx.persistence.update(decision.targetId, {
21323
21447
  content: decision.content,
21448
+ expectedContent,
21324
21449
  freeFormOnly: true,
21325
21450
  proofCountIncrement: 1,
21326
21451
  observationSourceIds: options.sourceChunkIds,
@@ -21333,10 +21458,10 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
21333
21458
  ...factTypeUpdate !== void 0 && { factType: factTypeUpdate }
21334
21459
  });
21335
21460
  if (!updated) {
21336
- await assertMergeTargetGoneOrThrow(ctx, decision.targetId);
21461
+ await assertMergeTargetGoneOrThrow(ctx, decision.targetId, expectedContent);
21337
21462
  return abandonToRace(
21338
21463
  options,
21339
- `update: target ${decision.targetId} deleted mid-write (consolidated rewrite lost)`
21464
+ `update: target ${decision.targetId} changed mid-write (consolidated rewrite lost)`
21340
21465
  );
21341
21466
  }
21342
21467
  cacheRowVector(
@@ -21363,9 +21488,9 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
21363
21488
  );
21364
21489
  return null;
21365
21490
  }
21366
- async function assertMergeTargetGoneOrThrow(ctx, targetId) {
21491
+ async function assertMergeTargetGoneOrThrow(ctx, targetId, expectedContent) {
21367
21492
  const stillExists = await (ctx.persistence.getFresh ? ctx.persistence.getFresh(targetId) : ctx.persistence.get(targetId));
21368
- if (stillExists && (stillExists.kind === null || stillExists.kind === void 0)) {
21493
+ if (stillExists && (stillExists.kind === null || stillExists.kind === void 0) && (expectedContent === void 0 || stillExists.content === expectedContent)) {
21369
21494
  throw new RetainError(
21370
21495
  "merge-not-persisted",
21371
21496
  `retain: merge into memory ${targetId} failed to persist`
@@ -23211,6 +23336,7 @@ async function recall(query, ctx, options = {}) {
23211
23336
  },
23212
23337
  ...options.rrfK !== void 0 && { rrfK: options.rrfK },
23213
23338
  ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
23339
+ ...options.vaultPreparation && { vaultPreparation: options.vaultPreparation },
23214
23340
  ...subQueries && { subQueries },
23215
23341
  ...options.scopes && { scopes: options.scopes },
23216
23342
  ...options.folderId !== void 0 && { folderId: options.folderId },
@@ -24802,16 +24928,16 @@ function createRemoteMemoryPipeline(options) {
24802
24928
  create: (input) => write(createRow(input), 0),
24803
24929
  update: async (id2, patch) => {
24804
24930
  const existing = await get(id2);
24805
- if (!existing || patch.freeFormOnly && existing.kind !== null && existing.kind !== void 0)
24931
+ if (!existing || patch.expectedContent !== void 0 && existing.content !== patch.expectedContent || patch.freeFormOnly && existing.kind !== null && existing.kind !== void 0)
24806
24932
  return null;
24807
24933
  const snapshot = snapshots.get(id2);
24808
24934
  const memory = {
24809
24935
  ...snapshot.memory,
24810
- content: patch.content,
24936
+ content: patch.content ?? snapshot.memory.content,
24811
24937
  updated_at: patch.preserveUpdatedAt ? snapshot.memory.updated_at : Math.max(Date.now(), (snapshot.memory.topics_extracted_at ?? 0) + 1)
24812
24938
  };
24813
24939
  if (patch.scope !== void 0) memory.scope = patch.scope;
24814
- if (patch.embedding === void 0 && patch.content !== snapshot.memory.content) {
24940
+ if (patch.embedding === void 0 && patch.content !== void 0 && patch.content !== snapshot.memory.content) {
24815
24941
  delete memory.embedding;
24816
24942
  delete memory.embedding_model;
24817
24943
  }
@@ -26816,13 +26942,20 @@ async function synthesizeProfile(ctx, options = {}) {
26816
26942
  }
26817
26943
  }
26818
26944
  }
26945
+ const vaultPreparation = createVaultPreparation();
26819
26946
  const settled = await Promise.allSettled(
26820
26947
  facets.map(async (facet) => {
26821
26948
  const prior = previous?.sections.find((s) => s.key === facet.key);
26822
26949
  if (prior && !staleKeys.has(facet.key)) {
26823
26950
  return prior;
26824
26951
  }
26825
- return synthesizeFacet(facet, recallCtx, options, fallbackPriors.get(facet.key));
26952
+ return synthesizeFacet(
26953
+ facet,
26954
+ recallCtx,
26955
+ options,
26956
+ fallbackPriors.get(facet.key),
26957
+ vaultPreparation
26958
+ );
26826
26959
  })
26827
26960
  );
26828
26961
  const sections = settled.map((r, i) => {
@@ -26976,7 +27109,7 @@ async function attributeFacts(ctx, candidates, facets) {
26976
27109
  }
26977
27110
  return keys;
26978
27111
  }
26979
- async function synthesizeFacet(facet, ctx, options, prior) {
27112
+ async function synthesizeFacet(facet, ctx, options, prior, vaultPreparation) {
26980
27113
  const scopes = options.scopes ?? DEFAULT_SCOPES;
26981
27114
  const limit = options.limit ?? DEFAULT_FACET_RECALL_LIMIT;
26982
27115
  const factTypeWeights = { ...DEFAULT_PROFILE_FACT_TYPE_WEIGHTS, ...options.factTypeWeights };
@@ -26993,6 +27126,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
26993
27126
  };
26994
27127
  }
26995
27128
  const recalled = await recall(facet.query, ctx, {
27129
+ vaultPreparation,
26996
27130
  scopes,
26997
27131
  limit: hasReviewGate ? RECALL_MAX_LIMIT : limit,
26998
27132
  types: ["fact"],
@@ -38557,17 +38691,24 @@ var import_react20 = require("react");
38557
38691
  init_clientConfig();
38558
38692
  function useTools(options) {
38559
38693
  const { getToken, baseUrl = BASE_URL, includeTools, autoFetch = true } = options;
38560
- const [tools, setTools] = (0, import_react20.useState)([]);
38694
+ const [allTools, setAllTools] = (0, import_react20.useState)([]);
38561
38695
  const [checksum, setChecksum] = (0, import_react20.useState)(getToolsChecksum2);
38562
38696
  const [isLoading, setIsLoading] = (0, import_react20.useState)(false);
38563
38697
  const [error, setError] = (0, import_react20.useState)(null);
38698
+ const includeToolsRef = (0, import_react20.useRef)(includeTools);
38699
+ if (includeTools !== includeToolsRef.current && (includeTools === void 0 || includeToolsRef.current === void 0 || includeTools.length !== includeToolsRef.current.length || includeTools.some((name, i) => name !== includeToolsRef.current?.[i]))) {
38700
+ includeToolsRef.current = includeTools;
38701
+ }
38702
+ const stableIncludeTools = includeToolsRef.current;
38703
+ const tools = (0, import_react20.useMemo)(
38704
+ () => filterServerTools(allTools, stableIncludeTools),
38705
+ [allTools, stableIncludeTools]
38706
+ );
38564
38707
  const getTokenRef = (0, import_react20.useRef)(getToken);
38565
38708
  const baseUrlRef = (0, import_react20.useRef)(baseUrl);
38566
- const includeToolsRef = (0, import_react20.useRef)(includeTools);
38567
38709
  (0, import_react20.useEffect)(() => {
38568
38710
  getTokenRef.current = getToken;
38569
38711
  baseUrlRef.current = baseUrl;
38570
- includeToolsRef.current = includeTools;
38571
38712
  });
38572
38713
  const fetchTools = (0, import_react20.useCallback)(async (forceRefresh = false) => {
38573
38714
  setIsLoading(true);
@@ -38578,8 +38719,7 @@ function useTools(options) {
38578
38719
  baseUrl: baseUrlRef.current,
38579
38720
  forceRefresh
38580
38721
  });
38581
- const filteredTools = filterServerTools(fetchedTools, includeToolsRef.current);
38582
- setTools(filteredTools);
38722
+ setAllTools(fetchedTools);
38583
38723
  setChecksum(getToolsChecksum2());
38584
38724
  } catch (err) {
38585
38725
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -38603,11 +38743,6 @@ function useTools(options) {
38603
38743
  },
38604
38744
  [refresh]
38605
38745
  );
38606
- (0, import_react20.useEffect)(() => {
38607
- if (tools.length > 0 && includeTools !== void 0) {
38608
- setTools((currentTools) => filterServerTools(currentTools, includeTools));
38609
- }
38610
- }, [includeTools, tools.length]);
38611
38746
  const hasFetchedRef = (0, import_react20.useRef)(false);
38612
38747
  (0, import_react20.useEffect)(() => {
38613
38748
  if (autoFetch && !hasFetchedRef.current) {
@@ -4402,6 +4402,13 @@ interface RecencyOptions {
4402
4402
  noDateMultiplier?: number;
4403
4403
  }
4404
4404
 
4405
+ type VaultCorpusCache = WeakMap<VaultMemoryOperationsContext, Map<string, Promise<StoredVaultMemory[]>>>;
4406
+ type RowRequests = Map<EmbeddingOptions["maskInput"], Map<string, Promise<number[]>>>;
4407
+ interface VaultPreparation {
4408
+ corpora: VaultCorpusCache;
4409
+ rows: WeakMap<VaultMemoryOperationsContext, RowRequests>;
4410
+ }
4411
+
4405
4412
  declare const DEFAULT_VAULT_CACHE_SIZE = 5000;
4406
4413
  /**
4407
4414
  * Create a VaultEmbeddingCache backed by an LRU with a default cap of
@@ -4419,6 +4426,7 @@ type VaultEmbeddingCache = Map<string, Float32Array>;
4419
4426
  * Options for the vault search tool.
4420
4427
  */
4421
4428
  interface MemoryVaultSearchOptions {
4429
+ /** @internal */ vaultPreparation?: VaultPreparation;
4422
4430
  onCandidateCount?: (count: number) => void;
4423
4431
  /** Maximum number of results to return (default: 5) */
4424
4432
  limit?: number;
@@ -4957,6 +4965,7 @@ interface RankedMemory {
4957
4965
  updatedAt: Date;
4958
4966
  }
4959
4967
  interface RecallOptions {
4968
+ /** @internal */ vaultPreparation?: VaultPreparation;
4960
4969
  /** Embedding cache shared with the rest of the turn, so the query is embedded once across lanes and the send. */
4961
4970
  embeddingCache?: Map<string, Float32Array>;
4962
4971
  /** Which kinds to search. Default: ['fact']. */
@@ -6176,7 +6185,8 @@ interface CreateVaultMemoryOptions {
6176
6185
  geohash?: string;
6177
6186
  }
6178
6187
  interface UpdateVaultMemoryOptions {
6179
- content: string;
6188
+ content?: string;
6189
+ expectedContent?: string;
6180
6190
  /** When true, the write is skipped (returns null) if the row carries a profile kind. */
6181
6191
  freeFormOnly?: boolean;
6182
6192
  /**
@@ -6869,9 +6879,10 @@ declare function ttlForType(factType: string | null, policy?: Partial<DecayPolic
6869
6879
  * null `eventTimeEnd` is still ongoing, so this rule does not fire for it.
6870
6880
  * 4. Age fallback — `now - updatedAt > TTL(factType)` → archive. Durable types
6871
6881
  * have Infinity TTL, so this never fires for them; null/unknown use medium.
6872
- * EXCEPTION: a `plan`/`ongoing_context` with a still-upcoming event
6873
- * (`eventTimeEnd >= now`) is kept — it must not age-archive before the event
6874
- * happens; only rule 3 (after it passes) may archive it.
6882
+ * EXCEPTION: a `plan` / `ongoing_context` with an upcoming event or an event
6883
+ * within the grace period (`eventTimeEnd >= now - pastEventGraceMs`) stays
6884
+ * active, even after its activity TTL expires. Rule 3 archives it after the
6885
+ * grace period ends.
6875
6886
  * 5. Otherwise → keep.
6876
6887
  *
6877
6888
  * @param m Plaintext decay inputs for one row.
@@ -7202,7 +7213,9 @@ type MemoryCreate = Pick<CreateVaultMemoryOptions, "content" | "scope" | "factTy
7202
7213
  * `embedding` (with `embeddingModel`) only when you already have the new one.
7203
7214
  * @public
7204
7215
  */
7205
- type MemoryUpdate = Pick<UpdateVaultMemoryOptions, "content" | "scope" | "factType" | "eventTime" | "embedding" | "embeddingModel" | "kind" | "kindValue" | "level">;
7216
+ type MemoryUpdate = Pick<UpdateVaultMemoryOptions, "scope" | "factType" | "eventTime" | "embedding" | "embeddingModel" | "kind" | "kindValue" | "level"> & {
7217
+ content: string;
7218
+ };
7206
7219
  /**
7207
7220
  * Fact-only recall. Conversation chunks are message storage, not memory, so
7208
7221
  * the chunk-lane knobs are not part of this surface; nor is `folderId` (see