@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.
- package/dist/expo/{chunk-VBO6KSNQ.mjs → chunk-SIMGZQ4P.mjs} +480 -196
- package/dist/expo/index.cjs +417 -120
- package/dist/expo/index.d.mts +161 -26
- package/dist/expo/index.d.ts +161 -26
- package/dist/expo/index.mjs +22 -6
- package/dist/expo/{recall-OCQOA7M3.mjs → recall-PIDYDFIQ.mjs} +1 -1
- package/dist/index.cjs +31 -17
- package/dist/index.mjs +31 -17
- package/dist/react/{chunk-BS7C4CUW.mjs → chunk-KTBOHRRE.mjs} +480 -196
- package/dist/react/index.cjs +417 -120
- package/dist/react/index.d.mts +155 -26
- package/dist/react/index.d.ts +155 -26
- package/dist/react/index.mjs +22 -6
- package/dist/react/{recall-WXCZHXHZ.mjs → recall-DVX4W2UD.mjs} +1 -1
- package/dist/server/{chunk-WADHFDMA.mjs → chunk-FKVYUOSD.mjs} +480 -196
- package/dist/server/index.cjs +417 -120
- package/dist/server/index.d.mts +154 -25
- package/dist/server/index.d.ts +154 -25
- package/dist/server/index.mjs +22 -6
- package/dist/server/{recall-QTVT7HCZ.mjs → recall-74L75BJ3.mjs} +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
14655
|
-
|
|
14656
|
-
return;
|
|
14702
|
+
if (!options?.force && getStoredKeyByVersion(walletAddress, "v3") && getStoredKeyByVersion(walletAddress, "v2")) {
|
|
14703
|
+
return true;
|
|
14657
14704
|
}
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
15155
|
+
const plaintext = await decryptData(detected.encryptedData, address, detected.version);
|
|
15156
|
+
return { status: "ok", value: plaintext, version: detected.version };
|
|
14982
15157
|
} catch (error) {
|
|
14983
|
-
|
|
14984
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
15113
|
-
const
|
|
15114
|
-
|
|
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
|
|
15342
|
+
const chunksResult = await decryptMaybeJsonFieldDetailed(
|
|
15119
15343
|
message.chunks,
|
|
15120
15344
|
address
|
|
15121
15345
|
);
|
|
15122
|
-
const
|
|
15346
|
+
const sourcesResult = await decryptMaybeJsonFieldDetailed(
|
|
15123
15347
|
message.sources,
|
|
15124
15348
|
address
|
|
15125
15349
|
);
|
|
15126
|
-
const
|
|
15350
|
+
const thoughtProcessResult = await decryptMaybeJsonFieldDetailed(
|
|
15127
15351
|
message.thoughtProcess,
|
|
15128
15352
|
address
|
|
15129
15353
|
);
|
|
15130
|
-
|
|
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:
|
|
15133
|
-
thinking:
|
|
15134
|
-
vector:
|
|
15135
|
-
chunks:
|
|
15136
|
-
sources:
|
|
15137
|
-
thoughtProcess:
|
|
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
|
-
|
|
20120
|
-
|
|
20121
|
-
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20131
|
-
|
|
20132
|
-
|
|
20133
|
-
|
|
20134
|
-
|
|
20135
|
-
|
|
20136
|
-
|
|
20137
|
-
|
|
20138
|
-
|
|
20139
|
-
|
|
20140
|
-
|
|
20141
|
-
|
|
20142
|
-
|
|
20143
|
-
|
|
20144
|
-
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20148
|
-
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
20152
|
-
|
|
20153
|
-
|
|
20154
|
-
|
|
20155
|
-
|
|
20156
|
-
|
|
20157
|
-
|
|
20158
|
-
|
|
20159
|
-
|
|
20160
|
-
|
|
20161
|
-
|
|
20162
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
...
|
|
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,
|
|
21179
|
+
return { rerank: true, traverse: true };
|
|
20900
21180
|
case "mid":
|
|
20901
|
-
return { rerank: true,
|
|
21181
|
+
return { rerank: true, traverse: false };
|
|
20902
21182
|
case "low":
|
|
20903
21183
|
default:
|
|
20904
|
-
return { rerank: 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
|
|
20924
|
-
const flags = flagsForBudget(
|
|
20925
|
-
const
|
|
20926
|
-
const
|
|
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 (
|
|
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
|
-
|
|
21061
|
-
|
|
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,
|