memorix 1.1.11 → 1.1.13

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/index.js CHANGED
@@ -2809,6 +2809,45 @@ function dimsCacheKey(config) {
2809
2809
  config.requestedDimensions ?? "native"
2810
2810
  ].join("|");
2811
2811
  }
2812
+ function isValidDimension(value) {
2813
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
2814
+ }
2815
+ async function loadCachedVectorDimensions(config) {
2816
+ try {
2817
+ const raw = await readFile2(CACHE_META_FILE, "utf-8");
2818
+ const data = JSON.parse(raw);
2819
+ const namespace = cacheNamespace(config);
2820
+ if (!Array.isArray(data?.entries)) return null;
2821
+ const entry = data.entries.find(
2822
+ (candidate) => typeof candidate === "object" && candidate !== null && candidate.namespace === namespace && isValidDimension(candidate.dimensions)
2823
+ );
2824
+ return entry?.dimensions ?? null;
2825
+ } catch {
2826
+ return null;
2827
+ }
2828
+ }
2829
+ async function saveCachedVectorDimensions(config, dimensions) {
2830
+ if (!isValidDimension(dimensions)) return;
2831
+ try {
2832
+ await mkdir2(CACHE_DIR2, { recursive: true });
2833
+ let entries = [];
2834
+ try {
2835
+ const raw = await readFile2(CACHE_META_FILE, "utf-8");
2836
+ const data = JSON.parse(raw);
2837
+ if (Array.isArray(data?.entries)) {
2838
+ entries = data.entries.filter(
2839
+ (candidate) => typeof candidate === "object" && candidate !== null && typeof candidate.namespace === "string" && isValidDimension(candidate.dimensions) && typeof candidate.ts === "number"
2840
+ );
2841
+ }
2842
+ } catch {
2843
+ }
2844
+ const namespace = cacheNamespace(config);
2845
+ entries = entries.filter((entry) => entry.namespace !== namespace);
2846
+ entries.push({ namespace, dimensions, ts: Date.now() });
2847
+ await writeFile2(CACHE_META_FILE, JSON.stringify({ version: 1, entries }));
2848
+ } catch {
2849
+ }
2850
+ }
2812
2851
  async function loadCachedDims(config) {
2813
2852
  try {
2814
2853
  const raw = await readFile2(DIMS_CACHE_FILE, "utf-8");
@@ -2871,6 +2910,7 @@ async function saveCachedDims(config, dimensions) {
2871
2910
  await writeFile2(DIMS_CACHE_FILE, JSON.stringify({ entries }));
2872
2911
  } catch {
2873
2912
  }
2913
+ await saveCachedVectorDimensions(config, dimensions);
2874
2914
  }
2875
2915
  async function saveDiskCacheNow() {
2876
2916
  if (!diskCacheDirty2) return;
@@ -2953,7 +2993,7 @@ async function fetchWithRetry(url, apiKey, body, attempt = 0) {
2953
2993
  const errorText2 = await response.text().catch(() => "unknown error");
2954
2994
  throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
2955
2995
  }
2956
- var CACHE_DIR2, CACHE_FILE2, DIMS_CACHE_FILE, cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES, BASE_DELAY_MS, APIEmbeddingProvider;
2996
+ var CACHE_DIR2, CACHE_FILE2, DIMS_CACHE_FILE, CACHE_META_FILE, cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES, BASE_DELAY_MS, APIEmbeddingProvider;
2957
2997
  var init_api_provider = __esm({
2958
2998
  "src/embedding/api-provider.ts"() {
2959
2999
  "use strict";
@@ -2961,6 +3001,7 @@ var init_api_provider = __esm({
2961
3001
  CACHE_DIR2 = process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
2962
3002
  CACHE_FILE2 = join3(CACHE_DIR2, ".embedding-api-cache.json");
2963
3003
  DIMS_CACHE_FILE = join3(CACHE_DIR2, ".embedding-dims-cache.json");
3004
+ CACHE_META_FILE = join3(CACHE_DIR2, ".embedding-api-cache-meta.json");
2964
3005
  cache3 = /* @__PURE__ */ new Map();
2965
3006
  MAX_CACHE_SIZE3 = 1e4;
2966
3007
  diskCacheDirty2 = false;
@@ -2987,13 +3028,22 @@ var init_api_provider = __esm({
2987
3028
  this.dimensions = detectedDimensions;
2988
3029
  this.name = `api-${config.model.replace(/\//g, "-")}`;
2989
3030
  }
2990
- static async create() {
3031
+ static async create(options = {}) {
2991
3032
  const config = _APIEmbeddingProvider.resolveConfig();
2992
- startDiskCacheLoad();
3033
+ const allowNetworkProbe = options.allowNetworkProbe !== false;
2993
3034
  let probeDimensions = await loadCachedDims(config);
3035
+ let dimensionSource = "dims-cache";
3036
+ if (probeDimensions === null) {
3037
+ probeDimensions = await loadCachedVectorDimensions(config);
3038
+ if (probeDimensions !== null) dimensionSource = "vector-cache";
3039
+ }
2994
3040
  if (probeDimensions !== null) {
2995
- console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d) [cached dims]`);
3041
+ console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d) [${dimensionSource}]`);
3042
+ if (dimensionSource === "dims-cache") {
3043
+ void saveCachedVectorDimensions(config, probeDimensions);
3044
+ }
2996
3045
  } else {
3046
+ if (!allowNetworkProbe) return null;
2997
3047
  probeDimensions = await _APIEmbeddingProvider.probeAPI(config);
2998
3048
  console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d)`);
2999
3049
  saveCachedDims(config, probeDimensions).catch(() => {
@@ -3002,6 +3052,7 @@ var init_api_provider = __esm({
3002
3052
  if (config.requestedDimensions) {
3003
3053
  console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
3004
3054
  }
3055
+ startDiskCacheLoad();
3005
3056
  return new _APIEmbeddingProvider(config, probeDimensions);
3006
3057
  }
3007
3058
  static resolveConfig() {
@@ -3180,6 +3231,13 @@ var init_api_provider = __esm({
3180
3231
  scheduleDiskSave();
3181
3232
  return results;
3182
3233
  }
3234
+ async getCachedEmbeddings(texts) {
3235
+ await ensureDiskCacheLoaded();
3236
+ return texts.map((text) => {
3237
+ const hash = textHash2(normalizeText(text), this.cacheKeyNamespace);
3238
+ return cache3.get(hash) ?? null;
3239
+ });
3240
+ }
3183
3241
  getStats() {
3184
3242
  return {
3185
3243
  totalTokens: this.totalTokensUsed,
@@ -3288,10 +3346,10 @@ async function createTransformersProvider() {
3288
3346
  return null;
3289
3347
  }
3290
3348
  }
3291
- async function createAPIProvider() {
3349
+ async function createAPIProvider(options) {
3292
3350
  try {
3293
3351
  const { APIEmbeddingProvider: APIEmbeddingProvider2 } = await Promise.resolve().then(() => (init_api_provider(), api_provider_exports));
3294
- return await APIEmbeddingProvider2.create();
3352
+ return await APIEmbeddingProvider2.create(options ?? {});
3295
3353
  } catch (e) {
3296
3354
  warnOnce(`[memorix] Failed to init API embedding: ${e instanceof Error ? e.message : e}`);
3297
3355
  return null;
@@ -3346,11 +3404,24 @@ function wrapProvider(candidate) {
3346
3404
  }
3347
3405
  throw error;
3348
3406
  }
3349
- }
3407
+ },
3408
+ ...candidate.getCachedEmbeddings ? {
3409
+ async getCachedEmbeddings(texts) {
3410
+ return candidate.getCachedEmbeddings(texts);
3411
+ }
3412
+ } : {}
3350
3413
  };
3351
3414
  }
3352
- async function getEmbeddingProvider() {
3415
+ async function getEmbeddingProvider(options = {}) {
3353
3416
  if (provider) return provider;
3417
+ if (options.allowNetworkProbe === false) {
3418
+ const mode = getEmbeddingMode2();
3419
+ if (mode !== "api" && !(mode === "auto" && hasAPIEmbeddingConfig())) {
3420
+ return null;
3421
+ }
3422
+ const cached = await createAPIProvider({ allowNetworkProbe: false });
3423
+ return cached ? wrapProvider(cached) : null;
3424
+ }
3354
3425
  if (lastInitWasTemporaryFailure) {
3355
3426
  const elapsed = Date.now() - lastFailureTimestamp;
3356
3427
  if (elapsed < RETRY_COOLDOWN_MS) {
@@ -4863,12 +4934,15 @@ __export(orama_store_exports, {
4863
4934
  batchGenerateEmbeddings: () => batchGenerateEmbeddings,
4864
4935
  generateEmbedding: () => generateEmbedding,
4865
4936
  getDb: () => getDb,
4937
+ getDeferredCachedVectorHydration: () => getDeferredCachedVectorHydration,
4866
4938
  getLastSearchMode: () => getLastSearchMode,
4867
4939
  getObservationCount: () => getObservationCount,
4868
4940
  getObservationsByIds: () => getObservationsByIds,
4869
4941
  getTimeline: () => getTimeline,
4870
4942
  getVectorDimensions: () => getVectorDimensions,
4943
+ hasObservationVector: () => hasObservationVector,
4871
4944
  hydrateIndex: () => hydrateIndex,
4945
+ hydrateIndexForStartup: () => hydrateIndexForStartup,
4872
4946
  insertObservation: () => insertObservation,
4873
4947
  isEmbeddingEnabled: () => isEmbeddingEnabled,
4874
4948
  makeOramaObservationId: () => makeOramaObservationId,
@@ -4929,11 +5003,10 @@ function stripVectorSearchParams(params) {
4929
5003
  const { mode, vector, similarity, hybridWeights, ...rest } = params;
4930
5004
  return rest;
4931
5005
  }
4932
- async function getDb() {
4933
- if (db) return db;
4934
- const provider2 = await getEmbeddingProvider();
4935
- embeddingEnabled = provider2 !== null;
4936
- embeddingDimensions = provider2?.dimensions ?? null;
5006
+ async function initializeDb(options, generation) {
5007
+ const provider2 = await getEmbeddingProvider({ allowNetworkProbe: options.allowNetworkProbe });
5008
+ const nextEmbeddingEnabled = provider2 !== null;
5009
+ const nextEmbeddingDimensions = provider2?.dimensions ?? null;
4937
5010
  const baseSchema = {
4938
5011
  id: "string",
4939
5012
  observationId: "number",
@@ -4956,15 +5029,39 @@ async function getDb() {
4956
5029
  documentType: "string",
4957
5030
  knowledgeLayer: "string"
4958
5031
  };
4959
- const dims = embeddingDimensions ?? 384;
4960
- const schema = embeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
4961
- db = await create({ schema });
4962
- return db;
5032
+ const dims = nextEmbeddingDimensions ?? 384;
5033
+ const schema = nextEmbeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
5034
+ const nextDb = await create({ schema });
5035
+ if (generation !== dbGeneration) {
5036
+ return getDb(options);
5037
+ }
5038
+ indexEmbeddingProvider = provider2;
5039
+ embeddingEnabled = nextEmbeddingEnabled;
5040
+ embeddingDimensions = nextEmbeddingDimensions;
5041
+ db = nextDb;
5042
+ return nextDb;
5043
+ }
5044
+ async function getDb(options = {}) {
5045
+ if (db) return db;
5046
+ if (dbInitPromise) return dbInitPromise;
5047
+ const initPromise2 = initializeDb(options, dbGeneration);
5048
+ dbInitPromise = initPromise2;
5049
+ try {
5050
+ return await initPromise2;
5051
+ } finally {
5052
+ if (dbInitPromise === initPromise2) {
5053
+ dbInitPromise = null;
5054
+ }
5055
+ }
4963
5056
  }
4964
5057
  async function resetDb() {
5058
+ dbGeneration++;
4965
5059
  db = null;
5060
+ dbInitPromise = null;
4966
5061
  embeddingEnabled = false;
4967
5062
  embeddingDimensions = null;
5063
+ indexEmbeddingProvider = null;
5064
+ deferredCachedVectorHydration = null;
4968
5065
  lastSearchModeByProject.clear();
4969
5066
  docByObservationKey.clear();
4970
5067
  }
@@ -4992,14 +5089,64 @@ async function batchGenerateEmbeddings(texts) {
4992
5089
  return texts.map(() => null);
4993
5090
  }
4994
5091
  }
4995
- async function hydrateIndex(observations2) {
4996
- const database = await getDb();
5092
+ async function getCachedEmbeddings(texts) {
5093
+ if (!embeddingEnabled || texts.length === 0) return texts.map(() => null);
5094
+ const provider2 = indexEmbeddingProvider;
5095
+ if (!provider2?.getCachedEmbeddings) return texts.map(() => null);
5096
+ try {
5097
+ return await provider2.getCachedEmbeddings(texts);
5098
+ } catch {
5099
+ return texts.map(() => null);
5100
+ }
5101
+ }
5102
+ function observationEmbeddingText(observation) {
5103
+ return [
5104
+ observation.title ?? "",
5105
+ observation.narrative ?? "",
5106
+ ...Array.isArray(observation.facts) ? observation.facts : []
5107
+ ].join(" ");
5108
+ }
5109
+ function documentEmbeddingText(document) {
5110
+ return [document.title ?? "", document.narrative ?? "", document.facts ?? ""].join(" ");
5111
+ }
5112
+ function isCompatibleCachedVector(vector) {
5113
+ return Boolean(
5114
+ vector && embeddingDimensions !== null && vector.length === embeddingDimensions && vector.every(Number.isFinite)
5115
+ );
5116
+ }
5117
+ async function attachCachedVectors(database, candidates) {
5118
+ const cachedVectors = await getCachedEmbeddings(
5119
+ candidates.map(({ observation }) => observationEmbeddingText(observation))
5120
+ );
5121
+ if (db !== database) return;
5122
+ for (let index = 0; index < candidates.length; index++) {
5123
+ const vector = cachedVectors[index];
5124
+ if (!isCompatibleCachedVector(vector)) continue;
5125
+ const existing = getByID(database, candidates[index].id);
5126
+ if (!existing || documentEmbeddingText(existing) !== observationEmbeddingText(candidates[index].observation)) continue;
5127
+ try {
5128
+ await update(database, candidates[index].id, { ...existing, embedding: vector });
5129
+ } catch {
5130
+ }
5131
+ }
5132
+ }
5133
+ async function hydrateIndex(observations2, options = {}) {
5134
+ const database = await getDb({ allowNetworkProbe: options.allowNetworkProbe });
5135
+ const candidates = [];
5136
+ for (const observation of observations2) {
5137
+ if (!observation || !observation.id || !observation.projectId) continue;
5138
+ const id = makeOramaObservationId(observation.projectId, observation.id);
5139
+ if (getByID(database, id)) continue;
5140
+ candidates.push({ observation, id });
5141
+ }
5142
+ const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
5143
+ const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
4997
5144
  let inserted = 0;
4998
- for (const obs of observations2) {
4999
- if (!obs || !obs.id || !obs.projectId) continue;
5145
+ for (let index = 0; index < candidates.length; index++) {
5146
+ const { observation: obs, id } = candidates[index];
5000
5147
  try {
5001
- const id = makeOramaObservationId(obs.projectId, obs.id);
5002
- if (getByID(database, id)) continue;
5148
+ const vector = cachedVectors[index];
5149
+ const compatibleVector = isCompatibleCachedVector(vector) ? vector : null;
5003
5150
  const doc = {
5004
5151
  id,
5005
5152
  observationId: obs.id,
@@ -5018,7 +5165,8 @@ async function hydrateIndex(observations2) {
5018
5165
  status: obs.status ?? "active",
5019
5166
  source: obs.source || "agent",
5020
5167
  documentType: "observation",
5021
- knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source)
5168
+ knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
5169
+ ...compatibleVector ? { embedding: compatibleVector } : {}
5022
5170
  };
5023
5171
  await insert2(database, doc);
5024
5172
  rememberObservationDoc(doc);
@@ -5026,8 +5174,24 @@ async function hydrateIndex(observations2) {
5026
5174
  } catch {
5027
5175
  }
5028
5176
  }
5177
+ deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
5178
+ }) : null;
5029
5179
  return inserted;
5030
5180
  }
5181
+ async function hydrateIndexForStartup(observations2) {
5182
+ return hydrateIndex(observations2, {
5183
+ allowNetworkProbe: false,
5184
+ deferCachedVectors: true
5185
+ });
5186
+ }
5187
+ function getDeferredCachedVectorHydration() {
5188
+ return deferredCachedVectorHydration;
5189
+ }
5190
+ function hasObservationVector(projectId, observationId) {
5191
+ if (!db || !embeddingEnabled || embeddingDimensions === null) return false;
5192
+ const document = getByID(db, makeOramaObservationId(projectId, observationId));
5193
+ return Array.isArray(document?.embedding) && document.embedding.length === embeddingDimensions && document.embedding.every(Number.isFinite);
5194
+ }
5031
5195
  async function insertObservation(doc) {
5032
5196
  const database = await getDb();
5033
5197
  await insert2(database, doc);
@@ -5561,7 +5725,7 @@ function formatTime(isoDate) {
5561
5725
  return isoDate;
5562
5726
  }
5563
5727
  }
5564
- var db, embeddingEnabled, embeddingDimensions, docByObservationKey, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, COMMAND_LOG_TITLE2, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
5728
+ var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration, docByObservationKey, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, COMMAND_LOG_TITLE2, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
5565
5729
  var init_orama_store = __esm({
5566
5730
  "src/store/orama-store.ts"() {
5567
5731
  "use strict";
@@ -5573,8 +5737,12 @@ var init_orama_store = __esm({
5573
5737
  init_intent_detector();
5574
5738
  init_query_expansion();
5575
5739
  db = null;
5740
+ dbInitPromise = null;
5741
+ dbGeneration = 0;
5576
5742
  embeddingEnabled = false;
5577
5743
  embeddingDimensions = null;
5744
+ indexEmbeddingProvider = null;
5745
+ deferredCachedVectorHydration = null;
5578
5746
  docByObservationKey = /* @__PURE__ */ new Map();
5579
5747
  NON_CJK_HYBRID_SIMILARITY = 0.45;
5580
5748
  lastSearchModeByProject = /* @__PURE__ */ new Map();
@@ -7355,7 +7523,18 @@ async function bindObservationCodeRefsBestEffort(observation) {
7355
7523
  function isVectorCompatibleWithCurrentIndex(embedding) {
7356
7524
  if (!embedding) return false;
7357
7525
  const vectorDimensions = getVectorDimensions();
7358
- return vectorDimensions === null || embedding.length === vectorDimensions;
7526
+ return vectorDimensions !== null && embedding.length === vectorDimensions && embedding.every(Number.isFinite);
7527
+ }
7528
+ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
7529
+ if (isVectorCompatibleWithCurrentIndex(embedding)) return true;
7530
+ if (getVectorDimensions() !== null || !embedding.every(Number.isFinite)) return false;
7531
+ const targetProjectDir = projectDir;
7532
+ if (!vectorSchemaUpgradePromise) {
7533
+ vectorSchemaUpgradePromise = reindexObservations().then(() => projectDir === targetProjectDir && isVectorCompatibleWithCurrentIndex(embedding)).catch(() => false).finally(() => {
7534
+ vectorSchemaUpgradePromise = null;
7535
+ });
7536
+ }
7537
+ return vectorSchemaUpgradePromise;
7359
7538
  }
7360
7539
  async function initObservations(dir) {
7361
7540
  if (projectDir === dir) return;
@@ -7365,6 +7544,7 @@ async function initObservations(dir) {
7365
7544
  nextId = await store.loadIdCounter();
7366
7545
  projectDir = dir;
7367
7546
  searchIndexPrepared = false;
7547
+ vectorSchemaUpgradePromise = null;
7368
7548
  }
7369
7549
  async function ensureFreshObservations() {
7370
7550
  if (!projectDir) return false;
@@ -7544,6 +7724,9 @@ async function storeObservation(input) {
7544
7724
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
7545
7725
  generateEmbedding(searchableText).then(async (embedding) => {
7546
7726
  if (embedding) {
7727
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7728
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
7729
+ }
7547
7730
  if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7548
7731
  const vectorDimensions = getVectorDimensions();
7549
7732
  console.error(
@@ -7654,6 +7837,13 @@ async function upsertObservation(existing, input, now) {
7654
7837
  vectorMissingIds.add(obsId);
7655
7838
  generateEmbedding(searchableText).then(async (embedding) => {
7656
7839
  if (embedding) {
7840
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7841
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
7842
+ }
7843
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7844
+ queueVectorBackfill(existing.projectId);
7845
+ return;
7846
+ }
7657
7847
  try {
7658
7848
  const { removeObservation: removeObs } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
7659
7849
  await removeObs(makeOramaObservationId(existing.projectId, obsId));
@@ -7798,14 +7988,21 @@ async function reindexObservations() {
7798
7988
  let embeddings = observations.map(() => null);
7799
7989
  const provider2 = await getEmbeddingProvider();
7800
7990
  const canBatchEmbedAtStartup = provider2 !== null && !provider2.name.startsWith("api-");
7991
+ await getDb();
7992
+ const texts = observations.map(
7993
+ (obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
7994
+ );
7995
+ if (provider2?.getCachedEmbeddings) {
7996
+ try {
7997
+ embeddings = await provider2.getCachedEmbeddings(texts);
7998
+ } catch {
7999
+ }
8000
+ }
7801
8001
  if (provider2 && !canBatchEmbedAtStartup) {
7802
- console.error("[memorix] Startup reindex: skipping synchronous API embeddings; background backfill will hydrate vectors");
8002
+ console.error("[memorix] Startup reindex: restored cached API embeddings; uncached vectors stay in background recovery");
7803
8003
  }
7804
8004
  if (canBatchEmbedAtStartup) {
7805
8005
  try {
7806
- const texts = observations.map(
7807
- (obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
7808
- );
7809
8006
  embeddings = await batchGenerateEmbeddings(texts);
7810
8007
  } catch {
7811
8008
  }
@@ -7858,16 +8055,31 @@ async function reindexObservations() {
7858
8055
  }
7859
8056
  async function prepareSearchIndex() {
7860
8057
  if (searchIndexPrepared) return 0;
7861
- const count2 = await hydrateIndex(observations);
8058
+ const count2 = await hydrateIndexForStartup(observations);
7862
8059
  if (count2 === 0) {
7863
8060
  searchIndexPrepared = true;
7864
8061
  return 0;
7865
8062
  }
7866
- vectorMissingIds.clear();
7867
- if (isEmbeddingEnabled()) {
7868
- for (const obs of observations) {
7869
- vectorMissingIds.add(obs.id);
8063
+ const hydratedProjectDir = projectDir;
8064
+ const queueMissingVectors = () => {
8065
+ if (projectDir !== hydratedProjectDir) return;
8066
+ vectorMissingIds.clear();
8067
+ if (!isEmbeddingExplicitlyDisabled()) {
8068
+ const projectsWithMissingVectors = /* @__PURE__ */ new Set();
8069
+ for (const obs of observations) {
8070
+ if (!hasObservationVector(obs.projectId, obs.id)) {
8071
+ vectorMissingIds.add(obs.id);
8072
+ projectsWithMissingVectors.add(obs.projectId);
8073
+ }
8074
+ }
8075
+ for (const projectId of projectsWithMissingVectors) queueVectorBackfill(projectId);
7870
8076
  }
8077
+ };
8078
+ const cacheHydration = getDeferredCachedVectorHydration();
8079
+ if (cacheHydration) {
8080
+ void cacheHydration.finally(queueMissingVectors);
8081
+ } else {
8082
+ queueMissingVectors();
7871
8083
  }
7872
8084
  searchIndexPrepared = true;
7873
8085
  return count2;
@@ -7927,6 +8139,9 @@ async function backfillVectorEmbeddings(options = {}) {
7927
8139
  try {
7928
8140
  const embedding = await generateEmbedding(text);
7929
8141
  if (embedding) {
8142
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
8143
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
8144
+ }
7930
8145
  if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7931
8146
  const vectorDimensions = getVectorDimensions();
7932
8147
  console.error(
@@ -7989,7 +8204,7 @@ async function backfillVectorEmbeddings(options = {}) {
7989
8204
  }
7990
8205
  return { attempted: ids.length, succeeded, failed };
7991
8206
  }
7992
- var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
8207
+ var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
7993
8208
  var init_observations = __esm({
7994
8209
  "src/memory/observations.ts"() {
7995
8210
  "use strict";
@@ -8007,6 +8222,7 @@ var init_observations = __esm({
8007
8222
  searchIndexPrepared = false;
8008
8223
  vectorMissingIds = /* @__PURE__ */ new Set();
8009
8224
  vectorBackfillRunning = false;
8225
+ vectorSchemaUpgradePromise = null;
8010
8226
  lastVectorBackfill = null;
8011
8227
  embeddingFailureLogTimestamps = /* @__PURE__ */ new Map();
8012
8228
  EMBEDDING_FAILURE_LOG_COOLDOWN_MS = 3e4;
@@ -16430,6 +16646,7 @@ __export(installers_exports, {
16430
16646
  getProjectConfigPath: () => getProjectConfigPath,
16431
16647
  installAgentGuidance: () => installAgentGuidance,
16432
16648
  installHooks: () => installHooks,
16649
+ resolveOpenCodeHookCommand: () => resolveOpenCodeHookCommand,
16433
16650
  uninstallHooks: () => uninstallHooks
16434
16651
  });
16435
16652
  import * as fs14 from "fs/promises";
@@ -16442,6 +16659,22 @@ function resolveHookCommand() {
16442
16659
  }
16443
16660
  return "memorix";
16444
16661
  }
16662
+ function resolveWindowsMemorixShim() {
16663
+ try {
16664
+ const output = String(execSync3("where.exe memorix.cmd", {
16665
+ encoding: "utf-8",
16666
+ stdio: ["ignore", "pipe", "ignore"],
16667
+ windowsHide: true
16668
+ }));
16669
+ return output.split(/\r?\n/).map((candidate) => candidate.trim()).find((candidate) => path19.isAbsolute(candidate)) ?? null;
16670
+ } catch {
16671
+ return null;
16672
+ }
16673
+ }
16674
+ function resolveOpenCodeHookCommand(platform = process.platform, resolveWindowsShim = resolveWindowsMemorixShim) {
16675
+ if (platform !== "win32") return "memorix";
16676
+ return resolveWindowsShim() ?? "memorix.cmd";
16677
+ }
16445
16678
  function generateClaudeConfig() {
16446
16679
  const cmd = `${resolveHookCommand()} hook`;
16447
16680
  const hookEntry = {
@@ -16641,6 +16874,7 @@ async function installOfficialSkillsForAgent(agent, projectRoot, global = false)
16641
16874
  return skillPaths;
16642
16875
  }
16643
16876
  function generateOpenCodePlugin() {
16877
+ const hookCommand = JSON.stringify(resolveOpenCodeHookCommand());
16644
16878
  return `/**
16645
16879
  * Memorix - Cross-Agent Memory Bridge Plugin for OpenCode
16646
16880
  * @generated-version ${OPENCODE_PLUGIN_VERSION}
@@ -16659,6 +16893,16 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
16659
16893
  const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
16660
16894
  let pendingAssistantResponse = null;
16661
16895
  let lastDeliveredAssistantKey = '';
16896
+ let hookFailureReported = false;
16897
+ const hookCommand = ${hookCommand};
16898
+
16899
+ function reportHookFailure(eventName, detail) {
16900
+ // OpenCode renders console.error output inside the conversation. Delivery is
16901
+ // best-effort, so keep normal sessions quiet while retaining opt-in diagnostics.
16902
+ if (process.env.MEMORIX_HOOK_DEBUG !== '1' || hookFailureReported) return;
16903
+ hookFailureReported = true;
16904
+ console.error('[memorix-plugin] hook delivery failed:', eventName, detail);
16905
+ }
16662
16906
 
16663
16907
  /**
16664
16908
  * Send event JSON to \`memorix hook\` via child_process.spawnSync.
@@ -16674,8 +16918,7 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
16674
16918
  const data = JSON.stringify(payload);
16675
16919
  const eventName = payload.hook_event_name || 'unknown';
16676
16920
  try {
16677
- const cmd = process.platform === 'win32' ? 'memorix.cmd' : 'memorix';
16678
- const result = spawnSync(cmd, ['hook'], {
16921
+ const result = spawnSync(hookCommand, ['hook'], {
16679
16922
  input: data,
16680
16923
  timeout: 10_000,
16681
16924
  encoding: 'utf-8',
@@ -16683,12 +16926,14 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
16683
16926
  shell: process.platform === 'win32',
16684
16927
  });
16685
16928
  if (result.status !== 0) {
16686
- console.error('[memorix-plugin] hook failed:', eventName,
16687
- 'exit=', result.status,
16688
- 'stderr=', (result.stderr || '').slice(0, 200));
16929
+ reportHookFailure(eventName, {
16930
+ exit: result.status,
16931
+ stderr: (result.stderr || '').slice(0, 200),
16932
+ error: result.error?.message,
16933
+ });
16689
16934
  }
16690
16935
  } catch (e) {
16691
- console.error('[memorix-plugin] hook delivery failed:', eventName, e?.message ?? e);
16936
+ reportHookFailure(eventName, e?.message ?? e);
16692
16937
  }
16693
16938
  }
16694
16939
 
@@ -17515,7 +17760,7 @@ var init_installers = __esm({
17515
17760
  "use strict";
17516
17761
  init_esm_shims();
17517
17762
  init_official_skills();
17518
- OPENCODE_PLUGIN_VERSION = 6;
17763
+ OPENCODE_PLUGIN_VERSION = 7;
17519
17764
  AGENT_SKILL_DIRS = {
17520
17765
  cursor: { project: path19.join(".cursor", "skills"), global: path19.join(".cursor", "skills") },
17521
17766
  windsurf: { project: path19.join(".windsurf", "skills"), global: path19.join(".windsurf", "skills") },
@@ -21639,7 +21884,7 @@ The path should point to a directory containing a .git folder.`
21639
21884
  };
21640
21885
  const server = existingServer ?? new McpServer({
21641
21886
  name: "memorix",
21642
- version: true ? "1.1.11" : "1.0.1"
21887
+ version: true ? "1.1.13" : "1.0.1"
21643
21888
  });
21644
21889
  const originalRegisterTool = server.registerTool.bind(server);
21645
21890
  server.registerTool = ((name, ...args) => {