memorix 1.9.6 → 1.9.7

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.
@@ -222,6 +222,74 @@ var init_launch_root = __esm({
222
222
  }
223
223
  });
224
224
 
225
+ // src/store/sqlite-reliability.ts
226
+ function isTransientSqliteError(error) {
227
+ const code = error?.code;
228
+ if (typeof code === "string") {
229
+ return /^SQLITE_BUSY(?:_|$)/i.test(code);
230
+ }
231
+ return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
232
+ }
233
+ function isSqliteUnavailableError(error) {
234
+ return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
235
+ }
236
+ function degradedReadError(storeName) {
237
+ return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
238
+ }
239
+ function degradedWriteError(storeName) {
240
+ return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
241
+ }
242
+ function formatSqliteFailure(error) {
243
+ return error instanceof Error && error.message ? error.message : String(error);
244
+ }
245
+ function delay(ms) {
246
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
247
+ }
248
+ async function retrySqliteBusy(operation, label) {
249
+ for (let attempt = 0; ; attempt++) {
250
+ try {
251
+ return await operation();
252
+ } catch (error) {
253
+ if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
254
+ const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
255
+ console.error(
256
+ `[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
257
+ );
258
+ await delay(waitMs);
259
+ }
260
+ }
261
+ }
262
+ async function initializeSqliteStore(factory, dataDir, label) {
263
+ try {
264
+ loadBetterSqlite3();
265
+ } catch (error) {
266
+ throw new SqliteUnavailableError(error);
267
+ }
268
+ return retrySqliteBusy(async () => {
269
+ const store = factory();
270
+ await store.init(dataDir);
271
+ return store;
272
+ }, label);
273
+ }
274
+ var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
275
+ var init_sqlite_reliability = __esm({
276
+ "src/store/sqlite-reliability.ts"() {
277
+ "use strict";
278
+ init_esm_shims();
279
+ init_sqlite_db();
280
+ SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
281
+ SqliteUnavailableError = class extends Error {
282
+ code = "MEMORIX_SQLITE_UNAVAILABLE";
283
+ constructor(cause) {
284
+ super(
285
+ cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
286
+ );
287
+ this.name = "SqliteUnavailableError";
288
+ }
289
+ };
290
+ }
291
+ });
292
+
225
293
  // src/store/sqlite-fts.ts
226
294
  function normalizeFtsToken(token) {
227
295
  return token.replace(/"/g, '""');
@@ -235,22 +303,27 @@ function buildFtsQuery(query) {
235
303
  function initializeObservationLexicalIndex(db2) {
236
304
  const known = ftsAvailability.get(db2);
237
305
  if (known !== void 0) return known;
238
- try {
239
- db2.exec(CREATE_OBSERVATION_FTS);
240
- const observationCount = Number(db2.prepare("SELECT COUNT(*) AS count FROM observations").get()?.count ?? 0);
241
- const indexedCount = Number(db2.prepare(`SELECT COUNT(*) AS count FROM ${FTS_TABLE}`).get()?.count ?? 0);
242
- if (observationCount !== indexedCount) {
243
- db2.exec(`INSERT INTO ${FTS_TABLE}(${FTS_TABLE}) VALUES ('rebuild')`);
306
+ let lastError;
307
+ for (let attempt = 1; attempt <= FTS_INIT_MAX_ATTEMPTS; attempt++) {
308
+ try {
309
+ db2.exec(CREATE_OBSERVATION_FTS);
310
+ const observationCount = Number(db2.prepare("SELECT COUNT(*) AS count FROM observations").get()?.count ?? 0);
311
+ const indexedCount = Number(db2.prepare(`SELECT COUNT(*) AS count FROM ${FTS_TABLE}`).get()?.count ?? 0);
312
+ if (observationCount !== indexedCount) {
313
+ db2.exec(`INSERT INTO ${FTS_TABLE}(${FTS_TABLE}) VALUES ('rebuild')`);
314
+ }
315
+ ftsAvailability.set(db2, true);
316
+ return true;
317
+ } catch (error) {
318
+ lastError = error;
319
+ if (!isTransientSqliteError(error) || attempt === FTS_INIT_MAX_ATTEMPTS) break;
244
320
  }
245
- ftsAvailability.set(db2, true);
246
- return true;
247
- } catch (error) {
248
- ftsAvailability.set(db2, false);
249
- console.warn(
250
- `[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${error instanceof Error ? error.message : String(error)}`
251
- );
252
- return false;
253
321
  }
322
+ ftsAvailability.set(db2, false);
323
+ console.warn(
324
+ `[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${lastError instanceof Error ? lastError.message : String(lastError)}`
325
+ );
326
+ return false;
254
327
  }
255
328
  function isObservationLexicalIndexEnabled(db2) {
256
329
  return ftsAvailability.get(db2) === true;
@@ -304,11 +377,12 @@ function searchObservationLexically(db2, options) {
304
377
  score: Math.max(0, Number(row.lexical_score ?? 0))
305
378
  }));
306
379
  }
307
- var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS;
380
+ var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS, FTS_INIT_MAX_ATTEMPTS;
308
381
  var init_sqlite_fts = __esm({
309
382
  "src/store/sqlite-fts.ts"() {
310
383
  "use strict";
311
384
  init_esm_shims();
385
+ init_sqlite_reliability();
312
386
  ftsAvailability = /* @__PURE__ */ new WeakMap();
313
387
  FTS_TABLE = "observations_fts";
314
388
  CREATE_OBSERVATION_FTS = `
@@ -342,6 +416,7 @@ CREATE TRIGGER IF NOT EXISTS observations_fts_au AFTER UPDATE ON observations BE
342
416
  VALUES (new.id, new.entityName, new.title, new.narrative, new.facts, new.filesModified, new.concepts, new.attachments);
343
417
  END;
344
418
  `;
419
+ FTS_INIT_MAX_ATTEMPTS = 3;
345
420
  }
346
421
  });
347
422
 
@@ -3061,74 +3136,6 @@ var init_provider = __esm({
3061
3136
  }
3062
3137
  });
3063
3138
 
3064
- // src/store/sqlite-reliability.ts
3065
- function isTransientSqliteError(error) {
3066
- const code = error?.code;
3067
- if (typeof code === "string") {
3068
- return /^SQLITE_BUSY(?:_|$)/i.test(code);
3069
- }
3070
- return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
3071
- }
3072
- function isSqliteUnavailableError(error) {
3073
- return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
3074
- }
3075
- function degradedReadError(storeName) {
3076
- return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
3077
- }
3078
- function degradedWriteError(storeName) {
3079
- return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
3080
- }
3081
- function formatSqliteFailure(error) {
3082
- return error instanceof Error && error.message ? error.message : String(error);
3083
- }
3084
- function delay(ms) {
3085
- return new Promise((resolve2) => setTimeout(resolve2, ms));
3086
- }
3087
- async function retrySqliteBusy(operation, label) {
3088
- for (let attempt = 0; ; attempt++) {
3089
- try {
3090
- return await operation();
3091
- } catch (error) {
3092
- if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
3093
- const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
3094
- console.error(
3095
- `[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
3096
- );
3097
- await delay(waitMs);
3098
- }
3099
- }
3100
- }
3101
- async function initializeSqliteStore(factory, dataDir, label) {
3102
- try {
3103
- loadBetterSqlite3();
3104
- } catch (error) {
3105
- throw new SqliteUnavailableError(error);
3106
- }
3107
- return retrySqliteBusy(async () => {
3108
- const store = factory();
3109
- await store.init(dataDir);
3110
- return store;
3111
- }, label);
3112
- }
3113
- var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
3114
- var init_sqlite_reliability = __esm({
3115
- "src/store/sqlite-reliability.ts"() {
3116
- "use strict";
3117
- init_esm_shims();
3118
- init_sqlite_db();
3119
- SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
3120
- SqliteUnavailableError = class extends Error {
3121
- code = "MEMORIX_SQLITE_UNAVAILABLE";
3122
- constructor(cause) {
3123
- super(
3124
- cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
3125
- );
3126
- this.name = "SqliteUnavailableError";
3127
- }
3128
- };
3129
- }
3130
- });
3131
-
3132
3139
  // src/store/file-lock.ts
3133
3140
  import { promises as fs2 } from "fs";
3134
3141
  import { randomUUID } from "crypto";
@@ -7686,6 +7693,7 @@ async function searchObservations(options) {
7686
7693
  };
7687
7694
  let queryVector = null;
7688
7695
  let embeddingProviderForSearch = null;
7696
+ let embeddingUnavailable = false;
7689
7697
  if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
7690
7698
  try {
7691
7699
  const provider2 = await getEmbeddingProvider();
@@ -7693,6 +7701,7 @@ async function searchObservations(options) {
7693
7701
  embeddingProviderForSearch = provider2;
7694
7702
  const activeVectorDimensions = getVectorDimensions();
7695
7703
  if (activeVectorDimensions !== null && provider2.dimensions !== activeVectorDimensions) {
7704
+ embeddingUnavailable = true;
7696
7705
  rememberSearchMode(
7697
7706
  modeKey,
7698
7707
  `fulltext (embedding dimension mismatch: provider ${provider2.dimensions}d vs index ${activeVectorDimensions}d)`
@@ -7732,8 +7741,11 @@ async function searchObservations(options) {
7732
7741
  hybridWeights: isCJKHeavy ? { text: 0.2, vector: 0.8 } : { text: 0.6, vector: 0.4 }
7733
7742
  };
7734
7743
  }
7744
+ } else {
7745
+ embeddingUnavailable = true;
7735
7746
  }
7736
7747
  } catch (error) {
7748
+ embeddingUnavailable = true;
7737
7749
  rememberSearchMode(modeKey, "fulltext (embedding unavailable)");
7738
7750
  console.error("[memorix] Embedding failed or timed out, falling back to fulltext search");
7739
7751
  }
@@ -7741,7 +7753,7 @@ async function searchObservations(options) {
7741
7753
  mark("preSearch");
7742
7754
  let results;
7743
7755
  const persistentCorpusEligible = await shouldUsePersistentSearch(projectIds, options.projectId);
7744
- const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled);
7756
+ const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled || embeddingUnavailable);
7745
7757
  let persistent = persistentCorpusEligible && queryVector && embeddingProviderForSearch ? await searchPersistentSemantically(options, projectIds, requestLimit, queryVector, embeddingProviderForSearch) : canUsePersistentLexical ? await searchPersistentLexically(options, projectIds, requestLimit) : null;
7746
7758
  const durableStore = getOptionalObservationStore();
7747
7759
  if (!persistent && queryVector && durableStore?.hasLexicalIndex?.() && count(database) === 0) {
@@ -389,6 +389,74 @@ var init_launch_root = __esm({
389
389
  }
390
390
  });
391
391
 
392
+ // src/store/sqlite-reliability.ts
393
+ function isTransientSqliteError(error) {
394
+ const code = error?.code;
395
+ if (typeof code === "string") {
396
+ return /^SQLITE_BUSY(?:_|$)/i.test(code);
397
+ }
398
+ return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
399
+ }
400
+ function isSqliteUnavailableError(error) {
401
+ return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
402
+ }
403
+ function degradedReadError(storeName) {
404
+ return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
405
+ }
406
+ function degradedWriteError(storeName) {
407
+ return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
408
+ }
409
+ function formatSqliteFailure(error) {
410
+ return error instanceof Error && error.message ? error.message : String(error);
411
+ }
412
+ function delay(ms) {
413
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
414
+ }
415
+ async function retrySqliteBusy(operation, label) {
416
+ for (let attempt = 0; ; attempt++) {
417
+ try {
418
+ return await operation();
419
+ } catch (error) {
420
+ if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
421
+ const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
422
+ console.error(
423
+ `[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
424
+ );
425
+ await delay(waitMs);
426
+ }
427
+ }
428
+ }
429
+ async function initializeSqliteStore(factory, dataDir, label) {
430
+ try {
431
+ loadBetterSqlite3();
432
+ } catch (error) {
433
+ throw new SqliteUnavailableError(error);
434
+ }
435
+ return retrySqliteBusy(async () => {
436
+ const store = factory();
437
+ await store.init(dataDir);
438
+ return store;
439
+ }, label);
440
+ }
441
+ var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
442
+ var init_sqlite_reliability = __esm({
443
+ "src/store/sqlite-reliability.ts"() {
444
+ "use strict";
445
+ init_esm_shims();
446
+ init_sqlite_db();
447
+ SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
448
+ SqliteUnavailableError = class extends Error {
449
+ code = "MEMORIX_SQLITE_UNAVAILABLE";
450
+ constructor(cause) {
451
+ super(
452
+ cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
453
+ );
454
+ this.name = "SqliteUnavailableError";
455
+ }
456
+ };
457
+ }
458
+ });
459
+
392
460
  // src/store/sqlite-fts.ts
393
461
  function normalizeFtsToken(token) {
394
462
  return token.replace(/"/g, '""');
@@ -402,22 +470,27 @@ function buildFtsQuery(query) {
402
470
  function initializeObservationLexicalIndex(db2) {
403
471
  const known = ftsAvailability.get(db2);
404
472
  if (known !== void 0) return known;
405
- try {
406
- db2.exec(CREATE_OBSERVATION_FTS);
407
- const observationCount = Number(db2.prepare("SELECT COUNT(*) AS count FROM observations").get()?.count ?? 0);
408
- const indexedCount = Number(db2.prepare(`SELECT COUNT(*) AS count FROM ${FTS_TABLE}`).get()?.count ?? 0);
409
- if (observationCount !== indexedCount) {
410
- db2.exec(`INSERT INTO ${FTS_TABLE}(${FTS_TABLE}) VALUES ('rebuild')`);
473
+ let lastError;
474
+ for (let attempt = 1; attempt <= FTS_INIT_MAX_ATTEMPTS; attempt++) {
475
+ try {
476
+ db2.exec(CREATE_OBSERVATION_FTS);
477
+ const observationCount = Number(db2.prepare("SELECT COUNT(*) AS count FROM observations").get()?.count ?? 0);
478
+ const indexedCount = Number(db2.prepare(`SELECT COUNT(*) AS count FROM ${FTS_TABLE}`).get()?.count ?? 0);
479
+ if (observationCount !== indexedCount) {
480
+ db2.exec(`INSERT INTO ${FTS_TABLE}(${FTS_TABLE}) VALUES ('rebuild')`);
481
+ }
482
+ ftsAvailability.set(db2, true);
483
+ return true;
484
+ } catch (error) {
485
+ lastError = error;
486
+ if (!isTransientSqliteError(error) || attempt === FTS_INIT_MAX_ATTEMPTS) break;
411
487
  }
412
- ftsAvailability.set(db2, true);
413
- return true;
414
- } catch (error) {
415
- ftsAvailability.set(db2, false);
416
- console.warn(
417
- `[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${error instanceof Error ? error.message : String(error)}`
418
- );
419
- return false;
420
488
  }
489
+ ftsAvailability.set(db2, false);
490
+ console.warn(
491
+ `[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${lastError instanceof Error ? lastError.message : String(lastError)}`
492
+ );
493
+ return false;
421
494
  }
422
495
  function isObservationLexicalIndexEnabled(db2) {
423
496
  return ftsAvailability.get(db2) === true;
@@ -471,11 +544,12 @@ function searchObservationLexically(db2, options) {
471
544
  score: Math.max(0, Number(row.lexical_score ?? 0))
472
545
  }));
473
546
  }
474
- var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS;
547
+ var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS, FTS_INIT_MAX_ATTEMPTS;
475
548
  var init_sqlite_fts = __esm({
476
549
  "src/store/sqlite-fts.ts"() {
477
550
  "use strict";
478
551
  init_esm_shims();
552
+ init_sqlite_reliability();
479
553
  ftsAvailability = /* @__PURE__ */ new WeakMap();
480
554
  FTS_TABLE = "observations_fts";
481
555
  CREATE_OBSERVATION_FTS = `
@@ -509,6 +583,7 @@ CREATE TRIGGER IF NOT EXISTS observations_fts_au AFTER UPDATE ON observations BE
509
583
  VALUES (new.id, new.entityName, new.title, new.narrative, new.facts, new.filesModified, new.concepts, new.attachments);
510
584
  END;
511
585
  `;
586
+ FTS_INIT_MAX_ATTEMPTS = 3;
512
587
  }
513
588
  });
514
589
 
@@ -2714,74 +2789,6 @@ var init_types = __esm({
2714
2789
  }
2715
2790
  });
2716
2791
 
2717
- // src/store/sqlite-reliability.ts
2718
- function isTransientSqliteError(error) {
2719
- const code = error?.code;
2720
- if (typeof code === "string") {
2721
- return /^SQLITE_BUSY(?:_|$)/i.test(code);
2722
- }
2723
- return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
2724
- }
2725
- function isSqliteUnavailableError(error) {
2726
- return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
2727
- }
2728
- function degradedReadError(storeName) {
2729
- return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
2730
- }
2731
- function degradedWriteError(storeName) {
2732
- return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
2733
- }
2734
- function formatSqliteFailure(error) {
2735
- return error instanceof Error && error.message ? error.message : String(error);
2736
- }
2737
- function delay(ms) {
2738
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2739
- }
2740
- async function retrySqliteBusy(operation, label) {
2741
- for (let attempt = 0; ; attempt++) {
2742
- try {
2743
- return await operation();
2744
- } catch (error) {
2745
- if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
2746
- const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
2747
- console.error(
2748
- `[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
2749
- );
2750
- await delay(waitMs);
2751
- }
2752
- }
2753
- }
2754
- async function initializeSqliteStore(factory, dataDir, label) {
2755
- try {
2756
- loadBetterSqlite3();
2757
- } catch (error) {
2758
- throw new SqliteUnavailableError(error);
2759
- }
2760
- return retrySqliteBusy(async () => {
2761
- const store = factory();
2762
- await store.init(dataDir);
2763
- return store;
2764
- }, label);
2765
- }
2766
- var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
2767
- var init_sqlite_reliability = __esm({
2768
- "src/store/sqlite-reliability.ts"() {
2769
- "use strict";
2770
- init_esm_shims();
2771
- init_sqlite_db();
2772
- SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
2773
- SqliteUnavailableError = class extends Error {
2774
- code = "MEMORIX_SQLITE_UNAVAILABLE";
2775
- constructor(cause) {
2776
- super(
2777
- cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
2778
- );
2779
- this.name = "SqliteUnavailableError";
2780
- }
2781
- };
2782
- }
2783
- });
2784
-
2785
2792
  // src/store/file-lock.ts
2786
2793
  import { promises as fs2 } from "fs";
2787
2794
  import { randomUUID as randomUUID3 } from "crypto";
@@ -8674,6 +8681,7 @@ async function searchObservations(options) {
8674
8681
  };
8675
8682
  let queryVector = null;
8676
8683
  let embeddingProviderForSearch = null;
8684
+ let embeddingUnavailable = false;
8677
8685
  if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
8678
8686
  try {
8679
8687
  const provider2 = await getEmbeddingProvider();
@@ -8681,6 +8689,7 @@ async function searchObservations(options) {
8681
8689
  embeddingProviderForSearch = provider2;
8682
8690
  const activeVectorDimensions = getVectorDimensions();
8683
8691
  if (activeVectorDimensions !== null && provider2.dimensions !== activeVectorDimensions) {
8692
+ embeddingUnavailable = true;
8684
8693
  rememberSearchMode(
8685
8694
  modeKey,
8686
8695
  `fulltext (embedding dimension mismatch: provider ${provider2.dimensions}d vs index ${activeVectorDimensions}d)`
@@ -8720,8 +8729,11 @@ async function searchObservations(options) {
8720
8729
  hybridWeights: isCJKHeavy ? { text: 0.2, vector: 0.8 } : { text: 0.6, vector: 0.4 }
8721
8730
  };
8722
8731
  }
8732
+ } else {
8733
+ embeddingUnavailable = true;
8723
8734
  }
8724
8735
  } catch (error) {
8736
+ embeddingUnavailable = true;
8725
8737
  rememberSearchMode(modeKey, "fulltext (embedding unavailable)");
8726
8738
  console.error("[memorix] Embedding failed or timed out, falling back to fulltext search");
8727
8739
  }
@@ -8729,7 +8741,7 @@ async function searchObservations(options) {
8729
8741
  mark("preSearch");
8730
8742
  let results;
8731
8743
  const persistentCorpusEligible = await shouldUsePersistentSearch(projectIds, options.projectId);
8732
- const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled);
8744
+ const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled || embeddingUnavailable);
8733
8745
  let persistent = persistentCorpusEligible && queryVector && embeddingProviderForSearch ? await searchPersistentSemantically(options, projectIds, requestLimit, queryVector, embeddingProviderForSearch) : canUsePersistentLexical ? await searchPersistentLexically(options, projectIds, requestLimit) : null;
8734
8746
  const durableStore = getOptionalObservationStore();
8735
8747
  if (!persistent && queryVector && durableStore?.hasLexicalIndex?.() && count(database) === 0) {