grepmax 0.26.60 → 0.26.61

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.60",
13
+ "version": "0.26.61",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -557,3 +557,16 @@ Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).
557
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
558
 
559
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.
560
+
561
+ ### Host guard policy
562
+
563
+ `hostGuardPolicy: "critical-only"` in `~/.gmax/config.json` (or
564
+ `GMAX_HOST_GUARD_POLICY=critical-only`) selects an explicit availability policy.
565
+ Warning and unavailable pressure probes remain diagnostic and do not pause
566
+ semantic search or indexing. Confirmed critical OS or kernel pressure still
567
+ persists a safety stop and shuts down. This mode disables aggregate memory
568
+ admission and the requirement to reconnect older MCP clients; native cache and
569
+ worker limits remain. The default `strict` policy keeps aggregate admission.
570
+ Restart the daemon after changing policy. An existing safety-stop marker requires
571
+ explicit operator review and clearing; selecting a policy never clears it.
572
+ Full-table compaction remains disabled in either policy.
@@ -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_guard_policy_1 = require("../utils/host-guard-policy");
70
71
  const host_resource_1 = require("../utils/host-resource");
71
72
  const kernel_zone_1 = require("../utils/kernel-zone");
72
73
  const keyed_mutex_1 = require("../utils/keyed-mutex");
@@ -263,6 +264,7 @@ class Daemon {
263
264
  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";
264
265
  }
265
266
  process.title = "gmax-daemon";
267
+ console.log(`[daemon] Host guard policy: ${(0, host_guard_policy_1.hostGuardPolicy)()}`);
266
268
  if (this.pausedReason === null) {
267
269
  try {
268
270
  this.lastHostResources = resource_budget_1.resourceBudget.check();
@@ -2239,6 +2241,8 @@ class Daemon {
2239
2241
  measured: true,
2240
2242
  critical: true,
2241
2243
  };
2244
+ if ((0, host_guard_policy_1.hostGuardPolicy)() === "critical-only")
2245
+ return null;
2242
2246
  for (const sample of [memory, freshMemory]) {
2243
2247
  if (sample.status !== "known")
2244
2248
  return {
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.hostGuardPolicy = hostGuardPolicy;
37
+ exports.sampleCriticalPressure = sampleCriticalPressure;
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const config_1 = require("../../config");
41
+ const kernel_zone_1 = require("./kernel-zone");
42
+ /** Explicit operator preference. Unknown values preserve strict admission. */
43
+ function hostGuardPolicy() {
44
+ const environment = process.env.GMAX_HOST_GUARD_POLICY;
45
+ if (environment !== undefined)
46
+ return environment === "critical-only" ? "critical-only" : "strict";
47
+ try {
48
+ const config = JSON.parse(fs.readFileSync(path.join(config_1.PATHS.sharedRoot, "config.json"), "utf8"));
49
+ return config.hostGuardPolicy === "critical-only"
50
+ ? "critical-only"
51
+ : "strict";
52
+ }
53
+ catch (_a) {
54
+ return "strict";
55
+ }
56
+ }
57
+ /** No footprint scan or reservation ledger in explicitly selected critical-only
58
+ * mode. Unknown probes stay visible as unknown; they are never called healthy. */
59
+ function sampleCriticalPressure() {
60
+ const at = Date.now();
61
+ const memory = (0, kernel_zone_1.probeMemoryPressure)();
62
+ const kernel = (0, kernel_zone_1.probeKernelZoneUsage)();
63
+ const freshMemory = (0, kernel_zone_1.probeMemoryPressure)();
64
+ const memoryPressure = [memory, freshMemory].some((p) => p.status === "known" && p.pressure === "critical")
65
+ ? "critical"
66
+ : freshMemory.status === "known"
67
+ ? freshMemory.pressure
68
+ : "unknown";
69
+ return {
70
+ at,
71
+ completedAt: Date.now(),
72
+ platform: process.platform,
73
+ processes: [],
74
+ aggregateFootprintMb: null,
75
+ physicalFreeMb: null,
76
+ swapUsedMb: null,
77
+ memoryPressure,
78
+ kernelPressure: kernel.status === "known" ? kernel.usage.pressure : "unknown",
79
+ kernelBytes: kernel.status === "known" ? kernel.usage.bytes : null,
80
+ incompleteReasons: [
81
+ "aggregate admission disabled by explicit critical-only policy",
82
+ ],
83
+ };
84
+ }
@@ -46,6 +46,7 @@ const path = __importStar(require("node:path"));
46
46
  const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
47
47
  const config_1 = require("../../config");
48
48
  const autostart_1 = require("./autostart");
49
+ const host_guard_policy_1 = require("./host-guard-policy");
49
50
  const host_resource_1 = require("./host-resource");
50
51
  const safety_latch_1 = require("./safety-latch");
51
52
  /** Admission thresholds, not an OS-enforced memory cap. Keep enough room for
@@ -107,7 +108,7 @@ class ResourceBudget {
107
108
  this.deps = Object.assign({ root: path.join(config_1.PATHS.sharedRoot, "resource-budget"), platform: process.platform, pid: process.pid, now: Date.now, sample: (extraPids = []) => {
108
109
  const records = this.readRecords();
109
110
  return (0, host_resource_1.sampleHostResources)([...records.map((r) => r.pid), ...extraPids], {}, records.flatMap((r) => (r.groupPid ? [r.groupPid] : [])));
110
- }, signal: (pid) => process.kill(pid, 0), quarantine: autostart_1.daemonStartDeniedReason, latch: safety_latch_1.latchSafetyStop, processStart: () => (0, node_child_process_1.execFileSync)("ps", ["-p", String(process.pid), "-o", "lstart="], {
111
+ }, signal: (pid) => process.kill(pid, 0), policy: host_guard_policy_1.hostGuardPolicy, criticalSample: host_guard_policy_1.sampleCriticalPressure, quarantine: autostart_1.daemonStartDeniedReason, latch: safety_latch_1.latchSafetyStop, processStart: () => (0, node_child_process_1.execFileSync)("ps", ["-p", String(process.pid), "-o", "lstart="], {
111
112
  encoding: "utf8",
112
113
  timeout: 500,
113
114
  maxBuffer: 1024,
@@ -315,6 +316,29 @@ class ResourceBudget {
315
316
  requiredPid === null ||
316
317
  requiredPid < 1))
317
318
  throw new ResourceAdmissionError("adopted model process identity unavailable; heavy work paused");
319
+ if (this.deps.policy() === "critical-only") {
320
+ const denied = this.deps.quarantine();
321
+ if (denied)
322
+ throw new ResourceAdmissionError(`heavy work refused: ${denied}`);
323
+ if (requiredPid)
324
+ this.deps.signal(requiredPid);
325
+ const snapshot = this.deps.criticalSample();
326
+ if (snapshot.memoryPressure === "critical" ||
327
+ snapshot.kernelPressure === "critical") {
328
+ const error = new ResourceAdmissionError("critical host pressure; heavy work refused", true);
329
+ try {
330
+ this.deps.latch(error.message);
331
+ }
332
+ catch (_a) {
333
+ error.message += "; safety stop persistence failed";
334
+ }
335
+ throw error;
336
+ }
337
+ const after = this.deps.quarantine();
338
+ if (after)
339
+ throw new ResourceAdmissionError(`heavy work refused: ${after}`);
340
+ return snapshot;
341
+ }
318
342
  return this.withLock(() => {
319
343
  const snapshot = this.deps.sample(requiredPid ? [requiredPid] : []);
320
344
  if (requiredPid && !snapshot.processes.some((p) => p.pid === requiredPid))
@@ -328,6 +352,10 @@ class ResourceBudget {
328
352
  throw new ResourceAdmissionError("invalid resource reservation");
329
353
  if (this.deps.platform !== "darwin")
330
354
  return { attach: () => { }, release: () => { } };
355
+ if (this.deps.policy() === "critical-only") {
356
+ this.check();
357
+ return { attach: () => { }, release: () => { } };
358
+ }
331
359
  let record = this.withLock(() => {
332
360
  var _a;
333
361
  const denied = this.deps.quarantine();
@@ -58,6 +58,7 @@ const config_1 = require("../../config");
58
58
  const embedding_generation_1 = require("../index/embedding-generation");
59
59
  const index_config_1 = require("../index/index-config");
60
60
  const autostart_1 = require("../utils/autostart");
61
+ const host_guard_policy_1 = require("../utils/host-guard-policy");
61
62
  const kernel_zone_1 = require("../utils/kernel-zone");
62
63
  const logger_1 = require("../utils/logger");
63
64
  const resource_budget_1 = require("../utils/resource-budget");
@@ -423,21 +424,29 @@ class WorkerPool {
423
424
  const quarantine = (0, autostart_1.daemonStartDeniedReason)();
424
425
  if (quarantine)
425
426
  return this.denySpawn(quarantine, false);
427
+ const strictHostGuard = (0, host_guard_policy_1.hostGuardPolicy)() === "strict";
426
428
  if (process.platform === "darwin") {
427
429
  const memory = (0, kernel_zone_1.probeMemoryPressure)();
428
- if (memory.status !== "known" || memory.pressure !== "normal") {
430
+ if ((memory.status === "known" && memory.pressure === "critical") ||
431
+ (strictHostGuard &&
432
+ (memory.status !== "known" || memory.pressure !== "normal"))) {
429
433
  return this.denySpawn(`OS memory pressure ${memory.status === "known" ? memory.pressure : memory.status}; ${(0, kernel_zone_1.formatPressureProbe)(memory)}`, memory.status === "known" && memory.pressure === "critical");
430
434
  }
431
435
  const kernel = (0, kernel_zone_1.probeKernelZoneUsage)();
432
- if (kernel.status !== "known" || kernel.usage.pressure !== "ok") {
436
+ if ((kernel.status === "known" && kernel.usage.pressure === "critical") ||
437
+ (strictHostGuard &&
438
+ (kernel.status !== "known" || kernel.usage.pressure !== "ok"))) {
433
439
  return this.denySpawn(`kernel pressure ${kernel.status === "known" ? kernel.usage.pressure : kernel.status}; ${(0, kernel_zone_1.formatPressureProbe)(kernel)}`, kernel.status === "known" && kernel.usage.pressure === "critical");
434
440
  }
435
441
  }
436
442
  if (process.platform === "darwin") {
437
443
  // Refresh the cheap OS sample after the potentially slow kernel probe.
438
444
  const freshMemory = (0, kernel_zone_1.probeMemoryPressure)();
439
- if (freshMemory.status !== "known" ||
440
- freshMemory.pressure !== "normal") {
445
+ if ((freshMemory.status === "known" &&
446
+ freshMemory.pressure === "critical") ||
447
+ (strictHostGuard &&
448
+ (freshMemory.status !== "known" ||
449
+ freshMemory.pressure !== "normal"))) {
441
450
  return this.denySpawn(`OS memory pressure ${freshMemory.status === "known" ? freshMemory.pressure : freshMemory.status}; ${(0, kernel_zone_1.formatPressureProbe)(freshMemory)}`, freshMemory.status === "known" &&
442
451
  freshMemory.pressure === "critical");
443
452
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.60",
3
+ "version": "0.26.61",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.60",
3
+ "version": "0.26.61",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",