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/index.js CHANGED
@@ -1195,8 +1195,8 @@ async function acquireLock(lockPath) {
1195
1195
  const code = err instanceof Error && "code" in err ? err.code : void 0;
1196
1196
  if (code === "EEXIST" || code === "EPERM") {
1197
1197
  try {
1198
- const stat4 = await fs2.stat(lockPath);
1199
- if (Date.now() - stat4.mtimeMs > LOCK_STALE_MS) {
1198
+ const stat5 = await fs2.stat(lockPath);
1199
+ if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
1200
1200
  await fs2.unlink(lockPath).catch(() => {
1201
1201
  });
1202
1202
  continue;
@@ -3713,24 +3713,131 @@ var init_transformers_provider = __esm({
3713
3713
  // src/embedding/api-provider.ts
3714
3714
  var api_provider_exports = {};
3715
3715
  __export(api_provider_exports, {
3716
- APIEmbeddingProvider: () => APIEmbeddingProvider
3716
+ APIEmbeddingProvider: () => APIEmbeddingProvider,
3717
+ convertArrayCacheToJsonl: () => convertArrayCacheToJsonl,
3718
+ ensureDiskCacheLoaded: () => ensureDiskCacheLoaded,
3719
+ getApiEmbeddingCachePayloadBytesForTests: () => getApiEmbeddingCachePayloadBytesForTests,
3720
+ getApiEmbeddingDiskCacheLoadCountForTests: () => getApiEmbeddingDiskCacheLoadCountForTests,
3721
+ getApiEmbeddingJsonlLineCountCallsForTests: () => getApiEmbeddingJsonlLineCountCallsForTests,
3722
+ getApiEmbeddingMaxEntriesForDimensionsForTests: () => getApiEmbeddingMaxEntriesForDimensionsForTests,
3723
+ migrateJsonArrayCacheToJsonl: () => migrateJsonArrayCacheToJsonl,
3724
+ parseCacheJsonInWorker: () => parseCacheJsonInWorker,
3725
+ resetApiEmbeddingCacheForTests: () => resetApiEmbeddingCacheForTests,
3726
+ saveDiskCacheNow: () => saveDiskCacheNow,
3727
+ seedApiEmbeddingCacheForTests: () => seedApiEmbeddingCacheForTests
3717
3728
  });
3718
3729
  import { createHash as createHash2 } from "crypto";
3719
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3730
+ import { createReadStream, createWriteStream, constants as fsConstants } from "fs";
3731
+ import { access, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rename, stat, unlink } from "fs/promises";
3720
3732
  import { join as join3 } from "path";
3721
3733
  import { homedir as homedir7 } from "os";
3734
+ import { createInterface } from "readline";
3722
3735
  function cacheDir() {
3723
3736
  return process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
3724
3737
  }
3725
3738
  function cacheFile() {
3726
3739
  return join3(cacheDir(), ".embedding-api-cache.json");
3727
3740
  }
3741
+ function cacheJsonlFile() {
3742
+ return join3(cacheDir(), ".embedding-api-cache.jsonl");
3743
+ }
3728
3744
  function dimsCacheFile() {
3729
3745
  return join3(cacheDir(), ".embedding-dims-cache.json");
3730
3746
  }
3731
3747
  function cacheMetaFile() {
3732
3748
  return join3(cacheDir(), ".embedding-api-cache-meta.json");
3733
3749
  }
3750
+ function cacheStatFile() {
3751
+ return join3(cacheDir(), ".embedding-api-cache-stat.json");
3752
+ }
3753
+ function payloadBytes(vector) {
3754
+ return vector.length * BYTES_PER_DIM;
3755
+ }
3756
+ function maxCachePayloadBytes() {
3757
+ const raw = process.env.MEMORIX_EMBEDDING_CACHE_MAX_BYTES;
3758
+ if (raw) {
3759
+ const parsed = Number.parseInt(raw, 10);
3760
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
3761
+ }
3762
+ return MAX_CACHE_PAYLOAD_BYTES;
3763
+ }
3764
+ function maxEntriesForDimensions(dimensions) {
3765
+ const dim = Math.max(1, Math.floor(dimensions));
3766
+ const byBytes = Math.floor(maxCachePayloadBytes() / (dim * BYTES_PER_DIM));
3767
+ return Math.max(1, Math.min(MAX_CACHE_ENTRIES, byBytes));
3768
+ }
3769
+ function rememberDimensions(vector) {
3770
+ if (vector.length > 0 && (cacheDimensions === null || vector.length > cacheDimensions)) {
3771
+ cacheDimensions = vector.length;
3772
+ }
3773
+ }
3774
+ function evictOldest() {
3775
+ const firstKey = cache3.keys().next().value;
3776
+ if (firstKey === void 0) return;
3777
+ const old = cache3.get(firstKey);
3778
+ cache3.delete(firstKey);
3779
+ if (old) cachePayloadBytes -= payloadBytes(old);
3780
+ }
3781
+ function evictToFit(incomingBytes, incomingDims) {
3782
+ const limit2 = maxEntriesForDimensions(incomingDims);
3783
+ while (cache3.size > 0 && (cache3.size >= limit2 || cachePayloadBytes + incomingBytes > maxCachePayloadBytes())) {
3784
+ evictOldest();
3785
+ }
3786
+ }
3787
+ function isNumberArray(value) {
3788
+ return Array.isArray(value) && value.every((entry) => typeof entry === "number");
3789
+ }
3790
+ function parseCacheRow(raw) {
3791
+ if (Array.isArray(raw)) {
3792
+ const hash4 = raw[0];
3793
+ const vector = raw[1];
3794
+ if (typeof hash4 === "string" && isNumberArray(vector)) {
3795
+ return { hash: hash4, vector };
3796
+ }
3797
+ return null;
3798
+ }
3799
+ if (raw && typeof raw === "object") {
3800
+ const row = raw;
3801
+ if (typeof row.h === "string" && isNumberArray(row.v)) {
3802
+ return { hash: row.h, vector: row.v };
3803
+ }
3804
+ }
3805
+ return null;
3806
+ }
3807
+ function cacheInsert(hash4, value, dirty) {
3808
+ rememberDimensions(value);
3809
+ const incoming = payloadBytes(value);
3810
+ const existing = cache3.get(hash4);
3811
+ if (existing) {
3812
+ cachePayloadBytes -= payloadBytes(existing);
3813
+ cache3.delete(hash4);
3814
+ }
3815
+ evictToFit(incoming, value.length || cacheDimensions || DEFAULT_ASSUMED_DIMENSIONS);
3816
+ cache3.set(hash4, value);
3817
+ cachePayloadBytes += incoming;
3818
+ if (dirty) diskCacheDirty2 = true;
3819
+ }
3820
+ async function readCacheStat() {
3821
+ try {
3822
+ const data = JSON.parse(await readFile2(cacheStatFile(), "utf-8"));
3823
+ if (typeof data.entryCount === "number" && Number.isFinite(data.entryCount) && data.entryCount >= 0) {
3824
+ return {
3825
+ version: 1,
3826
+ entryCount: Math.floor(data.entryCount),
3827
+ dimensions: isValidDimension(data.dimensions) ? data.dimensions : null
3828
+ };
3829
+ }
3830
+ } catch {
3831
+ }
3832
+ return null;
3833
+ }
3834
+ async function writeCacheStat(stat5) {
3835
+ try {
3836
+ await mkdir2(cacheDir(), { recursive: true });
3837
+ await writeFile2(cacheStatFile(), JSON.stringify(stat5));
3838
+ } catch {
3839
+ }
3840
+ }
3734
3841
  function normalizeText(text2) {
3735
3842
  return text2.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3736
3843
  }
@@ -3816,29 +3923,300 @@ function toProviderInput(input, baseUrl) {
3816
3923
  }
3817
3924
  return { type: input.modality, url: input.url };
3818
3925
  }
3926
+ function parseCacheJsonInWorker(filePath) {
3927
+ return new Promise((resolve2, reject) => {
3928
+ void import("worker_threads").then(({ Worker }) => {
3929
+ const worker = new Worker(
3930
+ `
3931
+ const { parentPort, workerData } = require('node:worker_threads');
3932
+ const { readFileSync } = require('node:fs');
3933
+ try {
3934
+ const raw = readFileSync(workerData.filePath, 'utf8');
3935
+ parentPort.postMessage({ ok: true, entries: JSON.parse(raw) });
3936
+ } catch (err) {
3937
+ parentPort.postMessage({
3938
+ ok: false,
3939
+ error: err instanceof Error ? err.message : String(err),
3940
+ });
3941
+ }
3942
+ `,
3943
+ { eval: true, workerData: { filePath } }
3944
+ );
3945
+ worker.once("message", (msg) => {
3946
+ void worker.terminate();
3947
+ if (msg?.ok && Array.isArray(msg.entries)) {
3948
+ resolve2(msg.entries);
3949
+ return;
3950
+ }
3951
+ reject(new Error(msg?.error || "embedding cache worker parse failed"));
3952
+ });
3953
+ worker.once("error", reject);
3954
+ }).catch(reject);
3955
+ });
3956
+ }
3957
+ function findJsonValueEnd(source) {
3958
+ let i2 = 0;
3959
+ while (i2 < source.length && /\s/.test(source[i2])) i2 += 1;
3960
+ if (i2 >= source.length) return -1;
3961
+ const start = source[i2];
3962
+ if (start === '"') {
3963
+ i2 += 1;
3964
+ while (i2 < source.length) {
3965
+ if (source[i2] === "\\") {
3966
+ i2 += 2;
3967
+ continue;
3968
+ }
3969
+ if (source[i2] === '"') return i2 + 1;
3970
+ i2 += 1;
3971
+ }
3972
+ return -1;
3973
+ }
3974
+ if (start === "{" || start === "[") {
3975
+ const close = start === "{" ? "}" : "]";
3976
+ let depth = 0;
3977
+ let inString = false;
3978
+ for (; i2 < source.length; i2 += 1) {
3979
+ const ch = source[i2];
3980
+ if (inString) {
3981
+ if (ch === "\\") {
3982
+ i2 += 1;
3983
+ continue;
3984
+ }
3985
+ if (ch === '"') inString = false;
3986
+ continue;
3987
+ }
3988
+ if (ch === '"') {
3989
+ inString = true;
3990
+ continue;
3991
+ }
3992
+ if (ch === start) depth += 1;
3993
+ else if (ch === close) {
3994
+ depth -= 1;
3995
+ if (depth === 0) return i2 + 1;
3996
+ }
3997
+ }
3998
+ return -1;
3999
+ }
4000
+ const rest = source.slice(i2);
4001
+ const match2 = rest.match(/^(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/);
4002
+ return match2 ? i2 + match2[0].length : -1;
4003
+ }
4004
+ async function streamJsonArrayToJsonl(filePath, tmpPath) {
4005
+ const input = createReadStream(filePath, { encoding: "utf8" });
4006
+ const out = createWriteStream(tmpPath);
4007
+ let buf = "";
4008
+ let started = false;
4009
+ let count2 = 0;
4010
+ const writeLine = async (line) => {
4011
+ if (!out.write(line)) {
4012
+ await new Promise((resolve2, reject) => {
4013
+ out.once("drain", resolve2);
4014
+ out.once("error", reject);
4015
+ });
4016
+ }
4017
+ };
4018
+ try {
4019
+ for await (const chunk of input) {
4020
+ buf += chunk;
4021
+ if (!started) {
4022
+ const idx = buf.search(/\S/);
4023
+ if (idx < 0) continue;
4024
+ if (buf[idx] !== "[") throw new Error("embedding cache is not a JSON array");
4025
+ buf = buf.slice(idx + 1);
4026
+ started = true;
4027
+ }
4028
+ while (true) {
4029
+ buf = buf.replace(/^\s*,?\s*/, "");
4030
+ if (!buf) break;
4031
+ if (buf[0] === "]") {
4032
+ buf = "";
4033
+ break;
4034
+ }
4035
+ const end = findJsonValueEnd(buf);
4036
+ if (end < 0) break;
4037
+ const value = JSON.parse(buf.slice(0, end));
4038
+ buf = buf.slice(end);
4039
+ const row = parseCacheRow(value);
4040
+ if (row) {
4041
+ await writeLine(`${JSON.stringify({ h: row.hash, v: row.vector })}
4042
+ `);
4043
+ count2 += 1;
4044
+ if (count2 % 50 === 0) {
4045
+ await new Promise((resolve2) => setImmediate(resolve2));
4046
+ }
4047
+ }
4048
+ }
4049
+ if (buf.length > MAX_MIGRATION_VALUE_BYTES) {
4050
+ throw new Error("embedding cache entry too large to migrate safely");
4051
+ }
4052
+ }
4053
+ } finally {
4054
+ await new Promise((resolve2, reject) => {
4055
+ out.end(() => resolve2());
4056
+ out.once("error", reject);
4057
+ });
4058
+ }
4059
+ return count2;
4060
+ }
4061
+ async function migrateJsonArrayCacheToJsonl(filePath, jsonlPath) {
4062
+ const tmpPath = `${jsonlPath}.tmp`;
4063
+ try {
4064
+ const count2 = await streamJsonArrayToJsonl(filePath, tmpPath);
4065
+ await rename(tmpPath, jsonlPath);
4066
+ diskCacheEntryCount = count2;
4067
+ await writeCacheStat({
4068
+ version: 1,
4069
+ entryCount: count2,
4070
+ dimensions: cacheDimensions
4071
+ });
4072
+ return count2;
4073
+ } catch (err) {
4074
+ await unlink(tmpPath).catch(() => void 0);
4075
+ throw err;
4076
+ }
4077
+ }
4078
+ function convertArrayCacheToJsonl(filePath, jsonlPath) {
4079
+ return migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
4080
+ }
4081
+ async function loadCacheJsonl(filePath) {
4082
+ const rl = createInterface({
4083
+ input: createReadStream(filePath, { encoding: "utf8" }),
4084
+ crlfDelay: Infinity
4085
+ });
4086
+ let loaded = 0;
4087
+ let onDiskCount = 0;
4088
+ for await (const line of rl) {
4089
+ const trimmed = line.trim();
4090
+ if (!trimmed) continue;
4091
+ try {
4092
+ const row = parseCacheRow(JSON.parse(trimmed));
4093
+ if (!row) continue;
4094
+ onDiskCount += 1;
4095
+ cacheInsert(row.hash, row.vector, false);
4096
+ loaded = cache3.size;
4097
+ } catch {
4098
+ }
4099
+ if (onDiskCount % 250 === 0) {
4100
+ await new Promise((resolve2) => setImmediate(resolve2));
4101
+ }
4102
+ }
4103
+ diskCacheEntryCount = onDiskCount;
4104
+ await writeCacheStat({
4105
+ version: 1,
4106
+ entryCount: onDiskCount,
4107
+ dimensions: cacheDimensions
4108
+ });
4109
+ return loaded;
4110
+ }
4111
+ async function countJsonlLines(filePath) {
4112
+ jsonlLineCountCalls += 1;
4113
+ try {
4114
+ const rl = createInterface({
4115
+ input: createReadStream(filePath, { encoding: "utf8" }),
4116
+ crlfDelay: Infinity
4117
+ });
4118
+ let count2 = 0;
4119
+ for await (const line of rl) {
4120
+ if (line.trim()) count2 += 1;
4121
+ }
4122
+ return count2;
4123
+ } catch {
4124
+ return 0;
4125
+ }
4126
+ }
4127
+ async function resolveOnDiskCount(jsonlPath) {
4128
+ if (diskCacheEntryCount > 0) return diskCacheEntryCount;
4129
+ const sidecar = await readCacheStat();
4130
+ if (sidecar) {
4131
+ diskCacheEntryCount = sidecar.entryCount;
4132
+ if (sidecar.dimensions && cacheDimensions === null) cacheDimensions = sidecar.dimensions;
4133
+ return sidecar.entryCount;
4134
+ }
4135
+ try {
4136
+ await access(jsonlPath, fsConstants.R_OK);
4137
+ } catch {
4138
+ return 0;
4139
+ }
4140
+ const counted = await countJsonlLines(jsonlPath);
4141
+ diskCacheEntryCount = counted;
4142
+ await writeCacheStat({
4143
+ version: 1,
4144
+ entryCount: counted,
4145
+ dimensions: cacheDimensions
4146
+ });
4147
+ return counted;
4148
+ }
3819
4149
  async function loadDiskCache2() {
3820
- if (diskCacheLoaded) return;
4150
+ if (diskCacheLoaded) return cache3.size;
4151
+ let loaded = 0;
3821
4152
  try {
3822
- const raw = await readFile2(cacheFile(), "utf-8");
3823
- const entries = JSON.parse(raw);
3824
- for (const [k, v] of entries) cache3.set(k, v);
3825
- console.error(`[memorix] Loaded ${entries.length} cached API embeddings from disk`);
4153
+ const jsonlPath = cacheJsonlFile();
4154
+ let hasJsonl = false;
4155
+ try {
4156
+ await access(jsonlPath, fsConstants.R_OK);
4157
+ hasJsonl = true;
4158
+ } catch {
4159
+ hasJsonl = false;
4160
+ }
4161
+ if (hasJsonl) {
4162
+ loaded = await loadCacheJsonl(jsonlPath);
4163
+ } else {
4164
+ const filePath = cacheFile();
4165
+ await stat(filePath);
4166
+ await migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
4167
+ loaded = await loadCacheJsonl(jsonlPath);
4168
+ }
4169
+ diskCacheLoadCount += 1;
4170
+ console.error(`[memorix] Loaded ${loaded} cached API embeddings from disk`);
3826
4171
  } catch {
3827
4172
  }
3828
4173
  diskCacheLoaded = true;
4174
+ return loaded;
3829
4175
  }
3830
4176
  function startDiskCacheLoad() {
3831
4177
  if (diskCacheLoaded || diskCacheLoadPromise) return;
3832
- diskCacheLoadPromise = loadDiskCache2().catch(() => {
4178
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
3833
4179
  });
3834
4180
  }
3835
4181
  async function ensureDiskCacheLoaded() {
3836
- if (diskCacheLoaded) return;
3837
- if (diskCacheLoadPromise) {
3838
- await diskCacheLoadPromise;
3839
- return;
3840
- }
3841
- await loadDiskCache2();
4182
+ if (diskCacheLoaded) return cache3.size;
4183
+ if (!diskCacheLoadPromise) {
4184
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
4185
+ });
4186
+ }
4187
+ await diskCacheLoadPromise;
4188
+ return cache3.size;
4189
+ }
4190
+ function getApiEmbeddingDiskCacheLoadCountForTests() {
4191
+ return diskCacheLoadCount;
4192
+ }
4193
+ function resetApiEmbeddingCacheForTests() {
4194
+ cache3.clear();
4195
+ cachePayloadBytes = 0;
4196
+ cacheDimensions = null;
4197
+ diskCacheEntryCount = 0;
4198
+ diskCacheLoaded = false;
4199
+ diskCacheLoadPromise = null;
4200
+ diskCacheLoadCount = 0;
4201
+ diskCacheDirty2 = false;
4202
+ jsonlLineCountCalls = 0;
4203
+ }
4204
+ function seedApiEmbeddingCacheForTests(entries) {
4205
+ cache3.clear();
4206
+ cachePayloadBytes = 0;
4207
+ cacheDimensions = null;
4208
+ for (const [key, value] of entries) cacheInsert(key, value, true);
4209
+ diskCacheLoaded = true;
4210
+ diskCacheDirty2 = true;
4211
+ }
4212
+ function getApiEmbeddingCachePayloadBytesForTests() {
4213
+ return cachePayloadBytes;
4214
+ }
4215
+ function getApiEmbeddingJsonlLineCountCallsForTests() {
4216
+ return jsonlLineCountCalls;
4217
+ }
4218
+ function getApiEmbeddingMaxEntriesForDimensionsForTests(dimensions) {
4219
+ return maxEntriesForDimensions(dimensions);
3842
4220
  }
3843
4221
  function dimsCacheKey(config) {
3844
4222
  return [
@@ -3954,8 +4332,46 @@ async function saveDiskCacheNow() {
3954
4332
  if (!diskCacheDirty2) return;
3955
4333
  try {
3956
4334
  await mkdir2(cacheDir(), { recursive: true });
3957
- const entries = Array.from(cache3.entries());
3958
- await writeFile2(cacheFile(), JSON.stringify(entries));
4335
+ const jsonlPath = cacheJsonlFile();
4336
+ const existing = await resolveOnDiskCount(jsonlPath);
4337
+ if (existing > 0 && cache3.size < Math.ceil(existing * 0.5)) {
4338
+ console.error(
4339
+ `[memorix] Refusing to overwrite embedding cache (${existing} on disk vs ${cache3.size} in memory)`
4340
+ );
4341
+ diskCacheDirty2 = false;
4342
+ return;
4343
+ }
4344
+ const tmpPath = `${jsonlPath}.tmp`;
4345
+ await new Promise((resolve2, reject) => {
4346
+ const out = createWriteStream(tmpPath);
4347
+ const entries = cache3.entries();
4348
+ const writeBatch = () => {
4349
+ for (let i2 = 0; i2 < 50; i2++) {
4350
+ const next = entries.next();
4351
+ if (next.done) {
4352
+ out.end();
4353
+ return;
4354
+ }
4355
+ const [hash4, vector] = next.value;
4356
+ if (!out.write(`${JSON.stringify({ h: hash4, v: vector })}
4357
+ `)) {
4358
+ out.once("drain", writeBatch);
4359
+ return;
4360
+ }
4361
+ }
4362
+ setImmediate(writeBatch);
4363
+ };
4364
+ out.on("finish", resolve2);
4365
+ out.on("error", reject);
4366
+ writeBatch();
4367
+ });
4368
+ await rename(tmpPath, jsonlPath);
4369
+ diskCacheEntryCount = cache3.size;
4370
+ await writeCacheStat({
4371
+ version: 1,
4372
+ entryCount: cache3.size,
4373
+ dimensions: cacheDimensions
4374
+ });
3959
4375
  diskCacheDirty2 = false;
3960
4376
  } catch {
3961
4377
  }
@@ -3969,12 +4385,7 @@ function scheduleDiskSave() {
3969
4385
  }, DISK_SAVE_DEBOUNCE_MS);
3970
4386
  }
3971
4387
  function cacheSet(hash4, value) {
3972
- if (cache3.size >= MAX_CACHE_SIZE3) {
3973
- const firstKey = cache3.keys().next().value;
3974
- if (firstKey !== void 0) cache3.delete(firstKey);
3975
- }
3976
- cache3.set(hash4, value);
3977
- diskCacheDirty2 = true;
4388
+ cacheInsert(hash4, value, true);
3978
4389
  }
3979
4390
  function resolveEnvEmbeddingApiKey() {
3980
4391
  return process.env.MEMORIX_EMBEDDING_API_KEY;
@@ -4093,18 +4504,27 @@ async function waitForRetry(waitMs, signal) {
4093
4504
  abortSignal.addEventListener("abort", onAbort, { once: true });
4094
4505
  });
4095
4506
  }
4096
- 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;
4507
+ 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;
4097
4508
  var init_api_provider = __esm({
4098
4509
  "src/embedding/api-provider.ts"() {
4099
4510
  "use strict";
4100
4511
  init_esm_shims();
4101
4512
  init_provider();
4102
4513
  cache3 = /* @__PURE__ */ new Map();
4103
- MAX_CACHE_SIZE3 = 1e4;
4514
+ MAX_CACHE_ENTRIES = 2e5;
4515
+ MAX_CACHE_PAYLOAD_BYTES = 256 * 1024 * 1024;
4516
+ BYTES_PER_DIM = 8;
4517
+ DEFAULT_ASSUMED_DIMENSIONS = 4096;
4518
+ MAX_MIGRATION_VALUE_BYTES = 16 * 1024 * 1024;
4519
+ cachePayloadBytes = 0;
4520
+ cacheDimensions = null;
4521
+ diskCacheEntryCount = 0;
4104
4522
  diskCacheDirty2 = false;
4105
4523
  diskSaveTimer = null;
4106
4524
  diskCacheLoaded = false;
4107
4525
  diskCacheLoadPromise = null;
4526
+ diskCacheLoadCount = 0;
4527
+ jsonlLineCountCalls = 0;
4108
4528
  MAX_INPUT_CHARS = 32e3;
4109
4529
  MAX_CONCURRENCY = 4;
4110
4530
  DISK_SAVE_DEBOUNCE_MS = 5e3;
@@ -4162,7 +4582,9 @@ var init_api_provider = __esm({
4162
4582
  if (config.requestedDimensions) {
4163
4583
  console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
4164
4584
  }
4165
- startDiskCacheLoad();
4585
+ if (allowNetworkProbe) {
4586
+ startDiskCacheLoad();
4587
+ }
4166
4588
  return new _APIEmbeddingProvider(config, probeDimensions);
4167
4589
  }
4168
4590
  static resolveConfig() {
@@ -4438,6 +4860,7 @@ __export(provider_exports, {
4438
4860
  MAX_INLINE_EMBEDDING_BYTES: () => MAX_INLINE_EMBEDDING_BYTES,
4439
4861
  UnsupportedEmbeddingModalityError: () => UnsupportedEmbeddingModalityError,
4440
4862
  getEmbeddingProvider: () => getEmbeddingProvider,
4863
+ getEmbeddingRuntimeHealth: () => getEmbeddingRuntimeHealth,
4441
4864
  isEmbeddingExplicitlyDisabled: () => isEmbeddingExplicitlyDisabled,
4442
4865
  isVectorSearchAvailable: () => isVectorSearchAvailable,
4443
4866
  resetProvider: () => resetProvider,
@@ -4499,7 +4922,12 @@ function warnOnce(message) {
4499
4922
  lastEmbeddingWarningAt = now3;
4500
4923
  console.error(message);
4501
4924
  }
4925
+ function embeddingModeToken() {
4926
+ return process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim() ?? "";
4927
+ }
4502
4928
  function getEmbeddingMode2() {
4929
+ const token = embeddingModeToken();
4930
+ if (cachedEmbeddingMode && cachedEmbeddingModeToken === token) return cachedEmbeddingMode;
4503
4931
  try {
4504
4932
  let cfg;
4505
4933
  try {
@@ -4508,10 +4936,17 @@ function getEmbeddingMode2() {
4508
4936
  cfg = (init_config(), __toCommonJS(config_exports));
4509
4937
  }
4510
4938
  const { getEmbeddingMode: cfgMode } = cfg;
4511
- return cfgMode();
4939
+ cachedEmbeddingMode = cfgMode();
4940
+ cachedEmbeddingModeToken = token;
4941
+ return cachedEmbeddingMode;
4512
4942
  } catch {
4513
- const env2 = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
4514
- if (env2 === "fastembed" || env2 === "transformers" || env2 === "api" || env2 === "auto") return env2;
4943
+ if (token === "fastembed" || token === "transformers" || token === "api" || token === "auto") {
4944
+ cachedEmbeddingMode = token;
4945
+ cachedEmbeddingModeToken = token;
4946
+ return token;
4947
+ }
4948
+ cachedEmbeddingMode = "off";
4949
+ cachedEmbeddingModeToken = token;
4515
4950
  return "off";
4516
4951
  }
4517
4952
  }
@@ -4545,13 +4980,36 @@ function isTemporaryEmbeddingFailure(error) {
4545
4980
  if (!(error instanceof Error)) return false;
4546
4981
  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);
4547
4982
  }
4983
+ function classifyEmbeddingFailure(error) {
4984
+ const message = error instanceof Error ? error.message : String(error);
4985
+ if (/embedding api error \(401\)|invalid_api_key|incorrect api key|unauthorized/i.test(message)) {
4986
+ return "authorization";
4987
+ }
4988
+ if (/embedding api error \(402\)|quota exceeded|account balance/i.test(message)) {
4989
+ return "billing";
4990
+ }
4991
+ if (/embedding api timeout|abort/i.test(message)) {
4992
+ return "timeout";
4993
+ }
4994
+ return "upstream";
4995
+ }
4548
4996
  function markTemporaryFailure(reason) {
4549
4997
  provider = null;
4550
4998
  initPromise = null;
4551
4999
  lastInitWasTemporaryFailure = true;
4552
5000
  lastFailureTimestamp = Date.now();
5001
+ lastEmbeddingFailure = {
5002
+ kind: classifyEmbeddingFailure(reason),
5003
+ occurredAt: lastFailureTimestamp
5004
+ };
4553
5005
  const message = reason instanceof Error ? reason.message : String(reason);
4554
- warnOnce(`[memorix] Embedding provider temporarily unavailable at runtime: ${message}`);
5006
+ warnOnce(`[memorix] Embedding provider unavailable at runtime: ${message}`);
5007
+ }
5008
+ function markProviderReady(candidate) {
5009
+ provider = wrapProvider(candidate);
5010
+ lastInitWasTemporaryFailure = false;
5011
+ lastEmbeddingFailure = null;
5012
+ return provider;
4555
5013
  }
4556
5014
  async function createFastEmbedProvider() {
4557
5015
  try {
@@ -4667,35 +5125,35 @@ async function getEmbeddingProvider(options = {}) {
4667
5125
  if (mode === "fastembed") {
4668
5126
  const initialized = await createFastEmbedProvider();
4669
5127
  if (!initialized) return null;
4670
- provider = wrapProvider(initialized);
5128
+ provider = markProviderReady(initialized);
4671
5129
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4672
5130
  return provider;
4673
5131
  }
4674
5132
  if (mode === "transformers") {
4675
5133
  const initialized = await createTransformersProvider();
4676
5134
  if (!initialized) return null;
4677
- provider = wrapProvider(initialized);
5135
+ provider = markProviderReady(initialized);
4678
5136
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4679
5137
  return provider;
4680
5138
  }
4681
5139
  if (mode === "api") {
4682
5140
  const initialized = await createAPIProvider(options);
4683
5141
  if (!initialized) return null;
4684
- provider = wrapProvider(initialized);
5142
+ provider = markProviderReady(initialized);
4685
5143
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4686
5144
  return provider;
4687
5145
  }
4688
5146
  if (hasAPIEmbeddingConfig()) {
4689
5147
  const initialized = await createAPIProvider(options);
4690
5148
  if (initialized) {
4691
- provider = wrapProvider(initialized);
5149
+ provider = markProviderReady(initialized);
4692
5150
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4693
5151
  return provider;
4694
5152
  }
4695
5153
  }
4696
5154
  const localFallback = await createLocalFallbackProvider();
4697
5155
  if (localFallback) {
4698
- provider = wrapProvider(localFallback);
5156
+ provider = markProviderReady(localFallback);
4699
5157
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4700
5158
  return provider;
4701
5159
  }
@@ -4717,18 +5175,42 @@ async function isVectorSearchAvailable() {
4717
5175
  const p = await getEmbeddingProvider();
4718
5176
  return p !== null;
4719
5177
  }
5178
+ function getEmbeddingRuntimeHealth() {
5179
+ const mode = getEmbeddingMode2();
5180
+ if (mode === "off") return { status: "disabled" };
5181
+ if (lastInitWasTemporaryFailure && lastEmbeddingFailure) {
5182
+ return {
5183
+ status: "degraded",
5184
+ failure: {
5185
+ kind: lastEmbeddingFailure.kind,
5186
+ occurredAt: new Date(lastEmbeddingFailure.occurredAt).toISOString(),
5187
+ retryAfterMs: Math.max(0, RETRY_COOLDOWN_MS - (Date.now() - lastEmbeddingFailure.occurredAt))
5188
+ }
5189
+ };
5190
+ }
5191
+ if (provider) {
5192
+ return {
5193
+ status: "ready",
5194
+ provider: { name: provider.name, dimensions: provider.dimensions }
5195
+ };
5196
+ }
5197
+ return { status: "uninitialized" };
5198
+ }
4720
5199
  function isEmbeddingExplicitlyDisabled() {
4721
5200
  return getEmbeddingMode2() === "off";
4722
5201
  }
4723
5202
  function resetProvider() {
4724
5203
  provider = null;
4725
5204
  initPromise = null;
5205
+ cachedEmbeddingMode = null;
5206
+ cachedEmbeddingModeToken = null;
4726
5207
  lastInitWasTemporaryFailure = false;
4727
5208
  lastFailureTimestamp = 0;
5209
+ lastEmbeddingFailure = null;
4728
5210
  lastEmbeddingWarning = "";
4729
5211
  lastEmbeddingWarningAt = 0;
4730
5212
  }
4731
- var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
5213
+ var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, cachedEmbeddingMode, cachedEmbeddingModeToken, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
4732
5214
  var init_provider = __esm({
4733
5215
  "src/embedding/provider.ts"() {
4734
5216
  "use strict";
@@ -4752,7 +5234,10 @@ var init_provider = __esm({
4752
5234
  MAX_INLINE_EMBEDDING_BYTES = 5 * 1024 * 1024;
4753
5235
  provider = null;
4754
5236
  initPromise = null;
5237
+ cachedEmbeddingMode = null;
5238
+ cachedEmbeddingModeToken = null;
4755
5239
  lastInitWasTemporaryFailure = false;
5240
+ lastEmbeddingFailure = null;
4756
5241
  lastEmbeddingWarning = "";
4757
5242
  lastEmbeddingWarningAt = 0;
4758
5243
  WARNING_COOLDOWN_MS = 3e4;
@@ -6508,6 +6993,9 @@ async function attachCachedVectors(database, candidates) {
6508
6993
  await update(database, candidates[index].id, { ...existing, embedding: vector });
6509
6994
  } catch {
6510
6995
  }
6996
+ if (index % 50 === 0) {
6997
+ await new Promise((resolve2) => setImmediate(resolve2));
6998
+ }
6511
6999
  }
6512
7000
  }
6513
7001
  async function hydrateIndex(observations2, options = {}) {
@@ -6558,17 +7046,24 @@ async function hydrateIndex(observations2, options = {}) {
6558
7046
  await insert2(database, doc);
6559
7047
  rememberObservationDoc(doc);
6560
7048
  inserted++;
7049
+ if (inserted % 50 === 0) {
7050
+ await new Promise((resolve2) => setImmediate(resolve2));
7051
+ }
7052
+ if (inserted % 200 === 0) {
7053
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
7054
+ }
6561
7055
  } catch {
6562
7056
  }
6563
7057
  }
6564
- deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
7058
+ deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6565
7059
  }) : null;
6566
7060
  return inserted;
6567
7061
  }
6568
- async function hydrateIndexForStartup(observations2) {
7062
+ async function hydrateIndexForStartup(observations2, options = {}) {
6569
7063
  return hydrateIndex(observations2, {
6570
7064
  allowNetworkProbe: false,
6571
- deferCachedVectors: true
7065
+ deferCachedVectors: true,
7066
+ skipCachedVectors: options.skipCachedVectors
6572
7067
  });
6573
7068
  }
6574
7069
  function getDeferredCachedVectorHydration() {
@@ -9891,7 +10386,7 @@ __export(asset_store_exports, {
9891
10386
  removeMediaAsset: () => removeMediaAsset
9892
10387
  });
9893
10388
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
9894
- import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
10389
+ import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
9895
10390
  import path13 from "path";
9896
10391
  function mediaRoot(dataDir) {
9897
10392
  return path13.resolve(dataDir, "media");
@@ -9981,7 +10476,7 @@ async function persistFile(source, destination) {
9981
10476
  const temporary = `${destination}.${randomUUID5()}.tmp`;
9982
10477
  try {
9983
10478
  await copyFile(source, temporary);
9984
- await rename(temporary, destination);
10479
+ await rename2(temporary, destination);
9985
10480
  return true;
9986
10481
  } catch (error) {
9987
10482
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -9997,7 +10492,7 @@ async function persistBuffer(bytes, destination) {
9997
10492
  const temporary = `${destination}.${randomUUID5()}.tmp`;
9998
10493
  try {
9999
10494
  await writeFile3(temporary, bytes, { flag: "wx" });
10000
- await rename(temporary, destination);
10495
+ await rename2(temporary, destination);
10001
10496
  return true;
10002
10497
  } catch (error) {
10003
10498
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -10037,11 +10532,11 @@ async function importKnownMedia(input) {
10037
10532
  async function importMediaFile(input) {
10038
10533
  const maxBytes = input.maxBytes ?? DEFAULT_MAX_MEDIA_BYTES;
10039
10534
  const source = path13.resolve(input.filePath);
10040
- const stat4 = await lstat(source);
10041
- if (stat4.isSymbolicLink()) throw new Error("Media import does not follow symbolic links");
10042
- if (!stat4.isFile()) throw new Error("Media import accepts regular files only");
10043
- if (stat4.size <= 0) throw new Error("Media import rejects empty files");
10044
- if (stat4.size > maxBytes) throw new Error(`Media file is too large (${stat4.size} bytes; limit ${maxBytes})`);
10535
+ const stat5 = await lstat(source);
10536
+ if (stat5.isSymbolicLink()) throw new Error("Media import does not follow symbolic links");
10537
+ if (!stat5.isFile()) throw new Error("Media import accepts regular files only");
10538
+ if (stat5.size <= 0) throw new Error("Media import rejects empty files");
10539
+ if (stat5.size > maxBytes) throw new Error(`Media file is too large (${stat5.size} bytes; limit ${maxBytes})`);
10045
10540
  const header = await readHeader(source);
10046
10541
  const detected = detectMedia(header);
10047
10542
  const sha256 = await sha256File(source);
@@ -10049,7 +10544,7 @@ async function importMediaFile(input) {
10049
10544
  dataDir: input.dataDir,
10050
10545
  projectId: input.projectId,
10051
10546
  sha256,
10052
- bytes: stat4.size,
10547
+ bytes: stat5.size,
10053
10548
  detected,
10054
10549
  persist: (destination) => persistFile(source, destination),
10055
10550
  sourceKind: input.sourceKind ?? "import",
@@ -10096,13 +10591,13 @@ async function removeMediaAsset(input) {
10096
10591
  const assetPath = getMediaAssetPath(input.dataDir, asset);
10097
10592
  const stagedPath = deletionStagingPath(mediaRoot(input.dataDir), asset.id);
10098
10593
  await mkdir3(path13.dirname(stagedPath), { recursive: true });
10099
- await rename(assetPath, stagedPath);
10594
+ await rename2(assetPath, stagedPath);
10100
10595
  let detachedLinks;
10101
10596
  try {
10102
10597
  ({ detachedLinks } = store.removeAssetMetadata(input.projectId, asset.id, { force: input.force }));
10103
10598
  } catch (error) {
10104
10599
  try {
10105
- await rename(stagedPath, assetPath);
10600
+ await rename2(stagedPath, assetPath);
10106
10601
  } catch (restoreError) {
10107
10602
  const original = error instanceof Error ? error.message : String(error);
10108
10603
  const restore = restoreError instanceof Error ? restoreError.message : String(restoreError);
@@ -10138,7 +10633,7 @@ async function cleanupMediaTrash(dataDir) {
10138
10633
  } catch (error) {
10139
10634
  if (error?.code === "ENOENT") continue;
10140
10635
  try {
10141
- pendingBytes += (await stat(candidate)).size;
10636
+ pendingBytes += (await stat2(candidate)).size;
10142
10637
  } catch {
10143
10638
  }
10144
10639
  pendingFiles += 1;
@@ -61220,9 +61715,9 @@ var require_node_domexception = __commonJS({
61220
61715
  });
61221
61716
 
61222
61717
  // node_modules/fetch-blob/from.js
61223
- import { statSync as statSync2, createReadStream, promises as fs10 } from "fs";
61718
+ import { statSync as statSync2, createReadStream as createReadStream2, promises as fs10 } from "fs";
61224
61719
  import { basename } from "path";
61225
- var import_node_domexception, stat2, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
61720
+ var import_node_domexception, stat3, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
61226
61721
  var init_from = __esm({
61227
61722
  "node_modules/fetch-blob/from.js"() {
61228
61723
  "use strict";
@@ -61230,23 +61725,23 @@ var init_from = __esm({
61230
61725
  import_node_domexception = __toESM(require_node_domexception(), 1);
61231
61726
  init_file();
61232
61727
  init_fetch_blob();
61233
- ({ stat: stat2 } = fs10);
61728
+ ({ stat: stat3 } = fs10);
61234
61729
  blobFromSync = (path37, type) => fromBlob(statSync2(path37), path37, type);
61235
- blobFrom = (path37, type) => stat2(path37).then((stat4) => fromBlob(stat4, path37, type));
61236
- fileFrom = (path37, type) => stat2(path37).then((stat4) => fromFile(stat4, path37, type));
61730
+ blobFrom = (path37, type) => stat3(path37).then((stat5) => fromBlob(stat5, path37, type));
61731
+ fileFrom = (path37, type) => stat3(path37).then((stat5) => fromFile(stat5, path37, type));
61237
61732
  fileFromSync = (path37, type) => fromFile(statSync2(path37), path37, type);
61238
- fromBlob = (stat4, path37, type = "") => new fetch_blob_default([new BlobDataItem({
61733
+ fromBlob = (stat5, path37, type = "") => new fetch_blob_default([new BlobDataItem({
61239
61734
  path: path37,
61240
- size: stat4.size,
61241
- lastModified: stat4.mtimeMs,
61735
+ size: stat5.size,
61736
+ lastModified: stat5.mtimeMs,
61242
61737
  start: 0
61243
61738
  })], { type });
61244
- fromFile = (stat4, path37, type = "") => new file_default([new BlobDataItem({
61739
+ fromFile = (stat5, path37, type = "") => new file_default([new BlobDataItem({
61245
61740
  path: path37,
61246
- size: stat4.size,
61247
- lastModified: stat4.mtimeMs,
61741
+ size: stat5.size,
61742
+ lastModified: stat5.mtimeMs,
61248
61743
  start: 0
61249
- })], basename(path37), { type, lastModified: stat4.mtimeMs });
61744
+ })], basename(path37), { type, lastModified: stat5.mtimeMs });
61250
61745
  BlobDataItem = class _BlobDataItem {
61251
61746
  #path;
61252
61747
  #start;
@@ -61269,11 +61764,11 @@ var init_from = __esm({
61269
61764
  });
61270
61765
  }
61271
61766
  async *stream() {
61272
- const { mtimeMs } = await stat2(this.#path);
61767
+ const { mtimeMs } = await stat3(this.#path);
61273
61768
  if (mtimeMs > this.lastModified) {
61274
61769
  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");
61275
61770
  }
61276
- yield* createReadStream(this.#path, {
61771
+ yield* createReadStream2(this.#path, {
61277
61772
  start: this.#start,
61278
61773
  end: this.#start + this.size - 1
61279
61774
  });
@@ -76903,7 +77398,7 @@ var init_wrapper = __esm({
76903
77398
  });
76904
77399
 
76905
77400
  // node_modules/@google/genai/dist/node/index.mjs
76906
- import { createWriteStream } from "fs";
77401
+ import { createWriteStream as createWriteStream2 } from "fs";
76907
77402
  import * as fs11 from "fs/promises";
76908
77403
  import { writeFile as writeFile4 } from "fs/promises";
76909
77404
  import { Readable } from "stream";
@@ -94183,7 +94678,7 @@ ${underline}`);
94183
94678
  if (params.downloadPath) {
94184
94679
  const response = await downloadFile(params, apiClient);
94185
94680
  if (response instanceof HttpResponse) {
94186
- const writer = createWriteStream(params.downloadPath);
94681
+ const writer = createWriteStream2(params.downloadPath);
94187
94682
  const body = Readable.fromWeb(response.responseInternal.body);
94188
94683
  body.pipe(writer);
94189
94684
  await finished(writer);
@@ -144217,7 +144712,7 @@ function explainRetention(doc, referenceTime) {
144217
144712
  };
144218
144713
  }
144219
144714
  function toRetentionDocument(obs, accessMap) {
144220
- const access2 = accessMap?.get(obs.id);
144715
+ const access3 = accessMap?.get(obs.id);
144221
144716
  return {
144222
144717
  id: `obs-${obs.id}`,
144223
144718
  observationId: obs.id,
@@ -144231,8 +144726,8 @@ function toRetentionDocument(obs, accessMap) {
144231
144726
  tokens: obs.tokens,
144232
144727
  createdAt: obs.createdAt,
144233
144728
  projectId: obs.projectId,
144234
- accessCount: access2?.accessCount ?? 0,
144235
- lastAccessedAt: access2?.lastAccessedAt ?? "",
144729
+ accessCount: access3?.accessCount ?? 0,
144730
+ lastAccessedAt: access3?.lastAccessedAt ?? "",
144236
144731
  status: obs.status ?? "active",
144237
144732
  source: obs.source ?? "agent",
144238
144733
  sourceDetail: obs.sourceDetail ?? "",
@@ -146346,7 +146841,7 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
146346
146841
  const rel = normalizeCodePath(relative(projectRoot, abs));
146347
146842
  try {
146348
146843
  const text2 = readFileSync6(abs, "utf-8");
146349
- const stat4 = fileStat ?? statSync3(abs);
146844
+ const stat5 = fileStat ?? statSync3(abs);
146350
146845
  const file = {
146351
146846
  id: makeCodeFileId(projectId, rel),
146352
146847
  projectId,
@@ -146355,8 +146850,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
146355
146850
  contentHash: hashText(text2),
146356
146851
  // Preserve filesystem precision. Rounding here made same-size edits in
146357
146852
  // a short Windows timestamp window indistinguishable to refresh.
146358
- mtimeMs: Number(stat4.mtimeMs),
146359
- sizeBytes: Number(stat4.size),
146853
+ mtimeMs: Number(stat5.mtimeMs),
146854
+ sizeBytes: Number(stat5.size),
146360
146855
  indexedAt
146361
146856
  };
146362
146857
  return {
@@ -146383,18 +146878,18 @@ async function indexProjectLite(options) {
146383
146878
  let skippedOversizedFiles = 0;
146384
146879
  let unreadableFiles = 0;
146385
146880
  for (const abs of paths) {
146386
- let stat4;
146881
+ let stat5;
146387
146882
  try {
146388
- stat4 = statSync3(abs);
146883
+ stat5 = statSync3(abs);
146389
146884
  } catch {
146390
146885
  unreadableFiles++;
146391
146886
  continue;
146392
146887
  }
146393
- if (Number(stat4.size) > maxFileBytes) {
146888
+ if (Number(stat5.size) > maxFileBytes) {
146394
146889
  skippedOversizedFiles++;
146395
146890
  continue;
146396
146891
  }
146397
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
146892
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
146398
146893
  if (!attempt.delta) {
146399
146894
  if (attempt.unreadable) unreadableFiles++;
146400
146895
  continue;
@@ -146422,16 +146917,16 @@ async function refreshProjectLite(store, options) {
146422
146917
  for (const abs of paths) {
146423
146918
  const rel = normalizeCodePath(relative(options.projectRoot, abs));
146424
146919
  seenPaths.add(rel);
146425
- let stat4;
146920
+ let stat5;
146426
146921
  try {
146427
- stat4 = statSync3(abs);
146922
+ stat5 = statSync3(abs);
146428
146923
  } catch {
146429
146924
  unreadableFiles++;
146430
146925
  continue;
146431
146926
  }
146432
146927
  const existing = existingByPath.get(rel);
146433
- const mtimeMs = Number(stat4.mtimeMs);
146434
- const sizeBytes = Number(stat4.size);
146928
+ const mtimeMs = Number(stat5.mtimeMs);
146929
+ const sizeBytes = Number(stat5.size);
146435
146930
  if (sizeBytes > maxFileBytes) {
146436
146931
  skippedOversizedFiles++;
146437
146932
  if (existing) oversizedExistingFileIds.add(existing.id);
@@ -146441,7 +146936,7 @@ async function refreshProjectLite(store, options) {
146441
146936
  unchangedFiles++;
146442
146937
  continue;
146443
146938
  }
146444
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
146939
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
146445
146940
  if (!attempt.delta) {
146446
146941
  if (attempt.unreadable) unreadableFiles++;
146447
146942
  continue;
@@ -150189,12 +150684,25 @@ __export(observations_exports, {
150189
150684
  prepareSearchIndex: () => prepareSearchIndex,
150190
150685
  probeSearchIndex: () => probeSearchIndex,
150191
150686
  reindexObservations: () => reindexObservations,
150687
+ resetObservationRuntime: () => resetObservationRuntime,
150192
150688
  resolveObservations: () => resolveObservations,
150193
150689
  storeObservation: () => storeObservation,
150194
150690
  suggestTopicKey: () => suggestTopicKey,
150195
150691
  updateObservationAdmission: () => updateObservationAdmission,
150196
150692
  withFreshObservations: () => withFreshObservations
150197
150693
  });
150694
+ function resetObservationRuntime() {
150695
+ observations = [];
150696
+ nextId = 1;
150697
+ projectDir = null;
150698
+ searchIndexPrepared = false;
150699
+ corpusLoaded = false;
150700
+ loadedObservationStore = null;
150701
+ embeddingWriteMode = "background";
150702
+ embeddingWorkerProjectRoot = void 0;
150703
+ vectorMissingIds.clear();
150704
+ vectorSchemaUpgradePromise = null;
150705
+ }
150198
150706
  function logEmbeddingFailureOnce(key, message) {
150199
150707
  const now3 = Date.now();
150200
150708
  const last = embeddingFailureLogTimestamps.get(key) ?? 0;
@@ -150303,23 +150811,33 @@ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
150303
150811
  async function initObservations(dir, options = {}) {
150304
150812
  embeddingWriteMode = options.embeddingWriteMode ?? "background";
150305
150813
  embeddingWorkerProjectRoot = options.projectRoot;
150306
- if (projectDir === dir) return;
150307
150814
  await initObservationStore(dir);
150308
150815
  const store = getObservationStore();
150309
- observations = await store.loadAll();
150816
+ if (projectDir === dir && loadedObservationStore === store) {
150817
+ if (options.skipCorpusLoad || corpusLoaded) return;
150818
+ }
150310
150819
  nextId = await store.loadIdCounter();
150311
150820
  projectDir = dir;
150312
150821
  searchIndexPrepared = false;
150313
150822
  vectorSchemaUpgradePromise = null;
150823
+ if (options.skipCorpusLoad) {
150824
+ observations = [];
150825
+ corpusLoaded = false;
150826
+ loadedObservationStore = store;
150827
+ return;
150828
+ }
150829
+ observations = await store.loadAll();
150830
+ corpusLoaded = true;
150831
+ loadedObservationStore = store;
150314
150832
  }
150315
150833
  function scheduleObservationEmbedding(input) {
150316
150834
  const { observationId: observationId2, projectId, searchableText, doc } = input;
150317
150835
  vectorMissingIds.add(observationId2);
150836
+ if (isEmbeddingExplicitlyDisabled()) {
150837
+ vectorMissingIds.delete(observationId2);
150838
+ return;
150839
+ }
150318
150840
  if (embeddingWriteMode === "deferred") {
150319
- if (isEmbeddingExplicitlyDisabled()) {
150320
- vectorMissingIds.delete(observationId2);
150321
- return;
150322
- }
150323
150841
  queueVectorBackfill(projectId, {
150324
150842
  detachedWorker: true,
150325
150843
  projectRoot: embeddingWorkerProjectRoot
@@ -150366,7 +150884,7 @@ function scheduleObservationEmbedding(input) {
150366
150884
  });
150367
150885
  }
150368
150886
  async function ensureFreshObservations() {
150369
- if (!projectDir) return false;
150887
+ if (!projectDir || !corpusLoaded) return false;
150370
150888
  try {
150371
150889
  const store = getObservationStore();
150372
150890
  const wasStale = await store.ensureFresh();
@@ -150407,7 +150925,7 @@ async function storeObservation(input) {
150407
150925
  });
150408
150926
  if (safeAttachments) input = { ...input, attachments: safeAttachments };
150409
150927
  await ensureFreshObservations();
150410
- if (input.topicKey) {
150928
+ if (input.topicKey && corpusLoaded) {
150411
150929
  const existing = observations.find(
150412
150930
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
150413
150931
  );
@@ -150504,13 +151022,17 @@ async function storeObservation(input) {
150504
151022
  });
150505
151023
  observation.writeGeneration = store.getGeneration();
150506
151024
  if (upsertedInsideLock || reloadCacheAfterCommit) {
150507
- observations = await store.loadAll();
150508
- nextId = await store.loadIdCounter();
150509
- if (upsertedInsideLock) {
150510
- observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
151025
+ if (corpusLoaded) {
151026
+ observations = await store.loadAll();
151027
+ nextId = await store.loadIdCounter();
151028
+ if (upsertedInsideLock) {
151029
+ observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
151030
+ }
151031
+ } else {
151032
+ nextId = await store.loadIdCounter();
150511
151033
  }
150512
151034
  } else {
150513
- observations.push(observation);
151035
+ if (corpusLoaded) observations.push(observation);
150514
151036
  nextId = observation.id + 1;
150515
151037
  }
150516
151038
  if (upsertedInsideLock) return;
@@ -150578,7 +151100,9 @@ async function storeObservation(input) {
150578
151100
  createdByAgentId: input.createdByAgentId ?? "",
150579
151101
  sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
150580
151102
  };
150581
- await insertObservation(doc);
151103
+ if (corpusLoaded) {
151104
+ await insertObservation(doc);
151105
+ }
150582
151106
  };
150583
151107
  await assignAndPersist();
150584
151108
  if (upsertedInsideLock) {
@@ -150940,9 +151464,11 @@ async function reindexObservations() {
150940
151464
  searchIndexPrepared = true;
150941
151465
  return count2;
150942
151466
  }
150943
- async function prepareSearchIndex() {
151467
+ async function prepareSearchIndex(options = {}) {
150944
151468
  if (searchIndexPrepared) return 0;
150945
- const count2 = await hydrateIndexForStartup(observations);
151469
+ const count2 = await hydrateIndexForStartup(observations, {
151470
+ skipCachedVectors: options.skipCachedVectors
151471
+ });
150946
151472
  if (count2 === 0) {
150947
151473
  searchIndexPrepared = true;
150948
151474
  return 0;
@@ -151134,7 +151660,7 @@ async function backfillVectorEmbeddings(options = {}) {
151134
151660
  ...lastFailure ? { lastError: lastFailure } : {}
151135
151661
  };
151136
151662
  }
151137
- var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
151663
+ var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, corpusLoaded, loadedObservationStore, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
151138
151664
  var init_observations = __esm({
151139
151665
  "src/memory/observations.ts"() {
151140
151666
  "use strict";
@@ -151153,6 +151679,8 @@ var init_observations = __esm({
151153
151679
  projectDir = null;
151154
151680
  searchIndexPrepared = false;
151155
151681
  embeddingWriteMode = "background";
151682
+ corpusLoaded = false;
151683
+ loadedObservationStore = null;
151156
151684
  vectorMissingIds = /* @__PURE__ */ new Set();
151157
151685
  vectorBackfillRunning = false;
151158
151686
  vectorSchemaUpgradePromise = null;
@@ -153741,8 +154269,8 @@ var init_syncer = __esm({
153741
154269
  const dir = path25.dirname(pattern);
153742
154270
  const fileGlob = path25.basename(pattern);
153743
154271
  try {
153744
- const stat4 = await fs17.stat(dir);
153745
- if (!stat4.isDirectory()) {
154272
+ const stat5 = await fs17.stat(dir);
154273
+ if (!stat5.isDirectory()) {
153746
154274
  try {
153747
154275
  await fs17.access(pattern);
153748
154276
  return [pattern];
@@ -159333,9 +159861,9 @@ function existingLensCandidates(rootPath, lens) {
159333
159861
  const absolute = path28.join(rootPath, candidate);
159334
159862
  try {
159335
159863
  if (!existsSync16(absolute)) continue;
159336
- const stat4 = statSync4(absolute);
159337
- if (stat4.isFile()) out.push(candidate);
159338
- if (stat4.isDirectory()) out.push(candidate.replace(/\\/g, "/"));
159864
+ const stat5 = statSync4(absolute);
159865
+ if (stat5.isFile()) out.push(candidate);
159866
+ if (stat5.isDirectory()) out.push(candidate.replace(/\\/g, "/"));
159339
159867
  } catch {
159340
159868
  }
159341
159869
  }
@@ -165155,13 +165683,13 @@ async function installHooks(agent, projectRoot, global2 = false) {
165155
165683
  const agentPrev = prevFiles.filter((e2) => e2.agent === agent);
165156
165684
  for (const entry of agentPrev) {
165157
165685
  try {
165158
- const { access: access2, unlink: unlink2 } = await import("fs/promises");
165159
- await access2(entry.path);
165686
+ const { access: access3, unlink: unlink3 } = await import("fs/promises");
165687
+ await access3(entry.path);
165160
165688
  const basename5 = path32.basename(entry.path);
165161
165689
  if (basename5 === "AGENTS.md" || basename5 === "GEMINI.md" || basename5 === "CONTEXT.md") {
165162
165690
  continue;
165163
165691
  }
165164
- await unlink2(entry.path);
165692
+ await unlink3(entry.path);
165165
165693
  await removeFile2(projectRoot, entry.path);
165166
165694
  } catch {
165167
165695
  }
@@ -165681,11 +166209,11 @@ import path33 from "path";
165681
166209
  function resolveGitDir(projectRoot) {
165682
166210
  const dotGit = path33.join(projectRoot, ".git");
165683
166211
  if (!existsSync19(dotGit)) return null;
165684
- const stat4 = statSync5(dotGit);
165685
- if (stat4.isDirectory()) {
166212
+ const stat5 = statSync5(dotGit);
166213
+ if (stat5.isDirectory()) {
165686
166214
  return dotGit;
165687
166215
  }
165688
- if (stat4.isFile()) {
166216
+ if (stat5.isFile()) {
165689
166217
  try {
165690
166218
  const content = readFileSync13(dotGit, "utf-8").trim();
165691
166219
  const match2 = content.match(/^gitdir:\s*(.+)$/);
@@ -166096,7 +166624,7 @@ The path should point to a directory containing a .git folder.`
166096
166624
  };
166097
166625
  const server = existingServer ?? new McpServer({
166098
166626
  name: "memorix",
166099
- version: true ? "1.6.0" : "1.0.1"
166627
+ version: true ? "1.6.2" : "1.0.1"
166100
166628
  });
166101
166629
  const originalRegisterTool = server.registerTool.bind(server);
166102
166630
  server.registerTool = ((name, ...args) => {
@@ -170502,7 +171030,7 @@ var init_serve_shared = __esm({
170502
171030
 
170503
171031
  // src/cli/version.ts
170504
171032
  function getCliVersion() {
170505
- return true ? "1.6.0" : pkg.version;
171033
+ return true ? "1.6.2" : pkg.version;
170506
171034
  }
170507
171035
  var init_version3 = __esm({
170508
171036
  "src/cli/version.ts"() {