@softerist/heuristic-mcp 3.0.15 → 3.0.16
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/README.md +104 -104
- package/config.jsonc +173 -173
- package/features/ann-config.js +131 -0
- package/features/clear-cache.js +84 -0
- package/features/find-similar-code.js +291 -0
- package/features/hybrid-search.js +544 -0
- package/features/index-codebase.js +3268 -0
- package/features/lifecycle.js +1189 -0
- package/features/package-version.js +302 -0
- package/features/register.js +408 -0
- package/features/resources.js +156 -0
- package/features/set-workspace.js +265 -0
- package/index.js +96 -96
- package/lib/cache-ops.js +22 -22
- package/lib/cache-utils.js +565 -565
- package/lib/cache.js +1870 -1870
- package/lib/call-graph.js +396 -396
- package/lib/cli.js +1 -1
- package/lib/config.js +517 -517
- package/lib/constants.js +39 -39
- package/lib/embed-query-process.js +7 -7
- package/lib/embedding-process.js +7 -7
- package/lib/embedding-worker.js +299 -299
- package/lib/ignore-patterns.js +316 -316
- package/lib/json-worker.js +14 -14
- package/lib/json-writer.js +337 -337
- package/lib/logging.js +164 -164
- package/lib/memory-logger.js +13 -13
- package/lib/onnx-backend.js +193 -193
- package/lib/project-detector.js +84 -84
- package/lib/server-lifecycle.js +165 -165
- package/lib/settings-editor.js +754 -754
- package/lib/tokenizer.js +256 -256
- package/lib/utils.js +428 -428
- package/lib/vector-store-binary.js +627 -627
- package/lib/vector-store-sqlite.js +95 -95
- package/lib/workspace-env.js +28 -28
- package/mcp_config.json +9 -9
- package/package.json +86 -75
- package/scripts/clear-cache.js +20 -0
- package/scripts/download-model.js +43 -0
- package/scripts/mcp-launcher.js +49 -0
- package/scripts/postinstall.js +12 -0
- package/search-configs.js +36 -36
- package/.prettierrc +0 -7
- package/debug-pids.js +0 -30
- package/eslint.config.js +0 -36
- package/specs/plan.md +0 -23
- package/vitest.config.js +0 -39
package/lib/embedding-worker.js
CHANGED
|
@@ -24,51 +24,51 @@ console.info = (...args) => console.error('[INFO]', ...args);
|
|
|
24
24
|
console.warn = (...args) => console.error('[WARN]', ...args);
|
|
25
25
|
|
|
26
26
|
import { RESULT_BATCH_SIZE, DEFAULT_INFERENCE_BATCH_SIZE } from './constants.js';
|
|
27
|
-
const workerId = Number.isInteger(workerData.workerId) ? workerData.workerId : null;
|
|
28
|
-
const workerLabel = workerId === null ? '[Worker]' : `[Worker ${workerId}]`;
|
|
29
|
-
const workerThreads = Number.isFinite(workerData.numThreads) ? workerData.numThreads : 1;
|
|
30
|
-
const explicitGcEnabled = workerData.enableExplicitGc !== false;
|
|
31
|
-
const failFastEmbeddingErrors = workerData.failFastEmbeddingErrors === true;
|
|
32
|
-
const FAIL_FAST_CONSECUTIVE_ERROR_LIMIT = 8;
|
|
33
|
-
const logInfo = (...args) => {
|
|
34
|
-
console.info(...args);
|
|
35
|
-
};
|
|
36
|
-
let nativeBackendConfigured = false;
|
|
37
|
-
|
|
38
|
-
function maybeRunGc() {
|
|
39
|
-
if (!explicitGcEnabled || typeof global.gc !== 'function') return;
|
|
40
|
-
global.gc();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function createFailFastState(scope) {
|
|
44
|
-
if (!failFastEmbeddingErrors) return null;
|
|
45
|
-
return { scope, consecutiveFailures: 0 };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function noteEmbeddingSuccess(failFastState) {
|
|
49
|
-
if (!failFastState) return;
|
|
50
|
-
failFastState.consecutiveFailures = 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function noteEmbeddingFailure(failFastState, err) {
|
|
54
|
-
if (!failFastState) return;
|
|
55
|
-
failFastState.consecutiveFailures += 1;
|
|
56
|
-
|
|
57
|
-
if (failFastState.consecutiveFailures >= FAIL_FAST_CONSECUTIVE_ERROR_LIMIT) {
|
|
58
|
-
const message =
|
|
59
|
-
`${failFastState.scope}: fail-fast breaker tripped after ` +
|
|
60
|
-
`${failFastState.consecutiveFailures} consecutive embedding failures (${err?.message || err})`;
|
|
61
|
-
console.warn(`${workerLabel} ${message}`);
|
|
62
|
-
throw new Error(message);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (workerData.verbose) {
|
|
66
|
-
console.warn(
|
|
67
|
-
`${workerLabel} ${failFastState.scope}: embedding failure ` +
|
|
68
|
-
`${failFastState.consecutiveFailures}/${FAIL_FAST_CONSECUTIVE_ERROR_LIMIT}`
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
27
|
+
const workerId = Number.isInteger(workerData.workerId) ? workerData.workerId : null;
|
|
28
|
+
const workerLabel = workerId === null ? '[Worker]' : `[Worker ${workerId}]`;
|
|
29
|
+
const workerThreads = Number.isFinite(workerData.numThreads) ? workerData.numThreads : 1;
|
|
30
|
+
const explicitGcEnabled = workerData.enableExplicitGc !== false;
|
|
31
|
+
const failFastEmbeddingErrors = workerData.failFastEmbeddingErrors === true;
|
|
32
|
+
const FAIL_FAST_CONSECUTIVE_ERROR_LIMIT = 8;
|
|
33
|
+
const logInfo = (...args) => {
|
|
34
|
+
console.info(...args);
|
|
35
|
+
};
|
|
36
|
+
let nativeBackendConfigured = false;
|
|
37
|
+
|
|
38
|
+
function maybeRunGc() {
|
|
39
|
+
if (!explicitGcEnabled || typeof global.gc !== 'function') return;
|
|
40
|
+
global.gc();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createFailFastState(scope) {
|
|
44
|
+
if (!failFastEmbeddingErrors) return null;
|
|
45
|
+
return { scope, consecutiveFailures: 0 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function noteEmbeddingSuccess(failFastState) {
|
|
49
|
+
if (!failFastState) return;
|
|
50
|
+
failFastState.consecutiveFailures = 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function noteEmbeddingFailure(failFastState, err) {
|
|
54
|
+
if (!failFastState) return;
|
|
55
|
+
failFastState.consecutiveFailures += 1;
|
|
56
|
+
|
|
57
|
+
if (failFastState.consecutiveFailures >= FAIL_FAST_CONSECUTIVE_ERROR_LIMIT) {
|
|
58
|
+
const message =
|
|
59
|
+
`${failFastState.scope}: fail-fast breaker tripped after ` +
|
|
60
|
+
`${failFastState.consecutiveFailures} consecutive embedding failures (${err?.message || err})`;
|
|
61
|
+
console.warn(`${workerLabel} ${message}`);
|
|
62
|
+
throw new Error(message);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (workerData.verbose) {
|
|
66
|
+
console.warn(
|
|
67
|
+
`${workerLabel} ${failFastState.scope}: embedding failure ` +
|
|
68
|
+
`${failFastState.consecutiveFailures}/${FAIL_FAST_CONSECUTIVE_ERROR_LIMIT}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
72
|
|
|
73
73
|
function ensureNativeBackend() {
|
|
74
74
|
if (nativeBackendConfigured) return;
|
|
@@ -172,7 +172,7 @@ const embeddingDimension = workerData.embeddingDimension || null;
|
|
|
172
172
|
// Use a promise to handle concurrent calls to initializeEmbedder safely
|
|
173
173
|
let embedderPromise = null;
|
|
174
174
|
|
|
175
|
-
async function initializeEmbedder() {
|
|
175
|
+
async function initializeEmbedder() {
|
|
176
176
|
if (!embedderPromise) {
|
|
177
177
|
const modelLoadStart = Date.now();
|
|
178
178
|
|
|
@@ -183,16 +183,16 @@ async function initializeEmbedder() {
|
|
|
183
183
|
|
|
184
184
|
embedderPromise = (async () => {
|
|
185
185
|
try {
|
|
186
|
-
ensureNativeBackend();
|
|
187
|
-
const model = await pipeline('feature-extraction', workerData.embeddingModel, {
|
|
188
|
-
quantized: true,
|
|
189
|
-
dtype: 'fp32',
|
|
190
|
-
session_options: {
|
|
191
|
-
numThreads: workerThreads,
|
|
192
|
-
intraOpNumThreads: workerThreads,
|
|
193
|
-
interOpNumThreads: 1,
|
|
194
|
-
},
|
|
195
|
-
});
|
|
186
|
+
ensureNativeBackend();
|
|
187
|
+
const model = await pipeline('feature-extraction', workerData.embeddingModel, {
|
|
188
|
+
quantized: true,
|
|
189
|
+
dtype: 'fp32',
|
|
190
|
+
session_options: {
|
|
191
|
+
numThreads: workerThreads,
|
|
192
|
+
intraOpNumThreads: workerThreads,
|
|
193
|
+
interOpNumThreads: 1,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
196
|
const loadSeconds = ((Date.now() - modelLoadStart) / 1000).toFixed(1);
|
|
197
197
|
logInfo(
|
|
198
198
|
`${workerLabel} Embedding model ready: ${workerData.embeddingModel} (${loadSeconds}s)`
|
|
@@ -204,48 +204,48 @@ async function initializeEmbedder() {
|
|
|
204
204
|
}
|
|
205
205
|
})();
|
|
206
206
|
}
|
|
207
|
-
return embedderPromise;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function isFatalRuntimeEmbeddingError(err) {
|
|
211
|
-
const message = String(err?.message || err || '').toLowerCase();
|
|
212
|
-
return (
|
|
213
|
-
message.includes('exception is pending') ||
|
|
214
|
-
message.includes('invalid embedding output') ||
|
|
215
|
-
message.includes("cannot read properties of undefined (reading 'data')") ||
|
|
216
|
-
message.includes("cannot read properties of null (reading 'data')")
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function getEmbeddingTensor(output, { requireDimsForBatch = false, batchSize = null } = {}) {
|
|
221
|
-
const data = output?.data;
|
|
222
|
-
if (!data || typeof data.length !== 'number') {
|
|
223
|
-
throw new Error('Invalid embedding output: missing tensor data');
|
|
224
|
-
}
|
|
225
|
-
if (!requireDimsForBatch) {
|
|
226
|
-
return { data };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const dims = Array.isArray(output?.dims) ? output.dims : null;
|
|
230
|
-
const hiddenSize = Number.isInteger(dims?.[dims.length - 1]) ? dims[dims.length - 1] : null;
|
|
231
|
-
if (!hiddenSize || hiddenSize <= 0) {
|
|
232
|
-
throw new Error('Invalid embedding output: missing tensor dims');
|
|
233
|
-
}
|
|
234
|
-
if (Number.isInteger(batchSize) && batchSize > 0 && data.length < hiddenSize * batchSize) {
|
|
235
|
-
throw new Error('Invalid embedding output: tensor length mismatch');
|
|
236
|
-
}
|
|
237
|
-
return { data, hiddenSize };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Legacy Protocol: Process chunks with optimized single-text embedding
|
|
242
|
-
* Streams results in batches.
|
|
243
|
-
*/
|
|
244
|
-
async function processChunks(chunks, batchId) {
|
|
245
|
-
const embedder = await initializeEmbedder();
|
|
246
|
-
let results = [];
|
|
247
|
-
let transferList = [];
|
|
248
|
-
const failFastState = createFailFastState('legacy chunk embedding');
|
|
207
|
+
return embedderPromise;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isFatalRuntimeEmbeddingError(err) {
|
|
211
|
+
const message = String(err?.message || err || '').toLowerCase();
|
|
212
|
+
return (
|
|
213
|
+
message.includes('exception is pending') ||
|
|
214
|
+
message.includes('invalid embedding output') ||
|
|
215
|
+
message.includes("cannot read properties of undefined (reading 'data')") ||
|
|
216
|
+
message.includes("cannot read properties of null (reading 'data')")
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getEmbeddingTensor(output, { requireDimsForBatch = false, batchSize = null } = {}) {
|
|
221
|
+
const data = output?.data;
|
|
222
|
+
if (!data || typeof data.length !== 'number') {
|
|
223
|
+
throw new Error('Invalid embedding output: missing tensor data');
|
|
224
|
+
}
|
|
225
|
+
if (!requireDimsForBatch) {
|
|
226
|
+
return { data };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const dims = Array.isArray(output?.dims) ? output.dims : null;
|
|
230
|
+
const hiddenSize = Number.isInteger(dims?.[dims.length - 1]) ? dims[dims.length - 1] : null;
|
|
231
|
+
if (!hiddenSize || hiddenSize <= 0) {
|
|
232
|
+
throw new Error('Invalid embedding output: missing tensor dims');
|
|
233
|
+
}
|
|
234
|
+
if (Number.isInteger(batchSize) && batchSize > 0 && data.length < hiddenSize * batchSize) {
|
|
235
|
+
throw new Error('Invalid embedding output: tensor length mismatch');
|
|
236
|
+
}
|
|
237
|
+
return { data, hiddenSize };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Legacy Protocol: Process chunks with optimized single-text embedding
|
|
242
|
+
* Streams results in batches.
|
|
243
|
+
*/
|
|
244
|
+
async function processChunks(chunks, batchId) {
|
|
245
|
+
const embedder = await initializeEmbedder();
|
|
246
|
+
let results = [];
|
|
247
|
+
let transferList = [];
|
|
248
|
+
const failFastState = createFailFastState('legacy chunk embedding');
|
|
249
249
|
|
|
250
250
|
const flush = (done = false) => {
|
|
251
251
|
// Only flush intermediate results when we have enough for a batch
|
|
@@ -270,16 +270,16 @@ async function processChunks(chunks, batchId) {
|
|
|
270
270
|
};
|
|
271
271
|
|
|
272
272
|
for (const chunk of chunks) {
|
|
273
|
-
try {
|
|
274
|
-
const output = await embedder(chunk.text, {
|
|
275
|
-
pooling: 'mean',
|
|
276
|
-
normalize: true,
|
|
277
|
-
});
|
|
278
|
-
// CRITICAL: Deep copy to release ONNX tensor memory
|
|
279
|
-
const { data } = getEmbeddingTensor(output);
|
|
280
|
-
let vector = new Float32Array(data);
|
|
281
|
-
// Apply MRL dimension slicing if configured
|
|
282
|
-
vector = sliceAndNormalize(vector, embeddingDimension);
|
|
273
|
+
try {
|
|
274
|
+
const output = await embedder(chunk.text, {
|
|
275
|
+
pooling: 'mean',
|
|
276
|
+
normalize: true,
|
|
277
|
+
});
|
|
278
|
+
// CRITICAL: Deep copy to release ONNX tensor memory
|
|
279
|
+
const { data } = getEmbeddingTensor(output);
|
|
280
|
+
let vector = new Float32Array(data);
|
|
281
|
+
// Apply MRL dimension slicing if configured
|
|
282
|
+
vector = sliceAndNormalize(vector, embeddingDimension);
|
|
283
283
|
// Properly dispose tensor to release ONNX runtime memory
|
|
284
284
|
if (typeof output.dispose === 'function')
|
|
285
285
|
try {
|
|
@@ -289,37 +289,37 @@ async function processChunks(chunks, batchId) {
|
|
|
289
289
|
console.warn(`${workerLabel} Failed to dispose tensor: ${disposeErr.message}`);
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
|
-
results.push({
|
|
293
|
-
file: chunk.file,
|
|
294
|
-
startLine: chunk.startLine,
|
|
295
|
-
endLine: chunk.endLine,
|
|
296
|
-
content: chunk.text,
|
|
297
|
-
vector,
|
|
298
|
-
success: true,
|
|
299
|
-
});
|
|
300
|
-
transferList.push(vector.buffer);
|
|
301
|
-
noteEmbeddingSuccess(failFastState);
|
|
302
|
-
} catch (error) {
|
|
303
|
-
results.push({
|
|
304
|
-
file: chunk.file,
|
|
305
|
-
startLine: chunk.startLine,
|
|
306
|
-
endLine: chunk.endLine,
|
|
307
|
-
error: error.message,
|
|
308
|
-
success: false,
|
|
309
|
-
});
|
|
310
|
-
noteEmbeddingFailure(failFastState, error);
|
|
311
|
-
if (isFatalRuntimeEmbeddingError(error)) {
|
|
312
|
-
throw error;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
flush();
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
flush(true);
|
|
319
|
-
|
|
320
|
-
// Force GC if available to free massive tensor buffers immediately
|
|
321
|
-
maybeRunGc();
|
|
322
|
-
}
|
|
292
|
+
results.push({
|
|
293
|
+
file: chunk.file,
|
|
294
|
+
startLine: chunk.startLine,
|
|
295
|
+
endLine: chunk.endLine,
|
|
296
|
+
content: chunk.text,
|
|
297
|
+
vector,
|
|
298
|
+
success: true,
|
|
299
|
+
});
|
|
300
|
+
transferList.push(vector.buffer);
|
|
301
|
+
noteEmbeddingSuccess(failFastState);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
results.push({
|
|
304
|
+
file: chunk.file,
|
|
305
|
+
startLine: chunk.startLine,
|
|
306
|
+
endLine: chunk.endLine,
|
|
307
|
+
error: error.message,
|
|
308
|
+
success: false,
|
|
309
|
+
});
|
|
310
|
+
noteEmbeddingFailure(failFastState, error);
|
|
311
|
+
if (isFatalRuntimeEmbeddingError(error)) {
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
flush();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
flush(true);
|
|
319
|
+
|
|
320
|
+
// Force GC if available to free massive tensor buffers immediately
|
|
321
|
+
maybeRunGc();
|
|
322
|
+
}
|
|
323
323
|
|
|
324
324
|
// =====================================================================
|
|
325
325
|
// SHARED HELPER FUNCTIONS
|
|
@@ -434,9 +434,9 @@ function processFileMetadata(file, content, options) {
|
|
|
434
434
|
* New Protocol: Process entire file (read, chunk, embed) in worker.
|
|
435
435
|
* Returns results once processing is complete.
|
|
436
436
|
*/
|
|
437
|
-
async function processFileTask(message) {
|
|
438
|
-
const embedder = await initializeEmbedder();
|
|
439
|
-
const failFastState = createFailFastState(`file-task ${path.basename(message.file || '')}`);
|
|
437
|
+
async function processFileTask(message) {
|
|
438
|
+
const embedder = await initializeEmbedder();
|
|
439
|
+
const failFastState = createFailFastState(`file-task ${path.basename(message.file || '')}`);
|
|
440
440
|
|
|
441
441
|
const file = message.file;
|
|
442
442
|
const force = !!message.force;
|
|
@@ -488,38 +488,38 @@ async function processFileTask(message) {
|
|
|
488
488
|
|
|
489
489
|
// Batch size for inference (balance between speed and memory)
|
|
490
490
|
// Configurable via workerData, default 4 balances memory and throughput
|
|
491
|
-
const INFERENCE_BATCH_SIZE = Number.isInteger(workerData.inferenceBatchSize)
|
|
492
|
-
? workerData.inferenceBatchSize
|
|
493
|
-
: DEFAULT_INFERENCE_BATCH_SIZE;
|
|
494
|
-
let processedSinceGc = 0;
|
|
495
|
-
|
|
496
|
-
for (let i = 0; i < chunks.length; i += INFERENCE_BATCH_SIZE) {
|
|
491
|
+
const INFERENCE_BATCH_SIZE = Number.isInteger(workerData.inferenceBatchSize)
|
|
492
|
+
? workerData.inferenceBatchSize
|
|
493
|
+
: DEFAULT_INFERENCE_BATCH_SIZE;
|
|
494
|
+
let processedSinceGc = 0;
|
|
495
|
+
|
|
496
|
+
for (let i = 0; i < chunks.length; i += INFERENCE_BATCH_SIZE) {
|
|
497
497
|
const batchChunks = chunks.slice(i, i + INFERENCE_BATCH_SIZE);
|
|
498
498
|
const batchTexts = batchChunks.map((c) => c.text);
|
|
499
499
|
|
|
500
|
-
try {
|
|
501
|
-
// Run inference on the batch
|
|
502
|
-
const output = await embedder(batchTexts, {
|
|
503
|
-
pooling: 'mean',
|
|
504
|
-
normalize: true,
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// Output is a Tensor with shape [batch_size, hidden_size]
|
|
508
|
-
// data is a flat Float32Array
|
|
509
|
-
const { data, hiddenSize } = getEmbeddingTensor(output, {
|
|
510
|
-
requireDimsForBatch: true,
|
|
511
|
-
batchSize: batchChunks.length,
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
for (let j = 0; j < batchChunks.length; j++) {
|
|
515
|
-
const c = batchChunks[j];
|
|
516
|
-
|
|
517
|
-
// Slice the flat buffer to get this chunk's vector
|
|
518
|
-
// specific slice for this element
|
|
519
|
-
const start = j * hiddenSize;
|
|
520
|
-
const end = start + hiddenSize;
|
|
521
|
-
const vectorView =
|
|
522
|
-
typeof data.subarray === 'function' ? data.subarray(start, end) : data.slice(start, end);
|
|
500
|
+
try {
|
|
501
|
+
// Run inference on the batch
|
|
502
|
+
const output = await embedder(batchTexts, {
|
|
503
|
+
pooling: 'mean',
|
|
504
|
+
normalize: true,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Output is a Tensor with shape [batch_size, hidden_size]
|
|
508
|
+
// data is a flat Float32Array
|
|
509
|
+
const { data, hiddenSize } = getEmbeddingTensor(output, {
|
|
510
|
+
requireDimsForBatch: true,
|
|
511
|
+
batchSize: batchChunks.length,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
for (let j = 0; j < batchChunks.length; j++) {
|
|
515
|
+
const c = batchChunks[j];
|
|
516
|
+
|
|
517
|
+
// Slice the flat buffer to get this chunk's vector
|
|
518
|
+
// specific slice for this element
|
|
519
|
+
const start = j * hiddenSize;
|
|
520
|
+
const end = start + hiddenSize;
|
|
521
|
+
const vectorView =
|
|
522
|
+
typeof data.subarray === 'function' ? data.subarray(start, end) : data.slice(start, end);
|
|
523
523
|
|
|
524
524
|
// Deep copy to ensure independent buffer for transfer
|
|
525
525
|
let vector = new Float32Array(vectorView);
|
|
@@ -535,29 +535,29 @@ async function processFileTask(message) {
|
|
|
535
535
|
transferList.push(vector.buffer);
|
|
536
536
|
}
|
|
537
537
|
// Properly dispose tensor to release ONNX runtime memory
|
|
538
|
-
if (typeof output.dispose === 'function')
|
|
539
|
-
try {
|
|
540
|
-
output.dispose();
|
|
541
|
-
} catch (disposeErr) {
|
|
542
|
-
if (workerData.verbose) {
|
|
543
|
-
console.warn(`${workerLabel} Failed to dispose tensor: ${disposeErr.message}`);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
noteEmbeddingSuccess(failFastState);
|
|
547
|
-
} catch (err) {
|
|
548
|
-
if (isFatalRuntimeEmbeddingError(err)) {
|
|
549
|
-
noteEmbeddingFailure(failFastState, err);
|
|
550
|
-
throw err;
|
|
551
|
-
}
|
|
552
|
-
// Fallback: if batch fails (e.g. OOM), try one by one for this batch
|
|
553
|
-
console.warn(`${workerLabel} Batch inference failed (${err.name}), retrying individually: ${err.message}`);
|
|
554
|
-
noteEmbeddingFailure(failFastState, err);
|
|
555
|
-
|
|
556
|
-
for (const c of batchChunks) {
|
|
557
|
-
try {
|
|
558
|
-
const output = await embedder(c.text, { pooling: 'mean', normalize: true });
|
|
559
|
-
const { data } = getEmbeddingTensor(output);
|
|
560
|
-
let vector = new Float32Array(data);
|
|
538
|
+
if (typeof output.dispose === 'function')
|
|
539
|
+
try {
|
|
540
|
+
output.dispose();
|
|
541
|
+
} catch (disposeErr) {
|
|
542
|
+
if (workerData.verbose) {
|
|
543
|
+
console.warn(`${workerLabel} Failed to dispose tensor: ${disposeErr.message}`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
noteEmbeddingSuccess(failFastState);
|
|
547
|
+
} catch (err) {
|
|
548
|
+
if (isFatalRuntimeEmbeddingError(err)) {
|
|
549
|
+
noteEmbeddingFailure(failFastState, err);
|
|
550
|
+
throw err;
|
|
551
|
+
}
|
|
552
|
+
// Fallback: if batch fails (e.g. OOM), try one by one for this batch
|
|
553
|
+
console.warn(`${workerLabel} Batch inference failed (${err.name}), retrying individually: ${err.message}`);
|
|
554
|
+
noteEmbeddingFailure(failFastState, err);
|
|
555
|
+
|
|
556
|
+
for (const c of batchChunks) {
|
|
557
|
+
try {
|
|
558
|
+
const output = await embedder(c.text, { pooling: 'mean', normalize: true });
|
|
559
|
+
const { data } = getEmbeddingTensor(output);
|
|
560
|
+
let vector = new Float32Array(data);
|
|
561
561
|
// Apply MRL dimension slicing if configured
|
|
562
562
|
vector = sliceAndNormalize(vector, embeddingDimension);
|
|
563
563
|
// Properly dispose tensor to release ONNX runtime memory
|
|
@@ -569,36 +569,36 @@ async function processFileTask(message) {
|
|
|
569
569
|
console.warn(`${workerLabel} Failed to dispose tensor: ${disposeErr.message}`);
|
|
570
570
|
}
|
|
571
571
|
}
|
|
572
|
-
results.push({
|
|
573
|
-
startLine: c.startLine,
|
|
574
|
-
endLine: c.endLine,
|
|
575
|
-
text: c.text,
|
|
576
|
-
vectorBuffer: vector.buffer,
|
|
577
|
-
});
|
|
578
|
-
transferList.push(vector.buffer);
|
|
579
|
-
noteEmbeddingSuccess(failFastState);
|
|
580
|
-
} catch (innerErr) {
|
|
581
|
-
// Note: No tensor disposal needed - embedder() threw before returning a tensor
|
|
582
|
-
console.warn(`${workerLabel} Chunk embedding failed: ${innerErr.message}`);
|
|
583
|
-
// We omit this chunk from results, effectively skipping it
|
|
584
|
-
noteEmbeddingFailure(failFastState, innerErr);
|
|
585
|
-
if (isFatalRuntimeEmbeddingError(innerErr)) {
|
|
586
|
-
throw innerErr;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// Yield to event loop briefly between batches and trigger GC
|
|
593
|
-
processedSinceGc += batchChunks.length;
|
|
594
|
-
if (chunks.length > INFERENCE_BATCH_SIZE) {
|
|
595
|
-
if (processedSinceGc >= 100) {
|
|
596
|
-
maybeRunGc();
|
|
597
|
-
processedSinceGc = 0;
|
|
598
|
-
}
|
|
599
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
600
|
-
}
|
|
601
|
-
}
|
|
572
|
+
results.push({
|
|
573
|
+
startLine: c.startLine,
|
|
574
|
+
endLine: c.endLine,
|
|
575
|
+
text: c.text,
|
|
576
|
+
vectorBuffer: vector.buffer,
|
|
577
|
+
});
|
|
578
|
+
transferList.push(vector.buffer);
|
|
579
|
+
noteEmbeddingSuccess(failFastState);
|
|
580
|
+
} catch (innerErr) {
|
|
581
|
+
// Note: No tensor disposal needed - embedder() threw before returning a tensor
|
|
582
|
+
console.warn(`${workerLabel} Chunk embedding failed: ${innerErr.message}`);
|
|
583
|
+
// We omit this chunk from results, effectively skipping it
|
|
584
|
+
noteEmbeddingFailure(failFastState, innerErr);
|
|
585
|
+
if (isFatalRuntimeEmbeddingError(innerErr)) {
|
|
586
|
+
throw innerErr;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Yield to event loop briefly between batches and trigger GC
|
|
593
|
+
processedSinceGc += batchChunks.length;
|
|
594
|
+
if (chunks.length > INFERENCE_BATCH_SIZE) {
|
|
595
|
+
if (processedSinceGc >= 100) {
|
|
596
|
+
maybeRunGc();
|
|
597
|
+
processedSinceGc = 0;
|
|
598
|
+
}
|
|
599
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
602
|
|
|
603
603
|
return { status: 'indexed', hash, mtimeMs, size, callData, results, transferList };
|
|
604
604
|
}
|
|
@@ -635,13 +635,13 @@ parentPort.on('message', async (message) => {
|
|
|
635
635
|
|
|
636
636
|
// Clear references
|
|
637
637
|
embedderPromise = null;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// Trigger garbage collection if available
|
|
641
|
-
if (explicitGcEnabled && typeof global.gc === 'function') {
|
|
642
|
-
const before = process.memoryUsage();
|
|
643
|
-
global.gc();
|
|
644
|
-
const after = process.memoryUsage();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Trigger garbage collection if available
|
|
641
|
+
if (explicitGcEnabled && typeof global.gc === 'function') {
|
|
642
|
+
const before = process.memoryUsage();
|
|
643
|
+
global.gc();
|
|
644
|
+
const after = process.memoryUsage();
|
|
645
645
|
logInfo(
|
|
646
646
|
`${workerLabel} Post-unload GC: rss ${(before.rss / 1024 / 1024).toFixed(1)}MB -> ${(after.rss / 1024 / 1024).toFixed(1)}MB`
|
|
647
647
|
);
|
|
@@ -672,10 +672,10 @@ parentPort.on('message', async (message) => {
|
|
|
672
672
|
}
|
|
673
673
|
|
|
674
674
|
// ---- Batch file processing ----
|
|
675
|
-
if (message.type === 'processFiles') {
|
|
676
|
-
const { files, batchId } = message;
|
|
677
|
-
const batchTransfer = [];
|
|
678
|
-
const failFastState = createFailFastState('cross-file batch embedding');
|
|
675
|
+
if (message.type === 'processFiles') {
|
|
676
|
+
const { files, batchId } = message;
|
|
677
|
+
const batchTransfer = [];
|
|
678
|
+
const failFastState = createFailFastState('cross-file batch embedding');
|
|
679
679
|
|
|
680
680
|
// 1. Pre-process all files: Read, Stat, and Chunk
|
|
681
681
|
// We do this first to gather a massive list of chunks for batched inference
|
|
@@ -760,13 +760,13 @@ parentPort.on('message', async (message) => {
|
|
|
760
760
|
continue;
|
|
761
761
|
}
|
|
762
762
|
|
|
763
|
-
const { hash, callData, chunks } = meta;
|
|
764
|
-
const chunkCount = chunks.length;
|
|
765
|
-
|
|
766
|
-
// Trigger GC every 100 files
|
|
767
|
-
if ((i + 1) % 100 === 0) {
|
|
768
|
-
maybeRunGc();
|
|
769
|
-
}
|
|
763
|
+
const { hash, callData, chunks } = meta;
|
|
764
|
+
const chunkCount = chunks.length;
|
|
765
|
+
|
|
766
|
+
// Trigger GC every 100 files
|
|
767
|
+
if ((i + 1) % 100 === 0) {
|
|
768
|
+
maybeRunGc();
|
|
769
|
+
}
|
|
770
770
|
|
|
771
771
|
// Register chunks for batching
|
|
772
772
|
if (chunks.length > 0) {
|
|
@@ -814,52 +814,52 @@ parentPort.on('message', async (message) => {
|
|
|
814
814
|
const batchSlice = allPendingChunks.slice(i, i + INFERENCE_BATCH_SIZE);
|
|
815
815
|
const batchTexts = batchSlice.map((c) => c.text);
|
|
816
816
|
|
|
817
|
-
try {
|
|
818
|
-
const output = await embedder(batchTexts, { pooling: 'mean', normalize: true });
|
|
819
|
-
const { data, hiddenSize } = getEmbeddingTensor(output, {
|
|
820
|
-
requireDimsForBatch: true,
|
|
821
|
-
batchSize: batchSlice.length,
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
for (let j = 0; j < batchSlice.length; j++) {
|
|
825
|
-
const start = j * hiddenSize;
|
|
826
|
-
const end = start + hiddenSize;
|
|
827
|
-
const vectorView =
|
|
828
|
-
typeof data.subarray === 'function'
|
|
829
|
-
? data.subarray(start, end)
|
|
830
|
-
: data.slice(start, end);
|
|
831
|
-
// Deep copy the view to avoid WASM memory issues, then apply MRL slicing
|
|
832
|
-
const vector = sliceAndNormalize(new Float32Array(vectorView), embeddingDimension);
|
|
817
|
+
try {
|
|
818
|
+
const output = await embedder(batchTexts, { pooling: 'mean', normalize: true });
|
|
819
|
+
const { data, hiddenSize } = getEmbeddingTensor(output, {
|
|
820
|
+
requireDimsForBatch: true,
|
|
821
|
+
batchSize: batchSlice.length,
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
for (let j = 0; j < batchSlice.length; j++) {
|
|
825
|
+
const start = j * hiddenSize;
|
|
826
|
+
const end = start + hiddenSize;
|
|
827
|
+
const vectorView =
|
|
828
|
+
typeof data.subarray === 'function'
|
|
829
|
+
? data.subarray(start, end)
|
|
830
|
+
: data.slice(start, end);
|
|
831
|
+
// Deep copy the view to avoid WASM memory issues, then apply MRL slicing
|
|
832
|
+
const vector = sliceAndNormalize(new Float32Array(vectorView), embeddingDimension);
|
|
833
833
|
|
|
834
834
|
batchSlice[j].vectorBuffer = vector.buffer;
|
|
835
835
|
batchTransfer.push(vector.buffer);
|
|
836
836
|
}
|
|
837
837
|
// Properly dispose tensor to release ONNX runtime memory
|
|
838
|
-
if (typeof output.dispose === 'function')
|
|
839
|
-
try {
|
|
840
|
-
output.dispose();
|
|
841
|
-
} catch (disposeErr) {
|
|
842
|
-
if (workerData.verbose) {
|
|
843
|
-
console.warn(`${workerLabel} Failed to dispose tensor: ${disposeErr.message}`);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
noteEmbeddingSuccess(failFastState);
|
|
847
|
-
} catch (err) {
|
|
848
|
-
if (isFatalRuntimeEmbeddingError(err)) {
|
|
849
|
-
noteEmbeddingFailure(failFastState, err);
|
|
850
|
-
throw err;
|
|
851
|
-
}
|
|
852
|
-
console.warn(
|
|
853
|
-
`${workerLabel} Cross-file batch inference failed, retrying individually: ${err.message}`
|
|
854
|
-
);
|
|
855
|
-
noteEmbeddingFailure(failFastState, err);
|
|
856
|
-
// Fallback: individual embedding for this failed batch
|
|
857
|
-
for (const item of batchSlice) {
|
|
858
|
-
try {
|
|
859
|
-
const output = await embedder(item.text, { pooling: 'mean', normalize: true });
|
|
860
|
-
const { data } = getEmbeddingTensor(output);
|
|
861
|
-
// Deep copy and apply MRL slicing
|
|
862
|
-
const vector = sliceAndNormalize(new Float32Array(data), embeddingDimension);
|
|
838
|
+
if (typeof output.dispose === 'function')
|
|
839
|
+
try {
|
|
840
|
+
output.dispose();
|
|
841
|
+
} catch (disposeErr) {
|
|
842
|
+
if (workerData.verbose) {
|
|
843
|
+
console.warn(`${workerLabel} Failed to dispose tensor: ${disposeErr.message}`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
noteEmbeddingSuccess(failFastState);
|
|
847
|
+
} catch (err) {
|
|
848
|
+
if (isFatalRuntimeEmbeddingError(err)) {
|
|
849
|
+
noteEmbeddingFailure(failFastState, err);
|
|
850
|
+
throw err;
|
|
851
|
+
}
|
|
852
|
+
console.warn(
|
|
853
|
+
`${workerLabel} Cross-file batch inference failed, retrying individually: ${err.message}`
|
|
854
|
+
);
|
|
855
|
+
noteEmbeddingFailure(failFastState, err);
|
|
856
|
+
// Fallback: individual embedding for this failed batch
|
|
857
|
+
for (const item of batchSlice) {
|
|
858
|
+
try {
|
|
859
|
+
const output = await embedder(item.text, { pooling: 'mean', normalize: true });
|
|
860
|
+
const { data } = getEmbeddingTensor(output);
|
|
861
|
+
// Deep copy and apply MRL slicing
|
|
862
|
+
const vector = sliceAndNormalize(new Float32Array(data), embeddingDimension);
|
|
863
863
|
// Properly dispose tensor to release ONNX runtime memory
|
|
864
864
|
if (typeof output.dispose === 'function')
|
|
865
865
|
try {
|
|
@@ -869,18 +869,18 @@ parentPort.on('message', async (message) => {
|
|
|
869
869
|
console.warn(`${workerLabel} Failed to dispose tensor: ${disposeErr.message}`);
|
|
870
870
|
}
|
|
871
871
|
}
|
|
872
|
-
item.vectorBuffer = vector.buffer;
|
|
873
|
-
batchTransfer.push(vector.buffer);
|
|
874
|
-
noteEmbeddingSuccess(failFastState);
|
|
875
|
-
} catch (innerErr) {
|
|
876
|
-
console.warn(`${workerLabel} Chunk embedding failed: ${innerErr.message}`);
|
|
877
|
-
noteEmbeddingFailure(failFastState, innerErr);
|
|
878
|
-
if (isFatalRuntimeEmbeddingError(innerErr)) {
|
|
879
|
-
throw innerErr;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
872
|
+
item.vectorBuffer = vector.buffer;
|
|
873
|
+
batchTransfer.push(vector.buffer);
|
|
874
|
+
noteEmbeddingSuccess(failFastState);
|
|
875
|
+
} catch (innerErr) {
|
|
876
|
+
console.warn(`${workerLabel} Chunk embedding failed: ${innerErr.message}`);
|
|
877
|
+
noteEmbeddingFailure(failFastState, innerErr);
|
|
878
|
+
if (isFatalRuntimeEmbeddingError(innerErr)) {
|
|
879
|
+
throw innerErr;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
884
|
|
|
885
885
|
// Minimal yield to keep event loop breathing (optional, can be removed for max throughput)
|
|
886
886
|
if (allPendingChunks.length > 50 && i % 50 === 0) {
|
|
@@ -944,10 +944,10 @@ parentPort.on('message', async (message) => {
|
|
|
944
944
|
batchTransfer
|
|
945
945
|
);
|
|
946
946
|
|
|
947
|
-
// Explicitly clear references and trigger GC
|
|
948
|
-
batchTransfer.length = 0;
|
|
949
|
-
maybeRunGc();
|
|
950
|
-
return;
|
|
947
|
+
// Explicitly clear references and trigger GC
|
|
948
|
+
batchTransfer.length = 0;
|
|
949
|
+
maybeRunGc();
|
|
950
|
+
return;
|
|
951
951
|
}
|
|
952
952
|
|
|
953
953
|
// ---- Legacy protocol: batch of chunks prepared by main thread ----
|