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
@@ -2352,8 +2352,8 @@ async function acquireLock(lockPath) {
2352
2352
  const code = err instanceof Error && "code" in err ? err.code : void 0;
2353
2353
  if (code === "EEXIST" || code === "EPERM") {
2354
2354
  try {
2355
- const stat4 = await fs2.stat(lockPath);
2356
- if (Date.now() - stat4.mtimeMs > LOCK_STALE_MS) {
2355
+ const stat5 = await fs2.stat(lockPath);
2356
+ if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
2357
2357
  await fs2.unlink(lockPath).catch(() => {
2358
2358
  });
2359
2359
  continue;
@@ -3464,24 +3464,131 @@ var init_transformers_provider = __esm({
3464
3464
  // src/embedding/api-provider.ts
3465
3465
  var api_provider_exports = {};
3466
3466
  __export(api_provider_exports, {
3467
- APIEmbeddingProvider: () => APIEmbeddingProvider
3467
+ APIEmbeddingProvider: () => APIEmbeddingProvider,
3468
+ convertArrayCacheToJsonl: () => convertArrayCacheToJsonl,
3469
+ ensureDiskCacheLoaded: () => ensureDiskCacheLoaded,
3470
+ getApiEmbeddingCachePayloadBytesForTests: () => getApiEmbeddingCachePayloadBytesForTests,
3471
+ getApiEmbeddingDiskCacheLoadCountForTests: () => getApiEmbeddingDiskCacheLoadCountForTests,
3472
+ getApiEmbeddingJsonlLineCountCallsForTests: () => getApiEmbeddingJsonlLineCountCallsForTests,
3473
+ getApiEmbeddingMaxEntriesForDimensionsForTests: () => getApiEmbeddingMaxEntriesForDimensionsForTests,
3474
+ migrateJsonArrayCacheToJsonl: () => migrateJsonArrayCacheToJsonl,
3475
+ parseCacheJsonInWorker: () => parseCacheJsonInWorker,
3476
+ resetApiEmbeddingCacheForTests: () => resetApiEmbeddingCacheForTests,
3477
+ saveDiskCacheNow: () => saveDiskCacheNow,
3478
+ seedApiEmbeddingCacheForTests: () => seedApiEmbeddingCacheForTests
3468
3479
  });
3469
3480
  import { createHash as createHash2 } from "crypto";
3470
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3481
+ import { createReadStream, createWriteStream, constants as fsConstants } from "fs";
3482
+ import { access, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rename, stat, unlink } from "fs/promises";
3471
3483
  import { join as join3 } from "path";
3472
3484
  import { homedir as homedir7 } from "os";
3485
+ import { createInterface } from "readline";
3473
3486
  function cacheDir() {
3474
3487
  return process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
3475
3488
  }
3476
3489
  function cacheFile() {
3477
3490
  return join3(cacheDir(), ".embedding-api-cache.json");
3478
3491
  }
3492
+ function cacheJsonlFile() {
3493
+ return join3(cacheDir(), ".embedding-api-cache.jsonl");
3494
+ }
3479
3495
  function dimsCacheFile() {
3480
3496
  return join3(cacheDir(), ".embedding-dims-cache.json");
3481
3497
  }
3482
3498
  function cacheMetaFile() {
3483
3499
  return join3(cacheDir(), ".embedding-api-cache-meta.json");
3484
3500
  }
3501
+ function cacheStatFile() {
3502
+ return join3(cacheDir(), ".embedding-api-cache-stat.json");
3503
+ }
3504
+ function payloadBytes(vector) {
3505
+ return vector.length * BYTES_PER_DIM;
3506
+ }
3507
+ function maxCachePayloadBytes() {
3508
+ const raw = process.env.MEMORIX_EMBEDDING_CACHE_MAX_BYTES;
3509
+ if (raw) {
3510
+ const parsed = Number.parseInt(raw, 10);
3511
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
3512
+ }
3513
+ return MAX_CACHE_PAYLOAD_BYTES;
3514
+ }
3515
+ function maxEntriesForDimensions(dimensions) {
3516
+ const dim = Math.max(1, Math.floor(dimensions));
3517
+ const byBytes = Math.floor(maxCachePayloadBytes() / (dim * BYTES_PER_DIM));
3518
+ return Math.max(1, Math.min(MAX_CACHE_ENTRIES, byBytes));
3519
+ }
3520
+ function rememberDimensions(vector) {
3521
+ if (vector.length > 0 && (cacheDimensions === null || vector.length > cacheDimensions)) {
3522
+ cacheDimensions = vector.length;
3523
+ }
3524
+ }
3525
+ function evictOldest() {
3526
+ const firstKey = cache3.keys().next().value;
3527
+ if (firstKey === void 0) return;
3528
+ const old = cache3.get(firstKey);
3529
+ cache3.delete(firstKey);
3530
+ if (old) cachePayloadBytes -= payloadBytes(old);
3531
+ }
3532
+ function evictToFit(incomingBytes, incomingDims) {
3533
+ const limit2 = maxEntriesForDimensions(incomingDims);
3534
+ while (cache3.size > 0 && (cache3.size >= limit2 || cachePayloadBytes + incomingBytes > maxCachePayloadBytes())) {
3535
+ evictOldest();
3536
+ }
3537
+ }
3538
+ function isNumberArray(value) {
3539
+ return Array.isArray(value) && value.every((entry) => typeof entry === "number");
3540
+ }
3541
+ function parseCacheRow(raw) {
3542
+ if (Array.isArray(raw)) {
3543
+ const hash4 = raw[0];
3544
+ const vector = raw[1];
3545
+ if (typeof hash4 === "string" && isNumberArray(vector)) {
3546
+ return { hash: hash4, vector };
3547
+ }
3548
+ return null;
3549
+ }
3550
+ if (raw && typeof raw === "object") {
3551
+ const row = raw;
3552
+ if (typeof row.h === "string" && isNumberArray(row.v)) {
3553
+ return { hash: row.h, vector: row.v };
3554
+ }
3555
+ }
3556
+ return null;
3557
+ }
3558
+ function cacheInsert(hash4, value, dirty) {
3559
+ rememberDimensions(value);
3560
+ const incoming = payloadBytes(value);
3561
+ const existing = cache3.get(hash4);
3562
+ if (existing) {
3563
+ cachePayloadBytes -= payloadBytes(existing);
3564
+ cache3.delete(hash4);
3565
+ }
3566
+ evictToFit(incoming, value.length || cacheDimensions || DEFAULT_ASSUMED_DIMENSIONS);
3567
+ cache3.set(hash4, value);
3568
+ cachePayloadBytes += incoming;
3569
+ if (dirty) diskCacheDirty2 = true;
3570
+ }
3571
+ async function readCacheStat() {
3572
+ try {
3573
+ const data = JSON.parse(await readFile2(cacheStatFile(), "utf-8"));
3574
+ if (typeof data.entryCount === "number" && Number.isFinite(data.entryCount) && data.entryCount >= 0) {
3575
+ return {
3576
+ version: 1,
3577
+ entryCount: Math.floor(data.entryCount),
3578
+ dimensions: isValidDimension(data.dimensions) ? data.dimensions : null
3579
+ };
3580
+ }
3581
+ } catch {
3582
+ }
3583
+ return null;
3584
+ }
3585
+ async function writeCacheStat(stat5) {
3586
+ try {
3587
+ await mkdir2(cacheDir(), { recursive: true });
3588
+ await writeFile2(cacheStatFile(), JSON.stringify(stat5));
3589
+ } catch {
3590
+ }
3591
+ }
3485
3592
  function normalizeText(text2) {
3486
3593
  return text2.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3487
3594
  }
@@ -3567,29 +3674,300 @@ function toProviderInput(input, baseUrl) {
3567
3674
  }
3568
3675
  return { type: input.modality, url: input.url };
3569
3676
  }
3677
+ function parseCacheJsonInWorker(filePath) {
3678
+ return new Promise((resolve, reject) => {
3679
+ void import("worker_threads").then(({ Worker }) => {
3680
+ const worker = new Worker(
3681
+ `
3682
+ const { parentPort, workerData } = require('node:worker_threads');
3683
+ const { readFileSync } = require('node:fs');
3684
+ try {
3685
+ const raw = readFileSync(workerData.filePath, 'utf8');
3686
+ parentPort.postMessage({ ok: true, entries: JSON.parse(raw) });
3687
+ } catch (err) {
3688
+ parentPort.postMessage({
3689
+ ok: false,
3690
+ error: err instanceof Error ? err.message : String(err),
3691
+ });
3692
+ }
3693
+ `,
3694
+ { eval: true, workerData: { filePath } }
3695
+ );
3696
+ worker.once("message", (msg) => {
3697
+ void worker.terminate();
3698
+ if (msg?.ok && Array.isArray(msg.entries)) {
3699
+ resolve(msg.entries);
3700
+ return;
3701
+ }
3702
+ reject(new Error(msg?.error || "embedding cache worker parse failed"));
3703
+ });
3704
+ worker.once("error", reject);
3705
+ }).catch(reject);
3706
+ });
3707
+ }
3708
+ function findJsonValueEnd(source) {
3709
+ let i2 = 0;
3710
+ while (i2 < source.length && /\s/.test(source[i2])) i2 += 1;
3711
+ if (i2 >= source.length) return -1;
3712
+ const start = source[i2];
3713
+ if (start === '"') {
3714
+ i2 += 1;
3715
+ while (i2 < source.length) {
3716
+ if (source[i2] === "\\") {
3717
+ i2 += 2;
3718
+ continue;
3719
+ }
3720
+ if (source[i2] === '"') return i2 + 1;
3721
+ i2 += 1;
3722
+ }
3723
+ return -1;
3724
+ }
3725
+ if (start === "{" || start === "[") {
3726
+ const close = start === "{" ? "}" : "]";
3727
+ let depth = 0;
3728
+ let inString = false;
3729
+ for (; i2 < source.length; i2 += 1) {
3730
+ const ch = source[i2];
3731
+ if (inString) {
3732
+ if (ch === "\\") {
3733
+ i2 += 1;
3734
+ continue;
3735
+ }
3736
+ if (ch === '"') inString = false;
3737
+ continue;
3738
+ }
3739
+ if (ch === '"') {
3740
+ inString = true;
3741
+ continue;
3742
+ }
3743
+ if (ch === start) depth += 1;
3744
+ else if (ch === close) {
3745
+ depth -= 1;
3746
+ if (depth === 0) return i2 + 1;
3747
+ }
3748
+ }
3749
+ return -1;
3750
+ }
3751
+ const rest = source.slice(i2);
3752
+ const match2 = rest.match(/^(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/);
3753
+ return match2 ? i2 + match2[0].length : -1;
3754
+ }
3755
+ async function streamJsonArrayToJsonl(filePath, tmpPath) {
3756
+ const input = createReadStream(filePath, { encoding: "utf8" });
3757
+ const out = createWriteStream(tmpPath);
3758
+ let buf = "";
3759
+ let started = false;
3760
+ let count2 = 0;
3761
+ const writeLine = async (line) => {
3762
+ if (!out.write(line)) {
3763
+ await new Promise((resolve, reject) => {
3764
+ out.once("drain", resolve);
3765
+ out.once("error", reject);
3766
+ });
3767
+ }
3768
+ };
3769
+ try {
3770
+ for await (const chunk of input) {
3771
+ buf += chunk;
3772
+ if (!started) {
3773
+ const idx = buf.search(/\S/);
3774
+ if (idx < 0) continue;
3775
+ if (buf[idx] !== "[") throw new Error("embedding cache is not a JSON array");
3776
+ buf = buf.slice(idx + 1);
3777
+ started = true;
3778
+ }
3779
+ while (true) {
3780
+ buf = buf.replace(/^\s*,?\s*/, "");
3781
+ if (!buf) break;
3782
+ if (buf[0] === "]") {
3783
+ buf = "";
3784
+ break;
3785
+ }
3786
+ const end = findJsonValueEnd(buf);
3787
+ if (end < 0) break;
3788
+ const value = JSON.parse(buf.slice(0, end));
3789
+ buf = buf.slice(end);
3790
+ const row = parseCacheRow(value);
3791
+ if (row) {
3792
+ await writeLine(`${JSON.stringify({ h: row.hash, v: row.vector })}
3793
+ `);
3794
+ count2 += 1;
3795
+ if (count2 % 50 === 0) {
3796
+ await new Promise((resolve) => setImmediate(resolve));
3797
+ }
3798
+ }
3799
+ }
3800
+ if (buf.length > MAX_MIGRATION_VALUE_BYTES) {
3801
+ throw new Error("embedding cache entry too large to migrate safely");
3802
+ }
3803
+ }
3804
+ } finally {
3805
+ await new Promise((resolve, reject) => {
3806
+ out.end(() => resolve());
3807
+ out.once("error", reject);
3808
+ });
3809
+ }
3810
+ return count2;
3811
+ }
3812
+ async function migrateJsonArrayCacheToJsonl(filePath, jsonlPath) {
3813
+ const tmpPath = `${jsonlPath}.tmp`;
3814
+ try {
3815
+ const count2 = await streamJsonArrayToJsonl(filePath, tmpPath);
3816
+ await rename(tmpPath, jsonlPath);
3817
+ diskCacheEntryCount = count2;
3818
+ await writeCacheStat({
3819
+ version: 1,
3820
+ entryCount: count2,
3821
+ dimensions: cacheDimensions
3822
+ });
3823
+ return count2;
3824
+ } catch (err) {
3825
+ await unlink(tmpPath).catch(() => void 0);
3826
+ throw err;
3827
+ }
3828
+ }
3829
+ function convertArrayCacheToJsonl(filePath, jsonlPath) {
3830
+ return migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
3831
+ }
3832
+ async function loadCacheJsonl(filePath) {
3833
+ const rl = createInterface({
3834
+ input: createReadStream(filePath, { encoding: "utf8" }),
3835
+ crlfDelay: Infinity
3836
+ });
3837
+ let loaded = 0;
3838
+ let onDiskCount = 0;
3839
+ for await (const line of rl) {
3840
+ const trimmed = line.trim();
3841
+ if (!trimmed) continue;
3842
+ try {
3843
+ const row = parseCacheRow(JSON.parse(trimmed));
3844
+ if (!row) continue;
3845
+ onDiskCount += 1;
3846
+ cacheInsert(row.hash, row.vector, false);
3847
+ loaded = cache3.size;
3848
+ } catch {
3849
+ }
3850
+ if (onDiskCount % 250 === 0) {
3851
+ await new Promise((resolve) => setImmediate(resolve));
3852
+ }
3853
+ }
3854
+ diskCacheEntryCount = onDiskCount;
3855
+ await writeCacheStat({
3856
+ version: 1,
3857
+ entryCount: onDiskCount,
3858
+ dimensions: cacheDimensions
3859
+ });
3860
+ return loaded;
3861
+ }
3862
+ async function countJsonlLines(filePath) {
3863
+ jsonlLineCountCalls += 1;
3864
+ try {
3865
+ const rl = createInterface({
3866
+ input: createReadStream(filePath, { encoding: "utf8" }),
3867
+ crlfDelay: Infinity
3868
+ });
3869
+ let count2 = 0;
3870
+ for await (const line of rl) {
3871
+ if (line.trim()) count2 += 1;
3872
+ }
3873
+ return count2;
3874
+ } catch {
3875
+ return 0;
3876
+ }
3877
+ }
3878
+ async function resolveOnDiskCount(jsonlPath) {
3879
+ if (diskCacheEntryCount > 0) return diskCacheEntryCount;
3880
+ const sidecar = await readCacheStat();
3881
+ if (sidecar) {
3882
+ diskCacheEntryCount = sidecar.entryCount;
3883
+ if (sidecar.dimensions && cacheDimensions === null) cacheDimensions = sidecar.dimensions;
3884
+ return sidecar.entryCount;
3885
+ }
3886
+ try {
3887
+ await access(jsonlPath, fsConstants.R_OK);
3888
+ } catch {
3889
+ return 0;
3890
+ }
3891
+ const counted = await countJsonlLines(jsonlPath);
3892
+ diskCacheEntryCount = counted;
3893
+ await writeCacheStat({
3894
+ version: 1,
3895
+ entryCount: counted,
3896
+ dimensions: cacheDimensions
3897
+ });
3898
+ return counted;
3899
+ }
3570
3900
  async function loadDiskCache2() {
3571
- if (diskCacheLoaded) return;
3901
+ if (diskCacheLoaded) return cache3.size;
3902
+ let loaded = 0;
3572
3903
  try {
3573
- const raw = await readFile2(cacheFile(), "utf-8");
3574
- const entries = JSON.parse(raw);
3575
- for (const [k, v] of entries) cache3.set(k, v);
3576
- console.error(`[memorix] Loaded ${entries.length} cached API embeddings from disk`);
3904
+ const jsonlPath = cacheJsonlFile();
3905
+ let hasJsonl = false;
3906
+ try {
3907
+ await access(jsonlPath, fsConstants.R_OK);
3908
+ hasJsonl = true;
3909
+ } catch {
3910
+ hasJsonl = false;
3911
+ }
3912
+ if (hasJsonl) {
3913
+ loaded = await loadCacheJsonl(jsonlPath);
3914
+ } else {
3915
+ const filePath = cacheFile();
3916
+ await stat(filePath);
3917
+ await migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
3918
+ loaded = await loadCacheJsonl(jsonlPath);
3919
+ }
3920
+ diskCacheLoadCount += 1;
3921
+ console.error(`[memorix] Loaded ${loaded} cached API embeddings from disk`);
3577
3922
  } catch {
3578
3923
  }
3579
3924
  diskCacheLoaded = true;
3925
+ return loaded;
3580
3926
  }
3581
3927
  function startDiskCacheLoad() {
3582
3928
  if (diskCacheLoaded || diskCacheLoadPromise) return;
3583
- diskCacheLoadPromise = loadDiskCache2().catch(() => {
3929
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
3584
3930
  });
3585
3931
  }
3586
3932
  async function ensureDiskCacheLoaded() {
3587
- if (diskCacheLoaded) return;
3588
- if (diskCacheLoadPromise) {
3589
- await diskCacheLoadPromise;
3590
- return;
3591
- }
3592
- await loadDiskCache2();
3933
+ if (diskCacheLoaded) return cache3.size;
3934
+ if (!diskCacheLoadPromise) {
3935
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
3936
+ });
3937
+ }
3938
+ await diskCacheLoadPromise;
3939
+ return cache3.size;
3940
+ }
3941
+ function getApiEmbeddingDiskCacheLoadCountForTests() {
3942
+ return diskCacheLoadCount;
3943
+ }
3944
+ function resetApiEmbeddingCacheForTests() {
3945
+ cache3.clear();
3946
+ cachePayloadBytes = 0;
3947
+ cacheDimensions = null;
3948
+ diskCacheEntryCount = 0;
3949
+ diskCacheLoaded = false;
3950
+ diskCacheLoadPromise = null;
3951
+ diskCacheLoadCount = 0;
3952
+ diskCacheDirty2 = false;
3953
+ jsonlLineCountCalls = 0;
3954
+ }
3955
+ function seedApiEmbeddingCacheForTests(entries) {
3956
+ cache3.clear();
3957
+ cachePayloadBytes = 0;
3958
+ cacheDimensions = null;
3959
+ for (const [key, value] of entries) cacheInsert(key, value, true);
3960
+ diskCacheLoaded = true;
3961
+ diskCacheDirty2 = true;
3962
+ }
3963
+ function getApiEmbeddingCachePayloadBytesForTests() {
3964
+ return cachePayloadBytes;
3965
+ }
3966
+ function getApiEmbeddingJsonlLineCountCallsForTests() {
3967
+ return jsonlLineCountCalls;
3968
+ }
3969
+ function getApiEmbeddingMaxEntriesForDimensionsForTests(dimensions) {
3970
+ return maxEntriesForDimensions(dimensions);
3593
3971
  }
3594
3972
  function dimsCacheKey(config) {
3595
3973
  return [
@@ -3705,8 +4083,46 @@ async function saveDiskCacheNow() {
3705
4083
  if (!diskCacheDirty2) return;
3706
4084
  try {
3707
4085
  await mkdir2(cacheDir(), { recursive: true });
3708
- const entries = Array.from(cache3.entries());
3709
- await writeFile2(cacheFile(), JSON.stringify(entries));
4086
+ const jsonlPath = cacheJsonlFile();
4087
+ const existing = await resolveOnDiskCount(jsonlPath);
4088
+ if (existing > 0 && cache3.size < Math.ceil(existing * 0.5)) {
4089
+ console.error(
4090
+ `[memorix] Refusing to overwrite embedding cache (${existing} on disk vs ${cache3.size} in memory)`
4091
+ );
4092
+ diskCacheDirty2 = false;
4093
+ return;
4094
+ }
4095
+ const tmpPath = `${jsonlPath}.tmp`;
4096
+ await new Promise((resolve, reject) => {
4097
+ const out = createWriteStream(tmpPath);
4098
+ const entries = cache3.entries();
4099
+ const writeBatch = () => {
4100
+ for (let i2 = 0; i2 < 50; i2++) {
4101
+ const next = entries.next();
4102
+ if (next.done) {
4103
+ out.end();
4104
+ return;
4105
+ }
4106
+ const [hash4, vector] = next.value;
4107
+ if (!out.write(`${JSON.stringify({ h: hash4, v: vector })}
4108
+ `)) {
4109
+ out.once("drain", writeBatch);
4110
+ return;
4111
+ }
4112
+ }
4113
+ setImmediate(writeBatch);
4114
+ };
4115
+ out.on("finish", resolve);
4116
+ out.on("error", reject);
4117
+ writeBatch();
4118
+ });
4119
+ await rename(tmpPath, jsonlPath);
4120
+ diskCacheEntryCount = cache3.size;
4121
+ await writeCacheStat({
4122
+ version: 1,
4123
+ entryCount: cache3.size,
4124
+ dimensions: cacheDimensions
4125
+ });
3710
4126
  diskCacheDirty2 = false;
3711
4127
  } catch {
3712
4128
  }
@@ -3720,12 +4136,7 @@ function scheduleDiskSave() {
3720
4136
  }, DISK_SAVE_DEBOUNCE_MS);
3721
4137
  }
3722
4138
  function cacheSet(hash4, value) {
3723
- if (cache3.size >= MAX_CACHE_SIZE3) {
3724
- const firstKey = cache3.keys().next().value;
3725
- if (firstKey !== void 0) cache3.delete(firstKey);
3726
- }
3727
- cache3.set(hash4, value);
3728
- diskCacheDirty2 = true;
4139
+ cacheInsert(hash4, value, true);
3729
4140
  }
3730
4141
  function resolveEnvEmbeddingApiKey() {
3731
4142
  return process.env.MEMORIX_EMBEDDING_API_KEY;
@@ -3844,18 +4255,27 @@ async function waitForRetry(waitMs, signal) {
3844
4255
  abortSignal.addEventListener("abort", onAbort, { once: true });
3845
4256
  });
3846
4257
  }
3847
- 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;
4258
+ 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;
3848
4259
  var init_api_provider = __esm({
3849
4260
  "src/embedding/api-provider.ts"() {
3850
4261
  "use strict";
3851
4262
  init_esm_shims();
3852
4263
  init_provider();
3853
4264
  cache3 = /* @__PURE__ */ new Map();
3854
- MAX_CACHE_SIZE3 = 1e4;
4265
+ MAX_CACHE_ENTRIES = 2e5;
4266
+ MAX_CACHE_PAYLOAD_BYTES = 256 * 1024 * 1024;
4267
+ BYTES_PER_DIM = 8;
4268
+ DEFAULT_ASSUMED_DIMENSIONS = 4096;
4269
+ MAX_MIGRATION_VALUE_BYTES = 16 * 1024 * 1024;
4270
+ cachePayloadBytes = 0;
4271
+ cacheDimensions = null;
4272
+ diskCacheEntryCount = 0;
3855
4273
  diskCacheDirty2 = false;
3856
4274
  diskSaveTimer = null;
3857
4275
  diskCacheLoaded = false;
3858
4276
  diskCacheLoadPromise = null;
4277
+ diskCacheLoadCount = 0;
4278
+ jsonlLineCountCalls = 0;
3859
4279
  MAX_INPUT_CHARS = 32e3;
3860
4280
  MAX_CONCURRENCY = 4;
3861
4281
  DISK_SAVE_DEBOUNCE_MS = 5e3;
@@ -3913,7 +4333,9 @@ var init_api_provider = __esm({
3913
4333
  if (config.requestedDimensions) {
3914
4334
  console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
3915
4335
  }
3916
- startDiskCacheLoad();
4336
+ if (allowNetworkProbe) {
4337
+ startDiskCacheLoad();
4338
+ }
3917
4339
  return new _APIEmbeddingProvider(config, probeDimensions);
3918
4340
  }
3919
4341
  static resolveConfig() {
@@ -4189,6 +4611,7 @@ __export(provider_exports, {
4189
4611
  MAX_INLINE_EMBEDDING_BYTES: () => MAX_INLINE_EMBEDDING_BYTES,
4190
4612
  UnsupportedEmbeddingModalityError: () => UnsupportedEmbeddingModalityError,
4191
4613
  getEmbeddingProvider: () => getEmbeddingProvider,
4614
+ getEmbeddingRuntimeHealth: () => getEmbeddingRuntimeHealth,
4192
4615
  isEmbeddingExplicitlyDisabled: () => isEmbeddingExplicitlyDisabled,
4193
4616
  isVectorSearchAvailable: () => isVectorSearchAvailable,
4194
4617
  resetProvider: () => resetProvider,
@@ -4250,7 +4673,12 @@ function warnOnce(message) {
4250
4673
  lastEmbeddingWarningAt = now3;
4251
4674
  console.error(message);
4252
4675
  }
4676
+ function embeddingModeToken() {
4677
+ return process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim() ?? "";
4678
+ }
4253
4679
  function getEmbeddingMode2() {
4680
+ const token = embeddingModeToken();
4681
+ if (cachedEmbeddingMode && cachedEmbeddingModeToken === token) return cachedEmbeddingMode;
4254
4682
  try {
4255
4683
  let cfg;
4256
4684
  try {
@@ -4259,10 +4687,17 @@ function getEmbeddingMode2() {
4259
4687
  cfg = (init_config(), __toCommonJS(config_exports));
4260
4688
  }
4261
4689
  const { getEmbeddingMode: cfgMode } = cfg;
4262
- return cfgMode();
4690
+ cachedEmbeddingMode = cfgMode();
4691
+ cachedEmbeddingModeToken = token;
4692
+ return cachedEmbeddingMode;
4263
4693
  } catch {
4264
- const env2 = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
4265
- if (env2 === "fastembed" || env2 === "transformers" || env2 === "api" || env2 === "auto") return env2;
4694
+ if (token === "fastembed" || token === "transformers" || token === "api" || token === "auto") {
4695
+ cachedEmbeddingMode = token;
4696
+ cachedEmbeddingModeToken = token;
4697
+ return token;
4698
+ }
4699
+ cachedEmbeddingMode = "off";
4700
+ cachedEmbeddingModeToken = token;
4266
4701
  return "off";
4267
4702
  }
4268
4703
  }
@@ -4296,13 +4731,36 @@ function isTemporaryEmbeddingFailure(error) {
4296
4731
  if (!(error instanceof Error)) return false;
4297
4732
  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);
4298
4733
  }
4734
+ function classifyEmbeddingFailure(error) {
4735
+ const message = error instanceof Error ? error.message : String(error);
4736
+ if (/embedding api error \(401\)|invalid_api_key|incorrect api key|unauthorized/i.test(message)) {
4737
+ return "authorization";
4738
+ }
4739
+ if (/embedding api error \(402\)|quota exceeded|account balance/i.test(message)) {
4740
+ return "billing";
4741
+ }
4742
+ if (/embedding api timeout|abort/i.test(message)) {
4743
+ return "timeout";
4744
+ }
4745
+ return "upstream";
4746
+ }
4299
4747
  function markTemporaryFailure(reason) {
4300
4748
  provider = null;
4301
4749
  initPromise = null;
4302
4750
  lastInitWasTemporaryFailure = true;
4303
4751
  lastFailureTimestamp = Date.now();
4752
+ lastEmbeddingFailure = {
4753
+ kind: classifyEmbeddingFailure(reason),
4754
+ occurredAt: lastFailureTimestamp
4755
+ };
4304
4756
  const message = reason instanceof Error ? reason.message : String(reason);
4305
- warnOnce(`[memorix] Embedding provider temporarily unavailable at runtime: ${message}`);
4757
+ warnOnce(`[memorix] Embedding provider unavailable at runtime: ${message}`);
4758
+ }
4759
+ function markProviderReady(candidate) {
4760
+ provider = wrapProvider(candidate);
4761
+ lastInitWasTemporaryFailure = false;
4762
+ lastEmbeddingFailure = null;
4763
+ return provider;
4306
4764
  }
4307
4765
  async function createFastEmbedProvider() {
4308
4766
  try {
@@ -4418,35 +4876,35 @@ async function getEmbeddingProvider(options = {}) {
4418
4876
  if (mode === "fastembed") {
4419
4877
  const initialized = await createFastEmbedProvider();
4420
4878
  if (!initialized) return null;
4421
- provider = wrapProvider(initialized);
4879
+ provider = markProviderReady(initialized);
4422
4880
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4423
4881
  return provider;
4424
4882
  }
4425
4883
  if (mode === "transformers") {
4426
4884
  const initialized = await createTransformersProvider();
4427
4885
  if (!initialized) return null;
4428
- provider = wrapProvider(initialized);
4886
+ provider = markProviderReady(initialized);
4429
4887
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4430
4888
  return provider;
4431
4889
  }
4432
4890
  if (mode === "api") {
4433
4891
  const initialized = await createAPIProvider(options);
4434
4892
  if (!initialized) return null;
4435
- provider = wrapProvider(initialized);
4893
+ provider = markProviderReady(initialized);
4436
4894
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4437
4895
  return provider;
4438
4896
  }
4439
4897
  if (hasAPIEmbeddingConfig()) {
4440
4898
  const initialized = await createAPIProvider(options);
4441
4899
  if (initialized) {
4442
- provider = wrapProvider(initialized);
4900
+ provider = markProviderReady(initialized);
4443
4901
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4444
4902
  return provider;
4445
4903
  }
4446
4904
  }
4447
4905
  const localFallback = await createLocalFallbackProvider();
4448
4906
  if (localFallback) {
4449
- provider = wrapProvider(localFallback);
4907
+ provider = markProviderReady(localFallback);
4450
4908
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
4451
4909
  return provider;
4452
4910
  }
@@ -4468,18 +4926,42 @@ async function isVectorSearchAvailable() {
4468
4926
  const p = await getEmbeddingProvider();
4469
4927
  return p !== null;
4470
4928
  }
4929
+ function getEmbeddingRuntimeHealth() {
4930
+ const mode = getEmbeddingMode2();
4931
+ if (mode === "off") return { status: "disabled" };
4932
+ if (lastInitWasTemporaryFailure && lastEmbeddingFailure) {
4933
+ return {
4934
+ status: "degraded",
4935
+ failure: {
4936
+ kind: lastEmbeddingFailure.kind,
4937
+ occurredAt: new Date(lastEmbeddingFailure.occurredAt).toISOString(),
4938
+ retryAfterMs: Math.max(0, RETRY_COOLDOWN_MS - (Date.now() - lastEmbeddingFailure.occurredAt))
4939
+ }
4940
+ };
4941
+ }
4942
+ if (provider) {
4943
+ return {
4944
+ status: "ready",
4945
+ provider: { name: provider.name, dimensions: provider.dimensions }
4946
+ };
4947
+ }
4948
+ return { status: "uninitialized" };
4949
+ }
4471
4950
  function isEmbeddingExplicitlyDisabled() {
4472
4951
  return getEmbeddingMode2() === "off";
4473
4952
  }
4474
4953
  function resetProvider() {
4475
4954
  provider = null;
4476
4955
  initPromise = null;
4956
+ cachedEmbeddingMode = null;
4957
+ cachedEmbeddingModeToken = null;
4477
4958
  lastInitWasTemporaryFailure = false;
4478
4959
  lastFailureTimestamp = 0;
4960
+ lastEmbeddingFailure = null;
4479
4961
  lastEmbeddingWarning = "";
4480
4962
  lastEmbeddingWarningAt = 0;
4481
4963
  }
4482
- var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
4964
+ var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, cachedEmbeddingMode, cachedEmbeddingModeToken, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
4483
4965
  var init_provider = __esm({
4484
4966
  "src/embedding/provider.ts"() {
4485
4967
  "use strict";
@@ -4503,7 +4985,10 @@ var init_provider = __esm({
4503
4985
  MAX_INLINE_EMBEDDING_BYTES = 5 * 1024 * 1024;
4504
4986
  provider = null;
4505
4987
  initPromise = null;
4988
+ cachedEmbeddingMode = null;
4989
+ cachedEmbeddingModeToken = null;
4506
4990
  lastInitWasTemporaryFailure = false;
4991
+ lastEmbeddingFailure = null;
4507
4992
  lastEmbeddingWarning = "";
4508
4993
  lastEmbeddingWarningAt = 0;
4509
4994
  WARNING_COOLDOWN_MS = 3e4;
@@ -6259,6 +6744,9 @@ async function attachCachedVectors(database, candidates) {
6259
6744
  await update(database, candidates[index].id, { ...existing, embedding: vector });
6260
6745
  } catch {
6261
6746
  }
6747
+ if (index % 50 === 0) {
6748
+ await new Promise((resolve) => setImmediate(resolve));
6749
+ }
6262
6750
  }
6263
6751
  }
6264
6752
  async function hydrateIndex(observations2, options = {}) {
@@ -6309,17 +6797,24 @@ async function hydrateIndex(observations2, options = {}) {
6309
6797
  await insert2(database, doc);
6310
6798
  rememberObservationDoc(doc);
6311
6799
  inserted++;
6800
+ if (inserted % 50 === 0) {
6801
+ await new Promise((resolve) => setImmediate(resolve));
6802
+ }
6803
+ if (inserted % 200 === 0) {
6804
+ await new Promise((resolve) => setTimeout(resolve, 0));
6805
+ }
6312
6806
  } catch {
6313
6807
  }
6314
6808
  }
6315
- deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6809
+ deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6316
6810
  }) : null;
6317
6811
  return inserted;
6318
6812
  }
6319
- async function hydrateIndexForStartup(observations2) {
6813
+ async function hydrateIndexForStartup(observations2, options = {}) {
6320
6814
  return hydrateIndex(observations2, {
6321
6815
  allowNetworkProbe: false,
6322
- deferCachedVectors: true
6816
+ deferCachedVectors: true,
6817
+ skipCachedVectors: options.skipCachedVectors
6323
6818
  });
6324
6819
  }
6325
6820
  function getDeferredCachedVectorHydration() {
@@ -8410,7 +8905,7 @@ var init_types2 = __esm({
8410
8905
 
8411
8906
  // src/media/asset-store.ts
8412
8907
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
8413
- import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
8908
+ import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
8414
8909
  import path9 from "path";
8415
8910
  function mediaRoot(dataDir) {
8416
8911
  return path9.resolve(dataDir, "media");
@@ -8483,7 +8978,7 @@ async function persistBuffer(bytes, destination) {
8483
8978
  const temporary = `${destination}.${randomUUID5()}.tmp`;
8484
8979
  try {
8485
8980
  await writeFile3(temporary, bytes, { flag: "wx" });
8486
- await rename(temporary, destination);
8981
+ await rename2(temporary, destination);
8487
8982
  return true;
8488
8983
  } catch (error) {
8489
8984
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -59563,9 +60058,9 @@ var require_node_domexception = __commonJS({
59563
60058
  });
59564
60059
 
59565
60060
  // node_modules/fetch-blob/from.js
59566
- import { statSync as statSync2, createReadStream, promises as fs7 } from "fs";
60061
+ import { statSync as statSync2, createReadStream as createReadStream2, promises as fs7 } from "fs";
59567
60062
  import { basename } from "path";
59568
- var import_node_domexception, stat2, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
60063
+ var import_node_domexception, stat3, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
59569
60064
  var init_from = __esm({
59570
60065
  "node_modules/fetch-blob/from.js"() {
59571
60066
  "use strict";
@@ -59573,23 +60068,23 @@ var init_from = __esm({
59573
60068
  import_node_domexception = __toESM(require_node_domexception(), 1);
59574
60069
  init_file();
59575
60070
  init_fetch_blob();
59576
- ({ stat: stat2 } = fs7);
60071
+ ({ stat: stat3 } = fs7);
59577
60072
  blobFromSync = (path21, type) => fromBlob(statSync2(path21), path21, type);
59578
- blobFrom = (path21, type) => stat2(path21).then((stat4) => fromBlob(stat4, path21, type));
59579
- fileFrom = (path21, type) => stat2(path21).then((stat4) => fromFile(stat4, path21, type));
60073
+ blobFrom = (path21, type) => stat3(path21).then((stat5) => fromBlob(stat5, path21, type));
60074
+ fileFrom = (path21, type) => stat3(path21).then((stat5) => fromFile(stat5, path21, type));
59580
60075
  fileFromSync = (path21, type) => fromFile(statSync2(path21), path21, type);
59581
- fromBlob = (stat4, path21, type = "") => new fetch_blob_default([new BlobDataItem({
60076
+ fromBlob = (stat5, path21, type = "") => new fetch_blob_default([new BlobDataItem({
59582
60077
  path: path21,
59583
- size: stat4.size,
59584
- lastModified: stat4.mtimeMs,
60078
+ size: stat5.size,
60079
+ lastModified: stat5.mtimeMs,
59585
60080
  start: 0
59586
60081
  })], { type });
59587
- fromFile = (stat4, path21, type = "") => new file_default([new BlobDataItem({
60082
+ fromFile = (stat5, path21, type = "") => new file_default([new BlobDataItem({
59588
60083
  path: path21,
59589
- size: stat4.size,
59590
- lastModified: stat4.mtimeMs,
60084
+ size: stat5.size,
60085
+ lastModified: stat5.mtimeMs,
59591
60086
  start: 0
59592
- })], basename(path21), { type, lastModified: stat4.mtimeMs });
60087
+ })], basename(path21), { type, lastModified: stat5.mtimeMs });
59593
60088
  BlobDataItem = class _BlobDataItem {
59594
60089
  #path;
59595
60090
  #start;
@@ -59612,11 +60107,11 @@ var init_from = __esm({
59612
60107
  });
59613
60108
  }
59614
60109
  async *stream() {
59615
- const { mtimeMs } = await stat2(this.#path);
60110
+ const { mtimeMs } = await stat3(this.#path);
59616
60111
  if (mtimeMs > this.lastModified) {
59617
60112
  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");
59618
60113
  }
59619
- yield* createReadStream(this.#path, {
60114
+ yield* createReadStream2(this.#path, {
59620
60115
  start: this.#start,
59621
60116
  end: this.#start + this.size - 1
59622
60117
  });
@@ -75246,7 +75741,7 @@ var init_wrapper = __esm({
75246
75741
  });
75247
75742
 
75248
75743
  // node_modules/@google/genai/dist/node/index.mjs
75249
- import { createWriteStream } from "fs";
75744
+ import { createWriteStream as createWriteStream2 } from "fs";
75250
75745
  import * as fs8 from "fs/promises";
75251
75746
  import { writeFile as writeFile4 } from "fs/promises";
75252
75747
  import { Readable } from "stream";
@@ -92526,7 +93021,7 @@ ${underline}`);
92526
93021
  if (params.downloadPath) {
92527
93022
  const response = await downloadFile(params, apiClient);
92528
93023
  if (response instanceof HttpResponse) {
92529
- const writer = createWriteStream(params.downloadPath);
93024
+ const writer = createWriteStream2(params.downloadPath);
92530
93025
  const body = Readable.fromWeb(response.responseInternal.body);
92531
93026
  body.pipe(writer);
92532
93027
  await finished(writer);
@@ -142150,7 +142645,7 @@ function explainRetention(doc, referenceTime) {
142150
142645
  };
142151
142646
  }
142152
142647
  function toRetentionDocument(obs, accessMap) {
142153
- const access = accessMap?.get(obs.id);
142648
+ const access2 = accessMap?.get(obs.id);
142154
142649
  return {
142155
142650
  id: `obs-${obs.id}`,
142156
142651
  observationId: obs.id,
@@ -142164,8 +142659,8 @@ function toRetentionDocument(obs, accessMap) {
142164
142659
  tokens: obs.tokens,
142165
142660
  createdAt: obs.createdAt,
142166
142661
  projectId: obs.projectId,
142167
- accessCount: access?.accessCount ?? 0,
142168
- lastAccessedAt: access?.lastAccessedAt ?? "",
142662
+ accessCount: access2?.accessCount ?? 0,
142663
+ lastAccessedAt: access2?.lastAccessedAt ?? "",
142169
142664
  status: obs.status ?? "active",
142170
142665
  source: obs.source ?? "agent",
142171
142666
  sourceDetail: obs.sourceDetail ?? "",
@@ -144275,7 +144770,7 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
144275
144770
  const rel = normalizeCodePath(relative(projectRoot, abs));
144276
144771
  try {
144277
144772
  const text2 = readFileSync6(abs, "utf-8");
144278
- const stat4 = fileStat ?? statSync3(abs);
144773
+ const stat5 = fileStat ?? statSync3(abs);
144279
144774
  const file = {
144280
144775
  id: makeCodeFileId(projectId, rel),
144281
144776
  projectId,
@@ -144284,8 +144779,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
144284
144779
  contentHash: hashText(text2),
144285
144780
  // Preserve filesystem precision. Rounding here made same-size edits in
144286
144781
  // a short Windows timestamp window indistinguishable to refresh.
144287
- mtimeMs: Number(stat4.mtimeMs),
144288
- sizeBytes: Number(stat4.size),
144782
+ mtimeMs: Number(stat5.mtimeMs),
144783
+ sizeBytes: Number(stat5.size),
144289
144784
  indexedAt
144290
144785
  };
144291
144786
  return {
@@ -144312,18 +144807,18 @@ async function indexProjectLite(options) {
144312
144807
  let skippedOversizedFiles = 0;
144313
144808
  let unreadableFiles = 0;
144314
144809
  for (const abs of paths) {
144315
- let stat4;
144810
+ let stat5;
144316
144811
  try {
144317
- stat4 = statSync3(abs);
144812
+ stat5 = statSync3(abs);
144318
144813
  } catch {
144319
144814
  unreadableFiles++;
144320
144815
  continue;
144321
144816
  }
144322
- if (Number(stat4.size) > maxFileBytes) {
144817
+ if (Number(stat5.size) > maxFileBytes) {
144323
144818
  skippedOversizedFiles++;
144324
144819
  continue;
144325
144820
  }
144326
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
144821
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
144327
144822
  if (!attempt.delta) {
144328
144823
  if (attempt.unreadable) unreadableFiles++;
144329
144824
  continue;
@@ -144351,16 +144846,16 @@ async function refreshProjectLite(store, options) {
144351
144846
  for (const abs of paths) {
144352
144847
  const rel = normalizeCodePath(relative(options.projectRoot, abs));
144353
144848
  seenPaths.add(rel);
144354
- let stat4;
144849
+ let stat5;
144355
144850
  try {
144356
- stat4 = statSync3(abs);
144851
+ stat5 = statSync3(abs);
144357
144852
  } catch {
144358
144853
  unreadableFiles++;
144359
144854
  continue;
144360
144855
  }
144361
144856
  const existing = existingByPath.get(rel);
144362
- const mtimeMs = Number(stat4.mtimeMs);
144363
- const sizeBytes = Number(stat4.size);
144857
+ const mtimeMs = Number(stat5.mtimeMs);
144858
+ const sizeBytes = Number(stat5.size);
144364
144859
  if (sizeBytes > maxFileBytes) {
144365
144860
  skippedOversizedFiles++;
144366
144861
  if (existing) oversizedExistingFileIds.add(existing.id);
@@ -144370,7 +144865,7 @@ async function refreshProjectLite(store, options) {
144370
144865
  unchangedFiles++;
144371
144866
  continue;
144372
144867
  }
144373
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
144868
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
144374
144869
  if (!attempt.delta) {
144375
144870
  if (attempt.unreadable) unreadableFiles++;
144376
144871
  continue;
@@ -147767,12 +148262,25 @@ __export(observations_exports, {
147767
148262
  prepareSearchIndex: () => prepareSearchIndex,
147768
148263
  probeSearchIndex: () => probeSearchIndex,
147769
148264
  reindexObservations: () => reindexObservations,
148265
+ resetObservationRuntime: () => resetObservationRuntime,
147770
148266
  resolveObservations: () => resolveObservations,
147771
148267
  storeObservation: () => storeObservation,
147772
148268
  suggestTopicKey: () => suggestTopicKey,
147773
148269
  updateObservationAdmission: () => updateObservationAdmission,
147774
148270
  withFreshObservations: () => withFreshObservations
147775
148271
  });
148272
+ function resetObservationRuntime() {
148273
+ observations = [];
148274
+ nextId = 1;
148275
+ projectDir = null;
148276
+ searchIndexPrepared = false;
148277
+ corpusLoaded = false;
148278
+ loadedObservationStore = null;
148279
+ embeddingWriteMode = "background";
148280
+ embeddingWorkerProjectRoot = void 0;
148281
+ vectorMissingIds.clear();
148282
+ vectorSchemaUpgradePromise = null;
148283
+ }
147776
148284
  function logEmbeddingFailureOnce(key, message) {
147777
148285
  const now3 = Date.now();
147778
148286
  const last = embeddingFailureLogTimestamps.get(key) ?? 0;
@@ -147881,23 +148389,33 @@ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
147881
148389
  async function initObservations(dir, options = {}) {
147882
148390
  embeddingWriteMode = options.embeddingWriteMode ?? "background";
147883
148391
  embeddingWorkerProjectRoot = options.projectRoot;
147884
- if (projectDir === dir) return;
147885
148392
  await initObservationStore(dir);
147886
148393
  const store = getObservationStore();
147887
- observations = await store.loadAll();
148394
+ if (projectDir === dir && loadedObservationStore === store) {
148395
+ if (options.skipCorpusLoad || corpusLoaded) return;
148396
+ }
147888
148397
  nextId = await store.loadIdCounter();
147889
148398
  projectDir = dir;
147890
148399
  searchIndexPrepared = false;
147891
148400
  vectorSchemaUpgradePromise = null;
148401
+ if (options.skipCorpusLoad) {
148402
+ observations = [];
148403
+ corpusLoaded = false;
148404
+ loadedObservationStore = store;
148405
+ return;
148406
+ }
148407
+ observations = await store.loadAll();
148408
+ corpusLoaded = true;
148409
+ loadedObservationStore = store;
147892
148410
  }
147893
148411
  function scheduleObservationEmbedding(input) {
147894
148412
  const { observationId: observationId2, projectId, searchableText, doc } = input;
147895
148413
  vectorMissingIds.add(observationId2);
148414
+ if (isEmbeddingExplicitlyDisabled()) {
148415
+ vectorMissingIds.delete(observationId2);
148416
+ return;
148417
+ }
147896
148418
  if (embeddingWriteMode === "deferred") {
147897
- if (isEmbeddingExplicitlyDisabled()) {
147898
- vectorMissingIds.delete(observationId2);
147899
- return;
147900
- }
147901
148419
  queueVectorBackfill(projectId, {
147902
148420
  detachedWorker: true,
147903
148421
  projectRoot: embeddingWorkerProjectRoot
@@ -147944,7 +148462,7 @@ function scheduleObservationEmbedding(input) {
147944
148462
  });
147945
148463
  }
147946
148464
  async function ensureFreshObservations() {
147947
- if (!projectDir) return false;
148465
+ if (!projectDir || !corpusLoaded) return false;
147948
148466
  try {
147949
148467
  const store = getObservationStore();
147950
148468
  const wasStale = await store.ensureFresh();
@@ -147985,7 +148503,7 @@ async function storeObservation(input) {
147985
148503
  });
147986
148504
  if (safeAttachments) input = { ...input, attachments: safeAttachments };
147987
148505
  await ensureFreshObservations();
147988
- if (input.topicKey) {
148506
+ if (input.topicKey && corpusLoaded) {
147989
148507
  const existing = observations.find(
147990
148508
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
147991
148509
  );
@@ -148082,13 +148600,17 @@ async function storeObservation(input) {
148082
148600
  });
148083
148601
  observation.writeGeneration = store.getGeneration();
148084
148602
  if (upsertedInsideLock || reloadCacheAfterCommit) {
148085
- observations = await store.loadAll();
148086
- nextId = await store.loadIdCounter();
148087
- if (upsertedInsideLock) {
148088
- observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
148603
+ if (corpusLoaded) {
148604
+ observations = await store.loadAll();
148605
+ nextId = await store.loadIdCounter();
148606
+ if (upsertedInsideLock) {
148607
+ observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
148608
+ }
148609
+ } else {
148610
+ nextId = await store.loadIdCounter();
148089
148611
  }
148090
148612
  } else {
148091
- observations.push(observation);
148613
+ if (corpusLoaded) observations.push(observation);
148092
148614
  nextId = observation.id + 1;
148093
148615
  }
148094
148616
  if (upsertedInsideLock) return;
@@ -148156,7 +148678,9 @@ async function storeObservation(input) {
148156
148678
  createdByAgentId: input.createdByAgentId ?? "",
148157
148679
  sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
148158
148680
  };
148159
- await insertObservation(doc);
148681
+ if (corpusLoaded) {
148682
+ await insertObservation(doc);
148683
+ }
148160
148684
  };
148161
148685
  await assignAndPersist();
148162
148686
  if (upsertedInsideLock) {
@@ -148518,9 +149042,11 @@ async function reindexObservations() {
148518
149042
  searchIndexPrepared = true;
148519
149043
  return count2;
148520
149044
  }
148521
- async function prepareSearchIndex() {
149045
+ async function prepareSearchIndex(options = {}) {
148522
149046
  if (searchIndexPrepared) return 0;
148523
- const count2 = await hydrateIndexForStartup(observations);
149047
+ const count2 = await hydrateIndexForStartup(observations, {
149048
+ skipCachedVectors: options.skipCachedVectors
149049
+ });
148524
149050
  if (count2 === 0) {
148525
149051
  searchIndexPrepared = true;
148526
149052
  return 0;
@@ -148712,7 +149238,7 @@ async function backfillVectorEmbeddings(options = {}) {
148712
149238
  ...lastFailure ? { lastError: lastFailure } : {}
148713
149239
  };
148714
149240
  }
148715
- var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
149241
+ var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, corpusLoaded, loadedObservationStore, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
148716
149242
  var init_observations = __esm({
148717
149243
  "src/memory/observations.ts"() {
148718
149244
  "use strict";
@@ -148731,6 +149257,8 @@ var init_observations = __esm({
148731
149257
  projectDir = null;
148732
149258
  searchIndexPrepared = false;
148733
149259
  embeddingWriteMode = "background";
149260
+ corpusLoaded = false;
149261
+ loadedObservationStore = null;
148734
149262
  vectorMissingIds = /* @__PURE__ */ new Set();
148735
149263
  vectorBackfillRunning = false;
148736
149264
  vectorSchemaUpgradePromise = null;