@anuma/sdk 4.2.0 → 5.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.
@@ -14277,8 +14277,21 @@ import { v7 as uuidv72 } from "uuid";
14277
14277
 
14278
14278
  // src/react/useEncryption.ts
14279
14279
  var SIGN_MESSAGE = "The app is asking you to sign this message to generate a key, which will be used to encrypt data.";
14280
+ var EncryptionKeyMissingError = class extends Error {
14281
+ constructor(address, version) {
14282
+ super(
14283
+ `Encryption key (${version}) not found for ${address}. Please sign a message to generate your encryption key.`
14284
+ );
14285
+ this.name = "EncryptionKeyMissingError";
14286
+ this.address = address;
14287
+ this.version = version;
14288
+ }
14289
+ };
14280
14290
  var encryptionKeyStore = /* @__PURE__ */ new Map();
14281
14291
  var pendingKeyRequests = /* @__PURE__ */ new Map();
14292
+ var pendingCandidateDerives = /* @__PURE__ */ new Map();
14293
+ var refreshCandidatesByAddress = /* @__PURE__ */ new Map();
14294
+ var divergentDeriveAddresses = /* @__PURE__ */ new Set();
14282
14295
  var sessionEpoch = 0;
14283
14296
  var cryptoKeyCache = /* @__PURE__ */ new Map();
14284
14297
  var keyAvailableCallbacks = /* @__PURE__ */ new Map();
@@ -14321,14 +14334,11 @@ function notifyKeyAvailable(address) {
14321
14334
  }
14322
14335
  }
14323
14336
  var keyPairStore = /* @__PURE__ */ new Map();
14324
- function getStoredKey(address) {
14325
- const keys = encryptionKeyStore.get(address);
14326
- return keys?.current ?? null;
14327
- }
14328
14337
  function getStoredKeyByVersion(address, version) {
14329
14338
  const keys = encryptionKeyStore.get(address);
14330
14339
  if (!keys) return null;
14331
- return version === "v2" ? keys.legacy : keys.current;
14340
+ const value = version === "v2" ? keys.legacy : keys.current;
14341
+ return value ?? null;
14332
14342
  }
14333
14343
  function setStoredKey(address, keys) {
14334
14344
  encryptionKeyStore.set(address, keys);
@@ -14337,6 +14347,8 @@ function clearEncryptionKey(address) {
14337
14347
  encryptionKeyStore.delete(address);
14338
14348
  cryptoKeyCache.delete(`${address}:v2`);
14339
14349
  cryptoKeyCache.delete(`${address}:v3`);
14350
+ refreshCandidatesByAddress.delete(address);
14351
+ divergentDeriveAddresses.delete(address);
14340
14352
  }
14341
14353
  function clearAllEncryptionState() {
14342
14354
  sessionEpoch += 1;
@@ -14344,6 +14356,9 @@ function clearAllEncryptionState() {
14344
14356
  cryptoKeyCache.clear();
14345
14357
  keyAvailableCallbacks.clear();
14346
14358
  pendingKeyRequests.clear();
14359
+ pendingCandidateDerives.clear();
14360
+ refreshCandidatesByAddress.clear();
14361
+ divergentDeriveAddresses.clear();
14347
14362
  clearAllKeyPairs();
14348
14363
  for (const cb of clearAllEncryptionStateCallbacks) {
14349
14364
  try {
@@ -14529,9 +14544,7 @@ async function getEncryptionKey(address, version = "v3") {
14529
14544
  }
14530
14545
  const keyHex = getStoredKeyByVersion(address, version);
14531
14546
  if (!keyHex) {
14532
- throw new Error(
14533
- "Encryption key not found. Please sign a message to generate your encryption key."
14534
- );
14547
+ throw new EncryptionKeyMissingError(address, version);
14535
14548
  }
14536
14549
  const keyBytes = hexToBytes(keyHex);
14537
14550
  const cryptoKey = await crypto.subtle.importKey(
@@ -14595,8 +14608,43 @@ async function decryptDataBytesFromBytes(encrypted, address, version = "v3") {
14595
14608
  );
14596
14609
  return new Uint8Array(decryptedData);
14597
14610
  }
14598
- function hasEncryptionKey(address) {
14599
- return getStoredKey(address) !== null;
14611
+ function hasEncryptionKey(address, version) {
14612
+ return getStoredKeyByVersion(address, version ?? "v3") !== null;
14613
+ }
14614
+ function seedEncryptionKeys(address, keys) {
14615
+ if (!isValidWalletAddress(address)) {
14616
+ throw new Error(
14617
+ `Invalid wallet address: ${address}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
14618
+ );
14619
+ }
14620
+ if (!keys.legacy && !keys.current) {
14621
+ throw new Error("seedEncryptionKeys: at least one of legacy or current is required");
14622
+ }
14623
+ if (keys.legacy !== void 0) {
14624
+ assertAes256KeyHex("legacy", keys.legacy);
14625
+ }
14626
+ if (keys.current !== void 0) {
14627
+ assertAes256KeyHex("current", keys.current);
14628
+ }
14629
+ const existing = encryptionKeyStore.get(address);
14630
+ const next = {
14631
+ legacy: keys.legacy ?? existing?.legacy,
14632
+ current: keys.current ?? existing?.current
14633
+ };
14634
+ setStoredKey(address, next);
14635
+ if (keys.legacy !== void 0) cryptoKeyCache.delete(`${address}:v2`);
14636
+ if (keys.current !== void 0) cryptoKeyCache.delete(`${address}:v3`);
14637
+ refreshCandidatesByAddress.delete(address);
14638
+ divergentDeriveAddresses.delete(address);
14639
+ notifyKeyAvailable(address);
14640
+ }
14641
+ var AES_256_KEY_HEX = /^[0-9a-fA-F]{64}$/;
14642
+ function assertAes256KeyHex(field, value) {
14643
+ if (!AES_256_KEY_HEX.test(value)) {
14644
+ throw new Error(
14645
+ `seedEncryptionKeys: ${field} must be 64 hex characters (32-byte AES key), got length ${value.length}`
14646
+ );
14647
+ }
14600
14648
  }
14601
14649
  async function encryptBytesWithKey(plaintext, key) {
14602
14650
  const plaintextBytes = typeof plaintext === "string" ? SHARED_TEXT_ENCODER.encode(plaintext) : plaintext;
@@ -14645,20 +14693,23 @@ async function decryptDataBatch(encryptedValues, address) {
14645
14693
  const key = await getEncryptionKey(address);
14646
14694
  return Promise.all(encryptedValues.map((value) => decryptDataWithKey(value, key)));
14647
14695
  }
14648
- async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner) {
14696
+ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner, options) {
14649
14697
  if (!isValidWalletAddress(walletAddress)) {
14650
14698
  throw new Error(
14651
14699
  `Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
14652
14700
  );
14653
14701
  }
14654
- const existingKey = getStoredKey(walletAddress);
14655
- if (existingKey) {
14656
- return;
14702
+ if (!options?.force && getStoredKeyByVersion(walletAddress, "v3") && getStoredKeyByVersion(walletAddress, "v2")) {
14703
+ return true;
14657
14704
  }
14658
- const pending = pendingKeyRequests.get(walletAddress);
14659
- if (pending) {
14660
- await pending;
14661
- return;
14705
+ if (!options?.force && divergentDeriveAddresses.has(walletAddress)) {
14706
+ return false;
14707
+ }
14708
+ if (!options?.force) {
14709
+ const pending = pendingKeyRequests.get(walletAddress);
14710
+ if (pending) {
14711
+ return pending;
14712
+ }
14662
14713
  }
14663
14714
  const startEpoch = sessionEpoch;
14664
14715
  const promise = (async () => {
@@ -14686,20 +14737,143 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
14686
14737
  deriveKeyFromSignatureV3(signature)
14687
14738
  ]);
14688
14739
  if (sessionEpoch !== startEpoch) {
14689
- return;
14740
+ return false;
14741
+ }
14742
+ const existing = encryptionKeyStore.get(walletAddress);
14743
+ if (!options?.force && existing && (existing.legacy || existing.current)) {
14744
+ const matchesCurrent = Boolean(existing.current && existing.current === currentKey);
14745
+ const matchesLegacy = Boolean(existing.legacy && existing.legacy === legacyKey);
14746
+ if (!matchesCurrent && !matchesLegacy) {
14747
+ getLogger().warn(
14748
+ "requestEncryptionKey: derived keys do not match existing store; leaving unchanged"
14749
+ );
14750
+ divergentDeriveAddresses.add(walletAddress);
14751
+ return false;
14752
+ }
14753
+ setStoredKey(walletAddress, {
14754
+ legacy: existing.legacy ?? legacyKey,
14755
+ current: existing.current ?? currentKey
14756
+ });
14757
+ } else {
14758
+ setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
14690
14759
  }
14691
- setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
14760
+ cryptoKeyCache.delete(`${walletAddress}:v2`);
14761
+ cryptoKeyCache.delete(`${walletAddress}:v3`);
14762
+ refreshCandidatesByAddress.delete(walletAddress);
14763
+ divergentDeriveAddresses.delete(walletAddress);
14692
14764
  notifyKeyAvailable(walletAddress);
14765
+ return true;
14693
14766
  })();
14694
14767
  pendingKeyRequests.set(walletAddress, promise);
14695
14768
  try {
14696
- await promise;
14769
+ return await promise;
14697
14770
  } finally {
14698
14771
  if (pendingKeyRequests.get(walletAddress) === promise) {
14699
14772
  pendingKeyRequests.delete(walletAddress);
14700
14773
  }
14701
14774
  }
14702
14775
  }
14776
+ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, signMessage, embeddedWalletSigner) {
14777
+ if (!isValidWalletAddress(walletAddress)) {
14778
+ throw new Error(
14779
+ `Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
14780
+ );
14781
+ }
14782
+ const parsed = parsePrefixedCiphertext(probeCiphertext);
14783
+ if (!parsed) {
14784
+ return false;
14785
+ }
14786
+ const cached = refreshCandidatesByAddress.get(walletAddress);
14787
+ if (cached) {
14788
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
14789
+ }
14790
+ let derivePromise = pendingCandidateDerives.get(walletAddress);
14791
+ if (!derivePromise) {
14792
+ derivePromise = (async () => {
14793
+ const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
14794
+ if (candidates2) {
14795
+ refreshCandidatesByAddress.set(walletAddress, candidates2);
14796
+ }
14797
+ return candidates2;
14798
+ })().finally(() => {
14799
+ if (pendingCandidateDerives.get(walletAddress) === derivePromise) {
14800
+ pendingCandidateDerives.delete(walletAddress);
14801
+ }
14802
+ });
14803
+ pendingCandidateDerives.set(walletAddress, derivePromise);
14804
+ }
14805
+ const candidates = await derivePromise;
14806
+ if (!candidates) {
14807
+ return false;
14808
+ }
14809
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
14810
+ }
14811
+ function parsePrefixedCiphertext(probeCiphertext) {
14812
+ const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
14813
+ if (!version) return null;
14814
+ const encryptedData = probeCiphertext.slice(
14815
+ version === "v3" ? "enc:v3:".length : "enc:v2:".length
14816
+ );
14817
+ if (encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(encryptedData)) {
14818
+ return null;
14819
+ }
14820
+ return { version, encryptedData };
14821
+ }
14822
+ async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
14823
+ const startEpoch = sessionEpoch;
14824
+ const signOptions = { showWalletUIs: false };
14825
+ let signature;
14826
+ try {
14827
+ if (embeddedWalletSigner) {
14828
+ signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
14829
+ } else {
14830
+ signature = await signMessage(SIGN_MESSAGE, signOptions);
14831
+ }
14832
+ } catch (error) {
14833
+ if (embeddedWalletSigner && error instanceof Error) {
14834
+ getLogger().warn(
14835
+ "Embedded wallet signing failed during key refresh, falling back to standard signMessage:",
14836
+ error.message
14837
+ );
14838
+ signature = await signMessage(SIGN_MESSAGE, signOptions);
14839
+ } else {
14840
+ throw error;
14841
+ }
14842
+ }
14843
+ const [legacyKey, currentKey] = await Promise.all([
14844
+ deriveKeyFromSignature(signature),
14845
+ deriveKeyFromSignatureV3(signature)
14846
+ ]);
14847
+ if (sessionEpoch !== startEpoch) {
14848
+ return null;
14849
+ }
14850
+ return { legacy: legacyKey, current: currentKey };
14851
+ }
14852
+ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
14853
+ const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
14854
+ const keyBytes = hexToBytes(candidateHex);
14855
+ const candidateCryptoKey = await crypto.subtle.importKey(
14856
+ "raw",
14857
+ keyBytes.buffer,
14858
+ { name: "AES-GCM" },
14859
+ false,
14860
+ ["encrypt", "decrypt"]
14861
+ );
14862
+ try {
14863
+ await decryptDataWithKey(parsed.encryptedData, candidateCryptoKey);
14864
+ } catch {
14865
+ getLogger().warn(
14866
+ "refreshEncryptionKeyIfMatches: candidate keys failed to decrypt probe; leaving store unchanged"
14867
+ );
14868
+ return false;
14869
+ }
14870
+ setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
14871
+ cryptoKeyCache.delete(`${walletAddress}:v2`);
14872
+ cryptoKeyCache.delete(`${walletAddress}:v3`);
14873
+ divergentDeriveAddresses.delete(walletAddress);
14874
+ notifyKeyAvailable(walletAddress);
14875
+ return true;
14876
+ }
14703
14877
  var KEYPAIR_STORAGE_PREFIX = "ecdh_keypair_";
14704
14878
  async function persistKeyPair(address, startEpoch) {
14705
14879
  if (typeof window === "undefined") {
@@ -14970,20 +15144,29 @@ async function encryptField(value, address, signMessage, embeddedWalletSigner, s
14970
15144
  const encrypted = await encryptData(value, address);
14971
15145
  return `${ENCRYPTION_PREFIX}${encrypted}`;
14972
15146
  }
14973
- async function decryptField(value, address) {
14974
- if (!value) return value;
15147
+ async function decryptFieldDetailed(value, address) {
15148
+ if (!value) return { status: "plaintext", value };
14975
15149
  const detected = detectEncryptionVersion(value);
14976
- if (!detected) return value;
15150
+ if (!detected) return { status: "plaintext", value };
14977
15151
  if (detected.encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(detected.encryptedData)) {
14978
- return value;
15152
+ return { status: "invalid_payload", value, version: detected.version };
14979
15153
  }
14980
15154
  try {
14981
- return await decryptData(detected.encryptedData, address, detected.version);
15155
+ const plaintext = await decryptData(detected.encryptedData, address, detected.version);
15156
+ return { status: "ok", value: plaintext, version: detected.version };
14982
15157
  } catch (error) {
14983
- getLogger().warn("Failed to decrypt field, returning as-is:", error);
14984
- return value;
15158
+ if (error instanceof EncryptionKeyMissingError) {
15159
+ getLogger().warn("Failed to decrypt field (key missing for version):", error.message);
15160
+ return { status: "key_missing", value, version: detected.version };
15161
+ }
15162
+ getLogger().warn("Failed to decrypt field (auth mismatch or crypto error):", error);
15163
+ return { status: "auth_mismatch", value, version: detected.version };
14985
15164
  }
14986
15165
  }
15166
+ async function decryptField(value, address) {
15167
+ const result = await decryptFieldDetailed(value, address);
15168
+ return result.value;
15169
+ }
14987
15170
  async function encryptJsonField(value, address, signMessage, embeddedWalletSigner, skipKeyRequest) {
14988
15171
  if (!value) return void 0;
14989
15172
  const jsonString = JSON.stringify(value);
@@ -15083,20 +15266,40 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
15083
15266
  throw error;
15084
15267
  }
15085
15268
  }
15086
- async function decryptMaybeJsonField(value, address) {
15087
- if (value === void 0) return void 0;
15088
- if (value === null) return value;
15269
+ async function decryptMaybeJsonFieldDetailed(value, address) {
15270
+ if (value === void 0) return { value: void 0, status: "plaintext" };
15271
+ if (value === null) return { value, status: "plaintext" };
15089
15272
  if (typeof value === "string") {
15090
15273
  if (isEncrypted(value)) {
15091
- return await decryptJsonField(value, address);
15274
+ const detailed = await decryptFieldDetailed(value, address);
15275
+ if (detailed.status !== "ok") {
15276
+ return { value: void 0, status: detailed.status, probe: value };
15277
+ }
15278
+ try {
15279
+ return { value: JSON.parse(detailed.value), status: "ok" };
15280
+ } catch (error) {
15281
+ getLogger().warn("Failed to parse decrypted JSON field:", error);
15282
+ return { value: void 0, status: "invalid_payload", probe: value };
15283
+ }
15092
15284
  }
15093
15285
  try {
15094
- return JSON.parse(value);
15286
+ return { value: JSON.parse(value), status: "plaintext" };
15095
15287
  } catch {
15096
- return void 0;
15288
+ return { value: void 0, status: "plaintext" };
15097
15289
  }
15098
15290
  }
15099
- return value;
15291
+ return { value, status: "plaintext" };
15292
+ }
15293
+ function worseDecryptStatus(a, b) {
15294
+ if (b === "ok" || b === "plaintext") return a;
15295
+ if (!a || a === "ok" || a === "plaintext") return b;
15296
+ const rank = (s) => {
15297
+ if (s === "auth_mismatch") return 3;
15298
+ if (s === "key_missing") return 2;
15299
+ if (s === "invalid_payload") return 1;
15300
+ return 0;
15301
+ };
15302
+ return rank(b) >= rank(a) ? b : a;
15100
15303
  }
15101
15304
  async function decryptMessageFields(message, address, signMessage, embeddedWalletSigner) {
15102
15305
  if (!address) {
@@ -15109,33 +15312,82 @@ async function decryptMessageFields(message, address, signMessage, embeddedWalle
15109
15312
  getLogger().warn("Failed to request encryption key for decryption:", error);
15110
15313
  }
15111
15314
  }
15112
- const decryptedContent = await decryptField(message.content, address);
15113
- const decryptedThinking = message.thinking ? await decryptField(message.thinking, address) : message.thinking;
15114
- const decryptedVector = await decryptMaybeJsonField(
15315
+ let assembled = await assembleDecryptedMessage(message, address);
15316
+ const needsRefresh = assembled.decryptionStatus === "auth_mismatch" || assembled.decryptionStatus === "key_missing";
15317
+ if (needsRefresh && signMessage && assembled.refreshProbe) {
15318
+ try {
15319
+ const refreshed = await refreshEncryptionKeyIfMatches(
15320
+ address,
15321
+ assembled.refreshProbe,
15322
+ signMessage,
15323
+ embeddedWalletSigner
15324
+ );
15325
+ if (refreshed) {
15326
+ assembled = await assembleDecryptedMessage(message, address);
15327
+ }
15328
+ } catch (error) {
15329
+ getLogger().warn("Failed to refresh encryption key after decrypt failure:", error);
15330
+ }
15331
+ }
15332
+ const { refreshProbe: _probe, ...result } = assembled;
15333
+ return result;
15334
+ }
15335
+ async function assembleDecryptedMessage(message, address) {
15336
+ const contentResult = await decryptFieldDetailed(message.content, address);
15337
+ const thinkingResult = message.thinking ? await decryptFieldDetailed(message.thinking, address) : null;
15338
+ const vectorResult = await decryptMaybeJsonFieldDetailed(
15115
15339
  message.vector,
15116
15340
  address
15117
15341
  );
15118
- const decryptedChunks = await decryptMaybeJsonField(
15342
+ const chunksResult = await decryptMaybeJsonFieldDetailed(
15119
15343
  message.chunks,
15120
15344
  address
15121
15345
  );
15122
- const decryptedSources = await decryptMaybeJsonField(
15346
+ const sourcesResult = await decryptMaybeJsonFieldDetailed(
15123
15347
  message.sources,
15124
15348
  address
15125
15349
  );
15126
- const decryptedThoughtProcess = await decryptMaybeJsonField(
15350
+ const thoughtProcessResult = await decryptMaybeJsonFieldDetailed(
15127
15351
  message.thoughtProcess,
15128
15352
  address
15129
15353
  );
15130
- return {
15354
+ let decryptionStatus;
15355
+ let refreshProbe;
15356
+ const consider = (status, probe) => {
15357
+ decryptionStatus = worseDecryptStatus(decryptionStatus, status);
15358
+ if (!refreshProbe && probe && (status === "auth_mismatch" || status === "key_missing")) {
15359
+ refreshProbe = probe;
15360
+ }
15361
+ };
15362
+ consider(contentResult.status, isEncrypted(message.content) ? message.content : void 0);
15363
+ if (thinkingResult && message.thinking && typeof message.thinking === "string") {
15364
+ consider(thinkingResult.status, isEncrypted(message.thinking) ? message.thinking : void 0);
15365
+ }
15366
+ if (vectorResult.probe) consider(vectorResult.status, vectorResult.probe);
15367
+ if (chunksResult.probe) consider(chunksResult.status, chunksResult.probe);
15368
+ if (sourcesResult.probe) consider(sourcesResult.status, sourcesResult.probe);
15369
+ if (thoughtProcessResult.probe) {
15370
+ consider(thoughtProcessResult.status, thoughtProcessResult.probe);
15371
+ }
15372
+ const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
15373
+ const result = {
15131
15374
  ...message,
15132
- content: decryptedContent,
15133
- thinking: decryptedThinking,
15134
- vector: decryptedVector,
15135
- chunks: decryptedChunks,
15136
- sources: decryptedSources,
15137
- thoughtProcess: decryptedThoughtProcess
15375
+ content: contentResult.value,
15376
+ thinking: thinkingResult ? thinkingResult.value : message.thinking,
15377
+ vector: vectorResult.value,
15378
+ chunks: chunksResult.value,
15379
+ sources: sourcesResult.value,
15380
+ thoughtProcess: thoughtProcessResult.value
15138
15381
  };
15382
+ if (statusForConsumer) {
15383
+ result.decryptionStatus = statusForConsumer;
15384
+ } else {
15385
+ delete result.decryptionStatus;
15386
+ }
15387
+ if (refreshProbe) {
15388
+ result.refreshProbe = refreshProbe;
15389
+ }
15390
+ return result;
15139
15391
  }
15140
15392
 
15141
15393
  // src/lib/db/chat/types.ts
@@ -18687,6 +18939,107 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
18687
18939
  return embeddedCount;
18688
18940
  }
18689
18941
 
18942
+ // src/lib/memoryVault/decomposeQuery.ts
18943
+ var DEFAULT_MODEL = "inclusionai/ling-2.6-flash";
18944
+ var MAX_SUB_QUERIES = 5;
18945
+ var SYSTEM_PROMPT = `You classify a memory query and, if needed, decompose it into concrete sub-queries.
18946
+
18947
+ CLASSIFICATION RULES \u2014 be strict:
18948
+ - "specific": asks for one fact OR a list of items in ONE category (one dimension).
18949
+ Examples: a name, a date, a preference, a location, an ID, a value, "what hobbies",
18950
+ "which conferences", "what open source projects", "what does X do for exercise".
18951
+ Even when the answer is a list, if all items belong to one dimension, it is specific.
18952
+ - "composite": asks about a person, system, or setup MULTI-DIMENSIONALLY \u2014 a summary,
18953
+ an overview, a profile, "tell me about X", "what is my X setup", "describe the team's
18954
+ X". The answer must span multiple distinct fact dimensions (e.g. for a "tech stack":
18955
+ language + database + framework + tools \u2014 four different dimensions).
18956
+
18957
+ When in doubt, prefer "specific". Mis-classifying a single-dimension list as composite
18958
+ hurts retrieval more than the reverse.
18959
+
18960
+ SUB-QUERY RULES (composite only, 3\u20135 questions):
18961
+ - Each sub-query targets a DIFFERENT fact dimension.
18962
+ - Use SHORT parenthetical examples \u2014 2\u20134 concrete options that anchor the
18963
+ embedding to likely memory content. Examples must be terms a user would
18964
+ actually have in their notes (e.g., "Postgres" not "PostgreSQL 15.2 with
18965
+ read replicas"). No verbose qualifiers like "and versions" or "and any
18966
+ relevant configuration details".
18967
+ - 8\u201314 words including the parenthetical. Like a human asking a follow-up.
18968
+
18969
+ Examples:
18970
+ "What is my dog's name?" \u2192 {"mode":"specific","subQueries":["What is my dog's name?"]}
18971
+ "What does the user do for exercise?" \u2192 {"mode":"specific","subQueries":["What does the user do for exercise?"]}
18972
+ "What conferences has the user attended?" \u2192 {"mode":"specific","subQueries":["What conferences has the user attended?"]}
18973
+ "What did I do last Tuesday?" \u2192 {"mode":"specific","subQueries":["What did I do last Tuesday?"]}
18974
+ "Tell me about the user as a person" \u2192 {"mode":"composite","subQueries":["What is the user's name?","Where does the user live?","What does the user do for work?","Any health conditions or allergies?","What are the user's hobbies (sports, music, games)?"]}
18975
+ "What is the user's tech stack?" \u2192 {"mode":"composite","subQueries":["What programming languages does the user use (Python, Go, TypeScript)?","What database does the user use (Postgres, MySQL, Mongo)?","What framework or ORM (React, Django, Prisma)?","What infrastructure (AWS, Docker, K8s)?","What testing tools (Jest, pytest, Cypress)?"]}
18976
+ "Describe the user's development environment" \u2192 {"mode":"composite","subQueries":["What operating system does the user use (macOS, Linux, Windows)?","What editor or IDE (VS Code, Vim, IntelliJ)?","What shell or terminal setup (zsh, bash, tmux)?","What keyboard or display setup (mechanical, vertical monitor)?","Any UI preferences (dark mode, font, vim keybindings)?"]}
18977
+
18978
+ Output strict JSON. No prose.`;
18979
+ async function decomposeQuery(query, options) {
18980
+ const fallback = { mode: "specific", subQueries: [query] };
18981
+ const trimmed = query.trim();
18982
+ if (trimmed.length === 0) return fallback;
18983
+ let parsed;
18984
+ try {
18985
+ parsed = await callPortalJsonCompletion({
18986
+ ...options.apiKey !== void 0 && { apiKey: options.apiKey },
18987
+ ...options.getToken !== void 0 && { getToken: options.getToken },
18988
+ ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
18989
+ model: options.model ?? DEFAULT_MODEL,
18990
+ systemPrompt: SYSTEM_PROMPT,
18991
+ // Wrap the bare query in a task framing so the model treats it as
18992
+ // input to classify, not as a question to answer conversationally.
18993
+ // Bare-query inputs caused Anthropic models to respond with prose
18994
+ // like "Do you mean...?" before this wrap.
18995
+ userMessage: `Classify the following memory query and decompose if composite. Respond with JSON only \u2014 do not answer the question, do not ask for clarification.
18996
+
18997
+ Query: ${trimmed}`,
18998
+ // Decompose runs ahead of recall in the tool layer — tighter than the
18999
+ // portalLlm default (consolidate/extract can wait longer).
19000
+ timeoutMs: 2e4,
19001
+ // No internal retry: a failure degrades to the safe `{specific,[query]}`
19002
+ // fallback below. The agent/tool loop is the retry surface (719/B4).
19003
+ maxAttempts: 1,
19004
+ tag: "memory/decompose",
19005
+ ...options.fetchFn && { fetchFn: options.fetchFn }
19006
+ });
19007
+ } catch (err) {
19008
+ getLogger().warn("memoryVault/decompose: portal call failed, falling back to specific", err);
19009
+ return fallback;
19010
+ }
19011
+ if (parsed === null) return fallback;
19012
+ return validate(parsed, trimmed) ?? fallback;
19013
+ }
19014
+ function normalizeSubQueries(raw) {
19015
+ if (!raw || raw.length === 0) return [];
19016
+ const seen = /* @__PURE__ */ new Set();
19017
+ const out = [];
19018
+ for (const s of raw) {
19019
+ if (typeof s !== "string") continue;
19020
+ const t = s.trim();
19021
+ if (t.length === 0) continue;
19022
+ const key = t.toLowerCase();
19023
+ if (seen.has(key)) continue;
19024
+ seen.add(key);
19025
+ out.push(t);
19026
+ if (out.length >= MAX_SUB_QUERIES) break;
19027
+ }
19028
+ return out;
19029
+ }
19030
+ function validate(parsed, originalQuery) {
19031
+ if (typeof parsed !== "object" || parsed === null) return null;
19032
+ const obj = parsed;
19033
+ const mode = obj.mode === "composite" ? "composite" : "specific";
19034
+ if (!Array.isArray(obj.subQueries)) return null;
19035
+ const subQueries = normalizeSubQueries(obj.subQueries);
19036
+ if (subQueries.length === 0) return null;
19037
+ if (mode === "specific") {
19038
+ return { mode: "specific", subQueries: [originalQuery] };
19039
+ }
19040
+ return { mode, subQueries };
19041
+ }
19042
+
18690
19043
  // src/lib/memory/mmr.ts
18691
19044
  function applyMMR(candidates, k, lambda = 0.5) {
18692
19045
  if (candidates.length === 0 || k <= 0) return [];
@@ -19064,91 +19417,6 @@ function scoreBM25(query, items) {
19064
19417
  return scoreBM25Prepared(query, prepareBM25Corpus(items));
19065
19418
  }
19066
19419
 
19067
- // src/lib/memoryVault/decomposeQuery.ts
19068
- var DEFAULT_MODEL = "inclusionai/ling-2.6-flash";
19069
- var MAX_SUB_QUERIES = 5;
19070
- var SYSTEM_PROMPT = `You classify a memory query and, if needed, decompose it into concrete sub-queries.
19071
-
19072
- CLASSIFICATION RULES \u2014 be strict:
19073
- - "specific": asks for one fact OR a list of items in ONE category (one dimension).
19074
- Examples: a name, a date, a preference, a location, an ID, a value, "what hobbies",
19075
- "which conferences", "what open source projects", "what does X do for exercise".
19076
- Even when the answer is a list, if all items belong to one dimension, it is specific.
19077
- - "composite": asks about a person, system, or setup MULTI-DIMENSIONALLY \u2014 a summary,
19078
- an overview, a profile, "tell me about X", "what is my X setup", "describe the team's
19079
- X". The answer must span multiple distinct fact dimensions (e.g. for a "tech stack":
19080
- language + database + framework + tools \u2014 four different dimensions).
19081
-
19082
- When in doubt, prefer "specific". Mis-classifying a single-dimension list as composite
19083
- hurts retrieval more than the reverse.
19084
-
19085
- SUB-QUERY RULES (composite only, 3\u20135 questions):
19086
- - Each sub-query targets a DIFFERENT fact dimension.
19087
- - Use SHORT parenthetical examples \u2014 2\u20134 concrete options that anchor the
19088
- embedding to likely memory content. Examples must be terms a user would
19089
- actually have in their notes (e.g., "Postgres" not "PostgreSQL 15.2 with
19090
- read replicas"). No verbose qualifiers like "and versions" or "and any
19091
- relevant configuration details".
19092
- - 8\u201314 words including the parenthetical. Like a human asking a follow-up.
19093
-
19094
- Examples:
19095
- "What is my dog's name?" \u2192 {"mode":"specific","subQueries":["What is my dog's name?"]}
19096
- "What does the user do for exercise?" \u2192 {"mode":"specific","subQueries":["What does the user do for exercise?"]}
19097
- "What conferences has the user attended?" \u2192 {"mode":"specific","subQueries":["What conferences has the user attended?"]}
19098
- "What did I do last Tuesday?" \u2192 {"mode":"specific","subQueries":["What did I do last Tuesday?"]}
19099
- "Tell me about the user as a person" \u2192 {"mode":"composite","subQueries":["What is the user's name?","Where does the user live?","What does the user do for work?","Any health conditions or allergies?","What are the user's hobbies (sports, music, games)?"]}
19100
- "What is the user's tech stack?" \u2192 {"mode":"composite","subQueries":["What programming languages does the user use (Python, Go, TypeScript)?","What database does the user use (Postgres, MySQL, Mongo)?","What framework or ORM (React, Django, Prisma)?","What infrastructure (AWS, Docker, K8s)?","What testing tools (Jest, pytest, Cypress)?"]}
19101
- "Describe the user's development environment" \u2192 {"mode":"composite","subQueries":["What operating system does the user use (macOS, Linux, Windows)?","What editor or IDE (VS Code, Vim, IntelliJ)?","What shell or terminal setup (zsh, bash, tmux)?","What keyboard or display setup (mechanical, vertical monitor)?","Any UI preferences (dark mode, font, vim keybindings)?"]}
19102
-
19103
- Output strict JSON. No prose.`;
19104
- async function decomposeQuery(query, options) {
19105
- const fallback = { mode: "specific", subQueries: [query] };
19106
- const trimmed = query.trim();
19107
- if (trimmed.length === 0) return fallback;
19108
- let parsed;
19109
- try {
19110
- parsed = await callPortalJsonCompletion({
19111
- ...options.apiKey !== void 0 && { apiKey: options.apiKey },
19112
- ...options.getToken !== void 0 && { getToken: options.getToken },
19113
- ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
19114
- model: options.model ?? DEFAULT_MODEL,
19115
- systemPrompt: SYSTEM_PROMPT,
19116
- // Wrap the bare query in a task framing so the model treats it as
19117
- // input to classify, not as a question to answer conversationally.
19118
- // Bare-query inputs caused Anthropic models to respond with prose
19119
- // like "Do you mean...?" before this wrap.
19120
- userMessage: `Classify the following memory query and decompose if composite. Respond with JSON only \u2014 do not answer the question, do not ask for clarification.
19121
-
19122
- Query: ${trimmed}`,
19123
- // Decompose runs on the recall hot path — tighter than the
19124
- // portalLlm default (consolidate/extract can wait longer).
19125
- timeoutMs: 2e4,
19126
- // No internal retry: a failure degrades to the safe `{specific,[query]}`
19127
- // fallback below, so retrying would only add latency on the hot path.
19128
- maxAttempts: 1,
19129
- tag: "memory/decompose",
19130
- ...options.fetchFn && { fetchFn: options.fetchFn }
19131
- });
19132
- } catch (err) {
19133
- getLogger().warn("memoryVault/decompose: portal call failed, falling back to specific", err);
19134
- return fallback;
19135
- }
19136
- if (parsed === null) return fallback;
19137
- return validate(parsed, trimmed) ?? fallback;
19138
- }
19139
- function validate(parsed, originalQuery) {
19140
- if (typeof parsed !== "object" || parsed === null) return null;
19141
- const obj = parsed;
19142
- const mode = obj.mode === "composite" ? "composite" : "specific";
19143
- if (!Array.isArray(obj.subQueries)) return null;
19144
- const subQueries = obj.subQueries.filter((s) => typeof s === "string").map((s) => s.trim()).filter((s) => s.length > 0).slice(0, MAX_SUB_QUERIES);
19145
- if (subQueries.length === 0) return null;
19146
- if (mode === "specific") {
19147
- return { mode: "specific", subQueries: [originalQuery] };
19148
- }
19149
- return { mode, subQueries };
19150
- }
19151
-
19152
19420
  // src/lib/memoryVault/lruCache.ts
19153
19421
  var DEFAULT_VAULT_CACHE_SIZE = 5e3;
19154
19422
  var LRUCache = class extends Map {
@@ -19182,6 +19450,7 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
19182
19450
  }
19183
19451
 
19184
19452
  // src/lib/memoryVault/searchTool.ts
19453
+ var warnedProgrammaticDecomposeIgnored = false;
19185
19454
  function normalizeEventTimeKind(kind) {
19186
19455
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
19187
19456
  }
@@ -20116,52 +20385,51 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
20116
20385
  ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
20117
20386
  ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
20118
20387
  };
20119
- if (useFusion && !embeddingsUnavailable && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
20120
- const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
20121
- if (decomp.mode === "composite") {
20122
- let subEmbeddings;
20123
- try {
20124
- subEmbeddings = await generateEmbeddings(decomp.subQueries, embeddingOptions);
20125
- } catch (err) {
20126
- getLogger().warn(
20127
- "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
20128
- );
20129
- subEmbeddings = [];
20130
- }
20131
- const subEmbeddingsUsable = decomp.subQueries.length > 0 && subEmbeddings.length === decomp.subQueries.length && subEmbeddings.every((v) => v.length > 0 && v.length === queryEmbedding.length);
20132
- if (subEmbeddings.length > 0 && !subEmbeddingsUsable) {
20133
- getLogger().warn(
20134
- `memoryVault: sub-query embedding returned ${subEmbeddings.length} vectors (dims ${subEmbeddings.map((v) => v.length).join(",")}) for ${decomp.subQueries.length} sub-queries against a ${queryEmbedding.length}-dim query \u2014 falling back to single-query ranking`
20135
- );
20136
- }
20137
- if (subEmbeddingsUsable) {
20138
- const subQueries = decomp.subQueries.map((sq, i) => ({
20139
- query: sq,
20140
- embedding: subEmbeddings[i]
20141
- }));
20142
- const v2HeadStats = { hadResults: false };
20143
- const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
20144
- limit,
20145
- minSimilarity,
20146
- rerank: !!searchOptions.rerank,
20147
- ...searchOptions.rerankTopN !== void 0 && {
20148
- rerankTopN: searchOptions.rerankTopN
20149
- },
20150
- ...searchOptions.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
20151
- ...searchOptions.mmr !== void 0 && { mmr: searchOptions.mmr },
20152
- ...tuning,
20153
- ...searchOptions.entityRanking && { entityRanking: searchOptions.entityRanking },
20154
- ...searchOptions.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
20155
- rerankStats,
20156
- v2HeadStats
20157
- });
20158
- return stampTimestamps({
20159
- results: composite,
20160
- vaultSize,
20161
- reranked: rerankStats.applied,
20162
- hadV2Head: v2HeadStats.hadResults
20163
- });
20164
- }
20388
+ const normalizedFacets = normalizeSubQueries(searchOptions?.subQueries);
20389
+ const facetQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
20390
+ if (useFusion && !embeddingsUnavailable && facetQueries) {
20391
+ let subEmbeddings;
20392
+ try {
20393
+ subEmbeddings = await generateEmbeddings(facetQueries, embeddingOptions);
20394
+ } catch (err) {
20395
+ getLogger().warn(
20396
+ "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
20397
+ );
20398
+ subEmbeddings = [];
20399
+ }
20400
+ const subEmbeddingsUsable = facetQueries.length > 0 && subEmbeddings.length === facetQueries.length && subEmbeddings.every((v) => v.length > 0 && v.length === queryEmbedding.length);
20401
+ if (subEmbeddings.length > 0 && !subEmbeddingsUsable) {
20402
+ getLogger().warn(
20403
+ `memoryVault: sub-query embedding returned ${subEmbeddings.length} vectors (dims ${subEmbeddings.map((v) => v.length).join(",")}) for ${facetQueries.length} sub-queries against a ${queryEmbedding.length}-dim query \u2014 falling back to single-query ranking`
20404
+ );
20405
+ }
20406
+ if (subEmbeddingsUsable) {
20407
+ const subQueries = facetQueries.map((sq, i) => ({
20408
+ query: sq,
20409
+ embedding: subEmbeddings[i]
20410
+ }));
20411
+ const v2HeadStats = { hadResults: false };
20412
+ const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
20413
+ limit,
20414
+ minSimilarity,
20415
+ rerank: !!searchOptions?.rerank,
20416
+ ...searchOptions?.rerankTopN !== void 0 && {
20417
+ rerankTopN: searchOptions.rerankTopN
20418
+ },
20419
+ ...searchOptions?.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
20420
+ ...searchOptions?.mmr !== void 0 && { mmr: searchOptions.mmr },
20421
+ ...tuning,
20422
+ ...searchOptions?.entityRanking && { entityRanking: searchOptions.entityRanking },
20423
+ ...searchOptions?.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
20424
+ rerankStats,
20425
+ v2HeadStats
20426
+ });
20427
+ return stampTimestamps({
20428
+ results: composite,
20429
+ vaultSize,
20430
+ reranked: rerankStats.applied,
20431
+ hadV2Head: v2HeadStats.hadResults
20432
+ });
20165
20433
  }
20166
20434
  }
20167
20435
  if (useFusion && searchOptions?.rerank) {
@@ -20221,6 +20489,12 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
20221
20489
  rankedOnCosine: false
20222
20490
  };
20223
20491
  }
20492
+ if (searchOptions?.decompose === "llm" && normalizeSubQueries(searchOptions.subQueries).length < 2 && !warnedProgrammaticDecomposeIgnored) {
20493
+ warnedProgrammaticDecomposeIgnored = true;
20494
+ getLogger().warn(
20495
+ 'memoryVault: decompose:"llm" is ignored by searchVaultMemories \u2014 pass subQueries or use createMemoryVaultSearchTool / createRecallTool (719/B4)'
20496
+ );
20497
+ }
20224
20498
  const prepared = await prepareVaultCandidates(
20225
20499
  query,
20226
20500
  vaultCtx,
@@ -20326,7 +20600,8 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
20326
20600
  return "Error: A search query is required.";
20327
20601
  }
20328
20602
  try {
20329
- const budget = searchOptions?.decompose === "llm" && searchOptions.decomposeOptions ? "high" : searchOptions?.rerank ? "mid" : "low";
20603
+ const wantsDecompose = searchOptions?.decompose === "llm" && !!searchOptions.decomposeOptions;
20604
+ const budget = wantsDecompose ? "high" : searchOptions?.rerank ? "mid" : "low";
20330
20605
  const folderId = searchOptions?.folderId ?? argFolderId;
20331
20606
  if (searchOptions?.useFusion === false) {
20332
20607
  const { results: legacy, embeddingsUnavailable } = await searchVaultMemoriesWithSize(
@@ -20350,7 +20625,14 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
20350
20625
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
20351
20626
  );
20352
20627
  }
20353
- const { recall: recall2 } = await import("./recall-QTVT7HCZ.mjs");
20628
+ let subQueries;
20629
+ if (wantsDecompose && searchOptions?.decomposeOptions) {
20630
+ const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
20631
+ if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
20632
+ subQueries = decomp.subQueries;
20633
+ }
20634
+ }
20635
+ const { recall: recall2 } = await import("./recall-74L75BJ3.mjs");
20354
20636
  let recallDegraded = [];
20355
20637
  const result = await recall2(
20356
20638
  query,
@@ -20366,9 +20648,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
20366
20648
  ...tuningForward,
20367
20649
  ...folderId !== void 0 && { folderId },
20368
20650
  ...searchOptions?.scopes && { scopes: searchOptions.scopes },
20369
- ...searchOptions?.decompose === "llm" && searchOptions.decomposeOptions && {
20370
- decomposeOptions: searchOptions.decomposeOptions
20371
- }
20651
+ ...subQueries && { subQueries }
20372
20652
  }
20373
20653
  );
20374
20654
  if (result.vaultSize === 0) {
@@ -20896,12 +21176,12 @@ var DEFAULT_CHUNK_MIN_SCORE = 0.5;
20896
21176
  function flagsForBudget(budget) {
20897
21177
  switch (budget) {
20898
21178
  case "high":
20899
- return { rerank: true, decompose: true, traverse: true };
21179
+ return { rerank: true, traverse: true };
20900
21180
  case "mid":
20901
- return { rerank: true, decompose: false, traverse: false };
21181
+ return { rerank: true, traverse: false };
20902
21182
  case "low":
20903
21183
  default:
20904
- return { rerank: false, decompose: false, traverse: false };
21184
+ return { rerank: false, traverse: false };
20905
21185
  }
20906
21186
  }
20907
21187
  function dedupeBy(items, ...keys) {
@@ -20920,10 +21200,10 @@ function dedupeBy(items, ...keys) {
20920
21200
  async function recall(query, ctx, options = {}) {
20921
21201
  const types = options.types ?? ["fact"];
20922
21202
  const limit = options.limit ?? DEFAULT_LIMIT;
20923
- const requestedBudget = options.budget ?? DEFAULT_BUDGET;
20924
- const flags = flagsForBudget(requestedBudget);
20925
- const decomposeAvailable = flags.decompose && !!options.decomposeOptions;
20926
- const usedBudget = requestedBudget === "high" && !decomposeAvailable ? "mid" : requestedBudget;
21203
+ const usedBudget = options.budget ?? DEFAULT_BUDGET;
21204
+ const flags = flagsForBudget(usedBudget);
21205
+ const normalizedFacets = normalizeSubQueries(options.subQueries);
21206
+ const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
20927
21207
  const t0 = nowMs();
20928
21208
  let prepMs = 0;
20929
21209
  let factLaneMs = 0;
@@ -20944,7 +21224,9 @@ async function recall(query, ctx, options = {}) {
20944
21224
  if (flags.rerank && factResults.length > 0 && hadV2Head && !didRerank) {
20945
21225
  degraded.push("rerank-unavailable");
20946
21226
  }
20947
- if (flags.decompose && !decomposeAvailable) degraded.push("decompose-unavailable");
21227
+ if (usedBudget === "high" && options.decomposeOptions && !subQueries && !options.graphRefine) {
21228
+ degraded.push("decompose-moved");
21229
+ }
20948
21230
  if (embeddingsUnavailable) degraded.push("embeddings-unavailable");
20949
21231
  const diagnostics = {
20950
21232
  usedBudget,
@@ -21057,10 +21339,8 @@ async function recall(query, ctx, options = {}) {
21057
21339
  },
21058
21340
  ...options.rrfK !== void 0 && { rrfK: options.rrfK },
21059
21341
  ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
21060
- ...decomposeAvailable && {
21061
- decompose: "llm",
21062
- decomposeOptions: options.decomposeOptions
21063
- },
21342
+ // 719/B4 — composite facets arrive pre-built; no LLM call here.
21343
+ ...subQueries && { subQueries },
21064
21344
  ...options.scopes && { scopes: options.scopes },
21065
21345
  ...options.folderId !== void 0 && { folderId: options.folderId },
21066
21346
  ...options.factTypes?.length && { factTypes: options.factTypes },
@@ -21302,6 +21582,7 @@ export {
21302
21582
  require_logError,
21303
21583
  require_Relation,
21304
21584
  require_watermelondb,
21585
+ EncryptionKeyMissingError,
21305
21586
  onClearAllEncryptionState,
21306
21587
  onKeyAvailable,
21307
21588
  clearEncryptionKey,
@@ -21314,11 +21595,13 @@ export {
21314
21595
  decryptDataBytes,
21315
21596
  decryptDataBytesFromBytes,
21316
21597
  hasEncryptionKey,
21598
+ seedEncryptionKeys,
21317
21599
  encryptDataWithKey,
21318
21600
  decryptDataWithKey,
21319
21601
  encryptDataBatch,
21320
21602
  decryptDataBatch,
21321
21603
  requestEncryptionKey,
21604
+ refreshEncryptionKeyIfMatches,
21322
21605
  requestKeyPair,
21323
21606
  exportPublicKey,
21324
21607
  hasKeyPair,
@@ -21405,6 +21688,7 @@ export {
21405
21688
  EMBEDDINGS_DEGRADED_EMPTY,
21406
21689
  RerankerUnavailableError,
21407
21690
  isRerankerAvailable,
21691
+ decomposeQuery,
21408
21692
  MAX_HOPS,
21409
21693
  ENTITY_FANOUT,
21410
21694
  NODE_BUDGET,