grepmax 0.26.60 → 0.26.62
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +19 -0
- package/dist/lib/daemon/daemon.js +4 -0
- package/dist/lib/daemon/watcher-manager.js +44 -5
- package/dist/lib/index/watcher-ignore.js +104 -0
- package/dist/lib/index/watcher.js +43 -10
- package/dist/lib/utils/host-guard-policy.js +84 -0
- package/dist/lib/utils/resource-budget.js +29 -1
- package/dist/lib/workers/pool.js +13 -4
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -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.
|
|
13
|
+
"version": "0.26.62",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/README.md
CHANGED
|
@@ -557,3 +557,22 @@ 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.
|
|
573
|
+
|
|
574
|
+
Root `.gmaxignore` exclusions also filter native filesystem events. Simple
|
|
575
|
+
positive rules update both daemon and standalone subscriptions when the policy
|
|
576
|
+
changes. Rules with negations, escapes or advanced pattern syntax remain in the
|
|
577
|
+
indexing policy only, preserving re-included files. Source JSON outside an
|
|
578
|
+
excluded path stays indexable.
|
|
@@ -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 {
|
|
@@ -57,6 +57,7 @@ const cache_coherence_1 = require("../index/cache-coherence");
|
|
|
57
57
|
const file_policy_1 = require("../index/file-policy");
|
|
58
58
|
const walker_1 = require("../index/walker");
|
|
59
59
|
const watcher_1 = require("../index/watcher");
|
|
60
|
+
const watcher_ignore_1 = require("../index/watcher-ignore");
|
|
60
61
|
const file_utils_1 = require("../utils/file-utils");
|
|
61
62
|
const logger_1 = require("../utils/logger");
|
|
62
63
|
const project_registry_1 = require("../utils/project-registry");
|
|
@@ -114,6 +115,7 @@ class WatcherManager {
|
|
|
114
115
|
this.degradedRoots = new Set();
|
|
115
116
|
this.terminalFailures = new Map();
|
|
116
117
|
this.watchLifecycles = new Map();
|
|
118
|
+
this.subscriptionUpdates = new Map();
|
|
117
119
|
}
|
|
118
120
|
health(root) {
|
|
119
121
|
var _a;
|
|
@@ -155,7 +157,17 @@ class WatcherManager {
|
|
|
155
157
|
let lastReindexAt;
|
|
156
158
|
processor = new batch_processor_1.ProjectBatchProcessor(Object.assign(Object.assign({ projectRoot: root, vectorDb,
|
|
157
159
|
metaCache }, (workerPool ? { workerPool } : {})), { filePolicy, onPolicyChange: () => {
|
|
158
|
-
void this.
|
|
160
|
+
void this.subscribeWatcher(root, processor)
|
|
161
|
+
.then(() => {
|
|
162
|
+
if (this.deps.processors.get(root) === processor &&
|
|
163
|
+
!this.deps.getShuttingDown())
|
|
164
|
+
return this.runCatchup(root, processor);
|
|
165
|
+
return;
|
|
166
|
+
}, (error) => {
|
|
167
|
+
this.recoverWatcher(root, processor);
|
|
168
|
+
throw error;
|
|
169
|
+
})
|
|
170
|
+
.catch((err) => {
|
|
159
171
|
console.error(`[daemon:${path.basename(root)}] Policy reconciliation failed:`, err);
|
|
160
172
|
});
|
|
161
173
|
}, onReindex: (files, ms) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -277,8 +289,27 @@ class WatcherManager {
|
|
|
277
289
|
}
|
|
278
290
|
subscribeWatcher(root, processor) {
|
|
279
291
|
return __awaiter(this, void 0, void 0, function* () {
|
|
292
|
+
var _a;
|
|
293
|
+
const previous = (_a = this.subscriptionUpdates.get(root)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
294
|
+
const update = previous
|
|
295
|
+
.catch(() => { })
|
|
296
|
+
.then(() => this.subscribeWatcherNow(root, processor));
|
|
297
|
+
this.subscriptionUpdates.set(root, update);
|
|
298
|
+
try {
|
|
299
|
+
yield update;
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
if (this.subscriptionUpdates.get(root) === update)
|
|
303
|
+
this.subscriptionUpdates.delete(root);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
subscribeWatcherNow(root, processor) {
|
|
308
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
309
|
+
var _a, _b;
|
|
280
310
|
const name = path.basename(root);
|
|
281
|
-
if (this.deps.processors.get(root) !== processor
|
|
311
|
+
if (this.deps.processors.get(root) !== processor ||
|
|
312
|
+
((_b = (_a = this.deps).getShuttingDown) === null || _b === void 0 ? void 0 : _b.call(_a)))
|
|
282
313
|
return;
|
|
283
314
|
// Unsubscribe existing watcher if any (e.g. during recovery)
|
|
284
315
|
const existingSub = this.deps.subscriptions.get(root);
|
|
@@ -286,11 +317,13 @@ class WatcherManager {
|
|
|
286
317
|
try {
|
|
287
318
|
yield existingSub.unsubscribe();
|
|
288
319
|
}
|
|
289
|
-
catch (
|
|
320
|
+
catch (_c) { }
|
|
290
321
|
this.deps.subscriptions.delete(root);
|
|
291
322
|
}
|
|
292
323
|
const sub = yield watcher.subscribe(root, (err, events) => {
|
|
293
324
|
var _a, _b;
|
|
325
|
+
if (this.deps.processors.get(root) !== processor)
|
|
326
|
+
return;
|
|
294
327
|
if (err) {
|
|
295
328
|
this.overflowCounts.set(root, ((_a = this.overflowCounts.get(root)) !== null && _a !== void 0 ? _a : 0) + 1);
|
|
296
329
|
const now = Date.now();
|
|
@@ -311,7 +344,12 @@ class WatcherManager {
|
|
|
311
344
|
processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
|
|
312
345
|
}
|
|
313
346
|
this.deps.touchActivity();
|
|
314
|
-
}, {
|
|
347
|
+
}, {
|
|
348
|
+
ignore: [
|
|
349
|
+
...watcher_1.WATCHER_IGNORE_GLOBS,
|
|
350
|
+
...(yield (0, watcher_ignore_1.readProjectWatcherIgnores)(root)),
|
|
351
|
+
],
|
|
352
|
+
});
|
|
315
353
|
if (this.deps.processors.get(root) !== processor) {
|
|
316
354
|
yield sub.unsubscribe();
|
|
317
355
|
return;
|
|
@@ -725,7 +763,7 @@ class WatcherManager {
|
|
|
725
763
|
}
|
|
726
764
|
unwatchProject(root) {
|
|
727
765
|
return __awaiter(this, void 0, void 0, function* () {
|
|
728
|
-
var _a;
|
|
766
|
+
var _a, _b;
|
|
729
767
|
// Stop poll-mode timers + their FSEvents recovery probe first, so a removed
|
|
730
768
|
// project can't keep scanning until full daemon shutdown. These live
|
|
731
769
|
// independently of the processor, so clear them even on the early return.
|
|
@@ -750,6 +788,7 @@ class WatcherManager {
|
|
|
750
788
|
this.pendingOps.delete(`recover:${root}`);
|
|
751
789
|
const processor = this.deps.processors.get(root);
|
|
752
790
|
this.deps.processors.delete(root);
|
|
791
|
+
yield (_b = this.subscriptionUpdates.get(root)) === null || _b === void 0 ? void 0 : _b.catch(() => { });
|
|
753
792
|
yield this.stopCatchup(root);
|
|
754
793
|
if (processor)
|
|
755
794
|
yield processor.close();
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.nativeProjectIgnoreGlobs = nativeProjectIgnoreGlobs;
|
|
49
|
+
exports.readProjectWatcherIgnores = readProjectWatcherIgnores;
|
|
50
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
53
|
+
/** Only the common positive gitignore/glob subset is safe to push into Parcel.
|
|
54
|
+
* Negations, escapes and unsupported syntax stay with ProjectFilePolicy, so a
|
|
55
|
+
* native ignore can never hide a file that an explicit re-inclusion needs. */
|
|
56
|
+
function nativeProjectIgnoreGlobs(content) {
|
|
57
|
+
const lines = content
|
|
58
|
+
.split("\n")
|
|
59
|
+
.map((line) => line.replace(/\r$/, "").replace(/ +$/, ""));
|
|
60
|
+
if (lines.some((line) => line.startsWith("!")))
|
|
61
|
+
return [];
|
|
62
|
+
const result = new Set();
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
if (!line || line.startsWith("#") || !/^[A-Za-z0-9_.*/-]+$/.test(line))
|
|
65
|
+
continue;
|
|
66
|
+
const pattern = line.replace(/^\//, "").replace(/\/$/, "");
|
|
67
|
+
if (!pattern ||
|
|
68
|
+
pattern.split("/").some((part) => part === "." || part === ".."))
|
|
69
|
+
continue;
|
|
70
|
+
// Keep root policy changes observable, including removal of a blanket rule.
|
|
71
|
+
const filter = (0, ignore_1.default)().add(line);
|
|
72
|
+
if (filter.ignores(".gmaxignore") || filter.ignores(".gitignore"))
|
|
73
|
+
continue;
|
|
74
|
+
if (pattern.split("/").some((part) => part.includes("**") && part !== "**"))
|
|
75
|
+
continue;
|
|
76
|
+
const glob = line.startsWith("/") || pattern.includes("/") ? pattern : `**/${pattern}`;
|
|
77
|
+
// A trailing slash ignores directories, never a same-named source file.
|
|
78
|
+
if (!line.endsWith("/"))
|
|
79
|
+
result.add(glob);
|
|
80
|
+
result.add(`${glob}/**`);
|
|
81
|
+
}
|
|
82
|
+
return [...result];
|
|
83
|
+
}
|
|
84
|
+
/** Root .gmaxignore rules are explicit project choices. Nested scopes and
|
|
85
|
+
* .gitignore remain authoritative in file policy, without lossy translation. */
|
|
86
|
+
function readProjectWatcherIgnores(root) {
|
|
87
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
try {
|
|
89
|
+
const file = path.join(root, ".gmaxignore");
|
|
90
|
+
const stat = yield fs.stat(file);
|
|
91
|
+
if (!stat.isFile() || stat.size > 65536)
|
|
92
|
+
return [];
|
|
93
|
+
const contents = yield fs.readFile(file, "utf8");
|
|
94
|
+
if (Buffer.byteLength(contents) > 65536)
|
|
95
|
+
return [];
|
|
96
|
+
return nativeProjectIgnoreGlobs(contents);
|
|
97
|
+
}
|
|
98
|
+
catch (_a) {
|
|
99
|
+
// File policy handles unreadable policy conservatively. Native watching
|
|
100
|
+
// must still deliver changes rather than treating a failed read as empty.
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -58,6 +58,7 @@ const cache_coherence_1 = require("./cache-coherence");
|
|
|
58
58
|
const file_policy_1 = require("./file-policy");
|
|
59
59
|
const ignore_patterns_1 = require("./ignore-patterns");
|
|
60
60
|
const walker_1 = require("./walker");
|
|
61
|
+
const watcher_ignore_1 = require("./watcher-ignore");
|
|
61
62
|
// Directory names ignored at ANY depth. @parcel/watcher treats a non-glob
|
|
62
63
|
// ignore entry as a literal path resolved against the watch root, so a bare
|
|
63
64
|
// "dist" only covered <root>/dist — nested build dirs (packages/*/dist,
|
|
@@ -121,6 +122,42 @@ function startWatcher(opts) {
|
|
|
121
122
|
let degradedRepairQueued = false;
|
|
122
123
|
const terminalFailures = new Set();
|
|
123
124
|
let processor;
|
|
125
|
+
let subscription = null;
|
|
126
|
+
let subscriptionUpdate = Promise.resolve();
|
|
127
|
+
let subscriptionPolicy = "";
|
|
128
|
+
const updateSubscription = () => {
|
|
129
|
+
const update = subscriptionUpdate
|
|
130
|
+
.catch(() => { })
|
|
131
|
+
.then(() => __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
const ignores = [
|
|
133
|
+
...exports.WATCHER_IGNORE_GLOBS,
|
|
134
|
+
...(yield (0, watcher_ignore_1.readProjectWatcherIgnores)(projectRoot)),
|
|
135
|
+
];
|
|
136
|
+
const policy = JSON.stringify(ignores);
|
|
137
|
+
if (closing || (subscription && subscriptionPolicy === policy))
|
|
138
|
+
return;
|
|
139
|
+
yield subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe();
|
|
140
|
+
subscription = null;
|
|
141
|
+
if (closing)
|
|
142
|
+
return;
|
|
143
|
+
const next = yield watcher.subscribe(projectRoot, (err, events) => {
|
|
144
|
+
if (err) {
|
|
145
|
+
console.error(`[${wtag}] Watcher error:`, err);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (const event of events)
|
|
149
|
+
processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
|
|
150
|
+
}, { ignore: ignores });
|
|
151
|
+
if (closing) {
|
|
152
|
+
yield next.unsubscribe();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
subscription = next;
|
|
156
|
+
subscriptionPolicy = policy;
|
|
157
|
+
}));
|
|
158
|
+
subscriptionUpdate = update;
|
|
159
|
+
return update;
|
|
160
|
+
};
|
|
124
161
|
const reportHealthyIfSettled = () => {
|
|
125
162
|
var _a;
|
|
126
163
|
if (scanHealthy &&
|
|
@@ -144,6 +181,9 @@ function startWatcher(opts) {
|
|
|
144
181
|
do {
|
|
145
182
|
reconcileRequested = false;
|
|
146
183
|
filePolicy.invalidateIgnoreCache();
|
|
184
|
+
yield updateSubscription();
|
|
185
|
+
if (closing)
|
|
186
|
+
return;
|
|
147
187
|
const rootPrefix = projectRoot.endsWith("/")
|
|
148
188
|
? projectRoot
|
|
149
189
|
: `${projectRoot}/`;
|
|
@@ -234,15 +274,7 @@ function startWatcher(opts) {
|
|
|
234
274
|
}
|
|
235
275
|
reportHealthyIfSettled();
|
|
236
276
|
} }));
|
|
237
|
-
|
|
238
|
-
if (err) {
|
|
239
|
-
console.error(`[${wtag}] Watcher error:`, err);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
for (const event of events) {
|
|
243
|
-
processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
|
|
244
|
-
}
|
|
245
|
-
}, { ignore: exports.WATCHER_IGNORE_GLOBS });
|
|
277
|
+
yield updateSubscription();
|
|
246
278
|
reconcile();
|
|
247
279
|
return {
|
|
248
280
|
get progress() {
|
|
@@ -250,7 +282,8 @@ function startWatcher(opts) {
|
|
|
250
282
|
},
|
|
251
283
|
close: () => __awaiter(this, void 0, void 0, function* () {
|
|
252
284
|
closing = true;
|
|
253
|
-
yield
|
|
285
|
+
yield subscriptionUpdate.catch(() => { });
|
|
286
|
+
yield subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe();
|
|
254
287
|
yield reconciliation;
|
|
255
288
|
yield processor.close();
|
|
256
289
|
}),
|
|
@@ -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();
|
package/dist/lib/workers/pool.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
440
|
-
freshMemory.pressure
|
|
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