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
@@ -1369,8 +1369,8 @@ async function acquireLock(lockPath) {
1369
1369
  const code = err instanceof Error && "code" in err ? err.code : void 0;
1370
1370
  if (code === "EEXIST" || code === "EPERM") {
1371
1371
  try {
1372
- const stat4 = await fs2.stat(lockPath);
1373
- if (Date.now() - stat4.mtimeMs > LOCK_STALE_MS) {
1372
+ const stat5 = await fs2.stat(lockPath);
1373
+ if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
1374
1374
  await fs2.unlink(lockPath).catch(() => {
1375
1375
  });
1376
1376
  continue;
@@ -2481,24 +2481,131 @@ var init_transformers_provider = __esm({
2481
2481
  // src/embedding/api-provider.ts
2482
2482
  var api_provider_exports = {};
2483
2483
  __export(api_provider_exports, {
2484
- APIEmbeddingProvider: () => APIEmbeddingProvider
2484
+ APIEmbeddingProvider: () => APIEmbeddingProvider,
2485
+ convertArrayCacheToJsonl: () => convertArrayCacheToJsonl,
2486
+ ensureDiskCacheLoaded: () => ensureDiskCacheLoaded,
2487
+ getApiEmbeddingCachePayloadBytesForTests: () => getApiEmbeddingCachePayloadBytesForTests,
2488
+ getApiEmbeddingDiskCacheLoadCountForTests: () => getApiEmbeddingDiskCacheLoadCountForTests,
2489
+ getApiEmbeddingJsonlLineCountCallsForTests: () => getApiEmbeddingJsonlLineCountCallsForTests,
2490
+ getApiEmbeddingMaxEntriesForDimensionsForTests: () => getApiEmbeddingMaxEntriesForDimensionsForTests,
2491
+ migrateJsonArrayCacheToJsonl: () => migrateJsonArrayCacheToJsonl,
2492
+ parseCacheJsonInWorker: () => parseCacheJsonInWorker,
2493
+ resetApiEmbeddingCacheForTests: () => resetApiEmbeddingCacheForTests,
2494
+ saveDiskCacheNow: () => saveDiskCacheNow,
2495
+ seedApiEmbeddingCacheForTests: () => seedApiEmbeddingCacheForTests
2485
2496
  });
2486
2497
  import { createHash as createHash2 } from "crypto";
2487
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2498
+ import { createReadStream, createWriteStream, constants as fsConstants } from "fs";
2499
+ import { access, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rename, stat, unlink } from "fs/promises";
2488
2500
  import { join as join3 } from "path";
2489
2501
  import { homedir as homedir7 } from "os";
2502
+ import { createInterface } from "readline";
2490
2503
  function cacheDir() {
2491
2504
  return process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
2492
2505
  }
2493
2506
  function cacheFile() {
2494
2507
  return join3(cacheDir(), ".embedding-api-cache.json");
2495
2508
  }
2509
+ function cacheJsonlFile() {
2510
+ return join3(cacheDir(), ".embedding-api-cache.jsonl");
2511
+ }
2496
2512
  function dimsCacheFile() {
2497
2513
  return join3(cacheDir(), ".embedding-dims-cache.json");
2498
2514
  }
2499
2515
  function cacheMetaFile() {
2500
2516
  return join3(cacheDir(), ".embedding-api-cache-meta.json");
2501
2517
  }
2518
+ function cacheStatFile() {
2519
+ return join3(cacheDir(), ".embedding-api-cache-stat.json");
2520
+ }
2521
+ function payloadBytes(vector) {
2522
+ return vector.length * BYTES_PER_DIM;
2523
+ }
2524
+ function maxCachePayloadBytes() {
2525
+ const raw = process.env.MEMORIX_EMBEDDING_CACHE_MAX_BYTES;
2526
+ if (raw) {
2527
+ const parsed = Number.parseInt(raw, 10);
2528
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
2529
+ }
2530
+ return MAX_CACHE_PAYLOAD_BYTES;
2531
+ }
2532
+ function maxEntriesForDimensions(dimensions) {
2533
+ const dim = Math.max(1, Math.floor(dimensions));
2534
+ const byBytes = Math.floor(maxCachePayloadBytes() / (dim * BYTES_PER_DIM));
2535
+ return Math.max(1, Math.min(MAX_CACHE_ENTRIES, byBytes));
2536
+ }
2537
+ function rememberDimensions(vector) {
2538
+ if (vector.length > 0 && (cacheDimensions === null || vector.length > cacheDimensions)) {
2539
+ cacheDimensions = vector.length;
2540
+ }
2541
+ }
2542
+ function evictOldest() {
2543
+ const firstKey = cache3.keys().next().value;
2544
+ if (firstKey === void 0) return;
2545
+ const old = cache3.get(firstKey);
2546
+ cache3.delete(firstKey);
2547
+ if (old) cachePayloadBytes -= payloadBytes(old);
2548
+ }
2549
+ function evictToFit(incomingBytes, incomingDims) {
2550
+ const limit2 = maxEntriesForDimensions(incomingDims);
2551
+ while (cache3.size > 0 && (cache3.size >= limit2 || cachePayloadBytes + incomingBytes > maxCachePayloadBytes())) {
2552
+ evictOldest();
2553
+ }
2554
+ }
2555
+ function isNumberArray(value) {
2556
+ return Array.isArray(value) && value.every((entry) => typeof entry === "number");
2557
+ }
2558
+ function parseCacheRow(raw) {
2559
+ if (Array.isArray(raw)) {
2560
+ const hash4 = raw[0];
2561
+ const vector = raw[1];
2562
+ if (typeof hash4 === "string" && isNumberArray(vector)) {
2563
+ return { hash: hash4, vector };
2564
+ }
2565
+ return null;
2566
+ }
2567
+ if (raw && typeof raw === "object") {
2568
+ const row = raw;
2569
+ if (typeof row.h === "string" && isNumberArray(row.v)) {
2570
+ return { hash: row.h, vector: row.v };
2571
+ }
2572
+ }
2573
+ return null;
2574
+ }
2575
+ function cacheInsert(hash4, value, dirty) {
2576
+ rememberDimensions(value);
2577
+ const incoming = payloadBytes(value);
2578
+ const existing = cache3.get(hash4);
2579
+ if (existing) {
2580
+ cachePayloadBytes -= payloadBytes(existing);
2581
+ cache3.delete(hash4);
2582
+ }
2583
+ evictToFit(incoming, value.length || cacheDimensions || DEFAULT_ASSUMED_DIMENSIONS);
2584
+ cache3.set(hash4, value);
2585
+ cachePayloadBytes += incoming;
2586
+ if (dirty) diskCacheDirty2 = true;
2587
+ }
2588
+ async function readCacheStat() {
2589
+ try {
2590
+ const data = JSON.parse(await readFile2(cacheStatFile(), "utf-8"));
2591
+ if (typeof data.entryCount === "number" && Number.isFinite(data.entryCount) && data.entryCount >= 0) {
2592
+ return {
2593
+ version: 1,
2594
+ entryCount: Math.floor(data.entryCount),
2595
+ dimensions: isValidDimension(data.dimensions) ? data.dimensions : null
2596
+ };
2597
+ }
2598
+ } catch {
2599
+ }
2600
+ return null;
2601
+ }
2602
+ async function writeCacheStat(stat5) {
2603
+ try {
2604
+ await mkdir2(cacheDir(), { recursive: true });
2605
+ await writeFile2(cacheStatFile(), JSON.stringify(stat5));
2606
+ } catch {
2607
+ }
2608
+ }
2502
2609
  function normalizeText(text2) {
2503
2610
  return text2.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
2504
2611
  }
@@ -2584,29 +2691,300 @@ function toProviderInput(input, baseUrl) {
2584
2691
  }
2585
2692
  return { type: input.modality, url: input.url };
2586
2693
  }
2694
+ function parseCacheJsonInWorker(filePath) {
2695
+ return new Promise((resolve, reject) => {
2696
+ void import("worker_threads").then(({ Worker }) => {
2697
+ const worker = new Worker(
2698
+ `
2699
+ const { parentPort, workerData } = require('node:worker_threads');
2700
+ const { readFileSync } = require('node:fs');
2701
+ try {
2702
+ const raw = readFileSync(workerData.filePath, 'utf8');
2703
+ parentPort.postMessage({ ok: true, entries: JSON.parse(raw) });
2704
+ } catch (err) {
2705
+ parentPort.postMessage({
2706
+ ok: false,
2707
+ error: err instanceof Error ? err.message : String(err),
2708
+ });
2709
+ }
2710
+ `,
2711
+ { eval: true, workerData: { filePath } }
2712
+ );
2713
+ worker.once("message", (msg) => {
2714
+ void worker.terminate();
2715
+ if (msg?.ok && Array.isArray(msg.entries)) {
2716
+ resolve(msg.entries);
2717
+ return;
2718
+ }
2719
+ reject(new Error(msg?.error || "embedding cache worker parse failed"));
2720
+ });
2721
+ worker.once("error", reject);
2722
+ }).catch(reject);
2723
+ });
2724
+ }
2725
+ function findJsonValueEnd(source) {
2726
+ let i2 = 0;
2727
+ while (i2 < source.length && /\s/.test(source[i2])) i2 += 1;
2728
+ if (i2 >= source.length) return -1;
2729
+ const start = source[i2];
2730
+ if (start === '"') {
2731
+ i2 += 1;
2732
+ while (i2 < source.length) {
2733
+ if (source[i2] === "\\") {
2734
+ i2 += 2;
2735
+ continue;
2736
+ }
2737
+ if (source[i2] === '"') return i2 + 1;
2738
+ i2 += 1;
2739
+ }
2740
+ return -1;
2741
+ }
2742
+ if (start === "{" || start === "[") {
2743
+ const close = start === "{" ? "}" : "]";
2744
+ let depth = 0;
2745
+ let inString = false;
2746
+ for (; i2 < source.length; i2 += 1) {
2747
+ const ch = source[i2];
2748
+ if (inString) {
2749
+ if (ch === "\\") {
2750
+ i2 += 1;
2751
+ continue;
2752
+ }
2753
+ if (ch === '"') inString = false;
2754
+ continue;
2755
+ }
2756
+ if (ch === '"') {
2757
+ inString = true;
2758
+ continue;
2759
+ }
2760
+ if (ch === start) depth += 1;
2761
+ else if (ch === close) {
2762
+ depth -= 1;
2763
+ if (depth === 0) return i2 + 1;
2764
+ }
2765
+ }
2766
+ return -1;
2767
+ }
2768
+ const rest = source.slice(i2);
2769
+ const match2 = rest.match(/^(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/);
2770
+ return match2 ? i2 + match2[0].length : -1;
2771
+ }
2772
+ async function streamJsonArrayToJsonl(filePath, tmpPath) {
2773
+ const input = createReadStream(filePath, { encoding: "utf8" });
2774
+ const out = createWriteStream(tmpPath);
2775
+ let buf = "";
2776
+ let started = false;
2777
+ let count2 = 0;
2778
+ const writeLine = async (line) => {
2779
+ if (!out.write(line)) {
2780
+ await new Promise((resolve, reject) => {
2781
+ out.once("drain", resolve);
2782
+ out.once("error", reject);
2783
+ });
2784
+ }
2785
+ };
2786
+ try {
2787
+ for await (const chunk of input) {
2788
+ buf += chunk;
2789
+ if (!started) {
2790
+ const idx = buf.search(/\S/);
2791
+ if (idx < 0) continue;
2792
+ if (buf[idx] !== "[") throw new Error("embedding cache is not a JSON array");
2793
+ buf = buf.slice(idx + 1);
2794
+ started = true;
2795
+ }
2796
+ while (true) {
2797
+ buf = buf.replace(/^\s*,?\s*/, "");
2798
+ if (!buf) break;
2799
+ if (buf[0] === "]") {
2800
+ buf = "";
2801
+ break;
2802
+ }
2803
+ const end = findJsonValueEnd(buf);
2804
+ if (end < 0) break;
2805
+ const value = JSON.parse(buf.slice(0, end));
2806
+ buf = buf.slice(end);
2807
+ const row = parseCacheRow(value);
2808
+ if (row) {
2809
+ await writeLine(`${JSON.stringify({ h: row.hash, v: row.vector })}
2810
+ `);
2811
+ count2 += 1;
2812
+ if (count2 % 50 === 0) {
2813
+ await new Promise((resolve) => setImmediate(resolve));
2814
+ }
2815
+ }
2816
+ }
2817
+ if (buf.length > MAX_MIGRATION_VALUE_BYTES) {
2818
+ throw new Error("embedding cache entry too large to migrate safely");
2819
+ }
2820
+ }
2821
+ } finally {
2822
+ await new Promise((resolve, reject) => {
2823
+ out.end(() => resolve());
2824
+ out.once("error", reject);
2825
+ });
2826
+ }
2827
+ return count2;
2828
+ }
2829
+ async function migrateJsonArrayCacheToJsonl(filePath, jsonlPath) {
2830
+ const tmpPath = `${jsonlPath}.tmp`;
2831
+ try {
2832
+ const count2 = await streamJsonArrayToJsonl(filePath, tmpPath);
2833
+ await rename(tmpPath, jsonlPath);
2834
+ diskCacheEntryCount = count2;
2835
+ await writeCacheStat({
2836
+ version: 1,
2837
+ entryCount: count2,
2838
+ dimensions: cacheDimensions
2839
+ });
2840
+ return count2;
2841
+ } catch (err) {
2842
+ await unlink(tmpPath).catch(() => void 0);
2843
+ throw err;
2844
+ }
2845
+ }
2846
+ function convertArrayCacheToJsonl(filePath, jsonlPath) {
2847
+ return migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
2848
+ }
2849
+ async function loadCacheJsonl(filePath) {
2850
+ const rl = createInterface({
2851
+ input: createReadStream(filePath, { encoding: "utf8" }),
2852
+ crlfDelay: Infinity
2853
+ });
2854
+ let loaded = 0;
2855
+ let onDiskCount = 0;
2856
+ for await (const line of rl) {
2857
+ const trimmed = line.trim();
2858
+ if (!trimmed) continue;
2859
+ try {
2860
+ const row = parseCacheRow(JSON.parse(trimmed));
2861
+ if (!row) continue;
2862
+ onDiskCount += 1;
2863
+ cacheInsert(row.hash, row.vector, false);
2864
+ loaded = cache3.size;
2865
+ } catch {
2866
+ }
2867
+ if (onDiskCount % 250 === 0) {
2868
+ await new Promise((resolve) => setImmediate(resolve));
2869
+ }
2870
+ }
2871
+ diskCacheEntryCount = onDiskCount;
2872
+ await writeCacheStat({
2873
+ version: 1,
2874
+ entryCount: onDiskCount,
2875
+ dimensions: cacheDimensions
2876
+ });
2877
+ return loaded;
2878
+ }
2879
+ async function countJsonlLines(filePath) {
2880
+ jsonlLineCountCalls += 1;
2881
+ try {
2882
+ const rl = createInterface({
2883
+ input: createReadStream(filePath, { encoding: "utf8" }),
2884
+ crlfDelay: Infinity
2885
+ });
2886
+ let count2 = 0;
2887
+ for await (const line of rl) {
2888
+ if (line.trim()) count2 += 1;
2889
+ }
2890
+ return count2;
2891
+ } catch {
2892
+ return 0;
2893
+ }
2894
+ }
2895
+ async function resolveOnDiskCount(jsonlPath) {
2896
+ if (diskCacheEntryCount > 0) return diskCacheEntryCount;
2897
+ const sidecar = await readCacheStat();
2898
+ if (sidecar) {
2899
+ diskCacheEntryCount = sidecar.entryCount;
2900
+ if (sidecar.dimensions && cacheDimensions === null) cacheDimensions = sidecar.dimensions;
2901
+ return sidecar.entryCount;
2902
+ }
2903
+ try {
2904
+ await access(jsonlPath, fsConstants.R_OK);
2905
+ } catch {
2906
+ return 0;
2907
+ }
2908
+ const counted = await countJsonlLines(jsonlPath);
2909
+ diskCacheEntryCount = counted;
2910
+ await writeCacheStat({
2911
+ version: 1,
2912
+ entryCount: counted,
2913
+ dimensions: cacheDimensions
2914
+ });
2915
+ return counted;
2916
+ }
2587
2917
  async function loadDiskCache2() {
2588
- if (diskCacheLoaded) return;
2918
+ if (diskCacheLoaded) return cache3.size;
2919
+ let loaded = 0;
2589
2920
  try {
2590
- const raw = await readFile2(cacheFile(), "utf-8");
2591
- const entries = JSON.parse(raw);
2592
- for (const [k, v] of entries) cache3.set(k, v);
2593
- console.error(`[memorix] Loaded ${entries.length} cached API embeddings from disk`);
2921
+ const jsonlPath = cacheJsonlFile();
2922
+ let hasJsonl = false;
2923
+ try {
2924
+ await access(jsonlPath, fsConstants.R_OK);
2925
+ hasJsonl = true;
2926
+ } catch {
2927
+ hasJsonl = false;
2928
+ }
2929
+ if (hasJsonl) {
2930
+ loaded = await loadCacheJsonl(jsonlPath);
2931
+ } else {
2932
+ const filePath = cacheFile();
2933
+ await stat(filePath);
2934
+ await migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
2935
+ loaded = await loadCacheJsonl(jsonlPath);
2936
+ }
2937
+ diskCacheLoadCount += 1;
2938
+ console.error(`[memorix] Loaded ${loaded} cached API embeddings from disk`);
2594
2939
  } catch {
2595
2940
  }
2596
2941
  diskCacheLoaded = true;
2942
+ return loaded;
2597
2943
  }
2598
2944
  function startDiskCacheLoad() {
2599
2945
  if (diskCacheLoaded || diskCacheLoadPromise) return;
2600
- diskCacheLoadPromise = loadDiskCache2().catch(() => {
2946
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
2601
2947
  });
2602
2948
  }
2603
2949
  async function ensureDiskCacheLoaded() {
2604
- if (diskCacheLoaded) return;
2605
- if (diskCacheLoadPromise) {
2606
- await diskCacheLoadPromise;
2607
- return;
2608
- }
2609
- await loadDiskCache2();
2950
+ if (diskCacheLoaded) return cache3.size;
2951
+ if (!diskCacheLoadPromise) {
2952
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
2953
+ });
2954
+ }
2955
+ await diskCacheLoadPromise;
2956
+ return cache3.size;
2957
+ }
2958
+ function getApiEmbeddingDiskCacheLoadCountForTests() {
2959
+ return diskCacheLoadCount;
2960
+ }
2961
+ function resetApiEmbeddingCacheForTests() {
2962
+ cache3.clear();
2963
+ cachePayloadBytes = 0;
2964
+ cacheDimensions = null;
2965
+ diskCacheEntryCount = 0;
2966
+ diskCacheLoaded = false;
2967
+ diskCacheLoadPromise = null;
2968
+ diskCacheLoadCount = 0;
2969
+ diskCacheDirty2 = false;
2970
+ jsonlLineCountCalls = 0;
2971
+ }
2972
+ function seedApiEmbeddingCacheForTests(entries) {
2973
+ cache3.clear();
2974
+ cachePayloadBytes = 0;
2975
+ cacheDimensions = null;
2976
+ for (const [key, value] of entries) cacheInsert(key, value, true);
2977
+ diskCacheLoaded = true;
2978
+ diskCacheDirty2 = true;
2979
+ }
2980
+ function getApiEmbeddingCachePayloadBytesForTests() {
2981
+ return cachePayloadBytes;
2982
+ }
2983
+ function getApiEmbeddingJsonlLineCountCallsForTests() {
2984
+ return jsonlLineCountCalls;
2985
+ }
2986
+ function getApiEmbeddingMaxEntriesForDimensionsForTests(dimensions) {
2987
+ return maxEntriesForDimensions(dimensions);
2610
2988
  }
2611
2989
  function dimsCacheKey(config) {
2612
2990
  return [
@@ -2722,8 +3100,46 @@ async function saveDiskCacheNow() {
2722
3100
  if (!diskCacheDirty2) return;
2723
3101
  try {
2724
3102
  await mkdir2(cacheDir(), { recursive: true });
2725
- const entries = Array.from(cache3.entries());
2726
- await writeFile2(cacheFile(), JSON.stringify(entries));
3103
+ const jsonlPath = cacheJsonlFile();
3104
+ const existing = await resolveOnDiskCount(jsonlPath);
3105
+ if (existing > 0 && cache3.size < Math.ceil(existing * 0.5)) {
3106
+ console.error(
3107
+ `[memorix] Refusing to overwrite embedding cache (${existing} on disk vs ${cache3.size} in memory)`
3108
+ );
3109
+ diskCacheDirty2 = false;
3110
+ return;
3111
+ }
3112
+ const tmpPath = `${jsonlPath}.tmp`;
3113
+ await new Promise((resolve, reject) => {
3114
+ const out = createWriteStream(tmpPath);
3115
+ const entries = cache3.entries();
3116
+ const writeBatch = () => {
3117
+ for (let i2 = 0; i2 < 50; i2++) {
3118
+ const next = entries.next();
3119
+ if (next.done) {
3120
+ out.end();
3121
+ return;
3122
+ }
3123
+ const [hash4, vector] = next.value;
3124
+ if (!out.write(`${JSON.stringify({ h: hash4, v: vector })}
3125
+ `)) {
3126
+ out.once("drain", writeBatch);
3127
+ return;
3128
+ }
3129
+ }
3130
+ setImmediate(writeBatch);
3131
+ };
3132
+ out.on("finish", resolve);
3133
+ out.on("error", reject);
3134
+ writeBatch();
3135
+ });
3136
+ await rename(tmpPath, jsonlPath);
3137
+ diskCacheEntryCount = cache3.size;
3138
+ await writeCacheStat({
3139
+ version: 1,
3140
+ entryCount: cache3.size,
3141
+ dimensions: cacheDimensions
3142
+ });
2727
3143
  diskCacheDirty2 = false;
2728
3144
  } catch {
2729
3145
  }
@@ -2737,12 +3153,7 @@ function scheduleDiskSave() {
2737
3153
  }, DISK_SAVE_DEBOUNCE_MS);
2738
3154
  }
2739
3155
  function cacheSet(hash4, value) {
2740
- if (cache3.size >= MAX_CACHE_SIZE3) {
2741
- const firstKey = cache3.keys().next().value;
2742
- if (firstKey !== void 0) cache3.delete(firstKey);
2743
- }
2744
- cache3.set(hash4, value);
2745
- diskCacheDirty2 = true;
3156
+ cacheInsert(hash4, value, true);
2746
3157
  }
2747
3158
  function resolveEnvEmbeddingApiKey() {
2748
3159
  return process.env.MEMORIX_EMBEDDING_API_KEY;
@@ -2861,18 +3272,27 @@ async function waitForRetry(waitMs, signal) {
2861
3272
  abortSignal.addEventListener("abort", onAbort, { once: true });
2862
3273
  });
2863
3274
  }
2864
- 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;
3275
+ 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;
2865
3276
  var init_api_provider = __esm({
2866
3277
  "src/embedding/api-provider.ts"() {
2867
3278
  "use strict";
2868
3279
  init_esm_shims();
2869
3280
  init_provider();
2870
3281
  cache3 = /* @__PURE__ */ new Map();
2871
- MAX_CACHE_SIZE3 = 1e4;
3282
+ MAX_CACHE_ENTRIES = 2e5;
3283
+ MAX_CACHE_PAYLOAD_BYTES = 256 * 1024 * 1024;
3284
+ BYTES_PER_DIM = 8;
3285
+ DEFAULT_ASSUMED_DIMENSIONS = 4096;
3286
+ MAX_MIGRATION_VALUE_BYTES = 16 * 1024 * 1024;
3287
+ cachePayloadBytes = 0;
3288
+ cacheDimensions = null;
3289
+ diskCacheEntryCount = 0;
2872
3290
  diskCacheDirty2 = false;
2873
3291
  diskSaveTimer = null;
2874
3292
  diskCacheLoaded = false;
2875
3293
  diskCacheLoadPromise = null;
3294
+ diskCacheLoadCount = 0;
3295
+ jsonlLineCountCalls = 0;
2876
3296
  MAX_INPUT_CHARS = 32e3;
2877
3297
  MAX_CONCURRENCY = 4;
2878
3298
  DISK_SAVE_DEBOUNCE_MS = 5e3;
@@ -2930,7 +3350,9 @@ var init_api_provider = __esm({
2930
3350
  if (config.requestedDimensions) {
2931
3351
  console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
2932
3352
  }
2933
- startDiskCacheLoad();
3353
+ if (allowNetworkProbe) {
3354
+ startDiskCacheLoad();
3355
+ }
2934
3356
  return new _APIEmbeddingProvider(config, probeDimensions);
2935
3357
  }
2936
3358
  static resolveConfig() {
@@ -3206,6 +3628,7 @@ __export(provider_exports, {
3206
3628
  MAX_INLINE_EMBEDDING_BYTES: () => MAX_INLINE_EMBEDDING_BYTES,
3207
3629
  UnsupportedEmbeddingModalityError: () => UnsupportedEmbeddingModalityError,
3208
3630
  getEmbeddingProvider: () => getEmbeddingProvider,
3631
+ getEmbeddingRuntimeHealth: () => getEmbeddingRuntimeHealth,
3209
3632
  isEmbeddingExplicitlyDisabled: () => isEmbeddingExplicitlyDisabled,
3210
3633
  isVectorSearchAvailable: () => isVectorSearchAvailable,
3211
3634
  resetProvider: () => resetProvider,
@@ -3267,7 +3690,12 @@ function warnOnce(message) {
3267
3690
  lastEmbeddingWarningAt = now3;
3268
3691
  console.error(message);
3269
3692
  }
3693
+ function embeddingModeToken() {
3694
+ return process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim() ?? "";
3695
+ }
3270
3696
  function getEmbeddingMode2() {
3697
+ const token = embeddingModeToken();
3698
+ if (cachedEmbeddingMode && cachedEmbeddingModeToken === token) return cachedEmbeddingMode;
3271
3699
  try {
3272
3700
  let cfg;
3273
3701
  try {
@@ -3276,10 +3704,17 @@ function getEmbeddingMode2() {
3276
3704
  cfg = (init_config(), __toCommonJS(config_exports));
3277
3705
  }
3278
3706
  const { getEmbeddingMode: cfgMode } = cfg;
3279
- return cfgMode();
3707
+ cachedEmbeddingMode = cfgMode();
3708
+ cachedEmbeddingModeToken = token;
3709
+ return cachedEmbeddingMode;
3280
3710
  } catch {
3281
- const env2 = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
3282
- if (env2 === "fastembed" || env2 === "transformers" || env2 === "api" || env2 === "auto") return env2;
3711
+ if (token === "fastembed" || token === "transformers" || token === "api" || token === "auto") {
3712
+ cachedEmbeddingMode = token;
3713
+ cachedEmbeddingModeToken = token;
3714
+ return token;
3715
+ }
3716
+ cachedEmbeddingMode = "off";
3717
+ cachedEmbeddingModeToken = token;
3283
3718
  return "off";
3284
3719
  }
3285
3720
  }
@@ -3313,13 +3748,36 @@ function isTemporaryEmbeddingFailure(error) {
3313
3748
  if (!(error instanceof Error)) return false;
3314
3749
  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);
3315
3750
  }
3751
+ function classifyEmbeddingFailure(error) {
3752
+ const message = error instanceof Error ? error.message : String(error);
3753
+ if (/embedding api error \(401\)|invalid_api_key|incorrect api key|unauthorized/i.test(message)) {
3754
+ return "authorization";
3755
+ }
3756
+ if (/embedding api error \(402\)|quota exceeded|account balance/i.test(message)) {
3757
+ return "billing";
3758
+ }
3759
+ if (/embedding api timeout|abort/i.test(message)) {
3760
+ return "timeout";
3761
+ }
3762
+ return "upstream";
3763
+ }
3316
3764
  function markTemporaryFailure(reason) {
3317
3765
  provider = null;
3318
3766
  initPromise = null;
3319
3767
  lastInitWasTemporaryFailure = true;
3320
3768
  lastFailureTimestamp = Date.now();
3769
+ lastEmbeddingFailure = {
3770
+ kind: classifyEmbeddingFailure(reason),
3771
+ occurredAt: lastFailureTimestamp
3772
+ };
3321
3773
  const message = reason instanceof Error ? reason.message : String(reason);
3322
- warnOnce(`[memorix] Embedding provider temporarily unavailable at runtime: ${message}`);
3774
+ warnOnce(`[memorix] Embedding provider unavailable at runtime: ${message}`);
3775
+ }
3776
+ function markProviderReady(candidate) {
3777
+ provider = wrapProvider(candidate);
3778
+ lastInitWasTemporaryFailure = false;
3779
+ lastEmbeddingFailure = null;
3780
+ return provider;
3323
3781
  }
3324
3782
  async function createFastEmbedProvider() {
3325
3783
  try {
@@ -3435,35 +3893,35 @@ async function getEmbeddingProvider(options = {}) {
3435
3893
  if (mode === "fastembed") {
3436
3894
  const initialized = await createFastEmbedProvider();
3437
3895
  if (!initialized) return null;
3438
- provider = wrapProvider(initialized);
3896
+ provider = markProviderReady(initialized);
3439
3897
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3440
3898
  return provider;
3441
3899
  }
3442
3900
  if (mode === "transformers") {
3443
3901
  const initialized = await createTransformersProvider();
3444
3902
  if (!initialized) return null;
3445
- provider = wrapProvider(initialized);
3903
+ provider = markProviderReady(initialized);
3446
3904
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3447
3905
  return provider;
3448
3906
  }
3449
3907
  if (mode === "api") {
3450
3908
  const initialized = await createAPIProvider(options);
3451
3909
  if (!initialized) return null;
3452
- provider = wrapProvider(initialized);
3910
+ provider = markProviderReady(initialized);
3453
3911
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3454
3912
  return provider;
3455
3913
  }
3456
3914
  if (hasAPIEmbeddingConfig()) {
3457
3915
  const initialized = await createAPIProvider(options);
3458
3916
  if (initialized) {
3459
- provider = wrapProvider(initialized);
3917
+ provider = markProviderReady(initialized);
3460
3918
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3461
3919
  return provider;
3462
3920
  }
3463
3921
  }
3464
3922
  const localFallback = await createLocalFallbackProvider();
3465
3923
  if (localFallback) {
3466
- provider = wrapProvider(localFallback);
3924
+ provider = markProviderReady(localFallback);
3467
3925
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3468
3926
  return provider;
3469
3927
  }
@@ -3485,18 +3943,42 @@ async function isVectorSearchAvailable() {
3485
3943
  const p = await getEmbeddingProvider();
3486
3944
  return p !== null;
3487
3945
  }
3946
+ function getEmbeddingRuntimeHealth() {
3947
+ const mode = getEmbeddingMode2();
3948
+ if (mode === "off") return { status: "disabled" };
3949
+ if (lastInitWasTemporaryFailure && lastEmbeddingFailure) {
3950
+ return {
3951
+ status: "degraded",
3952
+ failure: {
3953
+ kind: lastEmbeddingFailure.kind,
3954
+ occurredAt: new Date(lastEmbeddingFailure.occurredAt).toISOString(),
3955
+ retryAfterMs: Math.max(0, RETRY_COOLDOWN_MS - (Date.now() - lastEmbeddingFailure.occurredAt))
3956
+ }
3957
+ };
3958
+ }
3959
+ if (provider) {
3960
+ return {
3961
+ status: "ready",
3962
+ provider: { name: provider.name, dimensions: provider.dimensions }
3963
+ };
3964
+ }
3965
+ return { status: "uninitialized" };
3966
+ }
3488
3967
  function isEmbeddingExplicitlyDisabled() {
3489
3968
  return getEmbeddingMode2() === "off";
3490
3969
  }
3491
3970
  function resetProvider() {
3492
3971
  provider = null;
3493
3972
  initPromise = null;
3973
+ cachedEmbeddingMode = null;
3974
+ cachedEmbeddingModeToken = null;
3494
3975
  lastInitWasTemporaryFailure = false;
3495
3976
  lastFailureTimestamp = 0;
3977
+ lastEmbeddingFailure = null;
3496
3978
  lastEmbeddingWarning = "";
3497
3979
  lastEmbeddingWarningAt = 0;
3498
3980
  }
3499
- var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
3981
+ var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, cachedEmbeddingMode, cachedEmbeddingModeToken, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
3500
3982
  var init_provider = __esm({
3501
3983
  "src/embedding/provider.ts"() {
3502
3984
  "use strict";
@@ -3520,7 +4002,10 @@ var init_provider = __esm({
3520
4002
  MAX_INLINE_EMBEDDING_BYTES = 5 * 1024 * 1024;
3521
4003
  provider = null;
3522
4004
  initPromise = null;
4005
+ cachedEmbeddingMode = null;
4006
+ cachedEmbeddingModeToken = null;
3523
4007
  lastInitWasTemporaryFailure = false;
4008
+ lastEmbeddingFailure = null;
3524
4009
  lastEmbeddingWarning = "";
3525
4010
  lastEmbeddingWarningAt = 0;
3526
4011
  WARNING_COOLDOWN_MS = 3e4;
@@ -5276,6 +5761,9 @@ async function attachCachedVectors(database, candidates) {
5276
5761
  await update(database, candidates[index].id, { ...existing, embedding: vector });
5277
5762
  } catch {
5278
5763
  }
5764
+ if (index % 50 === 0) {
5765
+ await new Promise((resolve) => setImmediate(resolve));
5766
+ }
5279
5767
  }
5280
5768
  }
5281
5769
  async function hydrateIndex(observations2, options = {}) {
@@ -5326,17 +5814,24 @@ async function hydrateIndex(observations2, options = {}) {
5326
5814
  await insert2(database, doc);
5327
5815
  rememberObservationDoc(doc);
5328
5816
  inserted++;
5817
+ if (inserted % 50 === 0) {
5818
+ await new Promise((resolve) => setImmediate(resolve));
5819
+ }
5820
+ if (inserted % 200 === 0) {
5821
+ await new Promise((resolve) => setTimeout(resolve, 0));
5822
+ }
5329
5823
  } catch {
5330
5824
  }
5331
5825
  }
5332
- deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
5826
+ deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
5333
5827
  }) : null;
5334
5828
  return inserted;
5335
5829
  }
5336
- async function hydrateIndexForStartup(observations2) {
5830
+ async function hydrateIndexForStartup(observations2, options = {}) {
5337
5831
  return hydrateIndex(observations2, {
5338
5832
  allowNetworkProbe: false,
5339
- deferCachedVectors: true
5833
+ deferCachedVectors: true,
5834
+ skipCachedVectors: options.skipCachedVectors
5340
5835
  });
5341
5836
  }
5342
5837
  function getDeferredCachedVectorHydration() {
@@ -8455,7 +8950,7 @@ var init_types2 = __esm({
8455
8950
 
8456
8951
  // src/media/asset-store.ts
8457
8952
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
8458
- import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
8953
+ import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
8459
8954
  import path9 from "path";
8460
8955
  function mediaRoot(dataDir) {
8461
8956
  return path9.resolve(dataDir, "media");
@@ -8528,7 +9023,7 @@ async function persistBuffer(bytes, destination) {
8528
9023
  const temporary = `${destination}.${randomUUID5()}.tmp`;
8529
9024
  try {
8530
9025
  await writeFile3(temporary, bytes, { flag: "wx" });
8531
- await rename(temporary, destination);
9026
+ await rename2(temporary, destination);
8532
9027
  return true;
8533
9028
  } catch (error) {
8534
9029
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -59608,9 +60103,9 @@ var require_node_domexception = __commonJS({
59608
60103
  });
59609
60104
 
59610
60105
  // node_modules/fetch-blob/from.js
59611
- import { statSync as statSync2, createReadStream, promises as fs7 } from "fs";
60106
+ import { statSync as statSync2, createReadStream as createReadStream2, promises as fs7 } from "fs";
59612
60107
  import { basename } from "path";
59613
- var import_node_domexception, stat2, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
60108
+ var import_node_domexception, stat3, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
59614
60109
  var init_from = __esm({
59615
60110
  "node_modules/fetch-blob/from.js"() {
59616
60111
  "use strict";
@@ -59618,23 +60113,23 @@ var init_from = __esm({
59618
60113
  import_node_domexception = __toESM(require_node_domexception(), 1);
59619
60114
  init_file();
59620
60115
  init_fetch_blob();
59621
- ({ stat: stat2 } = fs7);
60116
+ ({ stat: stat3 } = fs7);
59622
60117
  blobFromSync = (path21, type) => fromBlob(statSync2(path21), path21, type);
59623
- blobFrom = (path21, type) => stat2(path21).then((stat4) => fromBlob(stat4, path21, type));
59624
- fileFrom = (path21, type) => stat2(path21).then((stat4) => fromFile(stat4, path21, type));
60118
+ blobFrom = (path21, type) => stat3(path21).then((stat5) => fromBlob(stat5, path21, type));
60119
+ fileFrom = (path21, type) => stat3(path21).then((stat5) => fromFile(stat5, path21, type));
59625
60120
  fileFromSync = (path21, type) => fromFile(statSync2(path21), path21, type);
59626
- fromBlob = (stat4, path21, type = "") => new fetch_blob_default([new BlobDataItem({
60121
+ fromBlob = (stat5, path21, type = "") => new fetch_blob_default([new BlobDataItem({
59627
60122
  path: path21,
59628
- size: stat4.size,
59629
- lastModified: stat4.mtimeMs,
60123
+ size: stat5.size,
60124
+ lastModified: stat5.mtimeMs,
59630
60125
  start: 0
59631
60126
  })], { type });
59632
- fromFile = (stat4, path21, type = "") => new file_default([new BlobDataItem({
60127
+ fromFile = (stat5, path21, type = "") => new file_default([new BlobDataItem({
59633
60128
  path: path21,
59634
- size: stat4.size,
59635
- lastModified: stat4.mtimeMs,
60129
+ size: stat5.size,
60130
+ lastModified: stat5.mtimeMs,
59636
60131
  start: 0
59637
- })], basename(path21), { type, lastModified: stat4.mtimeMs });
60132
+ })], basename(path21), { type, lastModified: stat5.mtimeMs });
59638
60133
  BlobDataItem = class _BlobDataItem {
59639
60134
  #path;
59640
60135
  #start;
@@ -59657,11 +60152,11 @@ var init_from = __esm({
59657
60152
  });
59658
60153
  }
59659
60154
  async *stream() {
59660
- const { mtimeMs } = await stat2(this.#path);
60155
+ const { mtimeMs } = await stat3(this.#path);
59661
60156
  if (mtimeMs > this.lastModified) {
59662
60157
  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");
59663
60158
  }
59664
- yield* createReadStream(this.#path, {
60159
+ yield* createReadStream2(this.#path, {
59665
60160
  start: this.#start,
59666
60161
  end: this.#start + this.size - 1
59667
60162
  });
@@ -75291,7 +75786,7 @@ var init_wrapper = __esm({
75291
75786
  });
75292
75787
 
75293
75788
  // node_modules/@google/genai/dist/node/index.mjs
75294
- import { createWriteStream } from "fs";
75789
+ import { createWriteStream as createWriteStream2 } from "fs";
75295
75790
  import * as fs8 from "fs/promises";
75296
75791
  import { writeFile as writeFile4 } from "fs/promises";
75297
75792
  import { Readable } from "stream";
@@ -92571,7 +93066,7 @@ ${underline}`);
92571
93066
  if (params.downloadPath) {
92572
93067
  const response = await downloadFile(params, apiClient);
92573
93068
  if (response instanceof HttpResponse) {
92574
- const writer = createWriteStream(params.downloadPath);
93069
+ const writer = createWriteStream2(params.downloadPath);
92575
93070
  const body = Readable.fromWeb(response.responseInternal.body);
92576
93071
  body.pipe(writer);
92577
93072
  await finished(writer);
@@ -142489,7 +142984,7 @@ function explainRetention(doc, referenceTime) {
142489
142984
  };
142490
142985
  }
142491
142986
  function toRetentionDocument(obs, accessMap) {
142492
- const access = accessMap?.get(obs.id);
142987
+ const access2 = accessMap?.get(obs.id);
142493
142988
  return {
142494
142989
  id: `obs-${obs.id}`,
142495
142990
  observationId: obs.id,
@@ -142503,8 +142998,8 @@ function toRetentionDocument(obs, accessMap) {
142503
142998
  tokens: obs.tokens,
142504
142999
  createdAt: obs.createdAt,
142505
143000
  projectId: obs.projectId,
142506
- accessCount: access?.accessCount ?? 0,
142507
- lastAccessedAt: access?.lastAccessedAt ?? "",
143001
+ accessCount: access2?.accessCount ?? 0,
143002
+ lastAccessedAt: access2?.lastAccessedAt ?? "",
142508
143003
  status: obs.status ?? "active",
142509
143004
  source: obs.source ?? "agent",
142510
143005
  sourceDetail: obs.sourceDetail ?? "",
@@ -144614,7 +145109,7 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
144614
145109
  const rel = normalizeCodePath(relative(projectRoot, abs));
144615
145110
  try {
144616
145111
  const text2 = readFileSync6(abs, "utf-8");
144617
- const stat4 = fileStat ?? statSync3(abs);
145112
+ const stat5 = fileStat ?? statSync3(abs);
144618
145113
  const file = {
144619
145114
  id: makeCodeFileId(projectId, rel),
144620
145115
  projectId,
@@ -144623,8 +145118,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
144623
145118
  contentHash: hashText(text2),
144624
145119
  // Preserve filesystem precision. Rounding here made same-size edits in
144625
145120
  // a short Windows timestamp window indistinguishable to refresh.
144626
- mtimeMs: Number(stat4.mtimeMs),
144627
- sizeBytes: Number(stat4.size),
145121
+ mtimeMs: Number(stat5.mtimeMs),
145122
+ sizeBytes: Number(stat5.size),
144628
145123
  indexedAt
144629
145124
  };
144630
145125
  return {
@@ -144651,18 +145146,18 @@ async function indexProjectLite(options) {
144651
145146
  let skippedOversizedFiles = 0;
144652
145147
  let unreadableFiles = 0;
144653
145148
  for (const abs of paths) {
144654
- let stat4;
145149
+ let stat5;
144655
145150
  try {
144656
- stat4 = statSync3(abs);
145151
+ stat5 = statSync3(abs);
144657
145152
  } catch {
144658
145153
  unreadableFiles++;
144659
145154
  continue;
144660
145155
  }
144661
- if (Number(stat4.size) > maxFileBytes) {
145156
+ if (Number(stat5.size) > maxFileBytes) {
144662
145157
  skippedOversizedFiles++;
144663
145158
  continue;
144664
145159
  }
144665
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
145160
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
144666
145161
  if (!attempt.delta) {
144667
145162
  if (attempt.unreadable) unreadableFiles++;
144668
145163
  continue;
@@ -144690,16 +145185,16 @@ async function refreshProjectLite(store, options) {
144690
145185
  for (const abs of paths) {
144691
145186
  const rel = normalizeCodePath(relative(options.projectRoot, abs));
144692
145187
  seenPaths.add(rel);
144693
- let stat4;
145188
+ let stat5;
144694
145189
  try {
144695
- stat4 = statSync3(abs);
145190
+ stat5 = statSync3(abs);
144696
145191
  } catch {
144697
145192
  unreadableFiles++;
144698
145193
  continue;
144699
145194
  }
144700
145195
  const existing = existingByPath.get(rel);
144701
- const mtimeMs = Number(stat4.mtimeMs);
144702
- const sizeBytes = Number(stat4.size);
145196
+ const mtimeMs = Number(stat5.mtimeMs);
145197
+ const sizeBytes = Number(stat5.size);
144703
145198
  if (sizeBytes > maxFileBytes) {
144704
145199
  skippedOversizedFiles++;
144705
145200
  if (existing) oversizedExistingFileIds.add(existing.id);
@@ -144709,7 +145204,7 @@ async function refreshProjectLite(store, options) {
144709
145204
  unchangedFiles++;
144710
145205
  continue;
144711
145206
  }
144712
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
145207
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
144713
145208
  if (!attempt.delta) {
144714
145209
  if (attempt.unreadable) unreadableFiles++;
144715
145210
  continue;
@@ -148106,12 +148601,25 @@ __export(observations_exports, {
148106
148601
  prepareSearchIndex: () => prepareSearchIndex,
148107
148602
  probeSearchIndex: () => probeSearchIndex,
148108
148603
  reindexObservations: () => reindexObservations,
148604
+ resetObservationRuntime: () => resetObservationRuntime,
148109
148605
  resolveObservations: () => resolveObservations,
148110
148606
  storeObservation: () => storeObservation,
148111
148607
  suggestTopicKey: () => suggestTopicKey,
148112
148608
  updateObservationAdmission: () => updateObservationAdmission,
148113
148609
  withFreshObservations: () => withFreshObservations
148114
148610
  });
148611
+ function resetObservationRuntime() {
148612
+ observations = [];
148613
+ nextId = 1;
148614
+ projectDir = null;
148615
+ searchIndexPrepared = false;
148616
+ corpusLoaded = false;
148617
+ loadedObservationStore = null;
148618
+ embeddingWriteMode = "background";
148619
+ embeddingWorkerProjectRoot = void 0;
148620
+ vectorMissingIds.clear();
148621
+ vectorSchemaUpgradePromise = null;
148622
+ }
148115
148623
  function logEmbeddingFailureOnce(key, message) {
148116
148624
  const now3 = Date.now();
148117
148625
  const last = embeddingFailureLogTimestamps.get(key) ?? 0;
@@ -148220,23 +148728,33 @@ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
148220
148728
  async function initObservations(dir, options = {}) {
148221
148729
  embeddingWriteMode = options.embeddingWriteMode ?? "background";
148222
148730
  embeddingWorkerProjectRoot = options.projectRoot;
148223
- if (projectDir === dir) return;
148224
148731
  await initObservationStore(dir);
148225
148732
  const store = getObservationStore();
148226
- observations = await store.loadAll();
148733
+ if (projectDir === dir && loadedObservationStore === store) {
148734
+ if (options.skipCorpusLoad || corpusLoaded) return;
148735
+ }
148227
148736
  nextId = await store.loadIdCounter();
148228
148737
  projectDir = dir;
148229
148738
  searchIndexPrepared = false;
148230
148739
  vectorSchemaUpgradePromise = null;
148740
+ if (options.skipCorpusLoad) {
148741
+ observations = [];
148742
+ corpusLoaded = false;
148743
+ loadedObservationStore = store;
148744
+ return;
148745
+ }
148746
+ observations = await store.loadAll();
148747
+ corpusLoaded = true;
148748
+ loadedObservationStore = store;
148231
148749
  }
148232
148750
  function scheduleObservationEmbedding(input) {
148233
148751
  const { observationId: observationId2, projectId, searchableText, doc } = input;
148234
148752
  vectorMissingIds.add(observationId2);
148753
+ if (isEmbeddingExplicitlyDisabled()) {
148754
+ vectorMissingIds.delete(observationId2);
148755
+ return;
148756
+ }
148235
148757
  if (embeddingWriteMode === "deferred") {
148236
- if (isEmbeddingExplicitlyDisabled()) {
148237
- vectorMissingIds.delete(observationId2);
148238
- return;
148239
- }
148240
148758
  queueVectorBackfill(projectId, {
148241
148759
  detachedWorker: true,
148242
148760
  projectRoot: embeddingWorkerProjectRoot
@@ -148283,7 +148801,7 @@ function scheduleObservationEmbedding(input) {
148283
148801
  });
148284
148802
  }
148285
148803
  async function ensureFreshObservations() {
148286
- if (!projectDir) return false;
148804
+ if (!projectDir || !corpusLoaded) return false;
148287
148805
  try {
148288
148806
  const store = getObservationStore();
148289
148807
  const wasStale = await store.ensureFresh();
@@ -148324,7 +148842,7 @@ async function storeObservation(input) {
148324
148842
  });
148325
148843
  if (safeAttachments) input = { ...input, attachments: safeAttachments };
148326
148844
  await ensureFreshObservations();
148327
- if (input.topicKey) {
148845
+ if (input.topicKey && corpusLoaded) {
148328
148846
  const existing = observations.find(
148329
148847
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
148330
148848
  );
@@ -148421,13 +148939,17 @@ async function storeObservation(input) {
148421
148939
  });
148422
148940
  observation.writeGeneration = store.getGeneration();
148423
148941
  if (upsertedInsideLock || reloadCacheAfterCommit) {
148424
- observations = await store.loadAll();
148425
- nextId = await store.loadIdCounter();
148426
- if (upsertedInsideLock) {
148427
- observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
148942
+ if (corpusLoaded) {
148943
+ observations = await store.loadAll();
148944
+ nextId = await store.loadIdCounter();
148945
+ if (upsertedInsideLock) {
148946
+ observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
148947
+ }
148948
+ } else {
148949
+ nextId = await store.loadIdCounter();
148428
148950
  }
148429
148951
  } else {
148430
- observations.push(observation);
148952
+ if (corpusLoaded) observations.push(observation);
148431
148953
  nextId = observation.id + 1;
148432
148954
  }
148433
148955
  if (upsertedInsideLock) return;
@@ -148495,7 +149017,9 @@ async function storeObservation(input) {
148495
149017
  createdByAgentId: input.createdByAgentId ?? "",
148496
149018
  sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
148497
149019
  };
148498
- await insertObservation(doc);
149020
+ if (corpusLoaded) {
149021
+ await insertObservation(doc);
149022
+ }
148499
149023
  };
148500
149024
  await assignAndPersist();
148501
149025
  if (upsertedInsideLock) {
@@ -148857,9 +149381,11 @@ async function reindexObservations() {
148857
149381
  searchIndexPrepared = true;
148858
149382
  return count2;
148859
149383
  }
148860
- async function prepareSearchIndex() {
149384
+ async function prepareSearchIndex(options = {}) {
148861
149385
  if (searchIndexPrepared) return 0;
148862
- const count2 = await hydrateIndexForStartup(observations);
149386
+ const count2 = await hydrateIndexForStartup(observations, {
149387
+ skipCachedVectors: options.skipCachedVectors
149388
+ });
148863
149389
  if (count2 === 0) {
148864
149390
  searchIndexPrepared = true;
148865
149391
  return 0;
@@ -149051,7 +149577,7 @@ async function backfillVectorEmbeddings(options = {}) {
149051
149577
  ...lastFailure ? { lastError: lastFailure } : {}
149052
149578
  };
149053
149579
  }
149054
- var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
149580
+ var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, corpusLoaded, loadedObservationStore, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
149055
149581
  var init_observations = __esm({
149056
149582
  "src/memory/observations.ts"() {
149057
149583
  "use strict";
@@ -149070,6 +149596,8 @@ var init_observations = __esm({
149070
149596
  projectDir = null;
149071
149597
  searchIndexPrepared = false;
149072
149598
  embeddingWriteMode = "background";
149599
+ corpusLoaded = false;
149600
+ loadedObservationStore = null;
149073
149601
  vectorMissingIds = /* @__PURE__ */ new Set();
149074
149602
  vectorBackfillRunning = false;
149075
149603
  vectorSchemaUpgradePromise = null;