memorix 1.6.1 → 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 (113) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +1574 -1063
  4. package/dist/index.js +588 -107
  5. package/dist/maintenance-runner.js +565 -84
  6. package/dist/media-audio-runner.js +565 -84
  7. package/dist/media-video-runner.js +565 -84
  8. package/dist/memcode-runtime/CHANGELOG.md +22 -0
  9. package/dist/sdk.js +621 -111
  10. package/dist/vector-backfill-runner.js +565 -84
  11. package/docs/DESIGN_DECISIONS.md +357 -357
  12. package/docs/PERFORMANCE.md +15 -0
  13. package/package.json +2 -1
  14. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  15. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  16. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  17. package/plugins/copilot/memorix/plugin.json +1 -1
  18. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  19. package/plugins/omp/memorix/package.json +1 -1
  20. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  21. package/plugins/pi/memorix/package.json +1 -1
  22. package/server.json +2 -2
  23. package/src/audit/index.ts +156 -156
  24. package/src/cli/commands/background.ts +34 -2
  25. package/src/cli/commands/formation.ts +48 -48
  26. package/src/cli/commands/git-hook-install.ts +111 -111
  27. package/src/cli/commands/handoff.ts +54 -54
  28. package/src/cli/commands/hooks-status.ts +63 -63
  29. package/src/cli/commands/ingest.ts +44 -44
  30. package/src/cli/commands/integrate-shared.ts +15 -15
  31. package/src/cli/commands/lock.ts +82 -82
  32. package/src/cli/commands/message.ts +104 -104
  33. package/src/cli/commands/operator-shared.ts +6 -3
  34. package/src/cli/commands/poll.ts +58 -58
  35. package/src/cli/commands/reasoning.ts +118 -118
  36. package/src/cli/commands/serve-http.ts +4 -0
  37. package/src/cli/commands/skills.ts +114 -114
  38. package/src/cli/commands/task.ts +167 -167
  39. package/src/cli/commands/transfer.ts +47 -47
  40. package/src/cli/index.ts +406 -406
  41. package/src/cli/tui/ChatView.tsx +234 -234
  42. package/src/cli/tui/CommandBar.tsx +312 -312
  43. package/src/cli/tui/ContextRail.tsx +118 -118
  44. package/src/cli/tui/HeaderBar.tsx +72 -72
  45. package/src/cli/tui/LogoBanner.tsx +51 -51
  46. package/src/cli/tui/index.ts +41 -41
  47. package/src/cli/version.ts +7 -7
  48. package/src/cli/workbench.ts +1 -1
  49. package/src/dashboard/project-classification.ts +64 -64
  50. package/src/embedding/api-provider.ts +503 -27
  51. package/src/embedding/provider.ts +20 -3
  52. package/src/git/hooks-path.ts +85 -85
  53. package/src/hooks/lifecycle.ts +1 -0
  54. package/src/hooks/pattern-detector.ts +173 -173
  55. package/src/hooks/significance-filter.ts +250 -250
  56. package/src/llm/memory-manager.ts +328 -328
  57. package/src/llm/quality.ts +248 -248
  58. package/src/memory/attribution-guard.ts +249 -249
  59. package/src/memory/disclosure-policy.ts +135 -135
  60. package/src/memory/entity-extractor.ts +197 -197
  61. package/src/memory/formation/evaluate.ts +217 -217
  62. package/src/memory/freshness.ts +122 -122
  63. package/src/memory/graph.ts +197 -197
  64. package/src/memory/observations.ts +68 -18
  65. package/src/memory/refs.ts +94 -94
  66. package/src/memory/secret-filter.ts +79 -79
  67. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  68. package/src/orchestrate/adapters/claude.ts +111 -111
  69. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  70. package/src/orchestrate/adapters/codex.ts +41 -41
  71. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  72. package/src/orchestrate/adapters/gemini.ts +42 -42
  73. package/src/orchestrate/adapters/index.ts +73 -73
  74. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  75. package/src/orchestrate/adapters/opencode.ts +47 -47
  76. package/src/orchestrate/adapters/types.ts +77 -77
  77. package/src/orchestrate/capability-router.ts +284 -284
  78. package/src/orchestrate/cost-tracker.ts +219 -219
  79. package/src/orchestrate/error-recovery.ts +191 -191
  80. package/src/orchestrate/evidence.ts +140 -140
  81. package/src/orchestrate/ledger.ts +110 -110
  82. package/src/orchestrate/memorix-bridge.ts +343 -343
  83. package/src/orchestrate/output-budget.ts +80 -80
  84. package/src/orchestrate/permission.ts +152 -152
  85. package/src/orchestrate/pipeline-trace.ts +131 -131
  86. package/src/orchestrate/prompt-builder.ts +155 -155
  87. package/src/orchestrate/ring-buffer.ts +37 -37
  88. package/src/orchestrate/task-graph.ts +389 -389
  89. package/src/project/aliases.ts +374 -374
  90. package/src/rules/adapters/trae.ts +56 -56
  91. package/src/rules/syncer.ts +235 -235
  92. package/src/sdk.ts +12 -8
  93. package/src/search/query-expansion.ts +52 -52
  94. package/src/server/formation-timeout.ts +27 -27
  95. package/src/skills/mini-skills.ts +386 -386
  96. package/src/store/chat-store.ts +119 -119
  97. package/src/store/graph-store.ts +249 -249
  98. package/src/store/mini-skill-store.ts +349 -349
  99. package/src/store/orama-store.ts +21 -3
  100. package/src/store/persistence-json.ts +212 -212
  101. package/src/store/persistence.ts +291 -291
  102. package/src/store/project-affinity.ts +195 -195
  103. package/src/team/event-bus.ts +76 -76
  104. package/src/team/handoff.ts +161 -161
  105. package/src/team/poll.ts +132 -132
  106. package/src/workspace/mcp-adapters/codex.ts +191 -191
  107. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  108. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  109. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  110. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  111. package/src/workspace/mcp-adapters/trae.ts +134 -134
  112. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  113. package/src/workspace/sanitizer.ts +60 -60
@@ -3126,8 +3126,8 @@ async function acquireLock(lockPath) {
3126
3126
  const code = err instanceof Error && "code" in err ? err.code : void 0;
3127
3127
  if (code === "EEXIST" || code === "EPERM") {
3128
3128
  try {
3129
- const stat4 = await fs3.stat(lockPath);
3130
- if (Date.now() - stat4.mtimeMs > LOCK_STALE_MS) {
3129
+ const stat5 = await fs3.stat(lockPath);
3130
+ if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
3131
3131
  await fs3.unlink(lockPath).catch(() => {
3132
3132
  });
3133
3133
  continue;
@@ -3583,24 +3583,131 @@ var init_transformers_provider = __esm({
3583
3583
  // src/embedding/api-provider.ts
3584
3584
  var api_provider_exports = {};
3585
3585
  __export(api_provider_exports, {
3586
- APIEmbeddingProvider: () => APIEmbeddingProvider
3586
+ APIEmbeddingProvider: () => APIEmbeddingProvider,
3587
+ convertArrayCacheToJsonl: () => convertArrayCacheToJsonl,
3588
+ ensureDiskCacheLoaded: () => ensureDiskCacheLoaded,
3589
+ getApiEmbeddingCachePayloadBytesForTests: () => getApiEmbeddingCachePayloadBytesForTests,
3590
+ getApiEmbeddingDiskCacheLoadCountForTests: () => getApiEmbeddingDiskCacheLoadCountForTests,
3591
+ getApiEmbeddingJsonlLineCountCallsForTests: () => getApiEmbeddingJsonlLineCountCallsForTests,
3592
+ getApiEmbeddingMaxEntriesForDimensionsForTests: () => getApiEmbeddingMaxEntriesForDimensionsForTests,
3593
+ migrateJsonArrayCacheToJsonl: () => migrateJsonArrayCacheToJsonl,
3594
+ parseCacheJsonInWorker: () => parseCacheJsonInWorker,
3595
+ resetApiEmbeddingCacheForTests: () => resetApiEmbeddingCacheForTests,
3596
+ saveDiskCacheNow: () => saveDiskCacheNow,
3597
+ seedApiEmbeddingCacheForTests: () => seedApiEmbeddingCacheForTests
3587
3598
  });
3588
3599
  import { createHash as createHash2 } from "crypto";
3589
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3600
+ import { createReadStream, createWriteStream, constants as fsConstants } from "fs";
3601
+ import { access, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rename, stat, unlink } from "fs/promises";
3590
3602
  import { join as join3 } from "path";
3591
3603
  import { homedir as homedir7 } from "os";
3604
+ import { createInterface } from "readline";
3592
3605
  function cacheDir() {
3593
3606
  return process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
3594
3607
  }
3595
3608
  function cacheFile() {
3596
3609
  return join3(cacheDir(), ".embedding-api-cache.json");
3597
3610
  }
3611
+ function cacheJsonlFile() {
3612
+ return join3(cacheDir(), ".embedding-api-cache.jsonl");
3613
+ }
3598
3614
  function dimsCacheFile() {
3599
3615
  return join3(cacheDir(), ".embedding-dims-cache.json");
3600
3616
  }
3601
3617
  function cacheMetaFile() {
3602
3618
  return join3(cacheDir(), ".embedding-api-cache-meta.json");
3603
3619
  }
3620
+ function cacheStatFile() {
3621
+ return join3(cacheDir(), ".embedding-api-cache-stat.json");
3622
+ }
3623
+ function payloadBytes(vector) {
3624
+ return vector.length * BYTES_PER_DIM;
3625
+ }
3626
+ function maxCachePayloadBytes() {
3627
+ const raw = process.env.MEMORIX_EMBEDDING_CACHE_MAX_BYTES;
3628
+ if (raw) {
3629
+ const parsed = Number.parseInt(raw, 10);
3630
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
3631
+ }
3632
+ return MAX_CACHE_PAYLOAD_BYTES;
3633
+ }
3634
+ function maxEntriesForDimensions(dimensions) {
3635
+ const dim = Math.max(1, Math.floor(dimensions));
3636
+ const byBytes = Math.floor(maxCachePayloadBytes() / (dim * BYTES_PER_DIM));
3637
+ return Math.max(1, Math.min(MAX_CACHE_ENTRIES, byBytes));
3638
+ }
3639
+ function rememberDimensions(vector) {
3640
+ if (vector.length > 0 && (cacheDimensions === null || vector.length > cacheDimensions)) {
3641
+ cacheDimensions = vector.length;
3642
+ }
3643
+ }
3644
+ function evictOldest() {
3645
+ const firstKey = cache3.keys().next().value;
3646
+ if (firstKey === void 0) return;
3647
+ const old = cache3.get(firstKey);
3648
+ cache3.delete(firstKey);
3649
+ if (old) cachePayloadBytes -= payloadBytes(old);
3650
+ }
3651
+ function evictToFit(incomingBytes, incomingDims) {
3652
+ const limit2 = maxEntriesForDimensions(incomingDims);
3653
+ while (cache3.size > 0 && (cache3.size >= limit2 || cachePayloadBytes + incomingBytes > maxCachePayloadBytes())) {
3654
+ evictOldest();
3655
+ }
3656
+ }
3657
+ function isNumberArray(value) {
3658
+ return Array.isArray(value) && value.every((entry) => typeof entry === "number");
3659
+ }
3660
+ function parseCacheRow(raw) {
3661
+ if (Array.isArray(raw)) {
3662
+ const hash4 = raw[0];
3663
+ const vector = raw[1];
3664
+ if (typeof hash4 === "string" && isNumberArray(vector)) {
3665
+ return { hash: hash4, vector };
3666
+ }
3667
+ return null;
3668
+ }
3669
+ if (raw && typeof raw === "object") {
3670
+ const row = raw;
3671
+ if (typeof row.h === "string" && isNumberArray(row.v)) {
3672
+ return { hash: row.h, vector: row.v };
3673
+ }
3674
+ }
3675
+ return null;
3676
+ }
3677
+ function cacheInsert(hash4, value, dirty) {
3678
+ rememberDimensions(value);
3679
+ const incoming = payloadBytes(value);
3680
+ const existing = cache3.get(hash4);
3681
+ if (existing) {
3682
+ cachePayloadBytes -= payloadBytes(existing);
3683
+ cache3.delete(hash4);
3684
+ }
3685
+ evictToFit(incoming, value.length || cacheDimensions || DEFAULT_ASSUMED_DIMENSIONS);
3686
+ cache3.set(hash4, value);
3687
+ cachePayloadBytes += incoming;
3688
+ if (dirty) diskCacheDirty2 = true;
3689
+ }
3690
+ async function readCacheStat() {
3691
+ try {
3692
+ const data = JSON.parse(await readFile2(cacheStatFile(), "utf-8"));
3693
+ if (typeof data.entryCount === "number" && Number.isFinite(data.entryCount) && data.entryCount >= 0) {
3694
+ return {
3695
+ version: 1,
3696
+ entryCount: Math.floor(data.entryCount),
3697
+ dimensions: isValidDimension(data.dimensions) ? data.dimensions : null
3698
+ };
3699
+ }
3700
+ } catch {
3701
+ }
3702
+ return null;
3703
+ }
3704
+ async function writeCacheStat(stat5) {
3705
+ try {
3706
+ await mkdir2(cacheDir(), { recursive: true });
3707
+ await writeFile2(cacheStatFile(), JSON.stringify(stat5));
3708
+ } catch {
3709
+ }
3710
+ }
3604
3711
  function normalizeText(text2) {
3605
3712
  return text2.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3606
3713
  }
@@ -3686,29 +3793,300 @@ function toProviderInput(input, baseUrl) {
3686
3793
  }
3687
3794
  return { type: input.modality, url: input.url };
3688
3795
  }
3796
+ function parseCacheJsonInWorker(filePath) {
3797
+ return new Promise((resolve, reject) => {
3798
+ void import("worker_threads").then(({ Worker }) => {
3799
+ const worker = new Worker(
3800
+ `
3801
+ const { parentPort, workerData } = require('node:worker_threads');
3802
+ const { readFileSync } = require('node:fs');
3803
+ try {
3804
+ const raw = readFileSync(workerData.filePath, 'utf8');
3805
+ parentPort.postMessage({ ok: true, entries: JSON.parse(raw) });
3806
+ } catch (err) {
3807
+ parentPort.postMessage({
3808
+ ok: false,
3809
+ error: err instanceof Error ? err.message : String(err),
3810
+ });
3811
+ }
3812
+ `,
3813
+ { eval: true, workerData: { filePath } }
3814
+ );
3815
+ worker.once("message", (msg) => {
3816
+ void worker.terminate();
3817
+ if (msg?.ok && Array.isArray(msg.entries)) {
3818
+ resolve(msg.entries);
3819
+ return;
3820
+ }
3821
+ reject(new Error(msg?.error || "embedding cache worker parse failed"));
3822
+ });
3823
+ worker.once("error", reject);
3824
+ }).catch(reject);
3825
+ });
3826
+ }
3827
+ function findJsonValueEnd(source) {
3828
+ let i2 = 0;
3829
+ while (i2 < source.length && /\s/.test(source[i2])) i2 += 1;
3830
+ if (i2 >= source.length) return -1;
3831
+ const start = source[i2];
3832
+ if (start === '"') {
3833
+ i2 += 1;
3834
+ while (i2 < source.length) {
3835
+ if (source[i2] === "\\") {
3836
+ i2 += 2;
3837
+ continue;
3838
+ }
3839
+ if (source[i2] === '"') return i2 + 1;
3840
+ i2 += 1;
3841
+ }
3842
+ return -1;
3843
+ }
3844
+ if (start === "{" || start === "[") {
3845
+ const close = start === "{" ? "}" : "]";
3846
+ let depth = 0;
3847
+ let inString = false;
3848
+ for (; i2 < source.length; i2 += 1) {
3849
+ const ch = source[i2];
3850
+ if (inString) {
3851
+ if (ch === "\\") {
3852
+ i2 += 1;
3853
+ continue;
3854
+ }
3855
+ if (ch === '"') inString = false;
3856
+ continue;
3857
+ }
3858
+ if (ch === '"') {
3859
+ inString = true;
3860
+ continue;
3861
+ }
3862
+ if (ch === start) depth += 1;
3863
+ else if (ch === close) {
3864
+ depth -= 1;
3865
+ if (depth === 0) return i2 + 1;
3866
+ }
3867
+ }
3868
+ return -1;
3869
+ }
3870
+ const rest = source.slice(i2);
3871
+ const match2 = rest.match(/^(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/);
3872
+ return match2 ? i2 + match2[0].length : -1;
3873
+ }
3874
+ async function streamJsonArrayToJsonl(filePath, tmpPath) {
3875
+ const input = createReadStream(filePath, { encoding: "utf8" });
3876
+ const out = createWriteStream(tmpPath);
3877
+ let buf = "";
3878
+ let started = false;
3879
+ let count2 = 0;
3880
+ const writeLine = async (line) => {
3881
+ if (!out.write(line)) {
3882
+ await new Promise((resolve, reject) => {
3883
+ out.once("drain", resolve);
3884
+ out.once("error", reject);
3885
+ });
3886
+ }
3887
+ };
3888
+ try {
3889
+ for await (const chunk of input) {
3890
+ buf += chunk;
3891
+ if (!started) {
3892
+ const idx = buf.search(/\S/);
3893
+ if (idx < 0) continue;
3894
+ if (buf[idx] !== "[") throw new Error("embedding cache is not a JSON array");
3895
+ buf = buf.slice(idx + 1);
3896
+ started = true;
3897
+ }
3898
+ while (true) {
3899
+ buf = buf.replace(/^\s*,?\s*/, "");
3900
+ if (!buf) break;
3901
+ if (buf[0] === "]") {
3902
+ buf = "";
3903
+ break;
3904
+ }
3905
+ const end = findJsonValueEnd(buf);
3906
+ if (end < 0) break;
3907
+ const value = JSON.parse(buf.slice(0, end));
3908
+ buf = buf.slice(end);
3909
+ const row = parseCacheRow(value);
3910
+ if (row) {
3911
+ await writeLine(`${JSON.stringify({ h: row.hash, v: row.vector })}
3912
+ `);
3913
+ count2 += 1;
3914
+ if (count2 % 50 === 0) {
3915
+ await new Promise((resolve) => setImmediate(resolve));
3916
+ }
3917
+ }
3918
+ }
3919
+ if (buf.length > MAX_MIGRATION_VALUE_BYTES) {
3920
+ throw new Error("embedding cache entry too large to migrate safely");
3921
+ }
3922
+ }
3923
+ } finally {
3924
+ await new Promise((resolve, reject) => {
3925
+ out.end(() => resolve());
3926
+ out.once("error", reject);
3927
+ });
3928
+ }
3929
+ return count2;
3930
+ }
3931
+ async function migrateJsonArrayCacheToJsonl(filePath, jsonlPath) {
3932
+ const tmpPath = `${jsonlPath}.tmp`;
3933
+ try {
3934
+ const count2 = await streamJsonArrayToJsonl(filePath, tmpPath);
3935
+ await rename(tmpPath, jsonlPath);
3936
+ diskCacheEntryCount = count2;
3937
+ await writeCacheStat({
3938
+ version: 1,
3939
+ entryCount: count2,
3940
+ dimensions: cacheDimensions
3941
+ });
3942
+ return count2;
3943
+ } catch (err) {
3944
+ await unlink(tmpPath).catch(() => void 0);
3945
+ throw err;
3946
+ }
3947
+ }
3948
+ function convertArrayCacheToJsonl(filePath, jsonlPath) {
3949
+ return migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
3950
+ }
3951
+ async function loadCacheJsonl(filePath) {
3952
+ const rl = createInterface({
3953
+ input: createReadStream(filePath, { encoding: "utf8" }),
3954
+ crlfDelay: Infinity
3955
+ });
3956
+ let loaded = 0;
3957
+ let onDiskCount = 0;
3958
+ for await (const line of rl) {
3959
+ const trimmed = line.trim();
3960
+ if (!trimmed) continue;
3961
+ try {
3962
+ const row = parseCacheRow(JSON.parse(trimmed));
3963
+ if (!row) continue;
3964
+ onDiskCount += 1;
3965
+ cacheInsert(row.hash, row.vector, false);
3966
+ loaded = cache3.size;
3967
+ } catch {
3968
+ }
3969
+ if (onDiskCount % 250 === 0) {
3970
+ await new Promise((resolve) => setImmediate(resolve));
3971
+ }
3972
+ }
3973
+ diskCacheEntryCount = onDiskCount;
3974
+ await writeCacheStat({
3975
+ version: 1,
3976
+ entryCount: onDiskCount,
3977
+ dimensions: cacheDimensions
3978
+ });
3979
+ return loaded;
3980
+ }
3981
+ async function countJsonlLines(filePath) {
3982
+ jsonlLineCountCalls += 1;
3983
+ try {
3984
+ const rl = createInterface({
3985
+ input: createReadStream(filePath, { encoding: "utf8" }),
3986
+ crlfDelay: Infinity
3987
+ });
3988
+ let count2 = 0;
3989
+ for await (const line of rl) {
3990
+ if (line.trim()) count2 += 1;
3991
+ }
3992
+ return count2;
3993
+ } catch {
3994
+ return 0;
3995
+ }
3996
+ }
3997
+ async function resolveOnDiskCount(jsonlPath) {
3998
+ if (diskCacheEntryCount > 0) return diskCacheEntryCount;
3999
+ const sidecar = await readCacheStat();
4000
+ if (sidecar) {
4001
+ diskCacheEntryCount = sidecar.entryCount;
4002
+ if (sidecar.dimensions && cacheDimensions === null) cacheDimensions = sidecar.dimensions;
4003
+ return sidecar.entryCount;
4004
+ }
4005
+ try {
4006
+ await access(jsonlPath, fsConstants.R_OK);
4007
+ } catch {
4008
+ return 0;
4009
+ }
4010
+ const counted = await countJsonlLines(jsonlPath);
4011
+ diskCacheEntryCount = counted;
4012
+ await writeCacheStat({
4013
+ version: 1,
4014
+ entryCount: counted,
4015
+ dimensions: cacheDimensions
4016
+ });
4017
+ return counted;
4018
+ }
3689
4019
  async function loadDiskCache2() {
3690
- if (diskCacheLoaded) return;
4020
+ if (diskCacheLoaded) return cache3.size;
4021
+ let loaded = 0;
3691
4022
  try {
3692
- const raw = await readFile2(cacheFile(), "utf-8");
3693
- const entries = JSON.parse(raw);
3694
- for (const [k, v] of entries) cache3.set(k, v);
3695
- console.error(`[memorix] Loaded ${entries.length} cached API embeddings from disk`);
4023
+ const jsonlPath = cacheJsonlFile();
4024
+ let hasJsonl = false;
4025
+ try {
4026
+ await access(jsonlPath, fsConstants.R_OK);
4027
+ hasJsonl = true;
4028
+ } catch {
4029
+ hasJsonl = false;
4030
+ }
4031
+ if (hasJsonl) {
4032
+ loaded = await loadCacheJsonl(jsonlPath);
4033
+ } else {
4034
+ const filePath = cacheFile();
4035
+ await stat(filePath);
4036
+ await migrateJsonArrayCacheToJsonl(filePath, jsonlPath);
4037
+ loaded = await loadCacheJsonl(jsonlPath);
4038
+ }
4039
+ diskCacheLoadCount += 1;
4040
+ console.error(`[memorix] Loaded ${loaded} cached API embeddings from disk`);
3696
4041
  } catch {
3697
4042
  }
3698
4043
  diskCacheLoaded = true;
4044
+ return loaded;
3699
4045
  }
3700
4046
  function startDiskCacheLoad() {
3701
4047
  if (diskCacheLoaded || diskCacheLoadPromise) return;
3702
- diskCacheLoadPromise = loadDiskCache2().catch(() => {
4048
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
3703
4049
  });
3704
4050
  }
3705
4051
  async function ensureDiskCacheLoaded() {
3706
- if (diskCacheLoaded) return;
3707
- if (diskCacheLoadPromise) {
3708
- await diskCacheLoadPromise;
3709
- return;
3710
- }
3711
- await loadDiskCache2();
4052
+ if (diskCacheLoaded) return cache3.size;
4053
+ if (!diskCacheLoadPromise) {
4054
+ diskCacheLoadPromise = loadDiskCache2().then(() => void 0).catch(() => {
4055
+ });
4056
+ }
4057
+ await diskCacheLoadPromise;
4058
+ return cache3.size;
4059
+ }
4060
+ function getApiEmbeddingDiskCacheLoadCountForTests() {
4061
+ return diskCacheLoadCount;
4062
+ }
4063
+ function resetApiEmbeddingCacheForTests() {
4064
+ cache3.clear();
4065
+ cachePayloadBytes = 0;
4066
+ cacheDimensions = null;
4067
+ diskCacheEntryCount = 0;
4068
+ diskCacheLoaded = false;
4069
+ diskCacheLoadPromise = null;
4070
+ diskCacheLoadCount = 0;
4071
+ diskCacheDirty2 = false;
4072
+ jsonlLineCountCalls = 0;
4073
+ }
4074
+ function seedApiEmbeddingCacheForTests(entries) {
4075
+ cache3.clear();
4076
+ cachePayloadBytes = 0;
4077
+ cacheDimensions = null;
4078
+ for (const [key, value] of entries) cacheInsert(key, value, true);
4079
+ diskCacheLoaded = true;
4080
+ diskCacheDirty2 = true;
4081
+ }
4082
+ function getApiEmbeddingCachePayloadBytesForTests() {
4083
+ return cachePayloadBytes;
4084
+ }
4085
+ function getApiEmbeddingJsonlLineCountCallsForTests() {
4086
+ return jsonlLineCountCalls;
4087
+ }
4088
+ function getApiEmbeddingMaxEntriesForDimensionsForTests(dimensions) {
4089
+ return maxEntriesForDimensions(dimensions);
3712
4090
  }
3713
4091
  function dimsCacheKey(config) {
3714
4092
  return [
@@ -3824,8 +4202,46 @@ async function saveDiskCacheNow() {
3824
4202
  if (!diskCacheDirty2) return;
3825
4203
  try {
3826
4204
  await mkdir2(cacheDir(), { recursive: true });
3827
- const entries = Array.from(cache3.entries());
3828
- await writeFile2(cacheFile(), JSON.stringify(entries));
4205
+ const jsonlPath = cacheJsonlFile();
4206
+ const existing = await resolveOnDiskCount(jsonlPath);
4207
+ if (existing > 0 && cache3.size < Math.ceil(existing * 0.5)) {
4208
+ console.error(
4209
+ `[memorix] Refusing to overwrite embedding cache (${existing} on disk vs ${cache3.size} in memory)`
4210
+ );
4211
+ diskCacheDirty2 = false;
4212
+ return;
4213
+ }
4214
+ const tmpPath = `${jsonlPath}.tmp`;
4215
+ await new Promise((resolve, reject) => {
4216
+ const out = createWriteStream(tmpPath);
4217
+ const entries = cache3.entries();
4218
+ const writeBatch = () => {
4219
+ for (let i2 = 0; i2 < 50; i2++) {
4220
+ const next = entries.next();
4221
+ if (next.done) {
4222
+ out.end();
4223
+ return;
4224
+ }
4225
+ const [hash4, vector] = next.value;
4226
+ if (!out.write(`${JSON.stringify({ h: hash4, v: vector })}
4227
+ `)) {
4228
+ out.once("drain", writeBatch);
4229
+ return;
4230
+ }
4231
+ }
4232
+ setImmediate(writeBatch);
4233
+ };
4234
+ out.on("finish", resolve);
4235
+ out.on("error", reject);
4236
+ writeBatch();
4237
+ });
4238
+ await rename(tmpPath, jsonlPath);
4239
+ diskCacheEntryCount = cache3.size;
4240
+ await writeCacheStat({
4241
+ version: 1,
4242
+ entryCount: cache3.size,
4243
+ dimensions: cacheDimensions
4244
+ });
3829
4245
  diskCacheDirty2 = false;
3830
4246
  } catch {
3831
4247
  }
@@ -3839,12 +4255,7 @@ function scheduleDiskSave() {
3839
4255
  }, DISK_SAVE_DEBOUNCE_MS);
3840
4256
  }
3841
4257
  function cacheSet(hash4, value) {
3842
- if (cache3.size >= MAX_CACHE_SIZE3) {
3843
- const firstKey = cache3.keys().next().value;
3844
- if (firstKey !== void 0) cache3.delete(firstKey);
3845
- }
3846
- cache3.set(hash4, value);
3847
- diskCacheDirty2 = true;
4258
+ cacheInsert(hash4, value, true);
3848
4259
  }
3849
4260
  function resolveEnvEmbeddingApiKey() {
3850
4261
  return process.env.MEMORIX_EMBEDDING_API_KEY;
@@ -3963,18 +4374,27 @@ async function waitForRetry(waitMs, signal) {
3963
4374
  abortSignal.addEventListener("abort", onAbort, { once: true });
3964
4375
  });
3965
4376
  }
3966
- 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;
4377
+ 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;
3967
4378
  var init_api_provider = __esm({
3968
4379
  "src/embedding/api-provider.ts"() {
3969
4380
  "use strict";
3970
4381
  init_esm_shims();
3971
4382
  init_provider2();
3972
4383
  cache3 = /* @__PURE__ */ new Map();
3973
- MAX_CACHE_SIZE3 = 1e4;
4384
+ MAX_CACHE_ENTRIES = 2e5;
4385
+ MAX_CACHE_PAYLOAD_BYTES = 256 * 1024 * 1024;
4386
+ BYTES_PER_DIM = 8;
4387
+ DEFAULT_ASSUMED_DIMENSIONS = 4096;
4388
+ MAX_MIGRATION_VALUE_BYTES = 16 * 1024 * 1024;
4389
+ cachePayloadBytes = 0;
4390
+ cacheDimensions = null;
4391
+ diskCacheEntryCount = 0;
3974
4392
  diskCacheDirty2 = false;
3975
4393
  diskSaveTimer = null;
3976
4394
  diskCacheLoaded = false;
3977
4395
  diskCacheLoadPromise = null;
4396
+ diskCacheLoadCount = 0;
4397
+ jsonlLineCountCalls = 0;
3978
4398
  MAX_INPUT_CHARS = 32e3;
3979
4399
  MAX_CONCURRENCY = 4;
3980
4400
  DISK_SAVE_DEBOUNCE_MS = 5e3;
@@ -4032,7 +4452,9 @@ var init_api_provider = __esm({
4032
4452
  if (config.requestedDimensions) {
4033
4453
  console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
4034
4454
  }
4035
- startDiskCacheLoad();
4455
+ if (allowNetworkProbe) {
4456
+ startDiskCacheLoad();
4457
+ }
4036
4458
  return new _APIEmbeddingProvider(config, probeDimensions);
4037
4459
  }
4038
4460
  static resolveConfig() {
@@ -4370,7 +4792,12 @@ function warnOnce(message) {
4370
4792
  lastEmbeddingWarningAt = now3;
4371
4793
  console.error(message);
4372
4794
  }
4795
+ function embeddingModeToken() {
4796
+ return process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim() ?? "";
4797
+ }
4373
4798
  function getEmbeddingMode2() {
4799
+ const token = embeddingModeToken();
4800
+ if (cachedEmbeddingMode && cachedEmbeddingModeToken === token) return cachedEmbeddingMode;
4374
4801
  try {
4375
4802
  let cfg;
4376
4803
  try {
@@ -4379,10 +4806,17 @@ function getEmbeddingMode2() {
4379
4806
  cfg = (init_config(), __toCommonJS(config_exports));
4380
4807
  }
4381
4808
  const { getEmbeddingMode: cfgMode } = cfg;
4382
- return cfgMode();
4809
+ cachedEmbeddingMode = cfgMode();
4810
+ cachedEmbeddingModeToken = token;
4811
+ return cachedEmbeddingMode;
4383
4812
  } catch {
4384
- const env2 = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
4385
- if (env2 === "fastembed" || env2 === "transformers" || env2 === "api" || env2 === "auto") return env2;
4813
+ if (token === "fastembed" || token === "transformers" || token === "api" || token === "auto") {
4814
+ cachedEmbeddingMode = token;
4815
+ cachedEmbeddingModeToken = token;
4816
+ return token;
4817
+ }
4818
+ cachedEmbeddingMode = "off";
4819
+ cachedEmbeddingModeToken = token;
4386
4820
  return "off";
4387
4821
  }
4388
4822
  }
@@ -4638,13 +5072,15 @@ function isEmbeddingExplicitlyDisabled() {
4638
5072
  function resetProvider() {
4639
5073
  provider = null;
4640
5074
  initPromise = null;
5075
+ cachedEmbeddingMode = null;
5076
+ cachedEmbeddingModeToken = null;
4641
5077
  lastInitWasTemporaryFailure = false;
4642
5078
  lastFailureTimestamp = 0;
4643
5079
  lastEmbeddingFailure = null;
4644
5080
  lastEmbeddingWarning = "";
4645
5081
  lastEmbeddingWarningAt = 0;
4646
5082
  }
4647
- var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
5083
+ var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, cachedEmbeddingMode, cachedEmbeddingModeToken, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
4648
5084
  var init_provider2 = __esm({
4649
5085
  "src/embedding/provider.ts"() {
4650
5086
  "use strict";
@@ -4668,6 +5104,8 @@ var init_provider2 = __esm({
4668
5104
  MAX_INLINE_EMBEDDING_BYTES = 5 * 1024 * 1024;
4669
5105
  provider = null;
4670
5106
  initPromise = null;
5107
+ cachedEmbeddingMode = null;
5108
+ cachedEmbeddingModeToken = null;
4671
5109
  lastInitWasTemporaryFailure = false;
4672
5110
  lastEmbeddingFailure = null;
4673
5111
  lastEmbeddingWarning = "";
@@ -5809,6 +6247,9 @@ async function attachCachedVectors(database, candidates) {
5809
6247
  await update(database, candidates[index].id, { ...existing, embedding: vector });
5810
6248
  } catch {
5811
6249
  }
6250
+ if (index % 50 === 0) {
6251
+ await new Promise((resolve) => setImmediate(resolve));
6252
+ }
5812
6253
  }
5813
6254
  }
5814
6255
  async function hydrateIndex(observations2, options = {}) {
@@ -5859,17 +6300,24 @@ async function hydrateIndex(observations2, options = {}) {
5859
6300
  await insert2(database, doc);
5860
6301
  rememberObservationDoc(doc);
5861
6302
  inserted++;
6303
+ if (inserted % 50 === 0) {
6304
+ await new Promise((resolve) => setImmediate(resolve));
6305
+ }
6306
+ if (inserted % 200 === 0) {
6307
+ await new Promise((resolve) => setTimeout(resolve, 0));
6308
+ }
5862
6309
  } catch {
5863
6310
  }
5864
6311
  }
5865
- deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6312
+ deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
5866
6313
  }) : null;
5867
6314
  return inserted;
5868
6315
  }
5869
- async function hydrateIndexForStartup(observations2) {
6316
+ async function hydrateIndexForStartup(observations2, options = {}) {
5870
6317
  return hydrateIndex(observations2, {
5871
6318
  allowNetworkProbe: false,
5872
- deferCachedVectors: true
6319
+ deferCachedVectors: true,
6320
+ skipCachedVectors: options.skipCachedVectors
5873
6321
  });
5874
6322
  }
5875
6323
  function getDeferredCachedVectorHydration() {
@@ -8531,7 +8979,7 @@ var init_types2 = __esm({
8531
8979
 
8532
8980
  // src/media/asset-store.ts
8533
8981
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
8534
- import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename, rm, stat, writeFile as writeFile3 } from "fs/promises";
8982
+ import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
8535
8983
  import path10 from "path";
8536
8984
  function mediaRoot(dataDir) {
8537
8985
  return path10.resolve(dataDir, "media");
@@ -8604,7 +9052,7 @@ async function persistBuffer(bytes, destination) {
8604
9052
  const temporary = `${destination}.${randomUUID5()}.tmp`;
8605
9053
  try {
8606
9054
  await writeFile3(temporary, bytes, { flag: "wx" });
8607
- await rename(temporary, destination);
9055
+ await rename2(temporary, destination);
8608
9056
  return true;
8609
9057
  } catch (error) {
8610
9058
  if (error?.code === "EEXIST" && await destinationExists(destination)) return false;
@@ -59684,9 +60132,9 @@ var require_node_domexception = __commonJS({
59684
60132
  });
59685
60133
 
59686
60134
  // node_modules/fetch-blob/from.js
59687
- import { statSync as statSync2, createReadStream, promises as fs7 } from "fs";
60135
+ import { statSync as statSync2, createReadStream as createReadStream2, promises as fs7 } from "fs";
59688
60136
  import { basename } from "path";
59689
- var import_node_domexception, stat2, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
60137
+ var import_node_domexception, stat3, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
59690
60138
  var init_from = __esm({
59691
60139
  "node_modules/fetch-blob/from.js"() {
59692
60140
  "use strict";
@@ -59694,23 +60142,23 @@ var init_from = __esm({
59694
60142
  import_node_domexception = __toESM(require_node_domexception(), 1);
59695
60143
  init_file();
59696
60144
  init_fetch_blob();
59697
- ({ stat: stat2 } = fs7);
60145
+ ({ stat: stat3 } = fs7);
59698
60146
  blobFromSync = (path21, type) => fromBlob(statSync2(path21), path21, type);
59699
- blobFrom = (path21, type) => stat2(path21).then((stat4) => fromBlob(stat4, path21, type));
59700
- fileFrom = (path21, type) => stat2(path21).then((stat4) => fromFile(stat4, path21, type));
60147
+ blobFrom = (path21, type) => stat3(path21).then((stat5) => fromBlob(stat5, path21, type));
60148
+ fileFrom = (path21, type) => stat3(path21).then((stat5) => fromFile(stat5, path21, type));
59701
60149
  fileFromSync = (path21, type) => fromFile(statSync2(path21), path21, type);
59702
- fromBlob = (stat4, path21, type = "") => new fetch_blob_default([new BlobDataItem({
60150
+ fromBlob = (stat5, path21, type = "") => new fetch_blob_default([new BlobDataItem({
59703
60151
  path: path21,
59704
- size: stat4.size,
59705
- lastModified: stat4.mtimeMs,
60152
+ size: stat5.size,
60153
+ lastModified: stat5.mtimeMs,
59706
60154
  start: 0
59707
60155
  })], { type });
59708
- fromFile = (stat4, path21, type = "") => new file_default([new BlobDataItem({
60156
+ fromFile = (stat5, path21, type = "") => new file_default([new BlobDataItem({
59709
60157
  path: path21,
59710
- size: stat4.size,
59711
- lastModified: stat4.mtimeMs,
60158
+ size: stat5.size,
60159
+ lastModified: stat5.mtimeMs,
59712
60160
  start: 0
59713
- })], basename(path21), { type, lastModified: stat4.mtimeMs });
60161
+ })], basename(path21), { type, lastModified: stat5.mtimeMs });
59714
60162
  BlobDataItem = class _BlobDataItem {
59715
60163
  #path;
59716
60164
  #start;
@@ -59733,11 +60181,11 @@ var init_from = __esm({
59733
60181
  });
59734
60182
  }
59735
60183
  async *stream() {
59736
- const { mtimeMs } = await stat2(this.#path);
60184
+ const { mtimeMs } = await stat3(this.#path);
59737
60185
  if (mtimeMs > this.lastModified) {
59738
60186
  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");
59739
60187
  }
59740
- yield* createReadStream(this.#path, {
60188
+ yield* createReadStream2(this.#path, {
59741
60189
  start: this.#start,
59742
60190
  end: this.#start + this.size - 1
59743
60191
  });
@@ -75367,7 +75815,7 @@ var init_wrapper = __esm({
75367
75815
  });
75368
75816
 
75369
75817
  // node_modules/@google/genai/dist/node/index.mjs
75370
- import { createWriteStream } from "fs";
75818
+ import { createWriteStream as createWriteStream2 } from "fs";
75371
75819
  import * as fs8 from "fs/promises";
75372
75820
  import { writeFile as writeFile4 } from "fs/promises";
75373
75821
  import { Readable } from "stream";
@@ -92647,7 +93095,7 @@ ${underline}`);
92647
93095
  if (params.downloadPath) {
92648
93096
  const response = await downloadFile(params, apiClient);
92649
93097
  if (response instanceof HttpResponse) {
92650
- const writer = createWriteStream(params.downloadPath);
93098
+ const writer = createWriteStream2(params.downloadPath);
92651
93099
  const body = Readable.fromWeb(response.responseInternal.body);
92652
93100
  body.pipe(writer);
92653
93101
  await finished(writer);
@@ -142565,7 +143013,7 @@ function explainRetention(doc, referenceTime) {
142565
143013
  };
142566
143014
  }
142567
143015
  function toRetentionDocument(obs, accessMap) {
142568
- const access = accessMap?.get(obs.id);
143016
+ const access2 = accessMap?.get(obs.id);
142569
143017
  return {
142570
143018
  id: `obs-${obs.id}`,
142571
143019
  observationId: obs.id,
@@ -142579,8 +143027,8 @@ function toRetentionDocument(obs, accessMap) {
142579
143027
  tokens: obs.tokens,
142580
143028
  createdAt: obs.createdAt,
142581
143029
  projectId: obs.projectId,
142582
- accessCount: access?.accessCount ?? 0,
142583
- lastAccessedAt: access?.lastAccessedAt ?? "",
143030
+ accessCount: access2?.accessCount ?? 0,
143031
+ lastAccessedAt: access2?.lastAccessedAt ?? "",
142584
143032
  status: obs.status ?? "active",
142585
143033
  source: obs.source ?? "agent",
142586
143034
  sourceDetail: obs.sourceDetail ?? "",
@@ -144690,7 +145138,7 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
144690
145138
  const rel = normalizeCodePath(relative(projectRoot, abs));
144691
145139
  try {
144692
145140
  const text2 = readFileSync6(abs, "utf-8");
144693
- const stat4 = fileStat ?? statSync3(abs);
145141
+ const stat5 = fileStat ?? statSync3(abs);
144694
145142
  const file = {
144695
145143
  id: makeCodeFileId(projectId, rel),
144696
145144
  projectId,
@@ -144699,8 +145147,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
144699
145147
  contentHash: hashText(text2),
144700
145148
  // Preserve filesystem precision. Rounding here made same-size edits in
144701
145149
  // a short Windows timestamp window indistinguishable to refresh.
144702
- mtimeMs: Number(stat4.mtimeMs),
144703
- sizeBytes: Number(stat4.size),
145150
+ mtimeMs: Number(stat5.mtimeMs),
145151
+ sizeBytes: Number(stat5.size),
144704
145152
  indexedAt
144705
145153
  };
144706
145154
  return {
@@ -144727,18 +145175,18 @@ async function indexProjectLite(options) {
144727
145175
  let skippedOversizedFiles = 0;
144728
145176
  let unreadableFiles = 0;
144729
145177
  for (const abs of paths) {
144730
- let stat4;
145178
+ let stat5;
144731
145179
  try {
144732
- stat4 = statSync3(abs);
145180
+ stat5 = statSync3(abs);
144733
145181
  } catch {
144734
145182
  unreadableFiles++;
144735
145183
  continue;
144736
145184
  }
144737
- if (Number(stat4.size) > maxFileBytes) {
145185
+ if (Number(stat5.size) > maxFileBytes) {
144738
145186
  skippedOversizedFiles++;
144739
145187
  continue;
144740
145188
  }
144741
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
145189
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
144742
145190
  if (!attempt.delta) {
144743
145191
  if (attempt.unreadable) unreadableFiles++;
144744
145192
  continue;
@@ -144766,16 +145214,16 @@ async function refreshProjectLite(store, options) {
144766
145214
  for (const abs of paths) {
144767
145215
  const rel = normalizeCodePath(relative(options.projectRoot, abs));
144768
145216
  seenPaths.add(rel);
144769
- let stat4;
145217
+ let stat5;
144770
145218
  try {
144771
- stat4 = statSync3(abs);
145219
+ stat5 = statSync3(abs);
144772
145220
  } catch {
144773
145221
  unreadableFiles++;
144774
145222
  continue;
144775
145223
  }
144776
145224
  const existing = existingByPath.get(rel);
144777
- const mtimeMs = Number(stat4.mtimeMs);
144778
- const sizeBytes = Number(stat4.size);
145225
+ const mtimeMs = Number(stat5.mtimeMs);
145226
+ const sizeBytes = Number(stat5.size);
144779
145227
  if (sizeBytes > maxFileBytes) {
144780
145228
  skippedOversizedFiles++;
144781
145229
  if (existing) oversizedExistingFileIds.add(existing.id);
@@ -144785,7 +145233,7 @@ async function refreshProjectLite(store, options) {
144785
145233
  unchangedFiles++;
144786
145234
  continue;
144787
145235
  }
144788
- const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat4);
145236
+ const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat5);
144789
145237
  if (!attempt.delta) {
144790
145238
  if (attempt.unreadable) unreadableFiles++;
144791
145239
  continue;
@@ -148182,12 +148630,25 @@ __export(observations_exports, {
148182
148630
  prepareSearchIndex: () => prepareSearchIndex,
148183
148631
  probeSearchIndex: () => probeSearchIndex,
148184
148632
  reindexObservations: () => reindexObservations,
148633
+ resetObservationRuntime: () => resetObservationRuntime,
148185
148634
  resolveObservations: () => resolveObservations,
148186
148635
  storeObservation: () => storeObservation,
148187
148636
  suggestTopicKey: () => suggestTopicKey,
148188
148637
  updateObservationAdmission: () => updateObservationAdmission,
148189
148638
  withFreshObservations: () => withFreshObservations
148190
148639
  });
148640
+ function resetObservationRuntime() {
148641
+ observations = [];
148642
+ nextId = 1;
148643
+ projectDir = null;
148644
+ searchIndexPrepared = false;
148645
+ corpusLoaded = false;
148646
+ loadedObservationStore = null;
148647
+ embeddingWriteMode = "background";
148648
+ embeddingWorkerProjectRoot = void 0;
148649
+ vectorMissingIds.clear();
148650
+ vectorSchemaUpgradePromise = null;
148651
+ }
148191
148652
  function logEmbeddingFailureOnce(key, message) {
148192
148653
  const now3 = Date.now();
148193
148654
  const last = embeddingFailureLogTimestamps.get(key) ?? 0;
@@ -148296,23 +148757,33 @@ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
148296
148757
  async function initObservations(dir, options = {}) {
148297
148758
  embeddingWriteMode = options.embeddingWriteMode ?? "background";
148298
148759
  embeddingWorkerProjectRoot = options.projectRoot;
148299
- if (projectDir === dir) return;
148300
148760
  await initObservationStore(dir);
148301
148761
  const store = getObservationStore();
148302
- observations = await store.loadAll();
148762
+ if (projectDir === dir && loadedObservationStore === store) {
148763
+ if (options.skipCorpusLoad || corpusLoaded) return;
148764
+ }
148303
148765
  nextId = await store.loadIdCounter();
148304
148766
  projectDir = dir;
148305
148767
  searchIndexPrepared = false;
148306
148768
  vectorSchemaUpgradePromise = null;
148769
+ if (options.skipCorpusLoad) {
148770
+ observations = [];
148771
+ corpusLoaded = false;
148772
+ loadedObservationStore = store;
148773
+ return;
148774
+ }
148775
+ observations = await store.loadAll();
148776
+ corpusLoaded = true;
148777
+ loadedObservationStore = store;
148307
148778
  }
148308
148779
  function scheduleObservationEmbedding(input) {
148309
148780
  const { observationId: observationId2, projectId, searchableText, doc } = input;
148310
148781
  vectorMissingIds.add(observationId2);
148782
+ if (isEmbeddingExplicitlyDisabled()) {
148783
+ vectorMissingIds.delete(observationId2);
148784
+ return;
148785
+ }
148311
148786
  if (embeddingWriteMode === "deferred") {
148312
- if (isEmbeddingExplicitlyDisabled()) {
148313
- vectorMissingIds.delete(observationId2);
148314
- return;
148315
- }
148316
148787
  queueVectorBackfill(projectId, {
148317
148788
  detachedWorker: true,
148318
148789
  projectRoot: embeddingWorkerProjectRoot
@@ -148359,7 +148830,7 @@ function scheduleObservationEmbedding(input) {
148359
148830
  });
148360
148831
  }
148361
148832
  async function ensureFreshObservations() {
148362
- if (!projectDir) return false;
148833
+ if (!projectDir || !corpusLoaded) return false;
148363
148834
  try {
148364
148835
  const store = getObservationStore();
148365
148836
  const wasStale = await store.ensureFresh();
@@ -148400,7 +148871,7 @@ async function storeObservation(input) {
148400
148871
  });
148401
148872
  if (safeAttachments) input = { ...input, attachments: safeAttachments };
148402
148873
  await ensureFreshObservations();
148403
- if (input.topicKey) {
148874
+ if (input.topicKey && corpusLoaded) {
148404
148875
  const existing = observations.find(
148405
148876
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
148406
148877
  );
@@ -148497,13 +148968,17 @@ async function storeObservation(input) {
148497
148968
  });
148498
148969
  observation.writeGeneration = store.getGeneration();
148499
148970
  if (upsertedInsideLock || reloadCacheAfterCommit) {
148500
- observations = await store.loadAll();
148501
- nextId = await store.loadIdCounter();
148502
- if (upsertedInsideLock) {
148503
- observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
148971
+ if (corpusLoaded) {
148972
+ observations = await store.loadAll();
148973
+ nextId = await store.loadIdCounter();
148974
+ if (upsertedInsideLock) {
148975
+ observation = observations.find((candidate) => candidate.id === observation.id) ?? observation;
148976
+ }
148977
+ } else {
148978
+ nextId = await store.loadIdCounter();
148504
148979
  }
148505
148980
  } else {
148506
- observations.push(observation);
148981
+ if (corpusLoaded) observations.push(observation);
148507
148982
  nextId = observation.id + 1;
148508
148983
  }
148509
148984
  if (upsertedInsideLock) return;
@@ -148571,7 +149046,9 @@ async function storeObservation(input) {
148571
149046
  createdByAgentId: input.createdByAgentId ?? "",
148572
149047
  sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
148573
149048
  };
148574
- await insertObservation(doc);
149049
+ if (corpusLoaded) {
149050
+ await insertObservation(doc);
149051
+ }
148575
149052
  };
148576
149053
  await assignAndPersist();
148577
149054
  if (upsertedInsideLock) {
@@ -148933,9 +149410,11 @@ async function reindexObservations() {
148933
149410
  searchIndexPrepared = true;
148934
149411
  return count2;
148935
149412
  }
148936
- async function prepareSearchIndex() {
149413
+ async function prepareSearchIndex(options = {}) {
148937
149414
  if (searchIndexPrepared) return 0;
148938
- const count2 = await hydrateIndexForStartup(observations);
149415
+ const count2 = await hydrateIndexForStartup(observations, {
149416
+ skipCachedVectors: options.skipCachedVectors
149417
+ });
148939
149418
  if (count2 === 0) {
148940
149419
  searchIndexPrepared = true;
148941
149420
  return 0;
@@ -149127,7 +149606,7 @@ async function backfillVectorEmbeddings(options = {}) {
149127
149606
  ...lastFailure ? { lastError: lastFailure } : {}
149128
149607
  };
149129
149608
  }
149130
- var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
149609
+ var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, corpusLoaded, loadedObservationStore, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
149131
149610
  var init_observations = __esm({
149132
149611
  "src/memory/observations.ts"() {
149133
149612
  "use strict";
@@ -149146,6 +149625,8 @@ var init_observations = __esm({
149146
149625
  projectDir = null;
149147
149626
  searchIndexPrepared = false;
149148
149627
  embeddingWriteMode = "background";
149628
+ corpusLoaded = false;
149629
+ loadedObservationStore = null;
149149
149630
  vectorMissingIds = /* @__PURE__ */ new Set();
149150
149631
  vectorBackfillRunning = false;
149151
149632
  vectorSchemaUpgradePromise = null;