@anuma/sdk 4.1.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15012,14 +15012,11 @@ function notifyKeyAvailable(address) {
15012
15012
  }
15013
15013
  }
15014
15014
  }
15015
- function getStoredKey(address) {
15016
- const keys = encryptionKeyStore.get(address);
15017
- return keys?.current ?? null;
15018
- }
15019
15015
  function getStoredKeyByVersion(address, version) {
15020
15016
  const keys = encryptionKeyStore.get(address);
15021
15017
  if (!keys) return null;
15022
- return version === "v2" ? keys.legacy : keys.current;
15018
+ const value = version === "v2" ? keys.legacy : keys.current;
15019
+ return value ?? null;
15023
15020
  }
15024
15021
  function setStoredKey(address, keys) {
15025
15022
  encryptionKeyStore.set(address, keys);
@@ -15028,6 +15025,8 @@ function clearEncryptionKey(address) {
15028
15025
  encryptionKeyStore.delete(address);
15029
15026
  cryptoKeyCache.delete(`${address}:v2`);
15030
15027
  cryptoKeyCache.delete(`${address}:v3`);
15028
+ refreshCandidatesByAddress.delete(address);
15029
+ divergentDeriveAddresses.delete(address);
15031
15030
  }
15032
15031
  function clearAllEncryptionState() {
15033
15032
  sessionEpoch += 1;
@@ -15035,6 +15034,9 @@ function clearAllEncryptionState() {
15035
15034
  cryptoKeyCache.clear();
15036
15035
  keyAvailableCallbacks.clear();
15037
15036
  pendingKeyRequests.clear();
15037
+ pendingCandidateDerives.clear();
15038
+ refreshCandidatesByAddress.clear();
15039
+ divergentDeriveAddresses.clear();
15038
15040
  clearAllKeyPairs();
15039
15041
  for (const cb of clearAllEncryptionStateCallbacks) {
15040
15042
  try {
@@ -15218,9 +15220,7 @@ async function getEncryptionKey(address, version = "v3") {
15218
15220
  }
15219
15221
  const keyHex = getStoredKeyByVersion(address, version);
15220
15222
  if (!keyHex) {
15221
- throw new Error(
15222
- "Encryption key not found. Please sign a message to generate your encryption key."
15223
- );
15223
+ throw new EncryptionKeyMissingError(address, version);
15224
15224
  }
15225
15225
  const keyBytes = hexToBytes(keyHex);
15226
15226
  const cryptoKey = await crypto.subtle.importKey(
@@ -15284,8 +15284,42 @@ async function decryptDataBytesFromBytes(encrypted, address, version = "v3") {
15284
15284
  );
15285
15285
  return new Uint8Array(decryptedData);
15286
15286
  }
15287
- function hasEncryptionKey(address) {
15288
- return getStoredKey(address) !== null;
15287
+ function hasEncryptionKey(address, version) {
15288
+ return getStoredKeyByVersion(address, version ?? "v3") !== null;
15289
+ }
15290
+ function seedEncryptionKeys(address, keys) {
15291
+ if (!isValidWalletAddress(address)) {
15292
+ throw new Error(
15293
+ `Invalid wallet address: ${address}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
15294
+ );
15295
+ }
15296
+ if (!keys.legacy && !keys.current) {
15297
+ throw new Error("seedEncryptionKeys: at least one of legacy or current is required");
15298
+ }
15299
+ if (keys.legacy !== void 0) {
15300
+ assertAes256KeyHex("legacy", keys.legacy);
15301
+ }
15302
+ if (keys.current !== void 0) {
15303
+ assertAes256KeyHex("current", keys.current);
15304
+ }
15305
+ const existing = encryptionKeyStore.get(address);
15306
+ const next = {
15307
+ legacy: keys.legacy ?? existing?.legacy,
15308
+ current: keys.current ?? existing?.current
15309
+ };
15310
+ setStoredKey(address, next);
15311
+ if (keys.legacy !== void 0) cryptoKeyCache.delete(`${address}:v2`);
15312
+ if (keys.current !== void 0) cryptoKeyCache.delete(`${address}:v3`);
15313
+ refreshCandidatesByAddress.delete(address);
15314
+ divergentDeriveAddresses.delete(address);
15315
+ notifyKeyAvailable(address);
15316
+ }
15317
+ function assertAes256KeyHex(field10, value) {
15318
+ if (!AES_256_KEY_HEX.test(value)) {
15319
+ throw new Error(
15320
+ `seedEncryptionKeys: ${field10} must be 64 hex characters (32-byte AES key), got length ${value.length}`
15321
+ );
15322
+ }
15289
15323
  }
15290
15324
  async function encryptBytesWithKey(plaintext, key) {
15291
15325
  const plaintextBytes = typeof plaintext === "string" ? SHARED_TEXT_ENCODER.encode(plaintext) : plaintext;
@@ -15334,20 +15368,23 @@ async function decryptDataBatch(encryptedValues, address) {
15334
15368
  const key = await getEncryptionKey(address);
15335
15369
  return Promise.all(encryptedValues.map((value) => decryptDataWithKey(value, key)));
15336
15370
  }
15337
- async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner) {
15371
+ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner, options) {
15338
15372
  if (!isValidWalletAddress(walletAddress)) {
15339
15373
  throw new Error(
15340
15374
  `Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
15341
15375
  );
15342
15376
  }
15343
- const existingKey = getStoredKey(walletAddress);
15344
- if (existingKey) {
15345
- return;
15377
+ if (!options?.force && getStoredKeyByVersion(walletAddress, "v3") && getStoredKeyByVersion(walletAddress, "v2")) {
15378
+ return true;
15346
15379
  }
15347
- const pending = pendingKeyRequests.get(walletAddress);
15348
- if (pending) {
15349
- await pending;
15350
- return;
15380
+ if (!options?.force && divergentDeriveAddresses.has(walletAddress)) {
15381
+ return false;
15382
+ }
15383
+ if (!options?.force) {
15384
+ const pending = pendingKeyRequests.get(walletAddress);
15385
+ if (pending) {
15386
+ return pending;
15387
+ }
15351
15388
  }
15352
15389
  const startEpoch = sessionEpoch;
15353
15390
  const promise = (async () => {
@@ -15375,20 +15412,143 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
15375
15412
  deriveKeyFromSignatureV3(signature)
15376
15413
  ]);
15377
15414
  if (sessionEpoch !== startEpoch) {
15378
- return;
15415
+ return false;
15416
+ }
15417
+ const existing = encryptionKeyStore.get(walletAddress);
15418
+ if (!options?.force && existing && (existing.legacy || existing.current)) {
15419
+ const matchesCurrent = Boolean(existing.current && existing.current === currentKey);
15420
+ const matchesLegacy = Boolean(existing.legacy && existing.legacy === legacyKey);
15421
+ if (!matchesCurrent && !matchesLegacy) {
15422
+ getLogger().warn(
15423
+ "requestEncryptionKey: derived keys do not match existing store; leaving unchanged"
15424
+ );
15425
+ divergentDeriveAddresses.add(walletAddress);
15426
+ return false;
15427
+ }
15428
+ setStoredKey(walletAddress, {
15429
+ legacy: existing.legacy ?? legacyKey,
15430
+ current: existing.current ?? currentKey
15431
+ });
15432
+ } else {
15433
+ setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
15379
15434
  }
15380
- setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
15435
+ cryptoKeyCache.delete(`${walletAddress}:v2`);
15436
+ cryptoKeyCache.delete(`${walletAddress}:v3`);
15437
+ refreshCandidatesByAddress.delete(walletAddress);
15438
+ divergentDeriveAddresses.delete(walletAddress);
15381
15439
  notifyKeyAvailable(walletAddress);
15440
+ return true;
15382
15441
  })();
15383
15442
  pendingKeyRequests.set(walletAddress, promise);
15384
15443
  try {
15385
- await promise;
15444
+ return await promise;
15386
15445
  } finally {
15387
15446
  if (pendingKeyRequests.get(walletAddress) === promise) {
15388
15447
  pendingKeyRequests.delete(walletAddress);
15389
15448
  }
15390
15449
  }
15391
15450
  }
15451
+ async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, signMessage, embeddedWalletSigner) {
15452
+ if (!isValidWalletAddress(walletAddress)) {
15453
+ throw new Error(
15454
+ `Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
15455
+ );
15456
+ }
15457
+ const parsed = parsePrefixedCiphertext(probeCiphertext);
15458
+ if (!parsed) {
15459
+ return false;
15460
+ }
15461
+ const cached = refreshCandidatesByAddress.get(walletAddress);
15462
+ if (cached) {
15463
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
15464
+ }
15465
+ let derivePromise = pendingCandidateDerives.get(walletAddress);
15466
+ if (!derivePromise) {
15467
+ derivePromise = (async () => {
15468
+ const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
15469
+ if (candidates2) {
15470
+ refreshCandidatesByAddress.set(walletAddress, candidates2);
15471
+ }
15472
+ return candidates2;
15473
+ })().finally(() => {
15474
+ if (pendingCandidateDerives.get(walletAddress) === derivePromise) {
15475
+ pendingCandidateDerives.delete(walletAddress);
15476
+ }
15477
+ });
15478
+ pendingCandidateDerives.set(walletAddress, derivePromise);
15479
+ }
15480
+ const candidates = await derivePromise;
15481
+ if (!candidates) {
15482
+ return false;
15483
+ }
15484
+ return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
15485
+ }
15486
+ function parsePrefixedCiphertext(probeCiphertext) {
15487
+ const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
15488
+ if (!version) return null;
15489
+ const encryptedData = probeCiphertext.slice(
15490
+ version === "v3" ? "enc:v3:".length : "enc:v2:".length
15491
+ );
15492
+ if (encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(encryptedData)) {
15493
+ return null;
15494
+ }
15495
+ return { version, encryptedData };
15496
+ }
15497
+ async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
15498
+ const startEpoch = sessionEpoch;
15499
+ const signOptions = { showWalletUIs: false };
15500
+ let signature;
15501
+ try {
15502
+ if (embeddedWalletSigner) {
15503
+ signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
15504
+ } else {
15505
+ signature = await signMessage(SIGN_MESSAGE, signOptions);
15506
+ }
15507
+ } catch (error) {
15508
+ if (embeddedWalletSigner && error instanceof Error) {
15509
+ getLogger().warn(
15510
+ "Embedded wallet signing failed during key refresh, falling back to standard signMessage:",
15511
+ error.message
15512
+ );
15513
+ signature = await signMessage(SIGN_MESSAGE, signOptions);
15514
+ } else {
15515
+ throw error;
15516
+ }
15517
+ }
15518
+ const [legacyKey, currentKey] = await Promise.all([
15519
+ deriveKeyFromSignature(signature),
15520
+ deriveKeyFromSignatureV3(signature)
15521
+ ]);
15522
+ if (sessionEpoch !== startEpoch) {
15523
+ return null;
15524
+ }
15525
+ return { legacy: legacyKey, current: currentKey };
15526
+ }
15527
+ async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
15528
+ const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
15529
+ const keyBytes = hexToBytes(candidateHex);
15530
+ const candidateCryptoKey = await crypto.subtle.importKey(
15531
+ "raw",
15532
+ keyBytes.buffer,
15533
+ { name: "AES-GCM" },
15534
+ false,
15535
+ ["encrypt", "decrypt"]
15536
+ );
15537
+ try {
15538
+ await decryptDataWithKey(parsed.encryptedData, candidateCryptoKey);
15539
+ } catch {
15540
+ getLogger().warn(
15541
+ "refreshEncryptionKeyIfMatches: candidate keys failed to decrypt probe; leaving store unchanged"
15542
+ );
15543
+ return false;
15544
+ }
15545
+ setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
15546
+ cryptoKeyCache.delete(`${walletAddress}:v2`);
15547
+ cryptoKeyCache.delete(`${walletAddress}:v3`);
15548
+ divergentDeriveAddresses.delete(walletAddress);
15549
+ notifyKeyAvailable(walletAddress);
15550
+ return true;
15551
+ }
15392
15552
  async function persistKeyPair(address, startEpoch) {
15393
15553
  if (typeof window === "undefined") {
15394
15554
  return;
@@ -15613,15 +15773,28 @@ function clearAllKeyPairs() {
15613
15773
  }
15614
15774
  }
15615
15775
  }
15616
- var SIGN_MESSAGE, encryptionKeyStore, pendingKeyRequests, sessionEpoch, cryptoKeyCache, keyAvailableCallbacks, clearAllEncryptionStateCallbacks, keyPairStore, SHARED_TEXT_ENCODER, SHARED_TEXT_DECODER, KEYPAIR_STORAGE_PREFIX;
15776
+ var SIGN_MESSAGE, EncryptionKeyMissingError, encryptionKeyStore, pendingKeyRequests, pendingCandidateDerives, refreshCandidatesByAddress, divergentDeriveAddresses, sessionEpoch, cryptoKeyCache, keyAvailableCallbacks, clearAllEncryptionStateCallbacks, keyPairStore, SHARED_TEXT_ENCODER, SHARED_TEXT_DECODER, AES_256_KEY_HEX, KEYPAIR_STORAGE_PREFIX;
15617
15777
  var init_useEncryption = __esm({
15618
15778
  "src/react/useEncryption.ts"() {
15619
15779
  "use strict";
15620
15780
  "use client";
15621
15781
  init_logger();
15622
15782
  SIGN_MESSAGE = "The app is asking you to sign this message to generate a key, which will be used to encrypt data.";
15783
+ EncryptionKeyMissingError = class extends Error {
15784
+ constructor(address, version) {
15785
+ super(
15786
+ `Encryption key (${version}) not found for ${address}. Please sign a message to generate your encryption key.`
15787
+ );
15788
+ this.name = "EncryptionKeyMissingError";
15789
+ this.address = address;
15790
+ this.version = version;
15791
+ }
15792
+ };
15623
15793
  encryptionKeyStore = /* @__PURE__ */ new Map();
15624
15794
  pendingKeyRequests = /* @__PURE__ */ new Map();
15795
+ pendingCandidateDerives = /* @__PURE__ */ new Map();
15796
+ refreshCandidatesByAddress = /* @__PURE__ */ new Map();
15797
+ divergentDeriveAddresses = /* @__PURE__ */ new Set();
15625
15798
  sessionEpoch = 0;
15626
15799
  cryptoKeyCache = /* @__PURE__ */ new Map();
15627
15800
  keyAvailableCallbacks = /* @__PURE__ */ new Map();
@@ -15629,6 +15802,7 @@ var init_useEncryption = __esm({
15629
15802
  keyPairStore = /* @__PURE__ */ new Map();
15630
15803
  SHARED_TEXT_ENCODER = new TextEncoder();
15631
15804
  SHARED_TEXT_DECODER = new TextDecoder();
15805
+ AES_256_KEY_HEX = /^[0-9a-fA-F]{64}$/;
15632
15806
  KEYPAIR_STORAGE_PREFIX = "ecdh_keypair_";
15633
15807
  }
15634
15808
  });
@@ -15661,20 +15835,29 @@ async function encryptField(value, address, signMessage, embeddedWalletSigner, s
15661
15835
  const encrypted = await encryptData(value, address);
15662
15836
  return `${ENCRYPTION_PREFIX}${encrypted}`;
15663
15837
  }
15664
- async function decryptField(value, address) {
15665
- if (!value) return value;
15838
+ async function decryptFieldDetailed(value, address) {
15839
+ if (!value) return { status: "plaintext", value };
15666
15840
  const detected = detectEncryptionVersion(value);
15667
- if (!detected) return value;
15841
+ if (!detected) return { status: "plaintext", value };
15668
15842
  if (detected.encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(detected.encryptedData)) {
15669
- return value;
15843
+ return { status: "invalid_payload", value, version: detected.version };
15670
15844
  }
15671
15845
  try {
15672
- return await decryptData(detected.encryptedData, address, detected.version);
15846
+ const plaintext = await decryptData(detected.encryptedData, address, detected.version);
15847
+ return { status: "ok", value: plaintext, version: detected.version };
15673
15848
  } catch (error) {
15674
- getLogger().warn("Failed to decrypt field, returning as-is:", error);
15675
- return value;
15849
+ if (error instanceof EncryptionKeyMissingError) {
15850
+ getLogger().warn("Failed to decrypt field (key missing for version):", error.message);
15851
+ return { status: "key_missing", value, version: detected.version };
15852
+ }
15853
+ getLogger().warn("Failed to decrypt field (auth mismatch or crypto error):", error);
15854
+ return { status: "auth_mismatch", value, version: detected.version };
15676
15855
  }
15677
15856
  }
15857
+ async function decryptField(value, address) {
15858
+ const result = await decryptFieldDetailed(value, address);
15859
+ return result.value;
15860
+ }
15678
15861
  async function encryptJsonField(value, address, signMessage, embeddedWalletSigner, skipKeyRequest) {
15679
15862
  if (!value) return void 0;
15680
15863
  const jsonString = JSON.stringify(value);
@@ -15811,20 +15994,40 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
15811
15994
  throw error;
15812
15995
  }
15813
15996
  }
15814
- async function decryptMaybeJsonField(value, address) {
15815
- if (value === void 0) return void 0;
15816
- if (value === null) return value;
15997
+ async function decryptMaybeJsonFieldDetailed(value, address) {
15998
+ if (value === void 0) return { value: void 0, status: "plaintext" };
15999
+ if (value === null) return { value, status: "plaintext" };
15817
16000
  if (typeof value === "string") {
15818
16001
  if (isEncrypted(value)) {
15819
- return await decryptJsonField(value, address);
16002
+ const detailed = await decryptFieldDetailed(value, address);
16003
+ if (detailed.status !== "ok") {
16004
+ return { value: void 0, status: detailed.status, probe: value };
16005
+ }
16006
+ try {
16007
+ return { value: JSON.parse(detailed.value), status: "ok" };
16008
+ } catch (error) {
16009
+ getLogger().warn("Failed to parse decrypted JSON field:", error);
16010
+ return { value: void 0, status: "invalid_payload", probe: value };
16011
+ }
15820
16012
  }
15821
16013
  try {
15822
- return JSON.parse(value);
16014
+ return { value: JSON.parse(value), status: "plaintext" };
15823
16015
  } catch {
15824
- return void 0;
16016
+ return { value: void 0, status: "plaintext" };
15825
16017
  }
15826
16018
  }
15827
- return value;
16019
+ return { value, status: "plaintext" };
16020
+ }
16021
+ function worseDecryptStatus(a, b) {
16022
+ if (b === "ok" || b === "plaintext") return a;
16023
+ if (!a || a === "ok" || a === "plaintext") return b;
16024
+ const rank = (s) => {
16025
+ if (s === "auth_mismatch") return 3;
16026
+ if (s === "key_missing") return 2;
16027
+ if (s === "invalid_payload") return 1;
16028
+ return 0;
16029
+ };
16030
+ return rank(b) >= rank(a) ? b : a;
15828
16031
  }
15829
16032
  async function decryptMessageFields(message, address, signMessage, embeddedWalletSigner) {
15830
16033
  if (!address) {
@@ -15837,33 +16040,82 @@ async function decryptMessageFields(message, address, signMessage, embeddedWalle
15837
16040
  getLogger().warn("Failed to request encryption key for decryption:", error);
15838
16041
  }
15839
16042
  }
15840
- const decryptedContent = await decryptField(message.content, address);
15841
- const decryptedThinking = message.thinking ? await decryptField(message.thinking, address) : message.thinking;
15842
- const decryptedVector = await decryptMaybeJsonField(
16043
+ let assembled = await assembleDecryptedMessage(message, address);
16044
+ const needsRefresh = assembled.decryptionStatus === "auth_mismatch" || assembled.decryptionStatus === "key_missing";
16045
+ if (needsRefresh && signMessage && assembled.refreshProbe) {
16046
+ try {
16047
+ const refreshed = await refreshEncryptionKeyIfMatches(
16048
+ address,
16049
+ assembled.refreshProbe,
16050
+ signMessage,
16051
+ embeddedWalletSigner
16052
+ );
16053
+ if (refreshed) {
16054
+ assembled = await assembleDecryptedMessage(message, address);
16055
+ }
16056
+ } catch (error) {
16057
+ getLogger().warn("Failed to refresh encryption key after decrypt failure:", error);
16058
+ }
16059
+ }
16060
+ const { refreshProbe: _probe, ...result } = assembled;
16061
+ return result;
16062
+ }
16063
+ async function assembleDecryptedMessage(message, address) {
16064
+ const contentResult = await decryptFieldDetailed(message.content, address);
16065
+ const thinkingResult = message.thinking ? await decryptFieldDetailed(message.thinking, address) : null;
16066
+ const vectorResult = await decryptMaybeJsonFieldDetailed(
15843
16067
  message.vector,
15844
16068
  address
15845
16069
  );
15846
- const decryptedChunks = await decryptMaybeJsonField(
16070
+ const chunksResult = await decryptMaybeJsonFieldDetailed(
15847
16071
  message.chunks,
15848
16072
  address
15849
16073
  );
15850
- const decryptedSources = await decryptMaybeJsonField(
16074
+ const sourcesResult = await decryptMaybeJsonFieldDetailed(
15851
16075
  message.sources,
15852
16076
  address
15853
16077
  );
15854
- const decryptedThoughtProcess = await decryptMaybeJsonField(
16078
+ const thoughtProcessResult = await decryptMaybeJsonFieldDetailed(
15855
16079
  message.thoughtProcess,
15856
16080
  address
15857
16081
  );
15858
- return {
16082
+ let decryptionStatus;
16083
+ let refreshProbe;
16084
+ const consider = (status, probe) => {
16085
+ decryptionStatus = worseDecryptStatus(decryptionStatus, status);
16086
+ if (!refreshProbe && probe && (status === "auth_mismatch" || status === "key_missing")) {
16087
+ refreshProbe = probe;
16088
+ }
16089
+ };
16090
+ consider(contentResult.status, isEncrypted(message.content) ? message.content : void 0);
16091
+ if (thinkingResult && message.thinking && typeof message.thinking === "string") {
16092
+ consider(thinkingResult.status, isEncrypted(message.thinking) ? message.thinking : void 0);
16093
+ }
16094
+ if (vectorResult.probe) consider(vectorResult.status, vectorResult.probe);
16095
+ if (chunksResult.probe) consider(chunksResult.status, chunksResult.probe);
16096
+ if (sourcesResult.probe) consider(sourcesResult.status, sourcesResult.probe);
16097
+ if (thoughtProcessResult.probe) {
16098
+ consider(thoughtProcessResult.status, thoughtProcessResult.probe);
16099
+ }
16100
+ const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
16101
+ const result = {
15859
16102
  ...message,
15860
- content: decryptedContent,
15861
- thinking: decryptedThinking,
15862
- vector: decryptedVector,
15863
- chunks: decryptedChunks,
15864
- sources: decryptedSources,
15865
- thoughtProcess: decryptedThoughtProcess
16103
+ content: contentResult.value,
16104
+ thinking: thinkingResult ? thinkingResult.value : message.thinking,
16105
+ vector: vectorResult.value,
16106
+ chunks: chunksResult.value,
16107
+ sources: sourcesResult.value,
16108
+ thoughtProcess: thoughtProcessResult.value
15866
16109
  };
16110
+ if (statusForConsumer) {
16111
+ result.decryptionStatus = statusForConsumer;
16112
+ } else {
16113
+ delete result.decryptionStatus;
16114
+ }
16115
+ if (refreshProbe) {
16116
+ result.refreshProbe = refreshProbe;
16117
+ }
16118
+ return result;
15867
16119
  }
15868
16120
  var init_encryption = __esm({
15869
16121
  "src/lib/db/chat/encryption.ts"() {
@@ -23465,6 +23717,7 @@ __export(index_exports, {
23465
23717
  DEFAULT_VAULT_CACHE_SIZE: () => DEFAULT_VAULT_CACHE_SIZE,
23466
23718
  DatabaseManager: () => DatabaseManager,
23467
23719
  ENTITY_FANOUT: () => ENTITY_FANOUT,
23720
+ EncryptionKeyMissingError: () => EncryptionKeyMissingError,
23468
23721
  ExcelProcessor: () => ExcelProcessor,
23469
23722
  HARD_DELETE_WINDOW_MS: () => HARD_DELETE_WINDOW_MS,
23470
23723
  INFERENCE_ID_HEADER: () => INFERENCE_ID_HEADER,
@@ -23665,6 +23918,7 @@ __export(index_exports, {
23665
23918
  rankProfileCandidates: () => rankProfileCandidates,
23666
23919
  recall: () => recall,
23667
23920
  reflect: () => reflect,
23921
+ refreshEncryptionKeyIfMatches: () => refreshEncryptionKeyIfMatches,
23668
23922
  relinkMemoryTopicsOp: () => relinkMemoryTopicsOp,
23669
23923
  relinkMisclassifiedVideosOp: () => relinkMisclassifiedVideosOp,
23670
23924
  renderTemplate: () => renderTemplate,
@@ -23685,6 +23939,7 @@ __export(index_exports, {
23685
23939
  searchMediaOp: () => searchMediaOp,
23686
23940
  searchMessagesOp: () => searchMessagesOp,
23687
23941
  searchVaultMemories: () => searchVaultMemories,
23942
+ seedEncryptionKeys: () => seedEncryptionKeys,
23688
23943
  selectServerToolsForPrompt: () => selectServerToolsForPrompt,
23689
23944
  serverPlatformStorage: () => serverPlatformStorage,
23690
23945
  setMemoryVisibilityOp: () => setMemoryVisibilityOp,
@@ -29893,21 +30148,63 @@ var DEFAULT_PROFILE_FACETS = [
29893
30148
  guidance: "Summarize what the person has been up to lately in one line, favoring the most recently reinforced facts. Frame it as current/ongoing (e.g. 'Lately: training for a first half-marathon and learning Portuguese'). Omit if nothing recent stands out."
29894
30149
  }
29895
30150
  ];
30151
+ var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
30152
+ var NEARBY_MAX_INTERESTS = 12;
30153
+ var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
30154
+ var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
30155
+ function codePointLength(value) {
30156
+ return [...value].length;
30157
+ }
30158
+ var FACET_BASE_PROPERTIES = {
30159
+ summary: {
30160
+ type: "string",
30161
+ description: "The synthesized section prose. Empty string if the memories don't cover it."
30162
+ },
30163
+ hasEvidence: {
30164
+ type: "boolean",
30165
+ description: "False when the supplied memories don't support any claim for this facet."
30166
+ }
30167
+ };
29896
30168
  var FACET_RESPONSE_SCHEMA = {
30169
+ type: "object",
30170
+ properties: FACET_BASE_PROPERTIES,
30171
+ required: ["summary", "hasEvidence"],
30172
+ additionalProperties: false
30173
+ };
30174
+ var WORK_ROLE_RESPONSE_SCHEMA = {
29897
30175
  type: "object",
29898
30176
  properties: {
29899
- summary: {
30177
+ ...FACET_BASE_PROPERTIES,
30178
+ occupation: {
29900
30179
  type: "string",
29901
- description: "The synthesized section prose. Empty string if the memories don't cover it."
29902
- },
29903
- hasEvidence: {
29904
- type: "boolean",
29905
- description: "False when the supplied memories don't support any claim for this facet."
30180
+ description: `The same role as a standalone phrase for a profile field, at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters (e.g. "Backend engineer, fintech"). Empty string when hasEvidence is false.`
29906
30181
  }
29907
30182
  },
29908
- required: ["summary", "hasEvidence"],
30183
+ required: ["summary", "hasEvidence", "occupation"],
29909
30184
  additionalProperties: false
29910
30185
  };
30186
+ var INTERESTS_RESPONSE_SCHEMA = {
30187
+ type: "object",
30188
+ properties: {
30189
+ ...FACET_BASE_PROPERTIES,
30190
+ interests: {
30191
+ type: "array",
30192
+ items: { type: "string" },
30193
+ description: `The same interests as discrete strings \u2014 at most ${NEARBY_MAX_INTERESTS} items, each at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters (e.g. ["trail running", "film photography"]). Empty array when hasEvidence is false.`
30194
+ }
30195
+ },
30196
+ required: ["summary", "hasEvidence", "interests"],
30197
+ additionalProperties: false
30198
+ };
30199
+ function facetResponseSchema(key) {
30200
+ if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
30201
+ if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
30202
+ return FACET_RESPONSE_SCHEMA;
30203
+ }
30204
+ var FACET_STRUCTURED_RESPONSE_HINT = {
30205
+ work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
30206
+ interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
30207
+ };
29911
30208
  async function synthesizeProfile(ctx, options = {}) {
29912
30209
  if (!ctx.vaultCtx) {
29913
30210
  throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
@@ -30003,7 +30300,7 @@ function reviewedMemoryIdsSignature(ids) {
30003
30300
  return [...new Set(ids)].sort().join("\n");
30004
30301
  }
30005
30302
  function facetsSignature(facets) {
30006
- return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
30303
+ return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
30007
30304
  }
30008
30305
  function trendsEqual(a, b) {
30009
30306
  if (!a) return false;
@@ -30151,7 +30448,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
30151
30448
  types: ["fact"],
30152
30449
  maxTokens: DEFAULT_FACET_MAX_TOKENS,
30153
30450
  systemPrompt: buildFacetSystemPrompt(facet),
30154
- responseSchema: FACET_RESPONSE_SCHEMA,
30451
+ responseSchema: facetResponseSchema(facet.key),
30155
30452
  memories
30156
30453
  });
30157
30454
  const noEvidence = result.basedOn.memoryIds.length === 0;
@@ -30174,6 +30471,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
30174
30471
  const redacted = await options.redactor.redactTextAsync(text12);
30175
30472
  section.text = redacted.text;
30176
30473
  }
30474
+ if (!legitimateEmpty && !noEvidence) {
30475
+ const values = extractStructuredValues(facet.key, result.structuredOutput);
30476
+ if (options.redactor) {
30477
+ if (values.occupation) {
30478
+ values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
30479
+ }
30480
+ if (values.interests) {
30481
+ const gated = [];
30482
+ for (const interest of values.interests) {
30483
+ gated.push((await options.redactor.redactTextAsync(interest)).text);
30484
+ }
30485
+ values.interests = gated;
30486
+ }
30487
+ }
30488
+ const occupation = normalizeOccupation(values.occupation);
30489
+ if (occupation !== void 0) section.occupation = occupation;
30490
+ const interests = normalizeInterests(values.interests);
30491
+ if (interests !== void 0) section.interests = interests;
30492
+ }
30177
30493
  return section;
30178
30494
  }
30179
30495
  function fallbackSection(facet, prior) {
@@ -30190,6 +30506,7 @@ function fallbackSection(facet, prior) {
30190
30506
  };
30191
30507
  }
30192
30508
  function buildFacetSystemPrompt(facet) {
30509
+ const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
30193
30510
  return `You are writing the "${facet.label}" section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth.
30194
30511
 
30195
30512
  Task for this section:
@@ -30200,7 +30517,7 @@ Rules:
30200
30517
  - If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
30201
30518
  - Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
30202
30519
  - Be concise and specific; no preamble, hedging, or meta-commentary.
30203
- - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false> }.`;
30520
+ - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
30204
30521
  }
30205
30522
  function extractFacetText(structured) {
30206
30523
  if (structured && typeof structured === "object") {
@@ -30211,6 +30528,73 @@ function extractFacetText(structured) {
30211
30528
  }
30212
30529
  return { text: "", legitimateEmpty: false };
30213
30530
  }
30531
+ function extractStructuredValues(key, structured) {
30532
+ if (!structured || typeof structured !== "object") return {};
30533
+ const obj = structured;
30534
+ if (key === "work_role") {
30535
+ return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
30536
+ }
30537
+ if (key === "interests") {
30538
+ const list = coerceInterestList(obj.interests);
30539
+ if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
30540
+ }
30541
+ return {};
30542
+ }
30543
+ function coerceInterestList(value) {
30544
+ if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
30545
+ if (typeof value !== "string") return void 0;
30546
+ const trimmed = value.trim();
30547
+ if (trimmed.startsWith("[")) {
30548
+ try {
30549
+ const parsed = JSON.parse(trimmed);
30550
+ if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
30551
+ } catch {
30552
+ if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
30553
+ }
30554
+ }
30555
+ return splitInterestFragments(trimmed);
30556
+ }
30557
+ function splitInterestFragments(source) {
30558
+ return source.split(",").map((fragment) => {
30559
+ let entry = fragment.trim();
30560
+ if (entry.startsWith("[")) entry = entry.slice(1);
30561
+ if (entry.endsWith("]")) entry = entry.slice(0, -1);
30562
+ entry = entry.trim();
30563
+ const quote = entry[0];
30564
+ if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
30565
+ return entry;
30566
+ });
30567
+ }
30568
+ function normalizeOccupation(value) {
30569
+ if (value === void 0) return void 0;
30570
+ const trimmed = value.trim();
30571
+ if (!trimmed) return void 0;
30572
+ const length = codePointLength(trimmed);
30573
+ if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
30574
+ getLogger().warn(
30575
+ "[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
30576
+ { length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
30577
+ );
30578
+ return void 0;
30579
+ }
30580
+ return trimmed;
30581
+ }
30582
+ function normalizeInterests(values) {
30583
+ if (values === void 0) return void 0;
30584
+ const seen = /* @__PURE__ */ new Set();
30585
+ const out = [];
30586
+ for (const value of values) {
30587
+ const trimmed = value.trim();
30588
+ if (!trimmed) continue;
30589
+ if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
30590
+ const key = trimmed.toLowerCase();
30591
+ if (seen.has(key)) continue;
30592
+ seen.add(key);
30593
+ out.push(trimmed);
30594
+ if (out.length === NEARBY_MAX_INTERESTS) break;
30595
+ }
30596
+ return out.length > 0 ? out : void 0;
30597
+ }
30214
30598
 
30215
30599
  // src/lib/memory/topicExtract.ts
30216
30600
  init_operations2();
@@ -36040,6 +36424,7 @@ function buildSystemPrompt(ctx, options) {
36040
36424
  DEFAULT_VAULT_CACHE_SIZE,
36041
36425
  DatabaseManager,
36042
36426
  ENTITY_FANOUT,
36427
+ EncryptionKeyMissingError,
36043
36428
  ExcelProcessor,
36044
36429
  HARD_DELETE_WINDOW_MS,
36045
36430
  INFERENCE_ID_HEADER,
@@ -36240,6 +36625,7 @@ function buildSystemPrompt(ctx, options) {
36240
36625
  rankProfileCandidates,
36241
36626
  recall,
36242
36627
  reflect,
36628
+ refreshEncryptionKeyIfMatches,
36243
36629
  relinkMemoryTopicsOp,
36244
36630
  relinkMisclassifiedVideosOp,
36245
36631
  renderTemplate,
@@ -36260,6 +36646,7 @@ function buildSystemPrompt(ctx, options) {
36260
36646
  searchMediaOp,
36261
36647
  searchMessagesOp,
36262
36648
  searchVaultMemories,
36649
+ seedEncryptionKeys,
36263
36650
  selectServerToolsForPrompt,
36264
36651
  serverPlatformStorage,
36265
36652
  setMemoryVisibilityOp,