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/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.13" : 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") },
@@ -23044,7 +23285,8 @@ __export(setup_exports, {
23044
23285
  installOmpPackage: () => installOmpPackage,
23045
23286
  installOpenClawBundlePackage: () => installOpenClawBundlePackage,
23046
23287
  installPiPackage: () => installPiPackage,
23047
- installPluginPackage: () => installPluginPackage
23288
+ installPluginPackage: () => installPluginPackage,
23289
+ tryInstallCodexPlugin: () => tryInstallCodexPlugin
23048
23290
  });
23049
23291
  import { spawnSync } from "child_process";
23050
23292
  import { existsSync as existsSync9 } from "fs";
@@ -23068,7 +23310,7 @@ function buildSetupPlan(options2) {
23068
23310
  if (wantsPlugin && options2.agent !== "all") {
23069
23311
  if (options2.agent === "opencode") {
23070
23312
  actions.push("opencode-local-plugin");
23071
- } else if (PLUGIN_PACKAGE_AGENTS.has(options2.agent)) {
23313
+ } else if (PLUGIN_PACKAGE_AGENTS.has(options2.agent) && options2.global) {
23072
23314
  actions.push("plugin-package");
23073
23315
  } else if (EXTENSION_PACKAGE_AGENTS.has(options2.agent)) {
23074
23316
  actions.push("extension-package");
@@ -23142,6 +23384,16 @@ async function stripHookCaptureFromPlugin(pluginPath, agent) {
23142
23384
  } catch {
23143
23385
  }
23144
23386
  }
23387
+ async function stampCodexPluginVersion(pluginPath) {
23388
+ const manifestPath = path14.join(pluginPath, ".codex-plugin", "plugin.json");
23389
+ const manifest = JSON.parse(await readFile5(manifestPath, "utf-8"));
23390
+ if (manifest.name !== "memorix") {
23391
+ throw new Error(`Unexpected Codex plugin manifest at ${manifestPath}`);
23392
+ }
23393
+ manifest.version = getCliVersion();
23394
+ await writeFile5(manifestPath, `${JSON.stringify(manifest, null, 2)}
23395
+ `, "utf-8");
23396
+ }
23145
23397
  async function stripHookCaptureFromOpenClawBundle(bundlePath) {
23146
23398
  await rm(path14.join(bundlePath, "hooks"), { recursive: true, force: true });
23147
23399
  const manifestPath = path14.join(bundlePath, ".codex-plugin", "plugin.json");
@@ -23406,6 +23658,7 @@ async function installPluginPackage(options2) {
23406
23658
  const pluginPath2 = path14.join(home, ".codex", "plugins", "memorix");
23407
23659
  const marketplacePath2 = path14.join(home, ".agents", "plugins", "marketplace.json");
23408
23660
  await copyDir(source, pluginPath2);
23661
+ await stampCodexPluginVersion(pluginPath2);
23409
23662
  if (!includeHooks) await stripHookCaptureFromPlugin(pluginPath2, "codex");
23410
23663
  await writeOfficialSkills(path14.join(pluginPath2, "skills"));
23411
23664
  await upsertCodexMarketplace(marketplacePath2, pluginPath2);
@@ -23745,6 +23998,9 @@ async function installAgentSetup(agent, plan, global2) {
23745
23998
  const hasHermesPlugin = plan.actions.includes("hermes-plugin") && agent === "hermes";
23746
23999
  const hasOmpPackage = plan.actions.includes("omp-package") && agent === "omp";
23747
24000
  const wantsMcpConfig = plan.mcp !== "none" && agent !== "pi";
24001
+ if (PLUGIN_PACKAGE_AGENTS.has(agent) && plan.includePlugin && !global2) {
24002
+ v3.info(`${agent}: project setup leaves user-level plugins untouched. Run \`memorix setup --agent ${agent} --global\` to install its plugin, skills, and lifecycle hooks.`);
24003
+ }
23748
24004
  if (hasPluginPackage) {
23749
24005
  const result = await installPluginPackage({
23750
24006
  agent,
@@ -24030,7 +24286,8 @@ var init_setup = __esm({
24030
24286
  mcp,
24031
24287
  hooks: !args.noHooks,
24032
24288
  rules: !args.noRules,
24033
- plugin: !args.noPlugin
24289
+ plugin: !args.noPlugin,
24290
+ global: Boolean(args.global)
24034
24291
  });
24035
24292
  await installAgentSetup(agent, plan, Boolean(args.global));
24036
24293
  }
@@ -63609,7 +63866,7 @@ The path should point to a directory containing a .git folder.`
63609
63866
  };
63610
63867
  const server = existingServer ?? new McpServer({
63611
63868
  name: "memorix",
63612
- version: true ? "1.1.11" : "1.0.1"
63869
+ version: true ? "1.1.13" : "1.0.1"
63613
63870
  });
63614
63871
  const originalRegisterTool = server.registerTool.bind(server);
63615
63872
  server.registerTool = ((name, ...args) => {
@@ -70349,6 +70606,8 @@ function extractEventName(payload, agent) {
70349
70606
  return payload.hook_event_name ?? inferCursorEvent(payload);
70350
70607
  case "claude":
70351
70608
  return payload.hook_event_name ?? "";
70609
+ case "codex":
70610
+ return payload.hook_event_name ?? "";
70352
70611
  case "antigravity":
70353
70612
  case "gemini-cli":
70354
70613
  return payload._memorix_event ?? payload.hook_event_name ?? "";
@@ -70397,6 +70656,13 @@ function normalizeClaude(payload, event) {
70397
70656
  if (payload.prompt) {
70398
70657
  result.userPrompt = payload.prompt;
70399
70658
  }
70659
+ const assistantMessage = firstString(
70660
+ payload.last_assistant_message,
70661
+ payload.assistant_message
70662
+ );
70663
+ if (assistantMessage) {
70664
+ result.aiResponse = assistantMessage;
70665
+ }
70400
70666
  return result;
70401
70667
  }
70402
70668
  function normalizeWindsurf(payload, event) {
@@ -71213,6 +71479,7 @@ var init_significance_filter = __esm({
71213
71479
  var handler_exports = {};
71214
71480
  __export(handler_exports, {
71215
71481
  TYPE_EMOJI: () => TYPE_EMOJI2,
71482
+ formatHookOutput: () => formatHookOutput,
71216
71483
  handleHookEvent: () => handleHookEvent,
71217
71484
  resetCooldowns: () => resetCooldowns,
71218
71485
  runHook: () => runHook
@@ -71465,6 +71732,38 @@ async function handleHookEvent(input) {
71465
71732
  output: defaultOutput
71466
71733
  };
71467
71734
  }
71735
+ function formatHookOutput(agent, rawEventName, output) {
71736
+ if (agent === "codex") {
71737
+ const codexOutput = { continue: output.continue };
71738
+ if (output.stopReason) codexOutput.stopReason = output.stopReason;
71739
+ if (rawEventName === "SessionStart" && output.systemMessage) {
71740
+ codexOutput.hookSpecificOutput = {
71741
+ hookEventName: "SessionStart",
71742
+ additionalContext: output.systemMessage
71743
+ };
71744
+ }
71745
+ return codexOutput;
71746
+ }
71747
+ const finalOutput = { ...output };
71748
+ const hookSpecificOutputEvents = /* @__PURE__ */ new Set([
71749
+ "PreToolUse",
71750
+ "UserPromptSubmit",
71751
+ "PostToolUse",
71752
+ "postToolUse",
71753
+ "preToolUse",
71754
+ "userPromptSubmitted"
71755
+ ]);
71756
+ if (rawEventName && hookSpecificOutputEvents.has(rawEventName)) {
71757
+ const hookSpecificOutput = { hookEventName: rawEventName };
71758
+ if (output.systemMessage) {
71759
+ hookSpecificOutput.additionalContext = output.systemMessage;
71760
+ } else if (rawEventName === "UserPromptSubmit") {
71761
+ hookSpecificOutput.additionalContext = "";
71762
+ }
71763
+ finalOutput.hookSpecificOutput = hookSpecificOutput;
71764
+ }
71765
+ return finalOutput;
71766
+ }
71468
71767
  async function runHook(agentOverride, eventOverride) {
71469
71768
  const rawInput = await new Promise((resolve4) => {
71470
71769
  const chunks = [];
@@ -71597,21 +71896,11 @@ ${emoji2} Memorix saved: ${observation.title} [${observation.type}]`;
71597
71896
  }
71598
71897
  }
71599
71898
  const rawEventName = payload._memorix_event ?? payload.hook_event_name ?? payload.hookEventName ?? "";
71600
- const finalOutput = { ...output };
71601
71899
  if (input.agent === "antigravity") {
71602
71900
  process.stdout.write(JSON.stringify(toAntigravityHookOutput(rawEventName, output)));
71603
71901
  return;
71604
71902
  }
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
- }
71903
+ const finalOutput = formatHookOutput(input.agent, rawEventName, output);
71615
71904
  process.stdout.write(JSON.stringify(finalOutput));
71616
71905
  }
71617
71906
  function toAntigravityHookOutput(rawEventName, output) {
@@ -71770,6 +72059,7 @@ async function installSingleAgent2(agent, cwd, global2) {
71770
72059
  function getAgentLabel2(agent) {
71771
72060
  const labels = {
71772
72061
  claude: "Claude Code",
72062
+ codex: "Codex",
71773
72063
  windsurf: "Windsurf",
71774
72064
  cursor: "Cursor",
71775
72065
  copilot: "VS Code Copilot",
@@ -71798,7 +72088,7 @@ function getAgentHint2(agent) {
71798
72088
  hermes: "Hermes plugin hooks; use `memorix setup --agent hermes`",
71799
72089
  omp: "Oh-my-Pi package hooks; use `memorix setup --agent omp`",
71800
72090
  trae: ".trae/rules/project_rules.md (rules only, no hooks system)",
71801
- codex: "AGENTS.md (rules only, hooks are experimental)"
72091
+ codex: "Codex plugin hooks; use `memorix setup --agent codex`"
71802
72092
  };
71803
72093
  return hints[agent] || "";
71804
72094
  }
@@ -73630,8 +73920,10 @@ __export(agent_integrations_exports, {
73630
73920
  formatAgentIntegrationReport: () => formatAgentIntegrationReport,
73631
73921
  formatAgentRepairResult: () => formatAgentRepairResult,
73632
73922
  inspectAgentIntegrations: () => inspectAgentIntegrations,
73923
+ parseCodexPluginList: () => parseCodexPluginList,
73633
73924
  repairAgentIntegrations: () => repairAgentIntegrations
73634
73925
  });
73926
+ import { spawnSync as spawnSync2 } from "child_process";
73635
73927
  import { existsSync as existsSync23 } from "fs";
73636
73928
  import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
73637
73929
  import { basename as basename3, dirname as dirname7, resolve as resolve2 } from "path";
@@ -73709,6 +74001,275 @@ function aggregateGuidanceIssues(checks) {
73709
74001
  function asRecord2(value) {
73710
74002
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
73711
74003
  }
74004
+ function asRecordArray(value) {
74005
+ return Array.isArray(value) ? value.map(asRecord2).filter((entry) => entry !== null) : [];
74006
+ }
74007
+ function codexPluginPath() {
74008
+ return `${homedir32()}/.codex/plugins/${CODEX_PLUGIN_NAME}`;
74009
+ }
74010
+ function codexMarketplacePath() {
74011
+ return `${homedir32()}/.agents/plugins/marketplace.json`;
74012
+ }
74013
+ function codexConfigPath() {
74014
+ return `${homedir32()}/.codex/config.toml`;
74015
+ }
74016
+ function normalizeMarketplacePath(value) {
74017
+ return value.replace(/\\/g, "/").replace(/^\.\//, "");
74018
+ }
74019
+ function isMemorixCodexPlugin(value) {
74020
+ return value.pluginId === CODEX_PLUGIN_ID || value.name === CODEX_PLUGIN_NAME && value.marketplaceName === CODEX_PLUGIN_MARKETPLACE;
74021
+ }
74022
+ function parseCodexPluginList(output) {
74023
+ try {
74024
+ const parsed = asRecord2(JSON.parse(output));
74025
+ if (!parsed) return null;
74026
+ const entries = [...asRecordArray(parsed.installed), ...asRecordArray(parsed.available)];
74027
+ const plugin = entries.find(isMemorixCodexPlugin);
74028
+ if (!plugin) return { installed: false, enabled: false };
74029
+ return {
74030
+ installed: plugin.installed === true,
74031
+ enabled: plugin.enabled === true,
74032
+ ...typeof plugin.version === "string" ? { version: plugin.version } : {}
74033
+ };
74034
+ } catch {
74035
+ return null;
74036
+ }
74037
+ }
74038
+ async function inspectCodexPluginBundle() {
74039
+ const pluginPath = codexPluginPath();
74040
+ const manifestPath = `${pluginPath}/.codex-plugin/plugin.json`;
74041
+ const hooksPath = `${pluginPath}/hooks/hooks.json`;
74042
+ if (!existsSync23(pluginPath)) {
74043
+ return {
74044
+ scope: "global",
74045
+ kind: "bundle",
74046
+ path: pluginPath,
74047
+ exists: false,
74048
+ status: "missing",
74049
+ issues: ["codex-plugin-bundle-missing"]
74050
+ };
74051
+ }
74052
+ if (!existsSync23(manifestPath)) {
74053
+ return {
74054
+ scope: "global",
74055
+ kind: "bundle",
74056
+ path: manifestPath,
74057
+ exists: false,
74058
+ status: "repairable",
74059
+ issues: ["codex-plugin-manifest-missing"]
74060
+ };
74061
+ }
74062
+ let manifest = null;
74063
+ try {
74064
+ manifest = asRecord2(JSON.parse(await readFile7(manifestPath, "utf-8")));
74065
+ } catch {
74066
+ return {
74067
+ scope: "global",
74068
+ kind: "bundle",
74069
+ path: manifestPath,
74070
+ exists: true,
74071
+ status: "repairable",
74072
+ issues: ["codex-plugin-manifest-unreadable"]
74073
+ };
74074
+ }
74075
+ if (!manifest || manifest.name !== CODEX_PLUGIN_NAME) {
74076
+ return {
74077
+ scope: "global",
74078
+ kind: "bundle",
74079
+ path: manifestPath,
74080
+ exists: true,
74081
+ status: "repairable",
74082
+ issues: ["codex-plugin-manifest-invalid"]
74083
+ };
74084
+ }
74085
+ const version2 = typeof manifest.version === "string" ? manifest.version : void 0;
74086
+ const issues = [];
74087
+ if (version2 !== getCliVersion()) issues.push("codex-plugin-version-mismatch");
74088
+ if (manifest.hooks !== "./hooks/hooks.json") issues.push("codex-hook-manifest-missing");
74089
+ let declared = [];
74090
+ if (!existsSync23(hooksPath)) {
74091
+ issues.push("codex-hook-manifest-missing");
74092
+ } else {
74093
+ try {
74094
+ const hooksConfig = asRecord2(JSON.parse(await readFile7(hooksPath, "utf-8")));
74095
+ const hooks = asRecord2(hooksConfig?.hooks);
74096
+ declared = hooks ? Object.keys(hooks) : [];
74097
+ if (CODEX_HOOK_EVENTS.some((event) => !declared.includes(event))) {
74098
+ issues.push("codex-hook-events-missing");
74099
+ }
74100
+ } catch {
74101
+ issues.push("codex-hook-manifest-unreadable");
74102
+ }
74103
+ }
74104
+ return {
74105
+ scope: "global",
74106
+ kind: "bundle",
74107
+ path: pluginPath,
74108
+ exists: true,
74109
+ status: issues.length > 0 ? "repairable" : "ok",
74110
+ issues: unique(issues),
74111
+ ...version2 ? { version: version2 } : {},
74112
+ hooks: { declared, expected: [...CODEX_HOOK_EVENTS] }
74113
+ };
74114
+ }
74115
+ async function inspectCodexMarketplace() {
74116
+ const marketplacePath = codexMarketplacePath();
74117
+ if (!existsSync23(marketplacePath)) {
74118
+ return {
74119
+ scope: "global",
74120
+ kind: "marketplace",
74121
+ path: marketplacePath,
74122
+ exists: false,
74123
+ status: "missing",
74124
+ issues: ["codex-marketplace-missing"]
74125
+ };
74126
+ }
74127
+ let catalog = null;
74128
+ try {
74129
+ catalog = asRecord2(JSON.parse(await readFile7(marketplacePath, "utf-8")));
74130
+ } catch {
74131
+ return {
74132
+ scope: "global",
74133
+ kind: "marketplace",
74134
+ path: marketplacePath,
74135
+ exists: true,
74136
+ status: "repairable",
74137
+ issues: ["codex-marketplace-unreadable"]
74138
+ };
74139
+ }
74140
+ const entry = asRecordArray(catalog?.plugins).find((plugin) => plugin.name === CODEX_PLUGIN_NAME);
74141
+ if (!entry) {
74142
+ return {
74143
+ scope: "global",
74144
+ kind: "marketplace",
74145
+ path: marketplacePath,
74146
+ exists: true,
74147
+ status: "repairable",
74148
+ issues: ["codex-marketplace-entry-missing"]
74149
+ };
74150
+ }
74151
+ const source = asRecord2(entry.source);
74152
+ const sourcePath = typeof source?.path === "string" ? source.path : "";
74153
+ const issues = source?.source === "local" && normalizeMarketplacePath(sourcePath) === ".codex/plugins/memorix" ? [] : ["codex-marketplace-entry-stale"];
74154
+ return {
74155
+ scope: "global",
74156
+ kind: "marketplace",
74157
+ path: marketplacePath,
74158
+ exists: true,
74159
+ status: issues.length > 0 ? "repairable" : "ok",
74160
+ issues
74161
+ };
74162
+ }
74163
+ function inspectCodexPluginRuntime() {
74164
+ const command = "codex plugin list --marketplace personal --available --json";
74165
+ const result = spawnSync2("codex", ["plugin", "list", "--marketplace", CODEX_PLUGIN_MARKETPLACE, "--available", "--json"], {
74166
+ encoding: "utf-8",
74167
+ stdio: "pipe",
74168
+ shell: process.platform === "win32"
74169
+ });
74170
+ if (result.error || result.status !== 0) {
74171
+ return {
74172
+ scope: "global",
74173
+ kind: "runtime",
74174
+ path: command,
74175
+ exists: false,
74176
+ status: "skipped",
74177
+ issues: ["codex-plugin-runtime-unavailable"]
74178
+ };
74179
+ }
74180
+ const runtime = parseCodexPluginList(String(result.stdout ?? ""));
74181
+ if (!runtime) {
74182
+ return {
74183
+ scope: "global",
74184
+ kind: "runtime",
74185
+ path: command,
74186
+ exists: true,
74187
+ status: "skipped",
74188
+ issues: ["codex-plugin-runtime-unreadable"]
74189
+ };
74190
+ }
74191
+ const issues = [];
74192
+ if (!runtime.installed) issues.push("codex-plugin-not-installed");
74193
+ else if (!runtime.enabled) issues.push("codex-plugin-disabled");
74194
+ return {
74195
+ scope: "global",
74196
+ kind: "runtime",
74197
+ path: command,
74198
+ exists: true,
74199
+ status: issues.length > 0 ? "repairable" : "ok",
74200
+ issues,
74201
+ runtime
74202
+ };
74203
+ }
74204
+ async function inspectCodexHookTrust() {
74205
+ const configPath = codexConfigPath();
74206
+ if (!existsSync23(configPath)) {
74207
+ return {
74208
+ scope: "global",
74209
+ kind: "hook-trust",
74210
+ path: configPath,
74211
+ exists: false,
74212
+ status: "skipped",
74213
+ issues: ["codex-hook-trust-unavailable"]
74214
+ };
74215
+ }
74216
+ let config2 = "";
74217
+ try {
74218
+ config2 = await readFile7(configPath, "utf-8");
74219
+ } catch {
74220
+ return {
74221
+ scope: "global",
74222
+ kind: "hook-trust",
74223
+ path: configPath,
74224
+ exists: true,
74225
+ status: "skipped",
74226
+ issues: ["codex-hook-trust-unreadable"]
74227
+ };
74228
+ }
74229
+ const trusted = CODEX_HOOK_STATE_NAMES.filter(
74230
+ (event) => config2.includes(`[hooks.state."${CODEX_PLUGIN_ID}:hooks/hooks.json:${event}:`)
74231
+ );
74232
+ const issues = trusted.length === CODEX_HOOK_STATE_NAMES.length ? [] : ["codex-hook-trust-pending"];
74233
+ return {
74234
+ scope: "global",
74235
+ kind: "hook-trust",
74236
+ path: configPath,
74237
+ exists: true,
74238
+ status: issues.length > 0 ? "repairable" : "ok",
74239
+ issues,
74240
+ hookTrust: { trusted, expected: [...CODEX_HOOK_STATE_NAMES] }
74241
+ };
74242
+ }
74243
+ function aggregatePluginStatus(checks) {
74244
+ if (checks.length === 0) return "skipped";
74245
+ return worstStatus(checks.map((check2) => check2.status));
74246
+ }
74247
+ function aggregatePluginIssues(checks) {
74248
+ const status = aggregatePluginStatus(checks);
74249
+ if (status === "ok") {
74250
+ return unique(checks.filter((check2) => check2.status === "skipped").flatMap((check2) => check2.issues));
74251
+ }
74252
+ if (status === "repairable") {
74253
+ return unique(checks.filter((check2) => check2.status === "repairable" || check2.status === "missing").flatMap((check2) => check2.issues));
74254
+ }
74255
+ return unique(checks.filter((check2) => check2.status === status).flatMap((check2) => check2.issues));
74256
+ }
74257
+ async function inspectPlugin(agent, scope) {
74258
+ if (agent !== "codex" || scope === "local" || scope === "project") {
74259
+ return { status: "skipped", issues: [], checks: [] };
74260
+ }
74261
+ const checks = [
74262
+ await inspectCodexPluginBundle(),
74263
+ await inspectCodexMarketplace(),
74264
+ inspectCodexPluginRuntime(),
74265
+ await inspectCodexHookTrust()
74266
+ ];
74267
+ return {
74268
+ status: aggregatePluginStatus(checks),
74269
+ issues: aggregatePluginIssues(checks),
74270
+ checks
74271
+ };
74272
+ }
73712
74273
  function looksLikeStaleMemorixCommand(server) {
73713
74274
  const text = [server.command, ...server.args ?? []].join(" ");
73714
74275
  return /[\\/]\.worktrees[\\/]/i.test(text) || /[\\/]dist[\\/]cli[\\/]index\.js/i.test(text);
@@ -73977,10 +74538,11 @@ async function inspectAgentIntegrations(options2 = {}) {
73977
74538
  entries.push({
73978
74539
  agent,
73979
74540
  mcp: await inspectMcp(agent, projectRoot, scope),
73980
- guidance: await inspectGuidance(agent, projectRoot, scope)
74541
+ guidance: await inspectGuidance(agent, projectRoot, scope),
74542
+ plugin: await inspectPlugin(agent, scope)
73981
74543
  });
73982
74544
  }
73983
- const entryStatuses = entries.map((entry) => worstStatus([entry.mcp.status, entry.guidance.status]));
74545
+ const entryStatuses = entries.map((entry) => worstStatus([entry.mcp.status, entry.guidance.status, entry.plugin.status]));
73984
74546
  return {
73985
74547
  projectRoot,
73986
74548
  scope,
@@ -74002,10 +74564,11 @@ function formatAgentIntegrationReport(report) {
74002
74564
  ""
74003
74565
  ];
74004
74566
  for (const entry of report.entries) {
74005
- const status = worstStatus([entry.mcp.status, entry.guidance.status]);
74567
+ const status = worstStatus([entry.mcp.status, entry.guidance.status, entry.plugin.status]);
74006
74568
  lines.push(`${entry.agent}: ${status}`);
74007
74569
  if (entry.mcp.issues.length > 0) lines.push(` MCP: ${entry.mcp.issues.join(", ")}`);
74008
74570
  if (entry.guidance.issues.length > 0) lines.push(` Guidance: ${entry.guidance.issues.join(", ")}`);
74571
+ if (entry.plugin.issues.length > 0) lines.push(` Plugin: ${entry.plugin.issues.join(", ")}`);
74009
74572
  }
74010
74573
  lines.push("");
74011
74574
  lines.push(`Repair: ${report.repairCommand}`);
@@ -74058,6 +74621,39 @@ async function repairAgentIntegrations(options2 = {}) {
74058
74621
  } else {
74059
74622
  skipped.push(`${entry.agent}:guidance`);
74060
74623
  }
74624
+ if (entry.agent === "codex" && entry.plugin.status !== "ok" && entry.plugin.status !== "skipped") {
74625
+ const bundleOrMarketplaceNeedsRepair = entry.plugin.checks.some(
74626
+ (check2) => (check2.kind === "bundle" || check2.kind === "marketplace") && check2.status !== "ok"
74627
+ );
74628
+ const runtime = entry.plugin.checks.find((check2) => check2.kind === "runtime");
74629
+ const hookTrust = entry.plugin.checks.find((check2) => check2.kind === "hook-trust");
74630
+ const needsInstall = runtime?.runtime?.installed === false;
74631
+ const needsManualEnable = runtime?.runtime?.installed === true && runtime.runtime.enabled === false;
74632
+ const needsHookTrust = hookTrust?.status === "repairable";
74633
+ const missingPluginFiles = entry.plugin.checks.some(
74634
+ (check2) => (check2.kind === "bundle" || check2.kind === "marketplace") && check2.status === "missing"
74635
+ );
74636
+ if (needsManualEnable) {
74637
+ skipped.push("codex:plugin:global:enable-in-plugin-browser");
74638
+ }
74639
+ if (needsHookTrust) {
74640
+ skipped.push("codex:plugin:global:review-hooks");
74641
+ }
74642
+ if (bundleOrMarketplaceNeedsRepair || needsInstall) {
74643
+ if ((missingPluginFiles || needsInstall) && !canInstallMissing) {
74644
+ skipped.push("codex:plugin:global:missing");
74645
+ } else {
74646
+ if (!options2.dry) {
74647
+ await installPluginPackage({ agent: "codex" });
74648
+ if (needsInstall) {
74649
+ const install = tryInstallCodexPlugin();
74650
+ if (!install.ok) skipped.push("codex:plugin:global:install-pending");
74651
+ }
74652
+ }
74653
+ changed.push("codex:plugin:global");
74654
+ }
74655
+ }
74656
+ }
74061
74657
  }
74062
74658
  return {
74063
74659
  projectRoot,
@@ -74088,13 +74684,14 @@ function formatAgentRepairResult(result) {
74088
74684
  }
74089
74685
  return lines.join("\n");
74090
74686
  }
74091
- var GUIDANCE_AGENTS;
74687
+ var GUIDANCE_AGENTS, CODEX_PLUGIN_NAME, CODEX_PLUGIN_MARKETPLACE, CODEX_PLUGIN_ID, CODEX_HOOK_EVENTS, CODEX_HOOK_STATE_NAMES;
74092
74688
  var init_agent_integrations = __esm({
74093
74689
  "src/cli/commands/agent-integrations.ts"() {
74094
74690
  "use strict";
74095
74691
  init_esm_shims();
74096
74692
  init_installers();
74097
74693
  init_setup();
74694
+ init_version();
74098
74695
  GUIDANCE_AGENTS = /* @__PURE__ */ new Set([
74099
74696
  "claude",
74100
74697
  "codex",
@@ -74107,6 +74704,11 @@ var init_agent_integrations = __esm({
74107
74704
  "opencode",
74108
74705
  "trae"
74109
74706
  ]);
74707
+ CODEX_PLUGIN_NAME = "memorix";
74708
+ CODEX_PLUGIN_MARKETPLACE = "personal";
74709
+ CODEX_PLUGIN_ID = `${CODEX_PLUGIN_NAME}@${CODEX_PLUGIN_MARKETPLACE}`;
74710
+ CODEX_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PostToolUse", "PreCompact", "Stop"];
74711
+ CODEX_HOOK_STATE_NAMES = ["session_start", "user_prompt_submit", "post_tool_use", "pre_compact", "stop"];
74110
74712
  }
74111
74713
  });
74112
74714
 
@@ -80450,6 +81052,7 @@ function formatSearchModeLabel(mode) {
80450
81052
  }
80451
81053
  function formatEmbeddingLabel(status, providerName) {
80452
81054
  if (status === "disabled") return "Disabled";
81055
+ if (status === "pending") return "On demand";
80453
81056
  if (status === "unavailable") return "Unavailable";
80454
81057
  if ((providerName || "").startsWith("api-")) return "API ready";
80455
81058
  if (providerName) return "Local ready";
@@ -80519,8 +81122,8 @@ async function getHealthInfo(projectId) {
80519
81122
  if (isEmbeddingExplicitlyDisabled2()) {
80520
81123
  defaults.embeddingProvider = "disabled";
80521
81124
  } else {
80522
- const provider2 = await getEmbeddingProvider2();
80523
- defaults.embeddingProvider = provider2 ? "ready" : "unavailable";
81125
+ const provider2 = await getEmbeddingProvider2({ allowNetworkProbe: false });
81126
+ defaults.embeddingProvider = provider2 ? "ready" : "pending";
80524
81127
  defaults.embeddingProviderName = provider2?.name;
80525
81128
  }
80526
81129
  defaults.embeddingLabel = formatEmbeddingLabel(
@@ -80548,6 +81151,8 @@ async function getHealthInfo(projectId) {
80548
81151
  defaults.searchDiagnostic = "Vector index ready";
80549
81152
  } else if (defaults.embeddingProvider === "ready" && !vectorActive) {
80550
81153
  defaults.searchDiagnostic = "Provider ready, no index yet";
81154
+ } else if (defaults.embeddingProvider === "pending") {
81155
+ defaults.searchDiagnostic = "BM25 ready; embeddings initialize on demand";
80551
81156
  } else if (defaults.embeddingProvider === "unavailable") {
80552
81157
  defaults.searchDiagnostic = "BM25 only (no provider)";
80553
81158
  } else {
@@ -80591,19 +81196,15 @@ async function getRecentMemories(limit = 8, projectId) {
80591
81196
  }
80592
81197
  async function searchMemories(query, limit = 10) {
80593
81198
  try {
80594
- const { searchObservations: searchObservations2, getDb: getDb2, hydrateIndex: hydrateIndex2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
81199
+ const { searchObservations: searchObservations2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
80595
81200
  const { getProjectDataDir: getProjectDataDir2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
80596
81201
  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));
81202
+ const { initObservations: initObservations2, prepareSearchIndex: prepareSearchIndex2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
80599
81203
  const proj = detectProject2(process.cwd());
80600
81204
  if (!proj) return [];
80601
81205
  const dataDir = await getProjectDataDir2(proj.id);
80602
- await initObservationStore2(dataDir);
80603
81206
  await initObservations2(dataDir);
80604
- await getDb2();
80605
- const allObs = await getStore().loadAll();
80606
- await hydrateIndex2(allObs);
81207
+ await prepareSearchIndex2();
80607
81208
  const results = await searchObservations2({ query, limit, projectId: proj.id });
80608
81209
  const typeIcons = {
80609
81210
  gotcha: "!",