grepmax 0.26.70 → 0.26.72
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/DOCUMENT-SEARCH.md +134 -0
- package/README.md +96 -0
- package/dist/bin.js +11 -2
- package/dist/lib/daemon/daemon.js +50 -0
- package/dist/lib/daemon/document-search-handler.js +194 -0
- package/dist/lib/daemon/ipc-handler.js +29 -2
- package/dist/lib/mcp/document-contract.js +281 -0
- package/dist/lib/mcp/document-search.js +274 -0
- package/dist/lib/store/vector-db.js +13 -0
- package/dist/lib/utils/resource-budget.js +39 -1
- package/dist/lib/workers/embeddings/granite.js +8 -3
- package/dist/lib/workers/embeddings/mlx-client.js +27 -0
- package/dist/lib/workers/orchestrator.js +47 -9
- package/dist/lib/workers/pool.js +192 -4
- package/dist/lib/workers/process-child.js +22 -5
- package/dist/lib/workers/worker.js +4 -1
- package/package.json +5 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -51,6 +51,8 @@ exports.validateMlxEmbeddingResponse = validateMlxEmbeddingResponse;
|
|
|
51
51
|
exports.requestMlxJSON = requestMlxJSON;
|
|
52
52
|
exports.isMlxUp = isMlxUp;
|
|
53
53
|
exports.mlxEmbed = mlxEmbed;
|
|
54
|
+
exports.isMlxExistingReady = isMlxExistingReady;
|
|
55
|
+
exports.mlxEmbedExisting = mlxEmbedExisting;
|
|
54
56
|
const http = __importStar(require("node:http"));
|
|
55
57
|
const logger_1 = require("../../utils/logger");
|
|
56
58
|
const MLX_PORT = parseInt(process.env.MLX_EMBED_PORT || "8100", 10);
|
|
@@ -267,3 +269,28 @@ function mlxEmbed(texts, options) {
|
|
|
267
269
|
return data.vectors.map((v) => new Float32Array(v));
|
|
268
270
|
});
|
|
269
271
|
}
|
|
272
|
+
/** Cached positive identity permits one bounded call to the existing backend.
|
|
273
|
+
* Every response verifies model/dimensions again; no polling or startup follows
|
|
274
|
+
* from an idle cache. A failed call revokes eligibility until external work
|
|
275
|
+
* confirms readiness again.
|
|
276
|
+
*/
|
|
277
|
+
function isMlxExistingReady(expectedModel) {
|
|
278
|
+
return mlxAvailable === true && checkedModel === expectedModel;
|
|
279
|
+
}
|
|
280
|
+
function mlxEmbedExisting(texts, options) {
|
|
281
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
282
|
+
if (!isMlxExistingReady(options.expectedModel))
|
|
283
|
+
throw new Error("embedding_unavailable");
|
|
284
|
+
const result = yield requestMlxJSON("/embed", {
|
|
285
|
+
texts,
|
|
286
|
+
expected_model: options.expectedModel,
|
|
287
|
+
});
|
|
288
|
+
if (!result.ok ||
|
|
289
|
+
!validateMlxEmbeddingResponse(result.data, texts.length, options.expectedModel, options.expectedDim)) {
|
|
290
|
+
mlxAvailable = false;
|
|
291
|
+
throw new Error("embedding_unavailable");
|
|
292
|
+
}
|
|
293
|
+
lastCheck = Date.now();
|
|
294
|
+
return result.data.vectors.map((v) => new Float32Array(v));
|
|
295
|
+
});
|
|
296
|
+
}
|
|
@@ -54,9 +54,11 @@ const chunker_1 = require("../index/chunker");
|
|
|
54
54
|
const embedding_generation_1 = require("../index/embedding-generation");
|
|
55
55
|
const embedding_reuse_1 = require("../index/embedding-reuse");
|
|
56
56
|
const skeleton_1 = require("../skeleton");
|
|
57
|
+
const autostart_1 = require("../utils/autostart");
|
|
57
58
|
const file_utils_1 = require("../utils/file-utils");
|
|
58
59
|
const logger_1 = require("../utils/logger");
|
|
59
60
|
const path_containment_1 = require("../utils/path-containment");
|
|
61
|
+
const resource_budget_1 = require("../utils/resource-budget");
|
|
60
62
|
const colbert_math_1 = require("./colbert-math");
|
|
61
63
|
const embedding_error_1 = require("./embedding-error");
|
|
62
64
|
const colbert_1 = require("./embeddings/colbert");
|
|
@@ -323,20 +325,56 @@ class WorkerOrchestrator {
|
|
|
323
325
|
: { vectors, hash, mtimeMs, size };
|
|
324
326
|
});
|
|
325
327
|
}
|
|
326
|
-
|
|
327
|
-
return
|
|
328
|
+
isExistingQueryReady() {
|
|
329
|
+
return this.embedMode === "cpu"
|
|
330
|
+
? this.granite.isReady()
|
|
331
|
+
: (0, mlx_client_1.isMlxExistingReady)(this.generation.mlxModel);
|
|
332
|
+
}
|
|
333
|
+
encodeQuery(text_1) {
|
|
334
|
+
return __awaiter(this, arguments, void 0, function* (text, existingOnly = false, expectedGeneration) {
|
|
328
335
|
var _a;
|
|
329
|
-
|
|
336
|
+
if (existingOnly) {
|
|
337
|
+
if (expectedGeneration !== this.generation.fingerprint)
|
|
338
|
+
throw Object.assign(new Error("embedding_mismatch"), {
|
|
339
|
+
code: "embedding_mismatch",
|
|
340
|
+
});
|
|
341
|
+
try {
|
|
342
|
+
if ((0, autostart_1.daemonStartDeniedReason)())
|
|
343
|
+
throw new Error();
|
|
344
|
+
resource_budget_1.resourceBudget.checkExisting();
|
|
345
|
+
if ((0, autostart_1.daemonStartDeniedReason)())
|
|
346
|
+
throw new Error();
|
|
347
|
+
}
|
|
348
|
+
catch (_b) {
|
|
349
|
+
throw Object.assign(new Error("host_pressure"), {
|
|
350
|
+
code: "host_pressure",
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
if (!this.isExistingQueryReady())
|
|
354
|
+
throw new Error("embedding_unavailable");
|
|
355
|
+
}
|
|
356
|
+
else
|
|
357
|
+
yield this.ensureReady();
|
|
330
358
|
// Try MLX GPU server first, fall back to ONNX CPU
|
|
331
|
-
const mlxResult =
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
359
|
+
const mlxResult = existingOnly
|
|
360
|
+
? this.embedMode === "gpu"
|
|
361
|
+
? yield (0, mlx_client_1.mlxEmbedExisting)([text], {
|
|
362
|
+
mode: this.embedMode,
|
|
363
|
+
expectedModel: this.generation.mlxModel,
|
|
364
|
+
expectedDim: this.generation.vectorDim,
|
|
365
|
+
})
|
|
366
|
+
: null
|
|
367
|
+
: yield (0, mlx_client_1.mlxEmbed)([text], {
|
|
368
|
+
mode: this.embedMode,
|
|
369
|
+
expectedModel: this.generation.mlxModel,
|
|
370
|
+
expectedDim: this.generation.vectorDim,
|
|
371
|
+
});
|
|
336
372
|
if (!mlxResult && !this.allowOnnxFallback) {
|
|
337
373
|
throw new Error(`MLX embedding model ${this.generation.mlxModel} is unavailable; ONNX fallback is disabled for this custom embedding generation`);
|
|
338
374
|
}
|
|
339
|
-
const denseVector = (_a = mlxResult === null || mlxResult === void 0 ? void 0 : mlxResult[0]) !== null && _a !== void 0 ? _a : (yield this.granite.runBatch([text]))[0];
|
|
375
|
+
const denseVector = (_a = mlxResult === null || mlxResult === void 0 ? void 0 : mlxResult[0]) !== null && _a !== void 0 ? _a : (yield this.granite.runBatch([text], existingOnly))[0];
|
|
376
|
+
if (existingOnly)
|
|
377
|
+
return { dense: Array.from(denseVector), colbert: [], colbertDim: 0 };
|
|
340
378
|
const encoded = yield this.colbert.encodeQuery(text);
|
|
341
379
|
const feeds = {
|
|
342
380
|
input_ids: new ort.Tensor("int64", encoded.input_ids, [
|
package/dist/lib/workers/pool.js
CHANGED
|
@@ -42,7 +42,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.WorkerPool = void 0;
|
|
45
|
+
exports.WorkerPool = exports.EXISTING_QUERY_QUEUE_LIMIT = exports.EXISTING_QUERY_WAIT_MS = void 0;
|
|
46
46
|
exports.embeddingEnv = embeddingEnv;
|
|
47
47
|
exports.getWorkerPool = getWorkerPool;
|
|
48
48
|
exports.destroyWorkerPool = destroyWorkerPool;
|
|
@@ -63,6 +63,9 @@ const kernel_zone_1 = require("../utils/kernel-zone");
|
|
|
63
63
|
const logger_1 = require("../utils/logger");
|
|
64
64
|
const resource_budget_1 = require("../utils/resource-budget");
|
|
65
65
|
const safety_latch_1 = require("../utils/safety-latch");
|
|
66
|
+
exports.EXISTING_QUERY_WAIT_MS = 2000;
|
|
67
|
+
exports.EXISTING_QUERY_QUEUE_LIMIT = 4;
|
|
68
|
+
const EXISTING_QUERY_DEADLINE_MS = 10000;
|
|
66
69
|
const TASK_TIMEOUT_MS = (() => {
|
|
67
70
|
var _a;
|
|
68
71
|
const fromEnv = Number.parseInt((_a = process.env.GMAX_WORKER_TASK_TIMEOUT_MS) !== null && _a !== void 0 ? _a : "", 10);
|
|
@@ -122,6 +125,8 @@ class ProcessWorker {
|
|
|
122
125
|
this.modulePath = modulePath;
|
|
123
126
|
this.execArgv = execArgv;
|
|
124
127
|
this.busy = false;
|
|
128
|
+
this.queryReady = false;
|
|
129
|
+
this.suppressReplacement = false;
|
|
125
130
|
this.pendingTaskId = null;
|
|
126
131
|
this.lastBusyTime = Date.now();
|
|
127
132
|
// Wall-clock at which this worker became busy with its current task; null
|
|
@@ -247,6 +252,9 @@ class WorkerPool {
|
|
|
247
252
|
this.priorityQueue = [];
|
|
248
253
|
this.taskQueue = [];
|
|
249
254
|
this.tasks = new Map();
|
|
255
|
+
// Separate from ordinary queues: these requests must never trigger expansion.
|
|
256
|
+
this.existingWaiters = new Map();
|
|
257
|
+
this.existingBurst = 0;
|
|
250
258
|
this.nextId = 1;
|
|
251
259
|
this.destroyed = false;
|
|
252
260
|
this.destroyPromise = null;
|
|
@@ -296,6 +304,10 @@ class WorkerPool {
|
|
|
296
304
|
: [{ pid: worker.child.pid, rssBytes: worker.lastRssBytes }]);
|
|
297
305
|
}
|
|
298
306
|
clearTaskTimeout(task) {
|
|
307
|
+
if (task.waitTimeout) {
|
|
308
|
+
clearTimeout(task.waitTimeout);
|
|
309
|
+
task.waitTimeout = undefined;
|
|
310
|
+
}
|
|
299
311
|
if (task.timeout) {
|
|
300
312
|
clearTimeout(task.timeout);
|
|
301
313
|
task.timeout = undefined;
|
|
@@ -350,6 +362,7 @@ class WorkerPool {
|
|
|
350
362
|
task.abortListener = undefined;
|
|
351
363
|
}
|
|
352
364
|
this.tasks.delete(task.id);
|
|
365
|
+
this.existingWaiters.delete(task.id);
|
|
353
366
|
this.removeFromQueue(task.id);
|
|
354
367
|
if (worker) {
|
|
355
368
|
worker.busy = false;
|
|
@@ -383,7 +396,8 @@ class WorkerPool {
|
|
|
383
396
|
if (!this.destroyed) {
|
|
384
397
|
// Only respawn if we have no workers left or there are pending tasks
|
|
385
398
|
const hasPendingTasks = this.hasUnassignedTasks();
|
|
386
|
-
if (this.workers.length === 0 ||
|
|
399
|
+
if ((!worker.suppressReplacement && this.workers.length === 0) ||
|
|
400
|
+
hasPendingTasks) {
|
|
387
401
|
this.maybeRespawn(reason, hasPendingTasks);
|
|
388
402
|
}
|
|
389
403
|
this.dispatch();
|
|
@@ -467,12 +481,16 @@ class WorkerPool {
|
|
|
467
481
|
const onMessage = (msg) => {
|
|
468
482
|
var _a, _b, _c, _d;
|
|
469
483
|
var _e, _f, _g, _h;
|
|
484
|
+
if (typeof msg.queryReady === "boolean")
|
|
485
|
+
worker.queryReady = msg.queryReady;
|
|
470
486
|
if (typeof msg.rss === "number")
|
|
471
487
|
worker.lastRssBytes = msg.rss;
|
|
472
488
|
const task = this.tasks.get(msg.id);
|
|
473
489
|
if (!task)
|
|
474
490
|
return;
|
|
475
491
|
if ("heartbeat" in msg) {
|
|
492
|
+
if (task.existingOnly)
|
|
493
|
+
return;
|
|
476
494
|
// Reset only the no-progress timeout. The hard deadline is left
|
|
477
495
|
// untouched on purpose — heartbeats must not be able to extend a task
|
|
478
496
|
// past its absolute ceiling.
|
|
@@ -610,19 +628,25 @@ class WorkerPool {
|
|
|
610
628
|
if (this.destroyed)
|
|
611
629
|
return;
|
|
612
630
|
if (this.spawnDeniedReason) {
|
|
631
|
+
this.rejectExistingWaiters("host_pressure");
|
|
613
632
|
this.rejectUnassignedTasks(this.spawnDeniedReason, "HOST_SAFETY");
|
|
614
633
|
return;
|
|
615
634
|
}
|
|
616
635
|
// Work admitted before another process quarantines the host may still
|
|
617
636
|
// be queued. Do not send it to an existing worker after that stop.
|
|
618
637
|
try {
|
|
638
|
+
this.dispatchExistingWaiters();
|
|
639
|
+
if (this.destroyed)
|
|
640
|
+
return;
|
|
619
641
|
const quarantine = (0, autostart_1.daemonStartDeniedReason)();
|
|
620
642
|
if (quarantine) {
|
|
643
|
+
this.rejectExistingWaiters("host_pressure");
|
|
621
644
|
this.denySpawn(quarantine, false);
|
|
622
645
|
return;
|
|
623
646
|
}
|
|
624
647
|
}
|
|
625
648
|
catch (_f) {
|
|
649
|
+
this.rejectExistingWaiters("host_pressure");
|
|
626
650
|
this.denySpawn("quarantine check failed");
|
|
627
651
|
return;
|
|
628
652
|
}
|
|
@@ -673,6 +697,8 @@ class WorkerPool {
|
|
|
673
697
|
: "aggregate resource admission unavailable");
|
|
674
698
|
return;
|
|
675
699
|
}
|
|
700
|
+
idle.suppressReplacement = false;
|
|
701
|
+
this.existingBurst = 0;
|
|
676
702
|
idle.busy = true;
|
|
677
703
|
idle.pendingTaskId = task.id;
|
|
678
704
|
idle.busySince = Date.now();
|
|
@@ -744,6 +770,165 @@ class WorkerPool {
|
|
|
744
770
|
processFile(input, signal) {
|
|
745
771
|
return this.enqueue("processFile", input, signal);
|
|
746
772
|
}
|
|
773
|
+
existingWorkerState() {
|
|
774
|
+
if (this.destroyed || this.spawnDeniedReason || (0, autostart_1.daemonStartDeniedReason)())
|
|
775
|
+
return "host_pressure";
|
|
776
|
+
const warm = this.workers.filter((w) => w.queryReady && !w.cleanedUp && w.child.connected !== false);
|
|
777
|
+
return warm.some((w) => !w.busy)
|
|
778
|
+
? "ready"
|
|
779
|
+
: warm.length
|
|
780
|
+
? "busy"
|
|
781
|
+
: "embedding_unavailable";
|
|
782
|
+
}
|
|
783
|
+
/** Admission readiness includes a short bounded wait for an already-warm worker. */
|
|
784
|
+
existingQueryState() {
|
|
785
|
+
const state = this.existingWorkerState();
|
|
786
|
+
return state === "busy" &&
|
|
787
|
+
this.existingWaiters.size < exports.EXISTING_QUERY_QUEUE_LIMIT
|
|
788
|
+
? "ready"
|
|
789
|
+
: this.existingWaiters.size >= exports.EXISTING_QUERY_QUEUE_LIMIT &&
|
|
790
|
+
state === "ready"
|
|
791
|
+
? "busy"
|
|
792
|
+
: state;
|
|
793
|
+
}
|
|
794
|
+
rejectExistingWaiters(state) {
|
|
795
|
+
for (const task of this.existingWaiters.values()) {
|
|
796
|
+
task.reject(Object.assign(new Error(state), { code: state }));
|
|
797
|
+
this.completeTask(task, null);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
dispatchExistingWaiters() {
|
|
801
|
+
while (this.existingWaiters.size) {
|
|
802
|
+
const state = this.existingWorkerState();
|
|
803
|
+
if (state === "host_pressure" || state === "embedding_unavailable") {
|
|
804
|
+
this.rejectExistingWaiters(state);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
// A continuous caller stream cannot keep indexing behind this queue.
|
|
808
|
+
if (this.existingBurst >= exports.EXISTING_QUERY_QUEUE_LIMIT &&
|
|
809
|
+
[...this.tasks.values()].some((t) => !t.worker && !t.existingOnly))
|
|
810
|
+
return;
|
|
811
|
+
if (state === "busy")
|
|
812
|
+
return;
|
|
813
|
+
const task = this.existingWaiters.values().next().value;
|
|
814
|
+
const age = Date.now() - task.queuedAt;
|
|
815
|
+
if (age < 0 || age >= exports.EXISTING_QUERY_WAIT_MS) {
|
|
816
|
+
task.reject(Object.assign(new Error("busy"), { code: "busy" }));
|
|
817
|
+
this.completeTask(task, null);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
// The worker wait ends here; synchronous admission has its own bound and
|
|
821
|
+
// the request's overall timer/signal still applies during inference.
|
|
822
|
+
if (task.waitTimeout)
|
|
823
|
+
clearTimeout(task.waitTimeout);
|
|
824
|
+
task.waitTimeout = undefined;
|
|
825
|
+
try {
|
|
826
|
+
resource_budget_1.resourceBudget.checkExisting();
|
|
827
|
+
}
|
|
828
|
+
catch (_a) {
|
|
829
|
+
this.rejectExistingWaiters("host_pressure");
|
|
830
|
+
// Ordinary work performs its own admission; a document refusal must
|
|
831
|
+
// not strand that queue. The dispatcher rechecks containment next.
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
// Cancellation during admission may have removed this waiter.
|
|
835
|
+
if (!this.existingWaiters.has(task.id))
|
|
836
|
+
continue;
|
|
837
|
+
const admittedAge = Date.now() - task.queuedAt;
|
|
838
|
+
if (admittedAge < 0 || admittedAge >= EXISTING_QUERY_DEADLINE_MS) {
|
|
839
|
+
task.reject(new Error("timeout"));
|
|
840
|
+
this.completeTask(task, null);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
const after = this.existingWorkerState();
|
|
844
|
+
if (after !== "ready") {
|
|
845
|
+
if (after === "busy") {
|
|
846
|
+
task.reject(Object.assign(new Error("busy"), { code: "busy" }));
|
|
847
|
+
this.completeTask(task, null);
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
this.rejectExistingWaiters(after);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
const worker = this.workers.find((w) => w.queryReady &&
|
|
854
|
+
!w.busy &&
|
|
855
|
+
!w.cleanedUp &&
|
|
856
|
+
w.child.connected !== false);
|
|
857
|
+
this.existingWaiters.delete(task.id);
|
|
858
|
+
if (task.waitTimeout)
|
|
859
|
+
clearTimeout(task.waitTimeout);
|
|
860
|
+
task.waitTimeout = undefined;
|
|
861
|
+
task.worker = worker;
|
|
862
|
+
task.startTime = Date.now();
|
|
863
|
+
worker.busy = true;
|
|
864
|
+
worker.suppressReplacement = true;
|
|
865
|
+
worker.busySince = Date.now();
|
|
866
|
+
worker.pendingTaskId = task.id;
|
|
867
|
+
this.tasks.set(task.id, task);
|
|
868
|
+
this.existingBurst++;
|
|
869
|
+
try {
|
|
870
|
+
worker.child.send({
|
|
871
|
+
id: task.id,
|
|
872
|
+
method: task.method,
|
|
873
|
+
payload: task.payload,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
catch (_b) {
|
|
877
|
+
this.completeTask(task, worker);
|
|
878
|
+
worker.queryReady = false;
|
|
879
|
+
task.reject(new Error("embedding_unavailable"));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
encodeQueryExisting(text, signal) {
|
|
884
|
+
const state = this.existingQueryState();
|
|
885
|
+
if (state !== "ready")
|
|
886
|
+
return Promise.reject(Object.assign(new Error(state), { code: state }));
|
|
887
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
888
|
+
return Promise.reject(Object.assign(new Error("Aborted"), { name: "AbortError" }));
|
|
889
|
+
return new Promise((resolve, reject) => {
|
|
890
|
+
const id = this.nextId++;
|
|
891
|
+
const task = {
|
|
892
|
+
id,
|
|
893
|
+
method: "encodeQuery",
|
|
894
|
+
payload: {
|
|
895
|
+
text,
|
|
896
|
+
existingOnly: true,
|
|
897
|
+
generation: this.generation.fingerprint,
|
|
898
|
+
},
|
|
899
|
+
resolve,
|
|
900
|
+
reject,
|
|
901
|
+
queuedAt: Date.now(),
|
|
902
|
+
signal,
|
|
903
|
+
existingOnly: true,
|
|
904
|
+
};
|
|
905
|
+
const cancel = () => {
|
|
906
|
+
task.callerAborted = true;
|
|
907
|
+
reject(Object.assign(new Error("Aborted"), { name: "AbortError" }));
|
|
908
|
+
if (!task.worker) {
|
|
909
|
+
this.completeTask(task, null);
|
|
910
|
+
this.dispatch();
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
task.abortListener = cancel;
|
|
914
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", cancel, { once: true });
|
|
915
|
+
// Caller deadline does not kill or replace this shared worker. Its bounded
|
|
916
|
+
// inference settles lifecycle independently, just like normal cancellation.
|
|
917
|
+
task.timeout = setTimeout(() => {
|
|
918
|
+
task.callerAborted = true;
|
|
919
|
+
reject(new Error("timeout"));
|
|
920
|
+
if (!task.worker)
|
|
921
|
+
this.completeTask(task, null);
|
|
922
|
+
}, EXISTING_QUERY_DEADLINE_MS);
|
|
923
|
+
task.waitTimeout = setTimeout(() => {
|
|
924
|
+
reject(Object.assign(new Error("busy"), { code: "busy" }));
|
|
925
|
+
this.completeTask(task, null);
|
|
926
|
+
this.dispatch();
|
|
927
|
+
}, exports.EXISTING_QUERY_WAIT_MS);
|
|
928
|
+
this.existingWaiters.set(id, task);
|
|
929
|
+
this.dispatch();
|
|
930
|
+
});
|
|
931
|
+
}
|
|
747
932
|
encodeQuery(text, signal) {
|
|
748
933
|
return this.enqueue("encodeQuery", { text }, signal);
|
|
749
934
|
}
|
|
@@ -826,7 +1011,7 @@ class WorkerPool {
|
|
|
826
1011
|
* floor with a fresh, lean worker. Shared by the idle and post-task RSS paths.
|
|
827
1012
|
*/
|
|
828
1013
|
recycleWorker(w, reason) {
|
|
829
|
-
if (w.cleanedUp)
|
|
1014
|
+
if (w.cleanedUp || w.busy)
|
|
830
1015
|
return;
|
|
831
1016
|
(0, logger_1.log)("pool", `recycle bloated worker PID:${w.child.pid} (${reason}, rss ${Math.round(w.lastRssBytes / 1048576)}MB > ${WORKER_RSS_RECYCLE_MB}MB)`);
|
|
832
1017
|
w.cleanedUp = true;
|
|
@@ -856,7 +1041,9 @@ class WorkerPool {
|
|
|
856
1041
|
}, REAP_FORCE_KILL_GRACE_MS);
|
|
857
1042
|
}
|
|
858
1043
|
// Replace anything we dropped below the floor with fresh, lean workers.
|
|
859
|
-
while (!
|
|
1044
|
+
while (!w.suppressReplacement &&
|
|
1045
|
+
!this.destroyed &&
|
|
1046
|
+
this.workers.length < MIN_KEEP_WORKERS) {
|
|
860
1047
|
if (!this.spawnWorker())
|
|
861
1048
|
break;
|
|
862
1049
|
}
|
|
@@ -924,6 +1111,7 @@ class WorkerPool {
|
|
|
924
1111
|
this.idleReapInterval = null;
|
|
925
1112
|
}
|
|
926
1113
|
this.clearScaleUpTimer();
|
|
1114
|
+
this.rejectExistingWaiters("embedding_unavailable");
|
|
927
1115
|
for (const task of this.tasks.values()) {
|
|
928
1116
|
task.reject(new Error("Worker pool destroyed"));
|
|
929
1117
|
this.completeTask(task, null);
|
|
@@ -59,11 +59,14 @@ const send = (msg) => {
|
|
|
59
59
|
if (node_process_1.default.send) {
|
|
60
60
|
// Attach current RSS so the pool can recycle workers whose native (ONNX)
|
|
61
61
|
// memory has ballooned — the V8 --max-old-space-size cap can't see it.
|
|
62
|
-
node_process_1.default.send(Object.assign(Object.assign({}, msg), { rss: node_process_1.default.memoryUsage().rss }));
|
|
62
|
+
node_process_1.default.send(Object.assign(Object.assign({}, msg), { rss: node_process_1.default.memoryUsage().rss, queryReady: (0, worker_1.isExistingQueryReady)() }));
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
|
+
let restrictedInFlight = false;
|
|
65
66
|
const handleMessage = (msg) => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
67
|
const { id, method, payload } = msg;
|
|
68
|
+
restrictedInFlight =
|
|
69
|
+
method === "encodeQuery" && payload.existingOnly === true;
|
|
67
70
|
const start = performance.now();
|
|
68
71
|
(0, logger_1.debug)("worker", `recv task=${id} method=${method}${method === "processFile" ? ` file=${payload.path}` : ""}`);
|
|
69
72
|
try {
|
|
@@ -91,23 +94,37 @@ const handleMessage = (msg) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
91
94
|
send({ id, error: `Unknown method: ${method}` });
|
|
92
95
|
}
|
|
93
96
|
catch (err) {
|
|
94
|
-
const
|
|
97
|
+
const rawCode = err === null || err === void 0 ? void 0 : err.code;
|
|
98
|
+
const safeCode = typeof rawCode === "string" &&
|
|
99
|
+
["host_pressure", "embedding_mismatch", "embedding_unavailable"].includes(rawCode)
|
|
100
|
+
? rawCode
|
|
101
|
+
: "embedding_unavailable";
|
|
102
|
+
const message = restrictedInFlight
|
|
103
|
+
? safeCode
|
|
104
|
+
: err instanceof Error
|
|
105
|
+
? err.message
|
|
106
|
+
: String(err);
|
|
95
107
|
(0, logger_1.debug)("worker", `fail task=${id} method=${method} ${(performance.now() - start).toFixed(0)}ms: ${message}`);
|
|
96
|
-
const code =
|
|
108
|
+
const code = restrictedInFlight
|
|
109
|
+
? safeCode
|
|
110
|
+
: err === null || err === void 0 ? void 0 : err.code;
|
|
97
111
|
send(Object.assign({ id, error: message }, (typeof code === "string" ? { code } : {})));
|
|
98
112
|
}
|
|
113
|
+
finally {
|
|
114
|
+
restrictedInFlight = false;
|
|
115
|
+
}
|
|
99
116
|
});
|
|
100
117
|
const handleMessageSerially = (0, serialized_handler_1.createSerializedHandler)(handleMessage);
|
|
101
118
|
node_process_1.default.on("message", (msg) => {
|
|
102
119
|
void handleMessageSerially(msg);
|
|
103
120
|
});
|
|
104
121
|
node_process_1.default.on("uncaughtException", (err) => {
|
|
105
|
-
console.error("[process-worker] uncaughtException", err);
|
|
122
|
+
console.error("[process-worker] uncaughtException", restrictedInFlight ? "restricted query failed" : err);
|
|
106
123
|
node_process_1.default.exitCode = 1;
|
|
107
124
|
node_process_1.default.exit();
|
|
108
125
|
});
|
|
109
126
|
node_process_1.default.on("unhandledRejection", (reason) => {
|
|
110
|
-
console.error("[process-worker] unhandledRejection", reason);
|
|
127
|
+
console.error("[process-worker] unhandledRejection", restrictedInFlight ? "restricted query failed" : reason);
|
|
111
128
|
node_process_1.default.exitCode = 1;
|
|
112
129
|
node_process_1.default.exit();
|
|
113
130
|
});
|
|
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.isExistingQueryReady = void 0;
|
|
12
13
|
exports.default = processFile;
|
|
13
14
|
exports.encodeQuery = encodeQuery;
|
|
14
15
|
exports.rerank = rerank;
|
|
@@ -33,9 +34,11 @@ function processFile(input, onProgress) {
|
|
|
33
34
|
}
|
|
34
35
|
function encodeQuery(input) {
|
|
35
36
|
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
-
return orchestrator.encodeQuery(input.text);
|
|
37
|
+
return orchestrator.encodeQuery(input.text, input.existingOnly, input.generation);
|
|
37
38
|
});
|
|
38
39
|
}
|
|
40
|
+
const isExistingQueryReady = () => orchestrator.isExistingQueryReady();
|
|
41
|
+
exports.isExistingQueryReady = isExistingQueryReady;
|
|
39
42
|
function rerank(input) {
|
|
40
43
|
return __awaiter(this, void 0, void 0, function* () {
|
|
41
44
|
return orchestrator.rerank(input);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.72",
|
|
4
4
|
"author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
|
|
5
5
|
"homepage": "https://github.com/reowens/grepmax",
|
|
6
6
|
"bugs": {
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"postversion": "bash scripts/postrelease.sh",
|
|
47
47
|
"audit:consumer": "node scripts/audit-consumer.cjs",
|
|
48
48
|
"audit:python": "python3 scripts/audit-python.py && python3 scripts/audit-python.py --lockfile lance-maintenance/uv.lock",
|
|
49
|
-
"test:python": "python3 -m unittest discover -s tests/python"
|
|
49
|
+
"test:python": "python3 -m unittest discover -s tests/python",
|
|
50
|
+
"audit:document-search": "node scripts/audit-document-search.cjs"
|
|
50
51
|
},
|
|
51
52
|
"keywords": [
|
|
52
53
|
"grepmax",
|
|
@@ -79,7 +80,8 @@
|
|
|
79
80
|
"lance-maintenance/uv.lock",
|
|
80
81
|
"README.md",
|
|
81
82
|
"LICENSE",
|
|
82
|
-
"NOTICE"
|
|
83
|
+
"NOTICE",
|
|
84
|
+
"DOCUMENT-SEARCH.md"
|
|
83
85
|
],
|
|
84
86
|
"license": "Apache-2.0",
|
|
85
87
|
"description": "Semantic code search for coding agents. Local embeddings, call graph tracing, compact code context.",
|