grepmax 0.26.59 → 0.26.60

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.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.59",
13
+ "version": "0.26.60",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -453,6 +453,7 @@ fixtures/
453
453
  | --- | --- | --- |
454
454
  | `GMAX_EMBED_MODE` | Force `cpu` or `gpu` | Auto-detect |
455
455
  | `GMAX_WORKER_THREADS` | Worker processes for embedding; overrides `gmax config --worker-threads` | `min(cores, 4, max(2, floor(cores/2)))` |
456
+ | `GMAX_RESOURCE_BUDGET_MB` | macOS aggregate admission budget across clients, stores, workers and models; accepts 256–6144 MiB | `6144` |
456
457
  | `GMAX_WORKER_RSS_RECYCLE_MB` | Recycle workers that remain above this RSS; `0` disables the check | `1536` |
457
458
  | `GMAX_DEBUG` | Debug logging | Off |
458
459
  | `GMAX_RERANK` | Force ColBERT rerank (`1`); concentrated candidates can also enable it automatically ([why](docs/known-limitations.md)) | Off |
@@ -552,3 +553,7 @@ grepmax is built upon the foundation of [mgrep](https://github.com/mixedbread-ai
552
553
  ## License
553
554
 
554
555
  Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).
556
+
557
+ On macOS, heavy work uses a shared resource ledger and a bounded batch footprint probe covering every gmax process and its children. Native stores, workers and model launches reserve headroom before allocating it. Warning or unknown measurements pause heavy work; known critical pressure persists a stop. The 6 GiB admission budget is a conservative scheduling threshold, not an OS-enforced memory cap. It may be lowered with `GMAX_RESOURCE_BUDGET_MB`. Existing safety markers remain in place after upgrades.
558
+
559
+ MCP sessions started before this resource policy must reconnect after upgrading before heavy work can resume. Keyword search through the bounded read service stays available while those sessions are running. Cached resource snapshots in `gmax status --json` distinguish aggregate physical footprint from daemon RSS.
@@ -101,6 +101,7 @@ const mcp_execution_1 = require("../lib/utils/mcp-execution");
101
101
  const path_containment_1 = require("../lib/utils/path-containment");
102
102
  const project_registry_1 = require("../lib/utils/project-registry");
103
103
  const project_root_1 = require("../lib/utils/project-root");
104
+ const resource_budget_1 = require("../lib/utils/resource-budget");
104
105
  const scope_filter_1 = require("../lib/utils/scope-filter");
105
106
  const store_access_1 = require("../lib/utils/store-access");
106
107
  const store_context_1 = require("../lib/utils/store-context");
@@ -368,6 +369,18 @@ exports.mcp = new commander_1.Command("mcp")
368
369
  .description("Start MCP server (stdio, auto-started by plugins)")
369
370
  .action((_optsArg, _cmd) => __awaiter(void 0, void 0, void 0, function* () {
370
371
  process.title = "gmax-mcp";
372
+ try {
373
+ const client = resource_budget_1.resourceBudget.registerClient();
374
+ process.once("exit", () => {
375
+ try {
376
+ client.release();
377
+ }
378
+ catch (_a) { }
379
+ });
380
+ }
381
+ catch (error) {
382
+ console.error(`gmax: resource coordination unavailable; heavy work paused (${error instanceof Error ? error.message : "unknown"})`);
383
+ }
371
384
  // --- Lifecycle ---
372
385
  // No per-session VectorDB. Every read tool goes through the daemon's read
373
386
  // verbs, and the only store this process may open is the short-lived one
@@ -67,6 +67,7 @@ const autostart_1 = require("../utils/autostart");
67
67
  const blocked_roots_1 = require("../utils/blocked-roots");
68
68
  const daemon_client_1 = require("../utils/daemon-client");
69
69
  const daemon_launcher_1 = require("../utils/daemon-launcher");
70
+ const host_resource_1 = require("../utils/host-resource");
70
71
  const kernel_zone_1 = require("../utils/kernel-zone");
71
72
  const keyed_mutex_1 = require("../utils/keyed-mutex");
72
73
  const log_rotate_1 = require("../utils/log-rotate");
@@ -76,6 +77,7 @@ const process_1 = require("../utils/process");
76
77
  const process_footprint_1 = require("../utils/process-footprint");
77
78
  const project_registry_1 = require("../utils/project-registry");
78
79
  const query_timeout_1 = require("../utils/query-timeout");
80
+ const resource_budget_1 = require("../utils/resource-budget");
79
81
  const resource_snapshot_1 = require("../utils/resource-snapshot");
80
82
  const root_availability_1 = require("../utils/root-availability");
81
83
  const safety_latch_1 = require("../utils/safety-latch");
@@ -165,6 +167,7 @@ class Daemon {
165
167
  // resources must be gated on this to avoid hitting null stores mid-startup.
166
168
  this.ready = false;
167
169
  this.pausedReason = null;
170
+ this.lastHostResources = null;
168
171
  this.pausePromise = null;
169
172
  this.pausedReadGate = new async_semaphore_1.AsyncSemaphore(1);
170
173
  this.processManager = new process_manager_1.ProcessManager({
@@ -246,7 +249,7 @@ class Daemon {
246
249
  }
247
250
  start() {
248
251
  return __awaiter(this, arguments, void 0, function* (options = {}) {
249
- var _a, _b;
252
+ var _a, _b, _c;
250
253
  const denied = (0, autostart_1.daemonStartDeniedReason)();
251
254
  if (denied !== null && !options.readOnly)
252
255
  throw new Error(`gmax: ${denied}; preserving containment`);
@@ -260,6 +263,19 @@ class Daemon {
260
263
  this.pausedReason = (_a = denied !== null && denied !== void 0 ? denied : pressure === null || pressure === void 0 ? void 0 : pressure.reason) !== null && _a !== void 0 ? _a : "read-only service requested";
261
264
  }
262
265
  process.title = "gmax-daemon";
266
+ if (this.pausedReason === null) {
267
+ try {
268
+ this.lastHostResources = resource_budget_1.resourceBudget.check();
269
+ }
270
+ catch (error) {
271
+ if (error instanceof resource_budget_1.ResourceAdmissionError && error.critical)
272
+ throw error;
273
+ this.pausedReason =
274
+ error instanceof Error
275
+ ? error.message
276
+ : "aggregate resource admission unavailable";
277
+ }
278
+ }
263
279
  // Read verbs register here rather than at read-verbs.ts module scope: the
264
280
  // registry is asserted on exactly in tests/ipc-read-verbs.test.ts, and a
265
281
  // module-scope registration would make it non-empty on import. Register
@@ -311,7 +327,7 @@ class Daemon {
311
327
  try {
312
328
  fs.unlinkSync(config_1.PATHS.daemonSocket);
313
329
  }
314
- catch (_c) { }
330
+ catch (_d) { }
315
331
  this.server = net.createServer((conn) => {
316
332
  (0, logger_1.debug)("daemon", "client connected");
317
333
  this.connections.add(conn);
@@ -405,9 +421,13 @@ class Daemon {
405
421
  }
406
422
  catch (error) {
407
423
  const freshPressure = this.hostPressureDeniedReason();
408
- if (freshPressure === null || freshPressure.critical)
424
+ if (freshPressure === null || freshPressure === void 0 ? void 0 : freshPressure.critical)
425
+ throw error;
426
+ if (freshPressure === null &&
427
+ !(error instanceof resource_budget_1.ResourceAdmissionError) &&
428
+ (error === null || error === void 0 ? void 0 : error.code) !== "HOST_SAFETY")
409
429
  throw error;
410
- this.pauseWork(freshPressure.reason);
430
+ this.pauseWork((_b = freshPressure === null || freshPressure === void 0 ? void 0 : freshPressure.reason) !== null && _b !== void 0 ? _b : error.message);
411
431
  yield this.pausePromise;
412
432
  }
413
433
  if (this.pausedReason === null) {
@@ -427,7 +447,7 @@ class Daemon {
427
447
  // 6. LLM server manager (constructed, not started — starts on first request)
428
448
  this.llmServer = new server_1.LlmServer();
429
449
  // 6b. MLX embed server — start if GPU mode is active
430
- const globalConfig = (_b = this.activeConfig) !== null && _b !== void 0 ? _b : (0, index_config_1.readGlobalConfig)();
450
+ const globalConfig = (_c = this.activeConfig) !== null && _c !== void 0 ? _c : (0, index_config_1.readGlobalConfig)();
431
451
  const isAppleSilicon = process.arch === "arm64" && process.platform === "darwin";
432
452
  if (this.pausedReason === null &&
433
453
  isAppleSilicon &&
@@ -510,6 +530,8 @@ class Daemon {
510
530
  this.logResourceSnapshot("paused-heartbeat", null);
511
531
  return;
512
532
  }
533
+ if (!this.checkAggregateResources())
534
+ return;
513
535
  // Every 5 ticks (5 min), probe the MLX embed server and respawn if
514
536
  // it's gone zombie (port held but /health unresponsive). Closes the
515
537
  // 42h-degradation window where workers silently fell back to ONNX CPU
@@ -758,6 +780,7 @@ class Daemon {
758
780
  });
759
781
  }
760
782
  assertHeavyOperationAdmission(name) {
783
+ var _a;
761
784
  const denied = (0, autostart_1.daemonStartDeniedReason)();
762
785
  if (denied !== null)
763
786
  throw new Error(`${name} refused: ${denied}`);
@@ -765,6 +788,8 @@ class Daemon {
765
788
  if (!this.checkKernelZonePressure()) {
766
789
  throw new Error(`${name} refused: host pressure is unsafe or unknown${this.pausedReason ? `: ${this.pausedReason}` : ""}`);
767
790
  }
791
+ if (!this.checkAggregateResources())
792
+ throw new Error(`${name} refused: ${(_a = this.pausedReason) !== null && _a !== void 0 ? _a : "aggregate resources unavailable"}`);
768
793
  // Another process can quarantine the machine while the probes run.
769
794
  const finalDenied = (0, autostart_1.daemonStartDeniedReason)();
770
795
  if (finalDenied !== null)
@@ -2037,6 +2062,8 @@ class Daemon {
2037
2062
  // Both ordinary probes and deferred-recycle ticks check safety first.
2038
2063
  if (!this.checkKernelZonePressure())
2039
2064
  return;
2065
+ if (sampleFootprint && !this.checkAggregateResources())
2066
+ return;
2040
2067
  this.maybeRecycle(sampleFootprint);
2041
2068
  if (this.shuttingDown || this.recycling || !sampleFootprint)
2042
2069
  return;
@@ -2048,6 +2075,8 @@ class Daemon {
2048
2075
  logResourceSnapshot(reason, footprintMb) {
2049
2076
  var _a, _b;
2050
2077
  var _c, _d;
2078
+ if (reason.startsWith("paused-"))
2079
+ this.lastHostResources = (0, host_resource_1.sampleHostResources)();
2051
2080
  let lanceCacheBytes = null;
2052
2081
  try {
2053
2082
  lanceCacheBytes = (_c = (_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.cacheSizeBytes()) !== null && _c !== void 0 ? _c : null;
@@ -2061,6 +2090,7 @@ class Daemon {
2061
2090
  pendingFiles: [...this.processors.values()].reduce((sum, processor) => sum + processor.progress.pendingFiles, 0),
2062
2091
  operations: this.operations.activeCount,
2063
2092
  maintenance: (_d = (_b = this.vectorDb) === null || _b === void 0 ? void 0 : _b.isMaintenanceActive()) !== null && _d !== void 0 ? _d : false,
2093
+ host: this.lastHostResources,
2064
2094
  });
2065
2095
  console.log(`[daemon] Resource snapshot: ${JSON.stringify(this.lastResourceSnapshot)}`);
2066
2096
  }
@@ -2165,6 +2195,24 @@ class Daemon {
2165
2195
  }
2166
2196
  return this.pausedReason === null;
2167
2197
  }
2198
+ checkAggregateResources() {
2199
+ if (this.shuttingDown || this.recycling || this.pausedReason !== null)
2200
+ return false;
2201
+ try {
2202
+ this.lastHostResources = resource_budget_1.resourceBudget.check();
2203
+ return true;
2204
+ }
2205
+ catch (error) {
2206
+ const reason = error instanceof Error
2207
+ ? error.message
2208
+ : "aggregate resource admission unavailable";
2209
+ if (error instanceof resource_budget_1.ResourceAdmissionError && error.critical)
2210
+ this.stopForSafety(reason);
2211
+ else
2212
+ this.pauseWork(reason);
2213
+ return false;
2214
+ }
2215
+ }
2168
2216
  /** Unknown/warning pauses resource expansion; only known critical shuts down. */
2169
2217
  hostPressureDeniedReason() {
2170
2218
  if (process.platform !== "darwin")
@@ -52,6 +52,7 @@ const config_1 = require("../../config");
52
52
  const log_rotate_1 = require("../utils/log-rotate");
53
53
  const mlx_hf_cache_1 = require("../utils/mlx-hf-cache");
54
54
  const process_1 = require("../utils/process");
55
+ const resource_budget_1 = require("../utils/resource-budget");
55
56
  const STARTUP_ATTEMPTS = 30;
56
57
  const STARTUP_POLL_MS = 1000;
57
58
  const MAX_HEALTH_BODY_BYTES = 16 * 1024;
@@ -151,11 +152,18 @@ class MlxServerManager {
151
152
  terminateOwned(server) {
152
153
  return __awaiter(this, void 0, void 0, function* () {
153
154
  var _a;
154
- const terminate = (_a = this.deps.terminateGroup) !== null && _a !== void 0 ? _a : process_1.terminateProcessGroup;
155
+ var _b;
156
+ const terminate = (_b = this.deps.terminateGroup) !== null && _b !== void 0 ? _b : process_1.terminateProcessGroup;
155
157
  const stopped = yield terminate(server.pid);
156
158
  if (!stopped) {
157
159
  console.error(`[daemon] Failed to fully stop owned MLX process group ${server.pid}`);
158
160
  }
161
+ else {
162
+ try {
163
+ (_a = server.reservation) === null || _a === void 0 ? void 0 : _a.release();
164
+ }
165
+ catch (_c) { }
166
+ }
159
167
  server.child.removeAllListeners("error");
160
168
  server.child.removeAllListeners("exit");
161
169
  if (this.stable === server)
@@ -204,7 +212,7 @@ class MlxServerManager {
204
212
  ensureInternal(model) {
205
213
  return __awaiter(this, void 0, void 0, function* () {
206
214
  var _a, _b, _c, _d, _e;
207
- var _f, _g, _h, _j, _k;
215
+ var _f, _g, _h, _j, _k, _l;
208
216
  this.phase = "probing";
209
217
  const health = yield this.probeHealth(this.port);
210
218
  if (this.shouldStop())
@@ -223,6 +231,7 @@ class MlxServerManager {
223
231
  this.stable.lastHealthyAt = Date.now();
224
232
  return;
225
233
  }
234
+ resource_budget_1.resourceBudget.check(this.getPortPid(this.port));
226
235
  if (this.stable.kind === "owned")
227
236
  yield this.terminateOwned(this.stable);
228
237
  this.stable = { kind: "adopted", model, lastHealthyAt: Date.now() };
@@ -278,17 +287,41 @@ class MlxServerManager {
278
287
  const logFd = openLog(path.join(config_1.PATHS.logsDir, "mlx-embed-server.log"));
279
288
  const env = Object.assign(Object.assign({}, process.env), { MLX_EMBED_MODEL: model, GMAX_EMBED_OWNER_TOKEN: owner, HF_HOME: (0, mlx_hf_cache_1.resolveMlxHfHome)(model) });
280
289
  this.phase = "starting";
281
- let child;
290
+ let child = null;
291
+ let reservation;
282
292
  try {
293
+ reservation = resource_budget_1.resourceBudget.reserve(resource_budget_1.EMBEDDING_RESOURCE_RESERVE_MB, "embedding");
283
294
  child = ((_j = this.deps.spawn) !== null && _j !== void 0 ? _j : node_child_process_1.spawn)("uv", ["run", "python", "server.py"], {
284
295
  cwd: serverDir,
285
296
  detached: true,
286
297
  stdio: ["ignore", logFd, logFd],
287
298
  env,
288
299
  });
300
+ if (!child.pid) {
301
+ reservation.release();
302
+ throw new Error("spawned MLX process has no PID");
303
+ }
304
+ try {
305
+ reservation.attach(child.pid, true);
306
+ }
307
+ catch (error) {
308
+ const stopped = yield ((_k = this.deps.terminateGroup) !== null && _k !== void 0 ? _k : process_1.terminateProcessGroup)(child.pid);
309
+ if (stopped)
310
+ reservation.release();
311
+ throw error;
312
+ }
289
313
  }
290
314
  catch (error) {
315
+ // If no child was created, there is no native group to keep charged.
316
+ if (!child) {
317
+ try {
318
+ reservation === null || reservation === void 0 ? void 0 : reservation.release();
319
+ }
320
+ catch (_m) { }
321
+ }
291
322
  this.lastError = error instanceof Error ? error.message : String(error);
323
+ if (error instanceof resource_budget_1.ResourceAdmissionError)
324
+ this.enabled = false;
292
325
  this.phase = "failed";
293
326
  console.error(`[daemon] MLX embed server failed to spawn: ${this.lastError} — falling back to CPU embeddings`);
294
327
  return;
@@ -297,9 +330,9 @@ class MlxServerManager {
297
330
  try {
298
331
  closeFd(logFd);
299
332
  }
300
- catch (_l) { }
333
+ catch (_o) { }
301
334
  }
302
- if (!child.pid) {
335
+ if (!(child === null || child === void 0 ? void 0 : child.pid)) {
303
336
  this.lastError = "spawned MLX process has no PID";
304
337
  this.phase = "failed";
305
338
  return;
@@ -312,6 +345,7 @@ class MlxServerManager {
312
345
  model,
313
346
  owner,
314
347
  lastHealthyAt: 0,
348
+ reservation,
315
349
  };
316
350
  const startup = { error: null, exited: false };
317
351
  const onError = (error) => {
@@ -338,6 +372,7 @@ class MlxServerManager {
338
372
  child.off("exit", onExit);
339
373
  if (readiness.owner !== owner) {
340
374
  yield this.terminateOwned(candidate);
375
+ resource_budget_1.resourceBudget.check(this.getPortPid(this.port));
341
376
  this.stable = {
342
377
  kind: "adopted",
343
378
  model,
@@ -356,7 +391,7 @@ class MlxServerManager {
356
391
  child.off("error", onError);
357
392
  child.off("exit", onExit);
358
393
  yield this.terminateOwned(candidate);
359
- this.lastError = (_k = (_e = startup.error) === null || _e === void 0 ? void 0 : _e.message) !== null && _k !== void 0 ? _k : (this.shouldStop() ? "MLX startup cancelled" : "MLX startup timed out");
394
+ this.lastError = (_l = (_e = startup.error) === null || _e === void 0 ? void 0 : _e.message) !== null && _l !== void 0 ? _l : (this.shouldStop() ? "MLX startup cancelled" : "MLX startup timed out");
360
395
  if (!this.shouldStop()) {
361
396
  this.phase = "failed";
362
397
  console.error(`[daemon] ${this.lastError} — falling back to CPU embeddings`);
@@ -50,6 +50,8 @@ const path = __importStar(require("node:path"));
50
50
  const config_1 = require("../../config");
51
51
  const index_config_1 = require("../index/index-config");
52
52
  const log_rotate_1 = require("../utils/log-rotate");
53
+ const process_1 = require("../utils/process");
54
+ const resource_budget_1 = require("../utils/resource-budget");
53
55
  const config_2 = require("./config");
54
56
  // ⛔ HARD STOP FOR AI AGENTS: This starts llama-server with a multi-GB local
55
57
  // LLM (Qwen3.5-35B GGUF ≈21GB by default). DO NOT start it — via start(),
@@ -124,6 +126,7 @@ class LlmServer {
124
126
  throw new Error("LLM is disabled. Run `gmax llm on` to enable.");
125
127
  }
126
128
  if (yield this.healthy()) {
129
+ resource_budget_1.resourceBudget.check(this.readPid());
127
130
  // Adopt an existing server (e.g. after daemon crash + restart)
128
131
  this.lastRequestTime = Date.now();
129
132
  this.startTime = Date.now();
@@ -148,7 +151,17 @@ class LlmServer {
148
151
  if (!fs.existsSync(this.config.model)) {
149
152
  throw new Error(`Model file not found: "${this.config.model}". Set GMAX_LLM_MODEL to a valid .gguf path`);
150
153
  }
151
- const logFd = (0, log_rotate_1.openRotatedLog)(config_1.PATHS.llmLogFile);
154
+ // Reserve the model's on-disk size plus runtime headroom before starting
155
+ // it. The default multi-GB model cannot fit the containment budget.
156
+ const reservation = resource_budget_1.resourceBudget.reserve(Math.ceil(fs.statSync(this.config.model).size / 1048576) + 512, "llm");
157
+ let logFd;
158
+ try {
159
+ logFd = (0, log_rotate_1.openRotatedLog)(config_1.PATHS.llmLogFile);
160
+ }
161
+ catch (error) {
162
+ reservation.release();
163
+ throw error;
164
+ }
152
165
  const args = [
153
166
  "-m",
154
167
  this.config.model,
@@ -164,16 +177,36 @@ class LlmServer {
164
177
  if (this.config.reasoningFormat) {
165
178
  args.push("--reasoning-format", this.config.reasoningFormat);
166
179
  }
167
- const child = (0, node_child_process_1.spawn)(binary, args, {
168
- detached: true,
169
- stdio: ["ignore", logFd, logFd],
170
- });
180
+ let child;
181
+ try {
182
+ child = (0, node_child_process_1.spawn)(binary, args, {
183
+ detached: true,
184
+ stdio: ["ignore", logFd, logFd],
185
+ });
186
+ }
187
+ catch (error) {
188
+ reservation.release();
189
+ throw error;
190
+ }
191
+ finally {
192
+ fs.closeSync(logFd);
193
+ }
171
194
  child.unref();
172
- fs.closeSync(logFd);
173
195
  const pid = child.pid;
174
196
  if (!pid) {
197
+ reservation.release();
175
198
  throw new Error("Failed to spawn llama-server — no PID returned");
176
199
  }
200
+ // Group reservations are reaped only after the whole detached group is
201
+ // confirmed dead. A launcher exit cannot make surviving children free.
202
+ try {
203
+ reservation.attach(pid, true);
204
+ }
205
+ catch (error) {
206
+ if (yield (0, process_1.terminateProcessGroup)(pid))
207
+ reservation.release();
208
+ throw error;
209
+ }
177
210
  fs.writeFileSync(config_1.PATHS.llmPidFile, String(pid));
178
211
  console.log(`[llm] Starting llama-server (PID: ${pid}, port: ${this.config.port})`);
179
212
  // Poll until ready
@@ -257,6 +290,7 @@ class LlmServer {
257
290
  throw new Error("LLM is disabled. Run `gmax llm on` to enable.");
258
291
  }
259
292
  if (yield this.healthy()) {
293
+ resource_budget_1.resourceBudget.check(this.readPid());
260
294
  this.touchIdle();
261
295
  return;
262
296
  }
@@ -63,6 +63,7 @@ const cleanup_1 = require("../utils/cleanup");
63
63
  const filter_builder_1 = require("../utils/filter-builder");
64
64
  const logger_1 = require("../utils/logger");
65
65
  const query_timeout_1 = require("../utils/query-timeout");
66
+ const resource_budget_1 = require("../utils/resource-budget");
66
67
  const ann_config_1 = require("./ann-config");
67
68
  const compaction_result_1 = require("./compaction-result");
68
69
  const lancedb = __importStar(require("./lance-sdk"));
@@ -252,6 +253,8 @@ class VectorDB {
252
253
  this.lastDiskCheckMs = 0;
253
254
  this.lastLoggedPressure = "ok";
254
255
  this.leasePromise = null;
256
+ this.resourceReservation = null;
257
+ this.connecting = null;
255
258
  this.leaseAbort = new AbortController();
256
259
  this.closePromise = null;
257
260
  this.releaseLeaseOnClose = true;
@@ -374,6 +377,8 @@ class VectorDB {
374
377
  }
375
378
  getDb() {
376
379
  return __awaiter(this, void 0, void 0, function* () {
380
+ var _a;
381
+ var _b, _c;
377
382
  if (this.closed) {
378
383
  throw new Error("VectorDB connection is closed");
379
384
  }
@@ -387,17 +392,38 @@ class VectorDB {
387
392
  throw new Error("VectorDB connection is closed");
388
393
  }
389
394
  if (!this.db) {
395
+ if (this.connecting)
396
+ return this.connecting;
390
397
  if (creatingStore || !fs.existsSync(this.lancedbDir)) {
391
398
  this.assertMutationAllowed();
392
399
  (0, maintenance_policy_1.assertFreshDiskMutationAllowed)(this.lancedbDir);
393
400
  }
394
401
  fs.mkdirSync(this.lancedbDir, { recursive: true });
402
+ // The explicit bounded reader remains available under warning/unknown
403
+ // pressure. All ordinary clients reserve their native cache capacity in
404
+ // one shared ledger before opening a connection, including offline MCP.
405
+ if (!this.options.readOnly) {
406
+ this.resourceReservation = resource_budget_1.resourceBudget.reserve(((_b = this.options.indexCacheMb) !== null && _b !== void 0 ? _b : LANCE_INDEX_CACHE_MB) +
407
+ ((_c = this.options.metadataCacheMb) !== null && _c !== void 0 ? _c : LANCE_METADATA_CACHE_MB), "native-store");
408
+ }
395
409
  this.session = createLanceSession(this.options);
396
410
  // 0.38 accepts the legacy third argument in its types but drops it in
397
411
  // the JS wrapper. Put the session in native ConnectionOptions instead.
398
- this.db = yield lancedb.connect(this.lancedbDir, {
412
+ this.connecting = lancedb.connect(this.lancedbDir, {
399
413
  session: this.session,
400
414
  });
415
+ try {
416
+ this.db = yield this.connecting;
417
+ }
418
+ catch (error) {
419
+ (_a = this.resourceReservation) === null || _a === void 0 ? void 0 : _a.release();
420
+ this.resourceReservation = null;
421
+ this.session = null;
422
+ throw error;
423
+ }
424
+ finally {
425
+ this.connecting = null;
426
+ }
401
427
  }
402
428
  return this.db;
403
429
  });
@@ -1874,7 +1900,8 @@ class VectorDB {
1874
1900
  }
1875
1901
  finishClose() {
1876
1902
  return __awaiter(this, void 0, void 0, function* () {
1877
- var _a, _b;
1903
+ var _a, _b, _c;
1904
+ yield (_a = this.connecting) === null || _a === void 0 ? void 0 : _a.catch(() => { });
1878
1905
  if (this.maintenanceTimer) {
1879
1906
  clearInterval(this.maintenanceTimer);
1880
1907
  this.maintenanceTimer = null;
@@ -1893,12 +1920,22 @@ class VectorDB {
1893
1920
  ]);
1894
1921
  this.maintenancePromise = null;
1895
1922
  }
1896
- (_a = this.unregisterCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
1923
+ (_b = this.unregisterCleanup) === null || _b === void 0 ? void 0 : _b.call(this);
1897
1924
  this.unregisterCleanup = undefined;
1898
1925
  let closeError;
1899
- if ((_b = this.db) === null || _b === void 0 ? void 0 : _b.close) {
1926
+ let nativeClosed = !this.db;
1927
+ if ((_c = this.db) === null || _c === void 0 ? void 0 : _c.close) {
1900
1928
  try {
1901
1929
  const closing = this.db.close();
1930
+ void Promise.resolve(closing).then(() => {
1931
+ var _a;
1932
+ nativeClosed = true;
1933
+ try {
1934
+ (_a = this.resourceReservation) === null || _a === void 0 ? void 0 : _a.release();
1935
+ this.resourceReservation = null;
1936
+ }
1937
+ catch (_b) { }
1938
+ }, () => { });
1902
1939
  if (this.requireClosedOnClose)
1903
1940
  yield closing;
1904
1941
  else {
@@ -1914,12 +1951,21 @@ class VectorDB {
1914
1951
  }
1915
1952
  this.db = null;
1916
1953
  this.session = null;
1954
+ if (nativeClosed && this.resourceReservation) {
1955
+ try {
1956
+ this.resourceReservation.release();
1957
+ this.resourceReservation = null;
1958
+ }
1959
+ catch (error) {
1960
+ closeError !== null && closeError !== void 0 ? closeError : (closeError = error);
1961
+ }
1962
+ }
1917
1963
  if (this.leasePromise) {
1918
1964
  let lease = null;
1919
1965
  try {
1920
1966
  lease = yield this.leasePromise;
1921
1967
  }
1922
- catch (_c) {
1968
+ catch (_d) {
1923
1969
  this.leasePromise = null;
1924
1970
  }
1925
1971
  if (lease) {