memorix 1.6.0 → 1.6.2

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/cli/index.js +1888 -1122
  3. package/dist/cli/memcode.js +12 -1
  4. package/dist/index.js +641 -113
  5. package/dist/maintenance-runner.js +618 -90
  6. package/dist/media-audio-runner.js +618 -90
  7. package/dist/media-video-runner.js +618 -90
  8. package/dist/memcode-runtime/CHANGELOG.md +46 -0
  9. package/dist/sdk.js +674 -117
  10. package/dist/vector-backfill-runner.js +618 -90
  11. package/docs/AGENT_OPERATOR_PLAYBOOK.md +11 -3
  12. package/docs/API_REFERENCE.md +1 -1
  13. package/docs/DOCKER.md +5 -1
  14. package/docs/PERFORMANCE.md +17 -2
  15. package/docs/README.md +3 -1
  16. package/docs/SETUP.md +8 -2
  17. package/package.json +2 -1
  18. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  19. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  20. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  21. package/plugins/copilot/memorix/plugin.json +1 -1
  22. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  23. package/plugins/omp/memorix/package.json +1 -1
  24. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  25. package/plugins/pi/memorix/package.json +1 -1
  26. package/server.json +2 -2
  27. package/src/cli/commands/agent-integrations.ts +9 -3
  28. package/src/cli/commands/background.ts +34 -2
  29. package/src/cli/commands/doctor.ts +46 -15
  30. package/src/cli/commands/hook.ts +13 -1
  31. package/src/cli/commands/operator-shared.ts +6 -3
  32. package/src/cli/commands/serve-http.ts +72 -7
  33. package/src/cli/heap.ts +76 -0
  34. package/src/cli/invocation.ts +7 -4
  35. package/src/embedding/api-provider.ts +503 -27
  36. package/src/embedding/provider.ts +96 -9
  37. package/src/hooks/handler.ts +9 -18
  38. package/src/hooks/lifecycle.ts +174 -0
  39. package/src/memory/observations.ts +68 -18
  40. package/src/sdk.ts +12 -8
  41. package/src/store/orama-store.ts +21 -3
package/dist/sdk.js CHANGED
@@ -249,6 +249,14 @@ var init_bun_sqlite_compat = __esm({
249
249
  });
250
250
 
251
251
  // src/store/sqlite-db.ts
252
+ var sqlite_db_exports = {};
253
+ __export(sqlite_db_exports, {
254
+ closeAllDatabases: () => closeAllDatabases,
255
+ closeDatabase: () => closeDatabase,
256
+ getDatabase: () => getDatabase,
257
+ isSqliteAvailable: () => isSqliteAvailable,
258
+ loadBetterSqlite3: () => loadBetterSqlite3
259
+ });
252
260
  import "module";
253
261
  import path2 from "path";
254
262
  import fs from "fs";
@@ -354,6 +362,17 @@ function getDatabase(dataDir) {
354
362
  _dbCache.set(normalized, db2);
355
363
  return db2;
356
364
  }
365
+ function closeDatabase(dataDir) {
366
+ const normalized = path2.resolve(dataDir);
367
+ const db2 = _dbCache.get(normalized);
368
+ if (db2) {
369
+ try {
370
+ db2.close();
371
+ } catch {
372
+ }
373
+ _dbCache.delete(normalized);
374
+ }
375
+ }
357
376
  function closeAllDatabases() {
358
377
  for (const [key, db2] of _dbCache) {
359
378
  try {
@@ -363,6 +382,14 @@ function closeAllDatabases() {
363
382
  _dbCache.delete(key);
364
383
  }
365
384
  }
385
+ function isSqliteAvailable() {
386
+ try {
387
+ loadBetterSqlite3();
388
+ return true;
389
+ } catch {
390
+ return false;
391
+ }
392
+ }
366
393
  var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEMORY_OUTCOME_SIGNALS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
367
394
  var init_sqlite_db = __esm({
368
395
  "src/store/sqlite-db.ts"() {
@@ -1246,8 +1273,8 @@ async function acquireLock(lockPath) {
1246
1273
  const code = err instanceof Error && "code" in err ? err.code : void 0;
1247
1274
  if (code === "EEXIST" || code === "EPERM") {
1248
1275
  try {
1249
- const stat4 = await fs2.stat(lockPath);
1250
- if (Date.now() - stat4.mtimeMs > LOCK_STALE_MS) {
1276
+ const stat5 = await fs2.stat(lockPath);
1277
+ if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
1251
1278
  await fs2.unlink(lockPath).catch(() => {
1252
1279
  });
1253
1280
  continue;
@@ -3717,24 +3744,131 @@ var init_transformers_provider = __esm({
3717
3744
  // src/embedding/api-provider.ts
3718
3745
  var api_provider_exports = {};
3719
3746
  __export(api_provider_exports, {
3720
- APIEmbeddingProvider: () => APIEmbeddingProvider
3747
+ APIEmbeddingProvider: () => APIEmbeddingProvider,
3748
+ convertArrayCacheToJsonl: () => convertArrayCacheToJsonl,
3749
+ ensureDiskCacheLoaded: () => ensureDiskCacheLoaded,
3750
+ getApiEmbeddingCachePayloadBytesForTests: () => getApiEmbeddingCachePayloadBytesForTests,
3751
+ getApiEmbeddingDiskCacheLoadCountForTests: () => getApiEmbeddingDiskCacheLoadCountForTests,
3752
+ getApiEmbeddingJsonlLineCountCallsForTests: () => getApiEmbeddingJsonlLineCountCallsForTests,
3753
+ getApiEmbeddingMaxEntriesForDimensionsForTests: () => getApiEmbeddingMaxEntriesForDimensionsForTests,
3754
+ migrateJsonArrayCacheToJsonl: () => migrateJsonArrayCacheToJsonl,
3755
+ parseCacheJsonInWorker: () => parseCacheJsonInWorker,
3756
+ resetApiEmbeddingCacheForTests: () => resetApiEmbeddingCacheForTests,
3757
+ saveDiskCacheNow: () => saveDiskCacheNow,
3758
+ seedApiEmbeddingCacheForTests: () => seedApiEmbeddingCacheForTests
3721
3759
  });
3722
3760
  import { createHash as createHash2 } from "crypto";
3723
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3761
+ import { createReadStream, createWriteStream, constants as fsConstants } from "fs";
3762
+ import { access, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rename, stat, unlink } from "fs/promises";
3724
3763
  import { join as join3 } from "path";
3725
3764
  import { homedir as homedir7 } from "os";
3765
+ import { createInterface } from "readline";
3726
3766
  function cacheDir() {
3727
3767
  return process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
3728
3768
  }
3729
3769
  function cacheFile() {
3730
3770
  return join3(cacheDir(), ".embedding-api-cache.json");
3731
3771
  }
3772
+ function cacheJsonlFile() {
3773
+ return join3(cacheDir(), ".embedding-api-cache.jsonl");
3774
+ }
3732
3775
  function dimsCacheFile() {
3733
3776
  return join3(cacheDir(), ".embedding-dims-cache.json");
3734
3777
  }
3735
3778
  function cacheMetaFile() {
3736
3779
  return join3(cacheDir(), ".embedding-api-cache-meta.json");
3737
3780
  }
3781
+ function cacheStatFile() {
3782
+ return join3(cacheDir(), ".embedding-api-cache-stat.json");
3783
+ }
3784
+ function payloadBytes(vector) {
3785
+ return vector.length * BYTES_PER_DIM;
3786
+ }
3787
+ function maxCachePayloadBytes() {
3788
+ const raw = process.env.MEMORIX_EMBEDDING_CACHE_MAX_BYTES;
3789
+ if (raw) {
3790
+ const parsed = Number.parseInt(raw, 10);
3791
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
3792
+ }
3793
+ return MAX_CACHE_PAYLOAD_BYTES;
3794
+ }
3795
+ function maxEntriesForDimensions(dimensions) {
3796
+ const dim = Math.max(1, Math.floor(dimensions));
3797
+ const byBytes = Math.floor(maxCachePayloadBytes() / (dim * BYTES_PER_DIM));
3798
+ return Math.max(1, Math.min(MAX_CACHE_ENTRIES, byBytes));
3799
+ }
3800
+ function rememberDimensions(vector) {
3801
+ if (vector.length > 0 && (cacheDimensions === null || vector.length > cacheDimensions)) {
3802
+ cacheDimensions = vector.length;
3803
+ }
3804
+ }
3805
+ function evictOldest() {
3806
+ const firstKey = cache3.keys().next().value;
3807
+ if (firstKey === void 0) return;
3808
+ const old = cache3.get(firstKey);
3809
+ cache3.delete(firstKey);
3810
+ if (old) cachePayloadBytes -= payloadBytes(old);
3811
+ }
3812
+ function evictToFit(incomingBytes, incomingDims) {
3813
+ const limit2 = maxEntriesForDimensions(incomingDims);
3814
+ while (cache3.size > 0 && (cache3.size >= limit2 || cachePayloadBytes + incomingBytes > maxCachePayloadBytes())) {
3815
+ evictOldest();
3816
+ }
3817
+ }
3818
+ function isNumberArray(value) {
3819
+ return Array.isArray(value) && value.every((entry) => typeof entry === "number");
3820
+ }
3821
+ function parseCacheRow(raw) {
3822
+ if (Array.isArray(raw)) {
3823
+ const hash4 = raw[0];
3824
+ const vector = raw[1];
3825
+ if (typeof hash4 === "string" && isNumberArray(vector)) {
3826
+ return { hash: hash4, vector };
3827
+ }
3828
+ return null;
3829
+ }
3830
+ if (raw && typeof raw === "object") {
3831
+ const row = raw;
3832
+ if (typeof row.h === "string" && isNumberArray(row.v)) {
3833
+ return { hash: row.h, vector: row.v };
3834
+ }
3835
+ }
3836
+ return null;
3837
+ }
3838
+ function cacheInsert(hash4, value, dirty) {
3839
+ rememberDimensions(value);
3840
+ const incoming = payloadBytes(value);
3841
+ const existing = cache3.get(hash4);
3842
+ if (existing) {
3843
+ cachePayloadBytes -= payloadBytes(existing);
3844
+ cache3.delete(hash4);
3845
+ }
3846
+ evictToFit(incoming, value.length || cacheDimensions || DEFAULT_ASSUMED_DIMENSIONS);
3847
+ cache3.set(hash4, value);
3848
+ cachePayloadBytes += incoming;
3849
+ if (dirty) diskCacheDirty2 = true;
3850
+ }
3851
+ async function readCacheStat() {
3852
+ try {
3853
+ const data = JSON.parse(await readFile2(cacheStatFile(), "utf-8"));
3854
+ if (typeof data.entryCount === "number" && Number.isFinite(data.entryCount) && data.entryCount >= 0) {
3855
+ return {
3856
+ version: 1,
3857
+ entryCount: Math.floor(data.entryCount),
3858
+ dimensions: isValidDimension(data.dimensions) ? data.dimensions : null
3859
+ };
3860
+ }
3861
+ } catch {
3862
+ }
3863
+ return null;
3864
+ }
3865
+ async function writeCacheStat(stat5) {
3866
+ try {
3867
+ await mkdir2(cacheDir(), { recursive: true });
3868
+ await writeFile2(cacheStatFile(), JSON.stringify(stat5));
3869
+ } catch {
3870
+ }
3871
+ }
3738
3872
  function normalizeText(text2) {
3739
3873
  return text2.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3740
3874
  }
@@ -3820,29 +3954,300 @@ function toProviderInput(input, baseUrl) {
3820
3954
  }
3821
3955
  return { type: input.modality, url: input.url };
3822
3956
  }
3957
+ function parseCacheJsonInWorker(filePath) {
3958
+ return new Promise((resolve2, reject) => {
3959
+ void import("worker_threads").then(({ Worker }) => {
3960
+ const worker = new Worker(
3961
+ `
3962
+ const { parentPort, workerData } = require('node:worker_threads');
3963
+ const { readFileSync } = require('node:fs');
3964
+ try {
3965
+ const raw = readFileSync(workerData.filePath, 'utf8');
3966
+ parentPort.postMessage({ ok: true, entries: JSON.parse(raw) });
3967
+ } catch (err) {
3968
+ parentPort.postMessage({
3969
+ ok: false,
3970
+ error: err instanceof Error ? err.message : String(err),
3971
+ });
3972
+ }
3973
+ `,
3974
+ { eval: true, workerData: { filePath } }
3975
+ );
3976
+ worker.once("message", (msg) => {
3977
+ void worker.terminate();
3978
+ if (msg?.ok && Array.isArray(msg.entries)) {
3979
+ resolve2(msg.entries);
3980
+ return;
3981
+ }
3982
+ reject(new Error(msg?.error || "embedding cache worker parse failed"));
3983
+ });
3984
+ worker.once("error", reject);
3985
+ }).catch(reject);
3986
+ });
3987
+ }
3988
+ function findJsonValueEnd(source) {
3989
+ let i2 = 0;
3990
+ while (i2 < source.length && /\s/.test(source[i2])) i2 += 1;
3991
+ if (i2 >= source.length) return -1;
3992
+ const start = source[i2];
3993
+ if (start === '"') {
3994
+ i2 += 1;
3995
+ while (i2 < source.length) {
3996
+ if (source[i2] === "\\") {
3997
+ i2 += 2;
3998
+ continue;
3999
+ }
4000
+ if (source[i2] === '"') return i2 + 1;
4001
+ i2 += 1;
4002
+ }
4003
+ return -1;
4004
+ }
4005
+ if (start === "{" || start === "[") {
4006
+ const close = start === "{" ? "}" : "]";
4007
+ let depth = 0;
4008
+ let inString = false;
4009
+ for (; i2 < source.length; i2 += 1) {
4010
+ const ch = source[i2];
4011
+ if (inString) {
4012
+ if (ch === "\\") {
4013
+ i2 += 1;
4014
+ continue;
4015
+ }
4016
+ if (ch === '"') inString = false;
4017
+ continue;
4018
+ }
4019
+ if (ch === '"') {
4020
+ inString = true;
4021
+ continue;
4022
+ }
4023
+ if (ch === start) depth += 1;
4024
+ else if (ch === close) {
4025
+ depth -= 1;
4026
+ if (depth === 0) return i2 + 1;
4027
+ }
4028
+ }
4029
+ return -1;
4030
+ }
4031
+ const rest = source.slice(i2);
4032
+ const match2 = rest.match(/^(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/);
4033
+ return match2 ? i2 + match2[0].length : -1;
4034
+ }
4035
+ async function streamJsonArrayToJsonl(filePath, tmpPath) {
4036
+ const input = createReadStream(filePath, { encoding: "utf8" });
4037
+ const out = createWriteStream(tmpPath);
4038
+ let buf = "";
4039
+ let started = false;
4040
+ let count2 = 0;
4041
+ const writeLine = async (line) => {
4042
+ if (!out.write(line)) {
4043
+ await new Promise((resolve2, reject) => {
4044
+ out.once("drain", resolve2);
4045
+ out.once("error", reject);
4046
+ });
4047
+ }
4048
+ };
4049
+ try {
4050
+ for await (const chunk of input) {
4051
+ buf += chunk;
4052
+ if (!started) {
4053
+ const idx = buf.search(/\S/);
4054
+ if (idx < 0) continue;
4055
+ if (buf[idx] !== "[") throw new Error("embedding cache is not a JSON array");
4056
+ buf = buf.slice(idx + 1);
4057
+ started = true;
4058
+ }
4059
+ while (true) {
4060
+ buf = buf.replace(/^\s*,?\s*/, "");
4061
+ if (!buf) break;
4062
+ if (buf[0] === "]") {
4063
+ buf = "";
4064
+ break;
4065
+ }
4066
+ const end = findJsonValueEnd(buf);
4067
+ if (end < 0) break;
4068
+ const value = JSON.parse(buf.slice(0, end));
4069
+ buf = buf.slice(end);
4070
+ const row = parseCacheRow(value);
4071
+ if (row) {
4072
+ await writeLine(`${JSON.stringify({ h: row.hash, v: row.vector })}
4073
+ `);
4074
+ count2 += 1;
4075
+ if (count2 % 50 === 0) {
4076
+ await new Promise((resolve2) => setImmediate(resolve2));
4077
+ }
4078
+ }
4079
+ }
4080
+ if (buf.length > MAX_MIGRATION_VALUE_BYTES) {
4081
+ throw new Error("embedding cache entry too large to migrate safely");
4082
+ }
4083
+ }
4084
+ } finally {
4085
+ await new Promise((resolve2, reject) => {
4086
+ out.end(() => resolve2());
4087
+ out.once("error", reject);
4088
+ });
4089
+ }
4090
+ return count2;
4091
+ }
4092
+ async function migrateJsonArrayCacheToJsonl(filePath, jsonlPath) {
4093
+ const tmpPath = `${jsonlPath}.tmp`;
4094
+ try {
4095
+ const count2 = await streamJsonArrayToJsonl(filePath, tmpPath);
4096
+ await rename(tmpPath, jsonlPath);
4097
+ diskCacheEntryCount = count2;
4098
+ await writeCacheStat({
4099
+ version: 1,
4100
+ entryCount: count2,
4101
+ dimensions: cacheDimensions
4102
+ });
4103
+ return count2;
4104
+ } catch (err) {
4105
+ await unlink(tmpPath).catch(() => void 0);
4106
+ throw err;
4107
+ }
4108
+ }
4109
+ function convertArrayCacheToJsonl(filePath, jsonlPath) {
4110
+ return migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
4111
+ }
4112
+ async function loadCacheJsonl(filePath) {
4113
+ const rl = createInterface({
4114
+ input: createReadStream(filePath, { encoding: "utf8" }),
4115
+ crlfDelay: Infinity
4116
+ });
4117
+ let loaded = 0;
4118
+ let onDiskCount = 0;
4119
+ for await (const line of rl) {
4120
+ const trimmed = line.trim();
4121
+ if (!trimmed) continue;
4122
+ try {
4123
+ const row = parseCacheRow(JSON.parse(trimmed));
4124
+ if (!row) continue;
4125
+ onDiskCount += 1;
4126
+ cacheInsert(row.hash, row.vector, false);
4127
+ loaded = cache3.size;
4128
+ } catch {
4129
+ }
4130
+ if (onDiskCount % 250 === 0) {
4131
+ await new Promise((resolve2) => setImmediate(resolve2));
4132
+ }
4133
+ }
4134
+ diskCacheEntryCount = onDiskCount;
4135
+ await writeCacheStat({
4136
+ version: 1,
4137
+ entryCount: onDiskCount,
4138
+ dimensions: cacheDimensions
4139
+ });
4140
+ return loaded;
4141
+ }
4142
+ async function countJsonlLines(filePath) {
4143
+ jsonlLineCountCalls += 1;
4144
+ try {
4145
+ const rl = createInterface({
4146
+ input: createReadStream(filePath, { encoding: "utf8" }),
4147
+ crlfDelay: Infinity
4148
+ });
4149
+ let count2 = 0;
4150
+ for await (const line of rl) {
4151
+ if (line.trim()) count2 += 1;
4152
+ }
4153
+ return count2;
4154
+ } catch {
4155
+ return 0;
4156
+ }
4157
+ }
4158
+ async function resolveOnDiskCount(jsonlPath) {
4159
+ if (diskCacheEntryCount > 0) return diskCacheEntryCount;
4160
+ const sidecar = await readCacheStat();
4161
+ if (sidecar) {
4162
+ diskCacheEntryCount = sidecar.entryCount;
4163
+ if (sidecar.dimensions && cacheDimensions === null) cacheDimensions = sidecar.dimensions;
4164
+ return sidecar.entryCount;
4165
+ }
4166
+ try {
4167
+ await access(jsonlPath, fsConstants.R_OK);
4168
+ } catch {
4169
+ return 0;
4170
+ }
4171
+ const counted = await countJsonlLines(jsonlPath);
4172
+ diskCacheEntryCount = counted;
4173
+ await writeCacheStat({
4174
+ version: 1,
4175
+ entryCount: counted,
4176
+ dimensions: cacheDimensions
4177
+ });
4178
+ return counted;
4179
+ }
3823
4180
  async function loadDiskCache2() {
3824
- if (diskCacheLoaded) return;
4181
+ if (diskCacheLoaded) return cache3.size;
4182
+ let loaded = 0;
3825
4183
  try {
3826
- const raw = await readFile2(cacheFile(), "utf-8");
3827
- const entries = JSON.parse(raw);
3828
- for (const [k, v] of entries) cache3.set(k, v);
3829
- console.error(`[memorix] Loaded ${entries.length} cached API embeddings from disk`);
4184
+ const jsonlPath = cacheJsonlFile();
4185
+ let hasJsonl = false;
4186
+ try {
4187
+ await access(jsonlPath, fsConstants.R_OK);
4188
+ hasJsonl = true;
4189
+ } catch {
4190
+ hasJsonl = false;
4191
+ }
4192
+ if (hasJsonl) {
4193
+ loaded = await loadCacheJsonl(jsonlPath);
4194
+ } else {
4195
+ const filePath = cacheFile();
4196
+ await stat(filePath);
4197
+ await migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
4198
+ loaded = await loadCacheJsonl(jsonlPath);
4199
+ }
4200
+ diskCacheLoadCount += 1;
4201
+ console.error(`[memorix] Loaded ${loaded} cached API embeddings from disk`);
3830
4202
  } catch {
3831
4203
  }
3832
4204
  diskCacheLoaded = true;
4205
+ return loaded;
3833
4206
  }
3834
4207
  function startDiskCacheLoad() {
3835
4208
  if (diskCacheLoaded || diskCacheLoadPromise) return;
3836
- diskCacheLoadPromise = loadDiskCache2().catch(() => {
4209
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
3837
4210
  });
3838
4211
  }
3839
4212
  async function ensureDiskCacheLoaded() {
3840
- if (diskCacheLoaded) return;
3841
- if (diskCacheLoadPromise) {
3842
- await diskCacheLoadPromise;
3843
- return;
3844
- }
3845
- await loadDiskCache2();
4213
+ if (diskCacheLoaded) return cache3.size;
4214
+ if (!diskCacheLoadPromise) {
4215
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
4216
+ });
4217
+ }
4218
+ await diskCacheLoadPromise;
4219
+ return cache3.size;
4220
+ }
4221
+ function getApiEmbeddingDiskCacheLoadCountForTests() {
4222
+ return diskCacheLoadCount;
4223
+ }
4224
+ function resetApiEmbeddingCacheForTests() {
4225
+ cache3.clear();
4226
+ cachePayloadBytes = 0;
4227
+ cacheDimensions = null;
4228
+ diskCacheEntryCount = 0;
4229
+ diskCacheLoaded = false;
4230
+ diskCacheLoadPromise = null;
4231
+ diskCacheLoadCount = 0;
4232
+ diskCacheDirty2 = false;
4233
+ jsonlLineCountCalls = 0;
4234
+ }
4235
+ function seedApiEmbeddingCacheForTests(entries) {
4236
+ cache3.clear();
4237
+ cachePayloadBytes = 0;
4238
+ cacheDimensions = null;
4239
+ for (const [key, value] of entries) cacheInsert(key, value, true);
4240
+ diskCacheLoaded = true;
4241
+ diskCacheDirty2 = true;
4242
+ }
4243
+ function getApiEmbeddingCachePayloadBytesForTests() {
4244
+ return cachePayloadBytes;
4245
+ }
4246
+ function getApiEmbeddingJsonlLineCountCallsForTests() {
4247
+ return jsonlLineCountCalls;
4248
+ }
4249
+ function getApiEmbeddingMaxEntriesForDimensionsForTests(dimensions) {
4250
+ return maxEntriesForDimensions(dimensions);
3846
4251
  }
3847
4252
  function dimsCacheKey(config) {
3848
4253
  return [
@@ -3958,8 +4363,46 @@ async function saveDiskCacheNow() {
3958
4363
  if (!diskCacheDirty2) return;
3959
4364
  try {
3960
4365
  await mkdir2(cacheDir(), { recursive: true });
3961
- const entries = Array.from(cache3.entries());
3962
- await writeFile2(cacheFile(), JSON.stringify(entries));
4366
+ const jsonlPath = cacheJsonlFile();
4367
+ const existing = await resolveOnDiskCount(jsonlPath);
4368
+ if (existing > 0 && cache3.size < Math.ceil(existing * 0.5)) {
4369
+ console.error(
4370
+ `[memorix] Refusing to overwrite embedding cache (${existing} on disk vs ${cache3.size} in memory)`
4371
+ );
4372
+ diskCacheDirty2 = false;
4373
+ return;
4374
+ }
4375
+ const tmpPath = `${jsonlPath}.tmp`;
4376
+ await new Promise((resolve2, reject) => {
4377
+ const out = createWriteStream(tmpPath);
4378
+ const entries = cache3.entries();
4379
+ const writeBatch = () => {
4380
+ for (let i2 = 0; i2 < 50; i2++) {
4381
+ const next = entries.next();
4382
+ if (next.done) {
4383
+ out.end();
4384
+ return;
4385
+ }
4386
+ const [hash4, vector] = next.value;
4387
+ if (!out.write(`${JSON.stringify({ h: hash4, v: vector })}
4388
+ `)) {
4389
+ out.once("drain", writeBatch);
4390
+ return;
4391
+ }
4392
+ }
4393
+ setImmediate(writeBatch);
4394
+ };
4395
+ out.on("finish", resolve2);
4396
+ out.on("error", reject);
4397
+ writeBatch();
4398
+ });
4399
+ await rename(tmpPath, jsonlPath);
4400
+ diskCacheEntryCount = cache3.size;
4401
+ await writeCacheStat({
4402
+ version: 1,
4403
+ entryCount: cache3.size,
4404
+ dimensions: cacheDimensions
4405
+ });
3963
4406
  diskCacheDirty2 = false;
3964
4407
  } catch {
3965
4408
  }
@@ -3973,12 +4416,7 @@ function scheduleDiskSave() {
3973
4416
  }, DISK_SAVE_DEBOUNCE_MS);
3974
4417
  }
3975
4418
  function cacheSet(hash4, value) {
3976
- if (cache3.size >= MAX_CACHE_SIZE3) {
3977
- const firstKey = cache3.keys().next().value;
3978
- if (firstKey !== void 0) cache3.delete(firstKey);
3979
- }
3980
- cache3.set(hash4, value);
3981
- diskCacheDirty2 = true;
4419
+ cacheInsert(hash4, value, true);
3982
4420
  }
3983
4421
  function resolveEnvEmbeddingApiKey() {
3984
4422
  return process.env.MEMORIX_EMBEDDING_API_KEY;
@@ -4097,18 +4535,27 @@ async function waitForRetry(waitMs, signal) {
4097
4535
  abortSignal.addEventListener("abort", onAbort, { once: true });
4098
4536
  });
4099
4537
  }
4100
- var 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_RETRIES2, BASE_DELAY_MS, EmbeddingAPIError, APIEmbeddingProvider;
4538
+ var cache3, MAX_CACHE_ENTRIES, MAX_CACHE_PAYLOAD_BYTES, BYTES_PER_DIM, DEFAULT_ASSUMED_DIMENSIONS, MAX_MIGRATION_VALUE_BYTES, cachePayloadBytes, cacheDimensions, diskCacheEntryCount, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, diskCacheLoadCount, jsonlLineCountCalls, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, EmbeddingAPIError, APIEmbeddingProvider;
4101
4539
  var init_api_provider = __esm({
4102
4540
  "src/embedding/api-provider.ts"() {
4103
4541
  "use strict";
4104
4542
  init_esm_shims();
4105
4543
  init_provider();
4106
4544
  cache3 = /* @__PURE__ */ new Map();
4107
- MAX_CACHE_SIZE3 = 1e4;
4545
+ MAX_CACHE_ENTRIES = 2e5;
4546
+ MAX_CACHE_PAYLOAD_BYTES = 256 * 1024 * 1024;
4547
+ BYTES_PER_DIM = 8;
4548
+ DEFAULT_ASSUMED_DIMENSIONS = 4096;
4549
+ MAX_MIGRATION_VALUE_BYTES = 16 * 1024 * 1024;
4550
+ cachePayloadBytes = 0;
4551
+ cacheDimensions = null;
4552
+ diskCacheEntryCount = 0;
4108
4553
  diskCacheDirty2 = false;
4109
4554
  diskSaveTimer = null;
4110
4555
  diskCacheLoaded = false;
4111
4556
  diskCacheLoadPromise = null;
4557
+ diskCacheLoadCount = 0;
4558
+ jsonlLineCountCalls = 0;
4112
4559
  MAX_INPUT_CHARS = 32e3;
4113
4560
  MAX_CONCURRENCY = 4;
4114
4561
  DISK_SAVE_DEBOUNCE_MS = 5e3;
@@ -4166,7 +4613,9 @@ var init_api_provider = __esm({
4166
4613
  if (config.requestedDimensions) {
4167
4614
  console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
4168
4615
  }
4169
- startDiskCacheLoad();
4616
+ if (allowNetworkProbe) {
4617
+ startDiskCacheLoad();
4618
+ }
4170
4619
  return new _APIEmbeddingProvider(config, probeDimensions);
4171
4620
  }
4172
4621
  static resolveConfig() {
@@ -4442,6 +4891,7 @@ __export(provider_exports, {
4442
4891
  MAX_INLINE_EMBEDDING_BYTES: () => MAX_INLINE_EMBEDDING_BYTES,
4443
4892
  UnsupportedEmbeddingModalityError: () => UnsupportedEmbeddingModalityError,
4444
4893
  getEmbeddingProvider: () => getEmbeddingProvider,
4894
+ getEmbeddingRuntimeHealth: () => getEmbeddingRuntimeHealth,
4445
4895
  isEmbeddingExplicitlyDisabled: () => isEmbeddingExplicitlyDisabled,
4446
4896
  isVectorSearchAvailable: () => isVectorSearchAvailable,
4447
4897
  resetProvider: () => resetProvider,
@@ -4503,7 +4953,12 @@ function warnOnce(message) {
4503
4953
  lastEmbeddingWarningAt = now3;
4504
4954
  console.error(message);
4505
4955
  }
4956
+ function embeddingModeToken() {
4957
+ return process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim() ?? "";
4958
+ }
4506
4959
  function getEmbeddingMode2() {
4960
+ const token = embeddingModeToken();
4961
+ if (cachedEmbeddingMode && cachedEmbeddingModeToken === token) return cachedEmbeddingMode;
4507
4962
  try {
4508
4963
  let cfg;
4509
4964
  try {
@@ -4512,10 +4967,17 @@ function getEmbeddingMode2() {
4512
4967
  cfg = (init_config(), __toCommonJS(config_exports));
4513
4968
  }
4514
4969
  const { getEmbeddingMode: cfgMode } = cfg;
4515
- return cfgMode();
4970
+ cachedEmbeddingMode = cfgMode();
4971
+ cachedEmbeddingModeToken = token;
4972
+ return cachedEmbeddingMode;
4516
4973
  } catch {
4517
- const env2 = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
4518
- if (env2 === "fastembed" || env2 === "transformers" || env2 === "api" || env2 === "auto") return env2;
4974
+ if (token === "fastembed" || token === "transformers" || token === "api" || token === "auto") {
4975
+ cachedEmbeddingMode = token;
4976
+ cachedEmbeddingModeToken = token;
4977
+ return token;
4978
+ }
4979
+ cachedEmbeddingMode = "off";
4980
+ cachedEmbeddingModeToken = token;
4519
4981
  return "off";
4520
4982
  }
4521
4983
  }
@@ -4549,13 +5011,36 @@ function isTemporaryEmbeddingFailure(error) {
4549
5011
  if (!(error instanceof Error)) return false;
4550
5012
  return /embedding api error \(401\)/i.test(error.message) || /invalid_api_key/i.test(error.message) || /incorrect api key/i.test(error.message) || /unauthorized/i.test(error.message) || /embedding api timeout/i.test(error.message) || /embedding api error \((402|429|5\d\d)\)/i.test(error.message) || /quota exceeded/i.test(error.message) || /account balance/i.test(error.message) || /fetch failed/i.test(error.message) || /econnreset/i.test(error.message) || /econnrefused/i.test(error.message) || /temporarily unavailable/i.test(error.message);
4551
5013
  }
5014
+ function classifyEmbeddingFailure(error) {
5015
+ const message = error instanceof Error ? error.message : String(error);
5016
+ if (/embedding api error \(401\)|invalid_api_key|incorrect api key|unauthorized/i.test(message)) {
5017
+ return "authorization";
5018
+ }
5019
+ if (/embedding api error \(402\)|quota exceeded|account balance/i.test(message)) {
5020
+ return "billing";
5021
+ }
5022
+ if (/embedding api timeout|abort/i.test(message)) {
5023
+ return "timeout";
5024
+ }
5025
+ return "upstream";
5026
+ }
4552
5027
  function markTemporaryFailure(reason) {
4553
5028
  provider = null;
4554
5029
  initPromise = null;
4555
5030
  lastInitWasTemporaryFailure = true;
4556
5031
  lastFailureTimestamp = Date.now();
5032
+ lastEmbeddingFailure = {
5033
+ kind: classifyEmbeddingFailure(reason),
5034
+ occurredAt: lastFailureTimestamp
5035
+ };
4557
5036
  const message = reason instanceof Error ? reason.message : String(reason);
4558
- warnOnce(`[memorix] Embedding provider temporarily unavailable at runtime: ${message}`);
5037
+ warnOnce(`[memorix] Embedding provider unavailable at runtime: ${message}`);
5038
+ }
5039
+ function markProviderReady(candidate) {
5040
+ provider = wrapProvider(candidate);
5041
+ lastInitWasTemporaryFailure = false;
5042
+ lastEmbeddingFailure = null;
5043
+ return provider;
4559
5044
  }
4560
5045
  async function createFastEmbedProvider() {
4561
5046
  try {
@@ -4671,35 +5156,35 @@ async function getEmbeddingProvider(options = {}) {
4671
5156
  if (mode === "fastembed") {
4672
5157
  const initialized = await createFastEmbedProvider();
4673
5158
  if (!initialized) return null;
4674
- provider = wrapProvider(initialized);
5159
+ provider = markProviderReady(initialized);
4675
5160
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4676
5161
  return provider;
4677
5162
  }
4678
5163
  if (mode === "transformers") {
4679
5164
  const initialized = await createTransformersProvider();
4680
5165
  if (!initialized) return null;
4681
- provider = wrapProvider(initialized);
5166
+ provider = markProviderReady(initialized);
4682
5167
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4683
5168
  return provider;
4684
5169
  }
4685
5170
  if (mode === "api") {
4686
5171
  const initialized = await createAPIProvider(options);
4687
5172
  if (!initialized) return null;
4688
- provider = wrapProvider(initialized);
5173
+ provider = markProviderReady(initialized);
4689
5174
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4690
5175
  return provider;
4691
5176
  }
4692
5177
  if (hasAPIEmbeddingConfig()) {
4693
5178
  const initialized = await createAPIProvider(options);
4694
5179
  if (initialized) {
4695
- provider = wrapProvider(initialized);
5180
+ provider = markProviderReady(initialized);
4696
5181
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4697
5182
  return provider;
4698
5183
  }
4699
5184
  }
4700
5185
  const localFallback = await createLocalFallbackProvider();
4701
5186
  if (localFallback) {
4702
- provider = wrapProvider(localFallback);
5187
+ provider = markProviderReady(localFallback);
4703
5188
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4704
5189
  return provider;
4705
5190
  }
@@ -4721,18 +5206,42 @@ async function isVectorSearchAvailable() {
4721
5206
  const p = await getEmbeddingProvider();
4722
5207
  return p !== null;
4723
5208
  }
5209
+ function getEmbeddingRuntimeHealth() {
5210
+ const mode = getEmbeddingMode2();
5211
+ if (mode === "off") return { status: "disabled" };
5212
+ if (lastInitWasTemporaryFailure && lastEmbeddingFailure) {
5213
+ return {
5214
+ status: "degraded",
5215
+ failure: {
5216
+ kind: lastEmbeddingFailure.kind,
5217
+ occurredAt: new Date(lastEmbeddingFailure.occurredAt).toISOString(),
5218
+ retryAfterMs: Math.max(0, RETRY_COOLDOWN_MS - (Date.now() - lastEmbeddingFailure.occurredAt))
5219
+ }
5220
+ };
5221
+ }
5222
+ if (provider) {
5223
+ return {
5224
+ status: "ready",
5225
+ provider: { name: provider.name, dimensions: provider.dimensions }
5226
+ };
5227
+ }
5228
+ return { status: "uninitialized" };
5229
+ }
4724
5230
  function isEmbeddingExplicitlyDisabled() {
4725
5231
  return getEmbeddingMode2() === "off";
4726
5232
  }
4727
5233
  function resetProvider() {
4728
5234
  provider = null;
4729
5235
  initPromise = null;
5236
+ cachedEmbeddingMode = null;
5237
+ cachedEmbeddingModeToken = null;
4730
5238
  lastInitWasTemporaryFailure = false;
4731
5239
  lastFailureTimestamp = 0;
5240
+ lastEmbeddingFailure = null;
4732
5241
  lastEmbeddingWarning = "";
4733
5242
  lastEmbeddingWarningAt = 0;
4734
5243
  }
4735
- var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
5244
+ var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, cachedEmbeddingMode, cachedEmbeddingModeToken, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
4736
5245
  var init_provider = __esm({
4737
5246
  "src/embedding/provider.ts"() {
4738
5247
  "use strict";
@@ -4756,7 +5265,10 @@ var init_provider = __esm({
4756
5265
  MAX_INLINE_EMBEDDING_BYTES = 5 * 1024 * 1024;
4757
5266
  provider = null;
4758
5267
  initPromise = null;
5268
+ cachedEmbeddingMode = null;
5269
+ cachedEmbeddingModeToken = null;
4759
5270
  lastInitWasTemporaryFailure = false;
5271
+ lastEmbeddingFailure = null;
4760
5272
  lastEmbeddingWarning = "";
4761
5273
  lastEmbeddingWarningAt = 0;
4762
5274
  WARNING_COOLDOWN_MS = 3e4;
@@ -6512,6 +7024,9 @@ async function attachCachedVectors(database, candidates) {
6512
7024
  await update(database, candidates[index].id, { ...existing, embedding: vector });
6513
7025
  } catch {
6514
7026
  }
7027
+ if (index % 50 === 0) {
7028
+ await new Promise((resolve2) => setImmediate(resolve2));
7029
+ }
6515
7030
  }
6516
7031
  }
6517
7032
  async function hydrateIndex(observations2, options = {}) {
@@ -6562,17 +7077,24 @@ async function hydrateIndex(observations2, options = {}) {
6562
7077
  await insert2(database, doc);
6563
7078
  rememberObservationDoc(doc);
6564
7079
  inserted++;
7080
+ if (inserted % 50 === 0) {
7081
+ await new Promise((resolve2) => setImmediate(resolve2));
7082
+ }
7083
+ if (inserted % 200 === 0) {
7084
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
7085
+ }
6565
7086
  } catch {
6566
7087
  }
6567
7088
  }
6568
- deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
7089
+ deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6569
7090
  }) : null;
6570
7091
  return inserted;
6571
7092
  }
6572
- async function hydrateIndexForStartup(observations2) {
7093
+ async function hydrateIndexForStartup(observations2, options = {}) {
6573
7094
  return hydrateIndex(observations2, {
6574
7095
  allowNetworkProbe: false,
6575
- deferCachedVectors: true
7096
+ deferCachedVectors: true,
7097
+ skipCachedVectors: options.skipCachedVectors
6576
7098
  });
6577
7099
  }
6578
7100
  function getDeferredCachedVectorHydration() {
@@ -9895,7 +10417,7 @@ __export(asset_store_exports, {
9895
10417
  removeMediaAsset: () => removeMediaAsset
9896
10418
  });
9897
10419
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
9898
- import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
10420
+ import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
9899
10421
  import path13 from "path";
9900
10422
  function mediaRoot(dataDir) {
9901
10423
  return path13.resolve(dataDir, "media");
@@ -9985,7 +10507,7 @@ async function persistFile(source, destination) {
9985
10507
  const temporary = `${destination}.${randomUUID5()}.tmp`;
9986
10508
  try {
9987
10509
  await copyFile(source, temporary);
9988
- await rename(temporary, destination);
10510
+ await rename2(temporary, destination);
9989
10511
  return true;
9990
10512
  } catch (error) {
9991
10513
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -10001,7 +10523,7 @@ async function persistBuffer(bytes, destination) {
10001
10523
  const temporary = `${destination}.${randomUUID5()}.tmp`;
10002
10524
  try {
10003
10525
  await writeFile3(temporary, bytes, { flag: "wx" });
10004
- await rename(temporary, destination);
10526
+ await rename2(temporary, destination);
10005
10527
  return true;
10006
10528
  } catch (error) {
10007
10529
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -10041,11 +10563,11 @@ async function importKnownMedia(input) {
10041
10563
  async function importMediaFile(input) {
10042
10564
  const maxBytes = input.maxBytes ?? DEFAULT_MAX_MEDIA_BYTES;
10043
10565
  const source = path13.resolve(input.filePath);
10044
- const stat4 = await lstat(source);
10045
- if (stat4.isSymbolicLink()) throw new Error("Media import does not follow symbolic links");
10046
- if (!stat4.isFile()) throw new Error("Media import accepts regular files only");
10047
- if (stat4.size <= 0) throw new Error("Media import rejects empty files");
10048
- if (stat4.size > maxBytes) throw new Error(`Media file is too large (${stat4.size} bytes; limit ${maxBytes})`);
10566
+ const stat5 = await lstat(source);
10567
+ if (stat5.isSymbolicLink()) throw new Error("Media import does not follow symbolic links");
10568
+ if (!stat5.isFile()) throw new Error("Media import accepts regular files only");
10569
+ if (stat5.size <= 0) throw new Error("Media import rejects empty files");
10570
+ if (stat5.size > maxBytes) throw new Error(`Media file is too large (${stat5.size} bytes; limit ${maxBytes})`);
10049
10571
  const header = await readHeader(source);
10050
10572
  const detected = detectMedia(header);
10051
10573
  const sha256 = await sha256File(source);
@@ -10053,7 +10575,7 @@ async function importMediaFile(input) {
10053
10575
  dataDir: input.dataDir,
10054
10576
  projectId: input.projectId,
10055
10577
  sha256,
10056
- bytes: stat4.size,
10578
+ bytes: stat5.size,
10057
10579
  detected,
10058
10580
  persist: (destination) => persistFile(source, destination),
10059
10581
  sourceKind: input.sourceKind ?? "import",
@@ -10100,13 +10622,13 @@ async function removeMediaAsset(input) {
10100
10622
  const assetPath = getMediaAssetPath(input.dataDir, asset);
10101
10623
  const stagedPath = deletionStagingPath(mediaRoot(input.dataDir), asset.id);
10102
10624
  await mkdir3(path13.dirname(stagedPath), { recursive: true });
10103
- await rename(assetPath, stagedPath);
10625
+ await rename2(assetPath, stagedPath);
10104
10626
  let detachedLinks;
10105
10627
  try {
10106
10628
  ({ detachedLinks } = store.removeAssetMetadata(input.projectId, asset.id, { force: input.force }));
10107
10629
  } catch (error) {
10108
10630
  try {
10109
- await rename(stagedPath, assetPath);
10631
+ await rename2(stagedPath, assetPath);
10110
10632
  } catch (restoreError) {
10111
10633
  const original = error instanceof Error ? error.message : String(error);
10112
10634
  const restore = restoreError instanceof Error ? restoreError.message : String(restoreError);
@@ -10142,7 +10664,7 @@ async function cleanupMediaTrash(dataDir) {
10142
10664
  } catch (error) {
10143
10665
  if (error?.code === "ENOENT") continue;
10144
10666
  try {
10145
- pendingBytes += (await stat(candidate)).size;
10667
+ pendingBytes += (await stat2(candidate)).size;
10146
10668
  } catch {
10147
10669
  }
10148
10670
  pendingFiles += 1;
@@ -61224,9 +61746,9 @@ var require_node_domexception = __commonJS({
61224
61746
  });
61225
61747
 
61226
61748
  // node_modules/fetch-blob/from.js
61227
- import { statSync as statSync2, createReadStream, promises as fs10 } from "fs";
61749
+ import { statSync as statSync2, createReadStream as createReadStream2, promises as fs10 } from "fs";
61228
61750
  import { basename } from "path";
61229
- var import_node_domexception, stat2, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
61751
+ var import_node_domexception, stat3, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
61230
61752
  var init_from = __esm({
61231
61753
  "node_modules/fetch-blob/from.js"() {
61232
61754
  "use strict";
@@ -61234,23 +61756,23 @@ var init_from = __esm({
61234
61756
  import_node_domexception = __toESM(require_node_domexception(), 1);
61235
61757
  init_file();
61236
61758
  init_fetch_blob();
61237
- ({ stat: stat2 } = fs10);
61759
+ ({ stat: stat3 } = fs10);
61238
61760
  blobFromSync = (path34, type) => fromBlob(statSync2(path34), path34, type);
61239
- blobFrom = (path34, type) => stat2(path34).then((stat4) => fromBlob(stat4, path34, type));
61240
- fileFrom = (path34, type) => stat2(path34).then((stat4) => fromFile(stat4, path34, type));
61761
+ blobFrom = (path34, type) => stat3(path34).then((stat5) => fromBlob(stat5, path34, type));
61762
+ fileFrom = (path34, type) => stat3(path34).then((stat5) => fromFile(stat5, path34, type));
61241
61763
  fileFromSync = (path34, type) => fromFile(statSync2(path34), path34, type);
61242
- fromBlob = (stat4, path34, type = "") => new fetch_blob_default([new BlobDataItem({
61764
+ fromBlob = (stat5, path34, type = "") => new fetch_blob_default([new BlobDataItem({
61243
61765
  path: path34,
61244
- size: stat4.size,
61245
- lastModified: stat4.mtimeMs,
61766
+ size: stat5.size,
61767
+ lastModified: stat5.mtimeMs,
61246
61768
  start: 0
61247
61769
  })], { type });
61248
- fromFile = (stat4, path34, type = "") => new file_default([new BlobDataItem({
61770
+ fromFile = (stat5, path34, type = "") => new file_default([new BlobDataItem({
61249
61771
  path: path34,
61250
- size: stat4.size,
61251
- lastModified: stat4.mtimeMs,
61772
+ size: stat5.size,
61773
+ lastModified: stat5.mtimeMs,
61252
61774
  start: 0
61253
- })], basename(path34), { type, lastModified: stat4.mtimeMs });
61775
+ })], basename(path34), { type, lastModified: stat5.mtimeMs });
61254
61776
  BlobDataItem = class _BlobDataItem {
61255
61777
  #path;
61256
61778
  #start;
@@ -61273,11 +61795,11 @@ var init_from = __esm({
61273
61795
  });
61274
61796
  }
61275
61797
  async *stream() {
61276
- const { mtimeMs } = await stat2(this.#path);
61798
+ const { mtimeMs } = await stat3(this.#path);
61277
61799
  if (mtimeMs > this.lastModified) {
61278
61800
  throw new import_node_domexception.default("The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.", "NotReadableError");
61279
61801
  }
61280
- yield* createReadStream(this.#path, {
61802
+ yield* createReadStream2(this.#path, {
61281
61803
  start: this.#start,
61282
61804
  end: this.#start + this.size - 1
61283
61805
  });
@@ -76907,7 +77429,7 @@ var init_wrapper = __esm({
76907
77429
  });
76908
77430
 
76909
77431
  // node_modules/@google/genai/dist/node/index.mjs
76910
- import { createWriteStream } from "fs";
77432
+ import { createWriteStream as createWriteStream2 } from "fs";
76911
77433
  import * as fs11 from "fs/promises";
76912
77434
  import { writeFile as writeFile4 } from "fs/promises";
76913
77435
  import { Readable } from "stream";
@@ -94187,7 +94709,7 @@ ${underline}`);
94187
94709
  if (params.downloadPath) {
94188
94710
  const response = await downloadFile(params, apiClient);
94189
94711
  if (response instanceof HttpResponse) {
94190
- const writer = createWriteStream(params.downloadPath);
94712
+ const writer = createWriteStream2(params.downloadPath);
94191
94713
  const body = Readable.fromWeb(response.responseInternal.body);
94192
94714
  body.pipe(writer);
94193
94715
  await finished(writer);
@@ -144221,7 +144743,7 @@ function explainRetention(doc, referenceTime) {
144221
144743
  };
144222
144744
  }
144223
144745
  function toRetentionDocument(obs, accessMap) {
144224
- const access2 = accessMap?.get(obs.id);
144746
+ const access3 = accessMap?.get(obs.id);
144225
144747
  return {
144226
144748
  id: `obs-${obs.id}`,
144227
144749
  observationId: obs.id,
@@ -144235,8 +144757,8 @@ function toRetentionDocument(obs, accessMap) {
144235
144757
  tokens: obs.tokens,
144236
144758
  createdAt: obs.createdAt,
144237
144759
  projectId: obs.projectId,
144238
- accessCount: access2?.accessCount ?? 0,
144239
- lastAccessedAt: access2?.lastAccessedAt ?? "",
144760
+ accessCount: access3?.accessCount ?? 0,
144761
+ lastAccessedAt: access3?.lastAccessedAt ?? "",
144240
144762
  status: obs.status ?? "active",
144241
144763
  source: obs.source ?? "agent",
144242
144764
  sourceDetail: obs.sourceDetail ?? "",
@@ -146350,7 +146872,7 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
146350
146872
  const rel = normalizeCodePath(relative(projectRoot, abs));
146351
146873
  try {
146352
146874
  const text2 = readFileSync6(abs, "utf-8");
146353
- const stat4 = fileStat ?? statSync3(abs);
146875
+ const stat5 = fileStat ?? statSync3(abs);
146354
146876
  const file = {
146355
146877
  id: makeCodeFileId(projectId, rel),
146356
146878
  projectId,
@@ -146359,8 +146881,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
146359
146881
  contentHash: hashText(text2),
146360
146882
  // Preserve filesystem precision. Rounding here made same-size edits in
146361
146883
  // a short Windows timestamp window indistinguishable to refresh.
146362
- mtimeMs: Number(stat4.mtimeMs),
146363
- sizeBytes: Number(stat4.size),
146884
+ mtimeMs: Number(stat5.mtimeMs),
146885
+ sizeBytes: Number(stat5.size),
146364
146886
  indexedAt
146365
146887
  };
146366
146888
  return {
@@ -146387,18 +146909,18 @@ async function indexProjectLite(options) {
146387
146909
  let skippedOversizedFiles = 0;
146388
146910
  let unreadableFiles = 0;
146389
146911
  for (const abs of paths) {
146390
- let stat4;
146912
+ let stat5;
146391
146913
  try {
146392
- stat4 = statSync3(abs);
146914
+ stat5 = statSync3(abs);
146393
146915
  } catch {
146394
146916
  unreadableFiles++;
146395
146917
  continue;
146396
146918
  }
146397
- if (Number(stat4.size) > maxFileBytes) {
146919
+ if (Number(stat5.size) > maxFileBytes) {
146398
146920
  skippedOversizedFiles++;
146399
146921
  continue;
146400
146922
  }
146401
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
146923
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
146402
146924
  if (!attempt.delta) {
146403
146925
  if (attempt.unreadable) unreadableFiles++;
146404
146926
  continue;
@@ -146426,16 +146948,16 @@ async function refreshProjectLite(store, options) {
146426
146948
  for (const abs of paths) {
146427
146949
  const rel = normalizeCodePath(relative(options.projectRoot, abs));
146428
146950
  seenPaths.add(rel);
146429
- let stat4;
146951
+ let stat5;
146430
146952
  try {
146431
- stat4 = statSync3(abs);
146953
+ stat5 = statSync3(abs);
146432
146954
  } catch {
146433
146955
  unreadableFiles++;
146434
146956
  continue;
146435
146957
  }
146436
146958
  const existing = existingByPath.get(rel);
146437
- const mtimeMs = Number(stat4.mtimeMs);
146438
- const sizeBytes = Number(stat4.size);
146959
+ const mtimeMs = Number(stat5.mtimeMs);
146960
+ const sizeBytes = Number(stat5.size);
146439
146961
  if (sizeBytes > maxFileBytes) {
146440
146962
  skippedOversizedFiles++;
146441
146963
  if (existing) oversizedExistingFileIds.add(existing.id);
@@ -146445,7 +146967,7 @@ async function refreshProjectLite(store, options) {
146445
146967
  unchangedFiles++;
146446
146968
  continue;
146447
146969
  }
146448
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
146970
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
146449
146971
  if (!attempt.delta) {
146450
146972
  if (attempt.unreadable) unreadableFiles++;
146451
146973
  continue;
@@ -150193,12 +150715,25 @@ __export(observations_exports, {
150193
150715
  prepareSearchIndex: () => prepareSearchIndex,
150194
150716
  probeSearchIndex: () => probeSearchIndex,
150195
150717
  reindexObservations: () => reindexObservations,
150718
+ resetObservationRuntime: () => resetObservationRuntime,
150196
150719
  resolveObservations: () => resolveObservations,
150197
150720
  storeObservation: () => storeObservation,
150198
150721
  suggestTopicKey: () => suggestTopicKey,
150199
150722
  updateObservationAdmission: () => updateObservationAdmission,
150200
150723
  withFreshObservations: () => withFreshObservations
150201
150724
  });
150725
+ function resetObservationRuntime() {
150726
+ observations = [];
150727
+ nextId = 1;
150728
+ projectDir = null;
150729
+ searchIndexPrepared = false;
150730
+ corpusLoaded = false;
150731
+ loadedObservationStore = null;
150732
+ embeddingWriteMode = "background";
150733
+ embeddingWorkerProjectRoot = void 0;
150734
+ vectorMissingIds.clear();
150735
+ vectorSchemaUpgradePromise = null;
150736
+ }
150202
150737
  function logEmbeddingFailureOnce(key, message) {
150203
150738
  const now3 = Date.now();
150204
150739
  const last = embeddingFailureLogTimestamps.get(key) ?? 0;
@@ -150307,23 +150842,33 @@ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
150307
150842
  async function initObservations(dir, options = {}) {
150308
150843
  embeddingWriteMode = options.embeddingWriteMode ?? "background";
150309
150844
  embeddingWorkerProjectRoot = options.projectRoot;
150310
- if (projectDir === dir) return;
150311
150845
  await initObservationStore(dir);
150312
150846
  const store = getObservationStore();
150313
- observations = await store.loadAll();
150847
+ if (projectDir === dir && loadedObservationStore === store) {
150848
+ if (options.skipCorpusLoad || corpusLoaded) return;
150849
+ }
150314
150850
  nextId = await store.loadIdCounter();
150315
150851
  projectDir = dir;
150316
150852
  searchIndexPrepared = false;
150317
150853
  vectorSchemaUpgradePromise = null;
150854
+ if (options.skipCorpusLoad) {
150855
+ observations = [];
150856
+ corpusLoaded = false;
150857
+ loadedObservationStore = store;
150858
+ return;
150859
+ }
150860
+ observations = await store.loadAll();
150861
+ corpusLoaded = true;
150862
+ loadedObservationStore = store;
150318
150863
  }
150319
150864
  function scheduleObservationEmbedding(input) {
150320
150865
  const { observationId: observationId2, projectId, searchableText, doc } = input;
150321
150866
  vectorMissingIds.add(observationId2);
150867
+ if (isEmbeddingExplicitlyDisabled()) {
150868
+ vectorMissingIds.delete(observationId2);
150869
+ return;
150870
+ }
150322
150871
  if (embeddingWriteMode === "deferred") {
150323
- if (isEmbeddingExplicitlyDisabled()) {
150324
- vectorMissingIds.delete(observationId2);
150325
- return;
150326
- }
150327
150872
  queueVectorBackfill(projectId, {
150328
150873
  detachedWorker: true,
150329
150874
  projectRoot: embeddingWorkerProjectRoot
@@ -150370,7 +150915,7 @@ function scheduleObservationEmbedding(input) {
150370
150915
  });
150371
150916
  }
150372
150917
  async function ensureFreshObservations() {
150373
- if (!projectDir) return false;
150918
+ if (!projectDir || !corpusLoaded) return false;
150374
150919
  try {
150375
150920
  const store = getObservationStore();
150376
150921
  const wasStale = await store.ensureFresh();
@@ -150411,7 +150956,7 @@ async function storeObservation(input) {
150411
150956
  });
150412
150957
  if (safeAttachments) input = { ...input, attachments: safeAttachments };
150413
150958
  await ensureFreshObservations();
150414
- if (input.topicKey) {
150959
+ if (input.topicKey && corpusLoaded) {
150415
150960
  const existing = observations.find(
150416
150961
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
150417
150962
  );
@@ -150508,13 +151053,17 @@ async function storeObservation(input) {
150508
151053
  });
150509
151054
  observation.writeGeneration = store.getGeneration();
150510
151055
  if (upsertedInsideLock || reloadCacheAfterCommit) {
150511
- observations = await store.loadAll();
150512
- nextId = await store.loadIdCounter();
150513
- if (upsertedInsideLock) {
150514
- observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
151056
+ if (corpusLoaded) {
151057
+ observations = await store.loadAll();
151058
+ nextId = await store.loadIdCounter();
151059
+ if (upsertedInsideLock) {
151060
+ observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
151061
+ }
151062
+ } else {
151063
+ nextId = await store.loadIdCounter();
150515
151064
  }
150516
151065
  } else {
150517
- observations.push(observation);
151066
+ if (corpusLoaded) observations.push(observation);
150518
151067
  nextId = observation.id + 1;
150519
151068
  }
150520
151069
  if (upsertedInsideLock) return;
@@ -150582,7 +151131,9 @@ async function storeObservation(input) {
150582
151131
  createdByAgentId: input.createdByAgentId ?? "",
150583
151132
  sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
150584
151133
  };
150585
- await insertObservation(doc);
151134
+ if (corpusLoaded) {
151135
+ await insertObservation(doc);
151136
+ }
150586
151137
  };
150587
151138
  await assignAndPersist();
150588
151139
  if (upsertedInsideLock) {
@@ -150944,9 +151495,11 @@ async function reindexObservations() {
150944
151495
  searchIndexPrepared = true;
150945
151496
  return count2;
150946
151497
  }
150947
- async function prepareSearchIndex() {
151498
+ async function prepareSearchIndex(options = {}) {
150948
151499
  if (searchIndexPrepared) return 0;
150949
- const count2 = await hydrateIndexForStartup(observations);
151500
+ const count2 = await hydrateIndexForStartup(observations, {
151501
+ skipCachedVectors: options.skipCachedVectors
151502
+ });
150950
151503
  if (count2 === 0) {
150951
151504
  searchIndexPrepared = true;
150952
151505
  return 0;
@@ -151138,7 +151691,7 @@ async function backfillVectorEmbeddings(options = {}) {
151138
151691
  ...lastFailure ? { lastError: lastFailure } : {}
151139
151692
  };
151140
151693
  }
151141
- var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
151694
+ var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, corpusLoaded, loadedObservationStore, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
151142
151695
  var init_observations = __esm({
151143
151696
  "src/memory/observations.ts"() {
151144
151697
  "use strict";
@@ -151157,6 +151710,8 @@ var init_observations = __esm({
151157
151710
  projectDir = null;
151158
151711
  searchIndexPrepared = false;
151159
151712
  embeddingWriteMode = "background";
151713
+ corpusLoaded = false;
151714
+ loadedObservationStore = null;
151160
151715
  vectorMissingIds = /* @__PURE__ */ new Set();
151161
151716
  vectorBackfillRunning = false;
151162
151717
  vectorSchemaUpgradePromise = null;
@@ -155352,9 +155907,9 @@ function existingLensCandidates(rootPath, lens) {
155352
155907
  const absolute = path28.join(rootPath, candidate);
155353
155908
  try {
155354
155909
  if (!existsSync16(absolute)) continue;
155355
- const stat4 = statSync4(absolute);
155356
- if (stat4.isFile()) out.push(candidate);
155357
- if (stat4.isDirectory()) out.push(candidate.replace(/\\/g, "/"));
155910
+ const stat5 = statSync4(absolute);
155911
+ if (stat5.isFile()) out.push(candidate);
155912
+ if (stat5.isDirectory()) out.push(candidate.replace(/\\/g, "/"));
155358
155913
  } catch {
155359
155914
  }
155360
155915
  }
@@ -161174,13 +161729,13 @@ async function installHooks(agent, projectRoot, global2 = false) {
161174
161729
  const agentPrev = prevFiles.filter((e2) => e2.agent === agent);
161175
161730
  for (const entry of agentPrev) {
161176
161731
  try {
161177
- const { access: access2, unlink: unlink2 } = await import("fs/promises");
161178
- await access2(entry.path);
161732
+ const { access: access3, unlink: unlink3 } = await import("fs/promises");
161733
+ await access3(entry.path);
161179
161734
  const basename5 = path32.basename(entry.path);
161180
161735
  if (basename5 === "AGENTS.md" || basename5 === "GEMINI.md" || basename5 === "CONTEXT.md") {
161181
161736
  continue;
161182
161737
  }
161183
- await unlink2(entry.path);
161738
+ await unlink3(entry.path);
161184
161739
  await removeFile2(projectRoot, entry.path);
161185
161740
  } catch {
161186
161741
  }
@@ -161700,11 +162255,11 @@ import path33 from "path";
161700
162255
  function resolveGitDir(projectRoot) {
161701
162256
  const dotGit = path33.join(projectRoot, ".git");
161702
162257
  if (!existsSync19(dotGit)) return null;
161703
- const stat4 = statSync5(dotGit);
161704
- if (stat4.isDirectory()) {
162258
+ const stat5 = statSync5(dotGit);
162259
+ if (stat5.isDirectory()) {
161705
162260
  return dotGit;
161706
162261
  }
161707
- if (stat4.isFile()) {
162262
+ if (stat5.isFile()) {
161708
162263
  try {
161709
162264
  const content = readFileSync13(dotGit, "utf-8").trim();
161710
162265
  const match2 = content.match(/^gitdir:\s*(.+)$/);
@@ -163848,8 +164403,8 @@ var RulesSyncer = class {
163848
164403
  const dir = path25.dirname(pattern);
163849
164404
  const fileGlob = path25.basename(pattern);
163850
164405
  try {
163851
- const stat4 = await fs17.stat(dir);
163852
- if (!stat4.isDirectory()) {
164406
+ const stat5 = await fs17.stat(dir);
164407
+ if (!stat5.isDirectory()) {
163853
164408
  try {
163854
164409
  await fs17.access(pattern);
163855
164410
  return [pattern];
@@ -165904,7 +166459,7 @@ The path should point to a directory containing a .git folder.`
165904
166459
  };
165905
166460
  const server = existingServer ?? new McpServer({
165906
166461
  name: "memorix",
165907
- version: true ? "1.6.0" : "1.0.1"
166462
+ version: true ? "1.6.2" : "1.0.1"
165908
166463
  });
165909
166464
  const originalRegisterTool = server.registerTool.bind(server);
165910
166465
  server.registerTool = ((name, ...args) => {
@@ -170332,6 +170887,11 @@ var MemoryClient = class {
170332
170887
  await this._oramaStore.resetDb();
170333
170888
  } catch {
170334
170889
  }
170890
+ try {
170891
+ const { closeDatabase: closeDatabase3 } = await Promise.resolve().then(() => (init_sqlite_db(), sqlite_db_exports));
170892
+ closeDatabase3(this._dataDir);
170893
+ } catch {
170894
+ }
170335
170895
  }
170336
170896
  };
170337
170897
  async function createMemoryClient(options) {
@@ -170343,11 +170903,8 @@ async function createMemoryClient(options) {
170343
170903
  `[memorix-sdk] No Git repository found at "${projectRoot}". Memorix requires a Git-tracked project for identity resolution.`
170344
170904
  );
170345
170905
  }
170346
- const path34 = await import("path");
170347
- const os4 = await import("os");
170348
- const dataDir = path34.join(os4.homedir(), ".memorix", "data", project.id.replace(/\//g, path34.sep));
170349
- const fs22 = await import("fs");
170350
- fs22.mkdirSync(dataDir, { recursive: true });
170906
+ const { getProjectDataDir: getProjectDataDir2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
170907
+ const dataDir = await getProjectDataDir2(project.id);
170351
170908
  const client = new MemoryClient(project.id, projectRoot, dataDir);
170352
170909
  await client._init(silent);
170353
170910
  return client;