memorix 1.1.11 → 1.1.12

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/cli/index.js CHANGED
@@ -3212,7 +3212,7 @@ ${import_picocolors.default.gray(m3)} ${s2}
3212
3212
 
3213
3213
  // src/cli/version.ts
3214
3214
  function getCliVersion() {
3215
- return true ? "1.1.11" : pkg.version;
3215
+ return true ? "1.1.12" : pkg.version;
3216
3216
  }
3217
3217
  var init_version = __esm({
3218
3218
  "src/cli/version.ts"() {
@@ -13667,6 +13667,45 @@ function dimsCacheKey(config2) {
13667
13667
  config2.requestedDimensions ?? "native"
13668
13668
  ].join("|");
13669
13669
  }
13670
+ function isValidDimension(value) {
13671
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
13672
+ }
13673
+ async function loadCachedVectorDimensions(config2) {
13674
+ try {
13675
+ const raw = await readFile2(CACHE_META_FILE, "utf-8");
13676
+ const data = JSON.parse(raw);
13677
+ const namespace = cacheNamespace(config2);
13678
+ if (!Array.isArray(data?.entries)) return null;
13679
+ const entry = data.entries.find(
13680
+ (candidate) => typeof candidate === "object" && candidate !== null && candidate.namespace === namespace && isValidDimension(candidate.dimensions)
13681
+ );
13682
+ return entry?.dimensions ?? null;
13683
+ } catch {
13684
+ return null;
13685
+ }
13686
+ }
13687
+ async function saveCachedVectorDimensions(config2, dimensions) {
13688
+ if (!isValidDimension(dimensions)) return;
13689
+ try {
13690
+ await mkdir2(CACHE_DIR2, { recursive: true });
13691
+ let entries = [];
13692
+ try {
13693
+ const raw = await readFile2(CACHE_META_FILE, "utf-8");
13694
+ const data = JSON.parse(raw);
13695
+ if (Array.isArray(data?.entries)) {
13696
+ entries = data.entries.filter(
13697
+ (candidate) => typeof candidate === "object" && candidate !== null && typeof candidate.namespace === "string" && isValidDimension(candidate.dimensions) && typeof candidate.ts === "number"
13698
+ );
13699
+ }
13700
+ } catch {
13701
+ }
13702
+ const namespace = cacheNamespace(config2);
13703
+ entries = entries.filter((entry) => entry.namespace !== namespace);
13704
+ entries.push({ namespace, dimensions, ts: Date.now() });
13705
+ await writeFile2(CACHE_META_FILE, JSON.stringify({ version: 1, entries }));
13706
+ } catch {
13707
+ }
13708
+ }
13670
13709
  async function loadCachedDims(config2) {
13671
13710
  try {
13672
13711
  const raw = await readFile2(DIMS_CACHE_FILE, "utf-8");
@@ -13729,6 +13768,7 @@ async function saveCachedDims(config2, dimensions) {
13729
13768
  await writeFile2(DIMS_CACHE_FILE, JSON.stringify({ entries }));
13730
13769
  } catch {
13731
13770
  }
13771
+ await saveCachedVectorDimensions(config2, dimensions);
13732
13772
  }
13733
13773
  async function saveDiskCacheNow() {
13734
13774
  if (!diskCacheDirty2) return;
@@ -13811,7 +13851,7 @@ async function fetchWithRetry(url, apiKey, body, attempt = 0) {
13811
13851
  const errorText2 = await response.text().catch(() => "unknown error");
13812
13852
  throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
13813
13853
  }
13814
- 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;
13854
+ 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;
13815
13855
  var init_api_provider = __esm({
13816
13856
  "src/embedding/api-provider.ts"() {
13817
13857
  "use strict";
@@ -13819,6 +13859,7 @@ var init_api_provider = __esm({
13819
13859
  CACHE_DIR2 = process.env.MEMORIX_DATA_DIR || join4(homedir7(), ".memorix", "data");
13820
13860
  CACHE_FILE2 = join4(CACHE_DIR2, ".embedding-api-cache.json");
13821
13861
  DIMS_CACHE_FILE = join4(CACHE_DIR2, ".embedding-dims-cache.json");
13862
+ CACHE_META_FILE = join4(CACHE_DIR2, ".embedding-api-cache-meta.json");
13822
13863
  cache3 = /* @__PURE__ */ new Map();
13823
13864
  MAX_CACHE_SIZE3 = 1e4;
13824
13865
  diskCacheDirty2 = false;
@@ -13845,13 +13886,22 @@ var init_api_provider = __esm({
13845
13886
  this.dimensions = detectedDimensions;
13846
13887
  this.name = `api-${config2.model.replace(/\//g, "-")}`;
13847
13888
  }
13848
- static async create() {
13889
+ static async create(options2 = {}) {
13849
13890
  const config2 = _APIEmbeddingProvider.resolveConfig();
13850
- startDiskCacheLoad();
13891
+ const allowNetworkProbe = options2.allowNetworkProbe !== false;
13851
13892
  let probeDimensions = await loadCachedDims(config2);
13893
+ let dimensionSource = "dims-cache";
13894
+ if (probeDimensions === null) {
13895
+ probeDimensions = await loadCachedVectorDimensions(config2);
13896
+ if (probeDimensions !== null) dimensionSource = "vector-cache";
13897
+ }
13852
13898
  if (probeDimensions !== null) {
13853
- console.error(`[memorix] API embedding: ${config2.model} @ ${config2.baseUrl} (${probeDimensions}d) [cached dims]`);
13899
+ console.error(`[memorix] API embedding: ${config2.model} @ ${config2.baseUrl} (${probeDimensions}d) [${dimensionSource}]`);
13900
+ if (dimensionSource === "dims-cache") {
13901
+ void saveCachedVectorDimensions(config2, probeDimensions);
13902
+ }
13854
13903
  } else {
13904
+ if (!allowNetworkProbe) return null;
13855
13905
  probeDimensions = await _APIEmbeddingProvider.probeAPI(config2);
13856
13906
  console.error(`[memorix] API embedding: ${config2.model} @ ${config2.baseUrl} (${probeDimensions}d)`);
13857
13907
  saveCachedDims(config2, probeDimensions).catch(() => {
@@ -13860,6 +13910,7 @@ var init_api_provider = __esm({
13860
13910
  if (config2.requestedDimensions) {
13861
13911
  console.error(`[memorix] Dimension shortening: ${config2.requestedDimensions}d requested`);
13862
13912
  }
13913
+ startDiskCacheLoad();
13863
13914
  return new _APIEmbeddingProvider(config2, probeDimensions);
13864
13915
  }
13865
13916
  static resolveConfig() {
@@ -14038,6 +14089,13 @@ var init_api_provider = __esm({
14038
14089
  scheduleDiskSave();
14039
14090
  return results;
14040
14091
  }
14092
+ async getCachedEmbeddings(texts) {
14093
+ await ensureDiskCacheLoaded();
14094
+ return texts.map((text) => {
14095
+ const hash = textHash2(normalizeText(text), this.cacheKeyNamespace);
14096
+ return cache3.get(hash) ?? null;
14097
+ });
14098
+ }
14041
14099
  getStats() {
14042
14100
  return {
14043
14101
  totalTokens: this.totalTokensUsed,
@@ -14146,10 +14204,10 @@ async function createTransformersProvider() {
14146
14204
  return null;
14147
14205
  }
14148
14206
  }
14149
- async function createAPIProvider() {
14207
+ async function createAPIProvider(options2) {
14150
14208
  try {
14151
14209
  const { APIEmbeddingProvider: APIEmbeddingProvider2 } = await Promise.resolve().then(() => (init_api_provider(), api_provider_exports));
14152
- return await APIEmbeddingProvider2.create();
14210
+ return await APIEmbeddingProvider2.create(options2 ?? {});
14153
14211
  } catch (e3) {
14154
14212
  warnOnce(`[memorix] Failed to init API embedding: ${e3 instanceof Error ? e3.message : e3}`);
14155
14213
  return null;
@@ -14204,11 +14262,24 @@ function wrapProvider(candidate) {
14204
14262
  }
14205
14263
  throw error2;
14206
14264
  }
14207
- }
14265
+ },
14266
+ ...candidate.getCachedEmbeddings ? {
14267
+ async getCachedEmbeddings(texts) {
14268
+ return candidate.getCachedEmbeddings(texts);
14269
+ }
14270
+ } : {}
14208
14271
  };
14209
14272
  }
14210
- async function getEmbeddingProvider() {
14273
+ async function getEmbeddingProvider(options2 = {}) {
14211
14274
  if (provider) return provider;
14275
+ if (options2.allowNetworkProbe === false) {
14276
+ const mode = getEmbeddingMode2();
14277
+ if (mode !== "api" && !(mode === "auto" && hasAPIEmbeddingConfig())) {
14278
+ return null;
14279
+ }
14280
+ const cached2 = await createAPIProvider({ allowNetworkProbe: false });
14281
+ return cached2 ? wrapProvider(cached2) : null;
14282
+ }
14212
14283
  if (lastInitWasTemporaryFailure) {
14213
14284
  const elapsed = Date.now() - lastFailureTimestamp;
14214
14285
  if (elapsed < RETRY_COOLDOWN_MS) {
@@ -15721,12 +15792,15 @@ __export(orama_store_exports, {
15721
15792
  batchGenerateEmbeddings: () => batchGenerateEmbeddings,
15722
15793
  generateEmbedding: () => generateEmbedding,
15723
15794
  getDb: () => getDb,
15795
+ getDeferredCachedVectorHydration: () => getDeferredCachedVectorHydration,
15724
15796
  getLastSearchMode: () => getLastSearchMode,
15725
15797
  getObservationCount: () => getObservationCount,
15726
15798
  getObservationsByIds: () => getObservationsByIds,
15727
15799
  getTimeline: () => getTimeline,
15728
15800
  getVectorDimensions: () => getVectorDimensions,
15801
+ hasObservationVector: () => hasObservationVector,
15729
15802
  hydrateIndex: () => hydrateIndex,
15803
+ hydrateIndexForStartup: () => hydrateIndexForStartup,
15730
15804
  insertObservation: () => insertObservation,
15731
15805
  isEmbeddingEnabled: () => isEmbeddingEnabled,
15732
15806
  makeOramaObservationId: () => makeOramaObservationId,
@@ -15786,11 +15860,10 @@ function stripVectorSearchParams(params) {
15786
15860
  const { mode, vector, similarity, hybridWeights, ...rest } = params;
15787
15861
  return rest;
15788
15862
  }
15789
- async function getDb() {
15790
- if (db) return db;
15791
- const provider2 = await getEmbeddingProvider();
15792
- embeddingEnabled = provider2 !== null;
15793
- embeddingDimensions = provider2?.dimensions ?? null;
15863
+ async function initializeDb(options2, generation) {
15864
+ const provider2 = await getEmbeddingProvider({ allowNetworkProbe: options2.allowNetworkProbe });
15865
+ const nextEmbeddingEnabled = provider2 !== null;
15866
+ const nextEmbeddingDimensions = provider2?.dimensions ?? null;
15794
15867
  const baseSchema = {
15795
15868
  id: "string",
15796
15869
  observationId: "number",
@@ -15813,15 +15886,39 @@ async function getDb() {
15813
15886
  documentType: "string",
15814
15887
  knowledgeLayer: "string"
15815
15888
  };
15816
- const dims = embeddingDimensions ?? 384;
15817
- const schema = embeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
15818
- db = await create5({ schema });
15819
- return db;
15889
+ const dims = nextEmbeddingDimensions ?? 384;
15890
+ const schema = nextEmbeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
15891
+ const nextDb = await create5({ schema });
15892
+ if (generation !== dbGeneration) {
15893
+ return getDb(options2);
15894
+ }
15895
+ indexEmbeddingProvider = provider2;
15896
+ embeddingEnabled = nextEmbeddingEnabled;
15897
+ embeddingDimensions = nextEmbeddingDimensions;
15898
+ db = nextDb;
15899
+ return nextDb;
15900
+ }
15901
+ async function getDb(options2 = {}) {
15902
+ if (db) return db;
15903
+ if (dbInitPromise) return dbInitPromise;
15904
+ const initPromise2 = initializeDb(options2, dbGeneration);
15905
+ dbInitPromise = initPromise2;
15906
+ try {
15907
+ return await initPromise2;
15908
+ } finally {
15909
+ if (dbInitPromise === initPromise2) {
15910
+ dbInitPromise = null;
15911
+ }
15912
+ }
15820
15913
  }
15821
15914
  async function resetDb() {
15915
+ dbGeneration++;
15822
15916
  db = null;
15917
+ dbInitPromise = null;
15823
15918
  embeddingEnabled = false;
15824
15919
  embeddingDimensions = null;
15920
+ indexEmbeddingProvider = null;
15921
+ deferredCachedVectorHydration = null;
15825
15922
  lastSearchModeByProject.clear();
15826
15923
  docByObservationKey.clear();
15827
15924
  }
@@ -15849,14 +15946,64 @@ async function batchGenerateEmbeddings(texts) {
15849
15946
  return texts.map(() => null);
15850
15947
  }
15851
15948
  }
15852
- async function hydrateIndex(observations2) {
15853
- const database = await getDb();
15949
+ async function getCachedEmbeddings(texts) {
15950
+ if (!embeddingEnabled || texts.length === 0) return texts.map(() => null);
15951
+ const provider2 = indexEmbeddingProvider;
15952
+ if (!provider2?.getCachedEmbeddings) return texts.map(() => null);
15953
+ try {
15954
+ return await provider2.getCachedEmbeddings(texts);
15955
+ } catch {
15956
+ return texts.map(() => null);
15957
+ }
15958
+ }
15959
+ function observationEmbeddingText(observation) {
15960
+ return [
15961
+ observation.title ?? "",
15962
+ observation.narrative ?? "",
15963
+ ...Array.isArray(observation.facts) ? observation.facts : []
15964
+ ].join(" ");
15965
+ }
15966
+ function documentEmbeddingText(document) {
15967
+ return [document.title ?? "", document.narrative ?? "", document.facts ?? ""].join(" ");
15968
+ }
15969
+ function isCompatibleCachedVector(vector) {
15970
+ return Boolean(
15971
+ vector && embeddingDimensions !== null && vector.length === embeddingDimensions && vector.every(Number.isFinite)
15972
+ );
15973
+ }
15974
+ async function attachCachedVectors(database, candidates) {
15975
+ const cachedVectors = await getCachedEmbeddings(
15976
+ candidates.map(({ observation }) => observationEmbeddingText(observation))
15977
+ );
15978
+ if (db !== database) return;
15979
+ for (let index = 0; index < candidates.length; index++) {
15980
+ const vector = cachedVectors[index];
15981
+ if (!isCompatibleCachedVector(vector)) continue;
15982
+ const existing = getByID(database, candidates[index].id);
15983
+ if (!existing || documentEmbeddingText(existing) !== observationEmbeddingText(candidates[index].observation)) continue;
15984
+ try {
15985
+ await update(database, candidates[index].id, { ...existing, embedding: vector });
15986
+ } catch {
15987
+ }
15988
+ }
15989
+ }
15990
+ async function hydrateIndex(observations2, options2 = {}) {
15991
+ const database = await getDb({ allowNetworkProbe: options2.allowNetworkProbe });
15992
+ const candidates = [];
15993
+ for (const observation of observations2) {
15994
+ if (!observation || !observation.id || !observation.projectId) continue;
15995
+ const id = makeOramaObservationId(observation.projectId, observation.id);
15996
+ if (getByID(database, id)) continue;
15997
+ candidates.push({ observation, id });
15998
+ }
15999
+ const deferCachedVectors = options2.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
16000
+ const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
15854
16001
  let inserted = 0;
15855
- for (const obs of observations2) {
15856
- if (!obs || !obs.id || !obs.projectId) continue;
16002
+ for (let index = 0; index < candidates.length; index++) {
16003
+ const { observation: obs, id } = candidates[index];
15857
16004
  try {
15858
- const id = makeOramaObservationId(obs.projectId, obs.id);
15859
- if (getByID(database, id)) continue;
16005
+ const vector = cachedVectors[index];
16006
+ const compatibleVector = isCompatibleCachedVector(vector) ? vector : null;
15860
16007
  const doc = {
15861
16008
  id,
15862
16009
  observationId: obs.id,
@@ -15875,7 +16022,8 @@ async function hydrateIndex(observations2) {
15875
16022
  status: obs.status ?? "active",
15876
16023
  source: obs.source || "agent",
15877
16024
  documentType: "observation",
15878
- knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source)
16025
+ knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
16026
+ ...compatibleVector ? { embedding: compatibleVector } : {}
15879
16027
  };
15880
16028
  await insert3(database, doc);
15881
16029
  rememberObservationDoc(doc);
@@ -15883,8 +16031,24 @@ async function hydrateIndex(observations2) {
15883
16031
  } catch {
15884
16032
  }
15885
16033
  }
16034
+ deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
16035
+ }) : null;
15886
16036
  return inserted;
15887
16037
  }
16038
+ async function hydrateIndexForStartup(observations2) {
16039
+ return hydrateIndex(observations2, {
16040
+ allowNetworkProbe: false,
16041
+ deferCachedVectors: true
16042
+ });
16043
+ }
16044
+ function getDeferredCachedVectorHydration() {
16045
+ return deferredCachedVectorHydration;
16046
+ }
16047
+ function hasObservationVector(projectId, observationId) {
16048
+ if (!db || !embeddingEnabled || embeddingDimensions === null) return false;
16049
+ const document = getByID(db, makeOramaObservationId(projectId, observationId));
16050
+ return Array.isArray(document?.embedding) && document.embedding.length === embeddingDimensions && document.embedding.every(Number.isFinite);
16051
+ }
15888
16052
  async function insertObservation(doc) {
15889
16053
  const database = await getDb();
15890
16054
  await insert3(database, doc);
@@ -16418,7 +16582,7 @@ function formatTime(isoDate) {
16418
16582
  return isoDate;
16419
16583
  }
16420
16584
  }
16421
- 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;
16585
+ 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;
16422
16586
  var init_orama_store = __esm({
16423
16587
  "src/store/orama-store.ts"() {
16424
16588
  "use strict";
@@ -16431,8 +16595,12 @@ var init_orama_store = __esm({
16431
16595
  init_intent_detector();
16432
16596
  init_query_expansion();
16433
16597
  db = null;
16598
+ dbInitPromise = null;
16599
+ dbGeneration = 0;
16434
16600
  embeddingEnabled = false;
16435
16601
  embeddingDimensions = null;
16602
+ indexEmbeddingProvider = null;
16603
+ deferredCachedVectorHydration = null;
16436
16604
  docByObservationKey = /* @__PURE__ */ new Map();
16437
16605
  NON_CJK_HYBRID_SIMILARITY = 0.45;
16438
16606
  lastSearchModeByProject = /* @__PURE__ */ new Map();
@@ -18213,7 +18381,18 @@ async function bindObservationCodeRefsBestEffort(observation) {
18213
18381
  function isVectorCompatibleWithCurrentIndex(embedding) {
18214
18382
  if (!embedding) return false;
18215
18383
  const vectorDimensions = getVectorDimensions();
18216
- return vectorDimensions === null || embedding.length === vectorDimensions;
18384
+ return vectorDimensions !== null && embedding.length === vectorDimensions && embedding.every(Number.isFinite);
18385
+ }
18386
+ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
18387
+ if (isVectorCompatibleWithCurrentIndex(embedding)) return true;
18388
+ if (getVectorDimensions() !== null || !embedding.every(Number.isFinite)) return false;
18389
+ const targetProjectDir = projectDir;
18390
+ if (!vectorSchemaUpgradePromise) {
18391
+ vectorSchemaUpgradePromise = reindexObservations().then(() => projectDir === targetProjectDir && isVectorCompatibleWithCurrentIndex(embedding)).catch(() => false).finally(() => {
18392
+ vectorSchemaUpgradePromise = null;
18393
+ });
18394
+ }
18395
+ return vectorSchemaUpgradePromise;
18217
18396
  }
18218
18397
  async function initObservations(dir) {
18219
18398
  if (projectDir === dir) return;
@@ -18223,6 +18402,7 @@ async function initObservations(dir) {
18223
18402
  nextId = await store2.loadIdCounter();
18224
18403
  projectDir = dir;
18225
18404
  searchIndexPrepared = false;
18405
+ vectorSchemaUpgradePromise = null;
18226
18406
  }
18227
18407
  async function ensureFreshObservations() {
18228
18408
  if (!projectDir) return false;
@@ -18402,6 +18582,9 @@ async function storeObservation(input) {
18402
18582
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
18403
18583
  generateEmbedding(searchableText).then(async (embedding) => {
18404
18584
  if (embedding) {
18585
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
18586
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
18587
+ }
18405
18588
  if (!isVectorCompatibleWithCurrentIndex(embedding)) {
18406
18589
  const vectorDimensions = getVectorDimensions();
18407
18590
  console.error(
@@ -18512,6 +18695,13 @@ async function upsertObservation(existing, input, now) {
18512
18695
  vectorMissingIds.add(obsId);
18513
18696
  generateEmbedding(searchableText).then(async (embedding) => {
18514
18697
  if (embedding) {
18698
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
18699
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
18700
+ }
18701
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
18702
+ queueVectorBackfill(existing.projectId);
18703
+ return;
18704
+ }
18515
18705
  try {
18516
18706
  const { removeObservation: removeObs } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
18517
18707
  await removeObs(makeOramaObservationId(existing.projectId, obsId));
@@ -18656,14 +18846,21 @@ async function reindexObservations() {
18656
18846
  let embeddings = observations.map(() => null);
18657
18847
  const provider2 = await getEmbeddingProvider();
18658
18848
  const canBatchEmbedAtStartup = provider2 !== null && !provider2.name.startsWith("api-");
18849
+ await getDb();
18850
+ const texts = observations.map(
18851
+ (obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
18852
+ );
18853
+ if (provider2?.getCachedEmbeddings) {
18854
+ try {
18855
+ embeddings = await provider2.getCachedEmbeddings(texts);
18856
+ } catch {
18857
+ }
18858
+ }
18659
18859
  if (provider2 && !canBatchEmbedAtStartup) {
18660
- console.error("[memorix] Startup reindex: skipping synchronous API embeddings; background backfill will hydrate vectors");
18860
+ console.error("[memorix] Startup reindex: restored cached API embeddings; uncached vectors stay in background recovery");
18661
18861
  }
18662
18862
  if (canBatchEmbedAtStartup) {
18663
18863
  try {
18664
- const texts = observations.map(
18665
- (obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
18666
- );
18667
18864
  embeddings = await batchGenerateEmbeddings(texts);
18668
18865
  } catch {
18669
18866
  }
@@ -18716,16 +18913,31 @@ async function reindexObservations() {
18716
18913
  }
18717
18914
  async function prepareSearchIndex() {
18718
18915
  if (searchIndexPrepared) return 0;
18719
- const count3 = await hydrateIndex(observations);
18916
+ const count3 = await hydrateIndexForStartup(observations);
18720
18917
  if (count3 === 0) {
18721
18918
  searchIndexPrepared = true;
18722
18919
  return 0;
18723
18920
  }
18724
- vectorMissingIds.clear();
18725
- if (isEmbeddingEnabled()) {
18726
- for (const obs of observations) {
18727
- vectorMissingIds.add(obs.id);
18921
+ const hydratedProjectDir = projectDir;
18922
+ const queueMissingVectors = () => {
18923
+ if (projectDir !== hydratedProjectDir) return;
18924
+ vectorMissingIds.clear();
18925
+ if (!isEmbeddingExplicitlyDisabled()) {
18926
+ const projectsWithMissingVectors = /* @__PURE__ */ new Set();
18927
+ for (const obs of observations) {
18928
+ if (!hasObservationVector(obs.projectId, obs.id)) {
18929
+ vectorMissingIds.add(obs.id);
18930
+ projectsWithMissingVectors.add(obs.projectId);
18931
+ }
18932
+ }
18933
+ for (const projectId of projectsWithMissingVectors) queueVectorBackfill(projectId);
18728
18934
  }
18935
+ };
18936
+ const cacheHydration = getDeferredCachedVectorHydration();
18937
+ if (cacheHydration) {
18938
+ void cacheHydration.finally(queueMissingVectors);
18939
+ } else {
18940
+ queueMissingVectors();
18729
18941
  }
18730
18942
  searchIndexPrepared = true;
18731
18943
  return count3;
@@ -18785,6 +18997,9 @@ async function backfillVectorEmbeddings(options2 = {}) {
18785
18997
  try {
18786
18998
  const embedding = await generateEmbedding(text);
18787
18999
  if (embedding) {
19000
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
19001
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
19002
+ }
18788
19003
  if (!isVectorCompatibleWithCurrentIndex(embedding)) {
18789
19004
  const vectorDimensions = getVectorDimensions();
18790
19005
  console.error(
@@ -18847,7 +19062,7 @@ async function backfillVectorEmbeddings(options2 = {}) {
18847
19062
  }
18848
19063
  return { attempted: ids.length, succeeded, failed };
18849
19064
  }
18850
- var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
19065
+ var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
18851
19066
  var init_observations = __esm({
18852
19067
  "src/memory/observations.ts"() {
18853
19068
  "use strict";
@@ -18865,6 +19080,7 @@ var init_observations = __esm({
18865
19080
  searchIndexPrepared = false;
18866
19081
  vectorMissingIds = /* @__PURE__ */ new Set();
18867
19082
  vectorBackfillRunning = false;
19083
+ vectorSchemaUpgradePromise = null;
18868
19084
  lastVectorBackfill = null;
18869
19085
  embeddingFailureLogTimestamps = /* @__PURE__ */ new Map();
18870
19086
  EMBEDDING_FAILURE_LOG_COOLDOWN_MS = 3e4;
@@ -19574,11 +19790,8 @@ function normalizeSearchMode(raw) {
19574
19790
  }
19575
19791
  async function prepareProjectSearch(projectId, dataDir) {
19576
19792
  if (preparedProjects.has(projectId)) return;
19577
- await initObservationStore(dataDir);
19578
19793
  await initObservations(dataDir);
19579
- await getDb();
19580
- const allObs = getAllObservations();
19581
- await hydrateIndex(allObs);
19794
+ await prepareSearchIndex();
19582
19795
  preparedProjects.add(projectId);
19583
19796
  }
19584
19797
  function executeSearchMemories(args, ctx) {
@@ -19984,7 +20197,6 @@ var init_chat_service = __esm({
19984
20197
  init_provider2();
19985
20198
  init_observations();
19986
20199
  init_detector();
19987
- init_obs_store();
19988
20200
  init_orama_store();
19989
20201
  init_persistence();
19990
20202
  SEARCH_LIMIT = 6;
@@ -21931,6 +22143,7 @@ __export(installers_exports, {
21931
22143
  getProjectConfigPath: () => getProjectConfigPath,
21932
22144
  installAgentGuidance: () => installAgentGuidance,
21933
22145
  installHooks: () => installHooks,
22146
+ resolveOpenCodeHookCommand: () => resolveOpenCodeHookCommand,
21934
22147
  uninstallHooks: () => uninstallHooks
21935
22148
  });
21936
22149
  import * as fs9 from "fs/promises";
@@ -21943,6 +22156,22 @@ function resolveHookCommand() {
21943
22156
  }
21944
22157
  return "memorix";
21945
22158
  }
22159
+ function resolveWindowsMemorixShim() {
22160
+ try {
22161
+ const output = String(execSync2("where.exe memorix.cmd", {
22162
+ encoding: "utf-8",
22163
+ stdio: ["ignore", "pipe", "ignore"],
22164
+ windowsHide: true
22165
+ }));
22166
+ return output.split(/\r?\n/).map((candidate) => candidate.trim()).find((candidate) => path13.isAbsolute(candidate)) ?? null;
22167
+ } catch {
22168
+ return null;
22169
+ }
22170
+ }
22171
+ function resolveOpenCodeHookCommand(platform2 = process.platform, resolveWindowsShim = resolveWindowsMemorixShim) {
22172
+ if (platform2 !== "win32") return "memorix";
22173
+ return resolveWindowsShim() ?? "memorix.cmd";
22174
+ }
21946
22175
  function generateClaudeConfig() {
21947
22176
  const cmd = `${resolveHookCommand()} hook`;
21948
22177
  const hookEntry = {
@@ -22142,6 +22371,7 @@ async function installOfficialSkillsForAgent(agent, projectRoot, global2 = false
22142
22371
  return skillPaths;
22143
22372
  }
22144
22373
  function generateOpenCodePlugin() {
22374
+ const hookCommand = JSON.stringify(resolveOpenCodeHookCommand());
22145
22375
  return `/**
22146
22376
  * Memorix - Cross-Agent Memory Bridge Plugin for OpenCode
22147
22377
  * @generated-version ${OPENCODE_PLUGIN_VERSION}
@@ -22160,6 +22390,16 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
22160
22390
  const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
22161
22391
  let pendingAssistantResponse = null;
22162
22392
  let lastDeliveredAssistantKey = '';
22393
+ let hookFailureReported = false;
22394
+ const hookCommand = ${hookCommand};
22395
+
22396
+ function reportHookFailure(eventName, detail) {
22397
+ // OpenCode renders console.error output inside the conversation. Delivery is
22398
+ // best-effort, so keep normal sessions quiet while retaining opt-in diagnostics.
22399
+ if (process.env.MEMORIX_HOOK_DEBUG !== '1' || hookFailureReported) return;
22400
+ hookFailureReported = true;
22401
+ console.error('[memorix-plugin] hook delivery failed:', eventName, detail);
22402
+ }
22163
22403
 
22164
22404
  /**
22165
22405
  * Send event JSON to \`memorix hook\` via child_process.spawnSync.
@@ -22175,8 +22415,7 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
22175
22415
  const data = JSON.stringify(payload);
22176
22416
  const eventName = payload.hook_event_name || 'unknown';
22177
22417
  try {
22178
- const cmd = process.platform === 'win32' ? 'memorix.cmd' : 'memorix';
22179
- const result = spawnSync(cmd, ['hook'], {
22418
+ const result = spawnSync(hookCommand, ['hook'], {
22180
22419
  input: data,
22181
22420
  timeout: 10_000,
22182
22421
  encoding: 'utf-8',
@@ -22184,12 +22423,14 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
22184
22423
  shell: process.platform === 'win32',
22185
22424
  });
22186
22425
  if (result.status !== 0) {
22187
- console.error('[memorix-plugin] hook failed:', eventName,
22188
- 'exit=', result.status,
22189
- 'stderr=', (result.stderr || '').slice(0, 200));
22426
+ reportHookFailure(eventName, {
22427
+ exit: result.status,
22428
+ stderr: (result.stderr || '').slice(0, 200),
22429
+ error: result.error?.message,
22430
+ });
22190
22431
  }
22191
22432
  } catch (e) {
22192
- console.error('[memorix-plugin] hook delivery failed:', eventName, e?.message ?? e);
22433
+ reportHookFailure(eventName, e?.message ?? e);
22193
22434
  }
22194
22435
  }
22195
22436
 
@@ -23016,7 +23257,7 @@ var init_installers = __esm({
23016
23257
  "use strict";
23017
23258
  init_esm_shims();
23018
23259
  init_official_skills();
23019
- OPENCODE_PLUGIN_VERSION = 6;
23260
+ OPENCODE_PLUGIN_VERSION = 7;
23020
23261
  AGENT_SKILL_DIRS = {
23021
23262
  cursor: { project: path13.join(".cursor", "skills"), global: path13.join(".cursor", "skills") },
23022
23263
  windsurf: { project: path13.join(".windsurf", "skills"), global: path13.join(".windsurf", "skills") },
@@ -23068,7 +23309,7 @@ function buildSetupPlan(options2) {
23068
23309
  if (wantsPlugin && options2.agent !== "all") {
23069
23310
  if (options2.agent === "opencode") {
23070
23311
  actions.push("opencode-local-plugin");
23071
- } else if (PLUGIN_PACKAGE_AGENTS.has(options2.agent)) {
23312
+ } else if (PLUGIN_PACKAGE_AGENTS.has(options2.agent) && options2.global) {
23072
23313
  actions.push("plugin-package");
23073
23314
  } else if (EXTENSION_PACKAGE_AGENTS.has(options2.agent)) {
23074
23315
  actions.push("extension-package");
@@ -23745,6 +23986,9 @@ async function installAgentSetup(agent, plan, global2) {
23745
23986
  const hasHermesPlugin = plan.actions.includes("hermes-plugin") && agent === "hermes";
23746
23987
  const hasOmpPackage = plan.actions.includes("omp-package") && agent === "omp";
23747
23988
  const wantsMcpConfig = plan.mcp !== "none" && agent !== "pi";
23989
+ if (PLUGIN_PACKAGE_AGENTS.has(agent) && plan.includePlugin && !global2) {
23990
+ v3.info(`${agent}: project setup leaves user-level plugins untouched. Run \`memorix setup --agent ${agent} --global\` to install its plugin, skills, and lifecycle hooks.`);
23991
+ }
23748
23992
  if (hasPluginPackage) {
23749
23993
  const result = await installPluginPackage({
23750
23994
  agent,
@@ -24030,7 +24274,8 @@ var init_setup = __esm({
24030
24274
  mcp,
24031
24275
  hooks: !args.noHooks,
24032
24276
  rules: !args.noRules,
24033
- plugin: !args.noPlugin
24277
+ plugin: !args.noPlugin,
24278
+ global: Boolean(args.global)
24034
24279
  });
24035
24280
  await installAgentSetup(agent, plan, Boolean(args.global));
24036
24281
  }
@@ -63609,7 +63854,7 @@ The path should point to a directory containing a .git folder.`
63609
63854
  };
63610
63855
  const server = existingServer ?? new McpServer({
63611
63856
  name: "memorix",
63612
- version: true ? "1.1.11" : "1.0.1"
63857
+ version: true ? "1.1.12" : "1.0.1"
63613
63858
  });
63614
63859
  const originalRegisterTool = server.registerTool.bind(server);
63615
63860
  server.registerTool = ((name, ...args) => {
@@ -70349,6 +70594,8 @@ function extractEventName(payload, agent) {
70349
70594
  return payload.hook_event_name ?? inferCursorEvent(payload);
70350
70595
  case "claude":
70351
70596
  return payload.hook_event_name ?? "";
70597
+ case "codex":
70598
+ return payload.hook_event_name ?? "";
70352
70599
  case "antigravity":
70353
70600
  case "gemini-cli":
70354
70601
  return payload._memorix_event ?? payload.hook_event_name ?? "";
@@ -70397,6 +70644,13 @@ function normalizeClaude(payload, event) {
70397
70644
  if (payload.prompt) {
70398
70645
  result.userPrompt = payload.prompt;
70399
70646
  }
70647
+ const assistantMessage = firstString(
70648
+ payload.last_assistant_message,
70649
+ payload.assistant_message
70650
+ );
70651
+ if (assistantMessage) {
70652
+ result.aiResponse = assistantMessage;
70653
+ }
70400
70654
  return result;
70401
70655
  }
70402
70656
  function normalizeWindsurf(payload, event) {
@@ -71213,6 +71467,7 @@ var init_significance_filter = __esm({
71213
71467
  var handler_exports = {};
71214
71468
  __export(handler_exports, {
71215
71469
  TYPE_EMOJI: () => TYPE_EMOJI2,
71470
+ formatHookOutput: () => formatHookOutput,
71216
71471
  handleHookEvent: () => handleHookEvent,
71217
71472
  resetCooldowns: () => resetCooldowns,
71218
71473
  runHook: () => runHook
@@ -71465,6 +71720,38 @@ async function handleHookEvent(input) {
71465
71720
  output: defaultOutput
71466
71721
  };
71467
71722
  }
71723
+ function formatHookOutput(agent, rawEventName, output) {
71724
+ if (agent === "codex") {
71725
+ const codexOutput = { continue: output.continue };
71726
+ if (output.stopReason) codexOutput.stopReason = output.stopReason;
71727
+ if (rawEventName === "SessionStart" && output.systemMessage) {
71728
+ codexOutput.hookSpecificOutput = {
71729
+ hookEventName: "SessionStart",
71730
+ additionalContext: output.systemMessage
71731
+ };
71732
+ }
71733
+ return codexOutput;
71734
+ }
71735
+ const finalOutput = { ...output };
71736
+ const hookSpecificOutputEvents = /* @__PURE__ */ new Set([
71737
+ "PreToolUse",
71738
+ "UserPromptSubmit",
71739
+ "PostToolUse",
71740
+ "postToolUse",
71741
+ "preToolUse",
71742
+ "userPromptSubmitted"
71743
+ ]);
71744
+ if (rawEventName && hookSpecificOutputEvents.has(rawEventName)) {
71745
+ const hookSpecificOutput = { hookEventName: rawEventName };
71746
+ if (output.systemMessage) {
71747
+ hookSpecificOutput.additionalContext = output.systemMessage;
71748
+ } else if (rawEventName === "UserPromptSubmit") {
71749
+ hookSpecificOutput.additionalContext = "";
71750
+ }
71751
+ finalOutput.hookSpecificOutput = hookSpecificOutput;
71752
+ }
71753
+ return finalOutput;
71754
+ }
71468
71755
  async function runHook(agentOverride, eventOverride) {
71469
71756
  const rawInput = await new Promise((resolve4) => {
71470
71757
  const chunks = [];
@@ -71597,21 +71884,11 @@ ${emoji2} Memorix saved: ${observation.title} [${observation.type}]`;
71597
71884
  }
71598
71885
  }
71599
71886
  const rawEventName = payload._memorix_event ?? payload.hook_event_name ?? payload.hookEventName ?? "";
71600
- const finalOutput = { ...output };
71601
71887
  if (input.agent === "antigravity") {
71602
71888
  process.stdout.write(JSON.stringify(toAntigravityHookOutput(rawEventName, output)));
71603
71889
  return;
71604
71890
  }
71605
- const HSO_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "UserPromptSubmit", "PostToolUse", "postToolUse", "preToolUse", "userPromptSubmitted"]);
71606
- if (rawEventName && HSO_EVENTS.has(rawEventName)) {
71607
- const hso = { hookEventName: rawEventName };
71608
- if (output.systemMessage) {
71609
- hso.additionalContext = output.systemMessage;
71610
- } else if (rawEventName === "UserPromptSubmit") {
71611
- hso.additionalContext = "";
71612
- }
71613
- finalOutput.hookSpecificOutput = hso;
71614
- }
71891
+ const finalOutput = formatHookOutput(input.agent, rawEventName, output);
71615
71892
  process.stdout.write(JSON.stringify(finalOutput));
71616
71893
  }
71617
71894
  function toAntigravityHookOutput(rawEventName, output) {
@@ -71770,6 +72047,7 @@ async function installSingleAgent2(agent, cwd, global2) {
71770
72047
  function getAgentLabel2(agent) {
71771
72048
  const labels = {
71772
72049
  claude: "Claude Code",
72050
+ codex: "Codex",
71773
72051
  windsurf: "Windsurf",
71774
72052
  cursor: "Cursor",
71775
72053
  copilot: "VS Code Copilot",
@@ -71798,7 +72076,7 @@ function getAgentHint2(agent) {
71798
72076
  hermes: "Hermes plugin hooks; use `memorix setup --agent hermes`",
71799
72077
  omp: "Oh-my-Pi package hooks; use `memorix setup --agent omp`",
71800
72078
  trae: ".trae/rules/project_rules.md (rules only, no hooks system)",
71801
- codex: "AGENTS.md (rules only, hooks are experimental)"
72079
+ codex: "Codex plugin hooks; use `memorix setup --agent codex`"
71802
72080
  };
71803
72081
  return hints[agent] || "";
71804
72082
  }
@@ -80450,6 +80728,7 @@ function formatSearchModeLabel(mode) {
80450
80728
  }
80451
80729
  function formatEmbeddingLabel(status, providerName) {
80452
80730
  if (status === "disabled") return "Disabled";
80731
+ if (status === "pending") return "On demand";
80453
80732
  if (status === "unavailable") return "Unavailable";
80454
80733
  if ((providerName || "").startsWith("api-")) return "API ready";
80455
80734
  if (providerName) return "Local ready";
@@ -80519,8 +80798,8 @@ async function getHealthInfo(projectId) {
80519
80798
  if (isEmbeddingExplicitlyDisabled2()) {
80520
80799
  defaults.embeddingProvider = "disabled";
80521
80800
  } else {
80522
- const provider2 = await getEmbeddingProvider2();
80523
- defaults.embeddingProvider = provider2 ? "ready" : "unavailable";
80801
+ const provider2 = await getEmbeddingProvider2({ allowNetworkProbe: false });
80802
+ defaults.embeddingProvider = provider2 ? "ready" : "pending";
80524
80803
  defaults.embeddingProviderName = provider2?.name;
80525
80804
  }
80526
80805
  defaults.embeddingLabel = formatEmbeddingLabel(
@@ -80548,6 +80827,8 @@ async function getHealthInfo(projectId) {
80548
80827
  defaults.searchDiagnostic = "Vector index ready";
80549
80828
  } else if (defaults.embeddingProvider === "ready" && !vectorActive) {
80550
80829
  defaults.searchDiagnostic = "Provider ready, no index yet";
80830
+ } else if (defaults.embeddingProvider === "pending") {
80831
+ defaults.searchDiagnostic = "BM25 ready; embeddings initialize on demand";
80551
80832
  } else if (defaults.embeddingProvider === "unavailable") {
80552
80833
  defaults.searchDiagnostic = "BM25 only (no provider)";
80553
80834
  } else {
@@ -80591,19 +80872,15 @@ async function getRecentMemories(limit = 8, projectId) {
80591
80872
  }
80592
80873
  async function searchMemories(query, limit = 10) {
80593
80874
  try {
80594
- const { searchObservations: searchObservations2, getDb: getDb2, hydrateIndex: hydrateIndex2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
80875
+ const { searchObservations: searchObservations2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
80595
80876
  const { getProjectDataDir: getProjectDataDir2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
80596
80877
  const { detectProject: detectProject2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
80597
- const { initObservations: initObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
80598
- const { initObservationStore: initObservationStore2, getObservationStore: getStore } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
80878
+ const { initObservations: initObservations2, prepareSearchIndex: prepareSearchIndex2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
80599
80879
  const proj = detectProject2(process.cwd());
80600
80880
  if (!proj) return [];
80601
80881
  const dataDir = await getProjectDataDir2(proj.id);
80602
- await initObservationStore2(dataDir);
80603
80882
  await initObservations2(dataDir);
80604
- await getDb2();
80605
- const allObs = await getStore().loadAll();
80606
- await hydrateIndex2(allObs);
80883
+ await prepareSearchIndex2();
80607
80884
  const results = await searchObservations2({ query, limit, projectId: proj.id });
80608
80885
  const typeIcons = {
80609
80886
  gotcha: "!",