grepmax 0.24.0 → 0.25.0
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 +11 -6
- package/dist/commands/add.js +38 -31
- package/dist/commands/config.js +16 -15
- package/dist/commands/context.js +38 -13
- package/dist/commands/doctor.js +76 -83
- package/dist/commands/extract.js +12 -1
- package/dist/commands/index.js +23 -24
- package/dist/commands/list.js +23 -14
- package/dist/commands/mcp.js +278 -143
- package/dist/commands/peek.js +16 -5
- package/dist/commands/repair.js +54 -120
- package/dist/commands/search-output.js +30 -9
- package/dist/commands/search-run.js +75 -47
- package/dist/commands/search-skeletons.js +28 -18
- package/dist/commands/search.js +45 -49
- package/dist/commands/serve.js +415 -373
- package/dist/commands/setup.js +2 -2
- package/dist/commands/similar.js +5 -5
- package/dist/commands/skeleton.js +67 -41
- package/dist/commands/status.js +5 -2
- package/dist/commands/summarize.js +6 -0
- package/dist/commands/watch.js +102 -38
- package/dist/config.js +3 -0
- package/dist/lib/daemon/daemon.js +1101 -379
- package/dist/lib/daemon/ipc-handler.js +122 -13
- package/dist/lib/daemon/mlx-server-manager.js +268 -125
- package/dist/lib/daemon/process-manager.js +7 -4
- package/dist/lib/daemon/search-handler.js +23 -9
- package/dist/lib/daemon/watcher-manager.js +353 -110
- package/dist/lib/index/batch-processor.js +231 -67
- package/dist/lib/index/embedding-generation.js +109 -0
- package/dist/lib/index/embedding-status.js +23 -0
- package/dist/lib/index/file-policy.js +273 -0
- package/dist/lib/index/ignore-patterns.js +4 -0
- package/dist/lib/index/index-config.js +18 -4
- package/dist/lib/index/syncer.js +256 -79
- package/dist/lib/index/walker.js +66 -86
- package/dist/lib/index/watcher-batch.js +9 -0
- package/dist/lib/index/watcher.js +129 -3
- package/dist/lib/llm/server.js +6 -0
- package/dist/lib/search/searcher.js +30 -19
- package/dist/lib/store/store-lease.js +473 -0
- package/dist/lib/store/vector-db.js +302 -57
- package/dist/lib/utils/blocked-roots.js +63 -0
- package/dist/lib/utils/cross-project.js +7 -3
- package/dist/lib/utils/daemon-client.js +24 -1
- package/dist/lib/utils/daemon-launcher.js +38 -13
- package/dist/lib/utils/doctor-status.js +76 -0
- package/dist/lib/utils/file-utils.js +74 -4
- package/dist/lib/utils/keyed-mutex.js +101 -0
- package/dist/lib/utils/logger.js +57 -1
- package/dist/lib/utils/mlx-hf-cache.js +114 -0
- package/dist/lib/utils/operation-coordinator.js +146 -0
- package/dist/lib/utils/path-containment.js +106 -0
- package/dist/lib/utils/process.js +44 -0
- package/dist/lib/utils/project-registry.js +351 -3
- package/dist/lib/utils/scope-filter.js +3 -9
- package/dist/lib/utils/stale-hint.js +12 -19
- package/dist/lib/utils/watcher-launcher.js +5 -1
- package/dist/lib/utils/watcher-store.js +2 -2
- package/dist/lib/workers/colbert-math.js +15 -12
- package/dist/lib/workers/embeddings/colbert.js +3 -2
- package/dist/lib/workers/embeddings/granite.js +4 -3
- package/dist/lib/workers/embeddings/mlx-client.js +59 -16
- package/dist/lib/workers/orchestrator.js +39 -8
- package/dist/lib/workers/pool.js +146 -82
- package/dist/lib/workers/process-child.js +11 -2
- package/dist/lib/workers/serialized-handler.js +10 -0
- package/dist/lib/workers/worker.js +13 -4
- package/mlx-embed-server/server.py +21 -3
- package/mlx-embed-server/summarizer.py +8 -0
- package/package.json +4 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +3 -170
|
@@ -51,15 +51,20 @@ const net = __importStar(require("node:net"));
|
|
|
51
51
|
const path = __importStar(require("node:path"));
|
|
52
52
|
const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
|
|
53
53
|
const config_1 = require("../../config");
|
|
54
|
+
const embedding_generation_1 = require("../index/embedding-generation");
|
|
55
|
+
const embedding_status_1 = require("../index/embedding-status");
|
|
54
56
|
const index_config_1 = require("../index/index-config");
|
|
55
57
|
const syncer_1 = require("../index/syncer");
|
|
56
58
|
const server_1 = require("../llm/server");
|
|
57
59
|
const meta_cache_1 = require("../store/meta-cache");
|
|
60
|
+
const store_lease_1 = require("../store/store-lease");
|
|
58
61
|
const vector_db_1 = require("../store/vector-db");
|
|
59
62
|
const daemon_client_1 = require("../utils/daemon-client");
|
|
60
63
|
const daemon_launcher_1 = require("../utils/daemon-launcher");
|
|
64
|
+
const keyed_mutex_1 = require("../utils/keyed-mutex");
|
|
61
65
|
const log_rotate_1 = require("../utils/log-rotate");
|
|
62
66
|
const logger_1 = require("../utils/logger");
|
|
67
|
+
const operation_coordinator_1 = require("../utils/operation-coordinator");
|
|
63
68
|
const process_1 = require("../utils/process");
|
|
64
69
|
const project_registry_1 = require("../utils/project-registry");
|
|
65
70
|
const watcher_store_1 = require("../utils/watcher-store");
|
|
@@ -107,8 +112,14 @@ class Daemon {
|
|
|
107
112
|
this.searchers = new Map();
|
|
108
113
|
this.subscriptions = new Map();
|
|
109
114
|
this.vectorDb = null;
|
|
115
|
+
this.activeConfig = null;
|
|
116
|
+
this.activeGeneration = null;
|
|
117
|
+
this.workerPool = null;
|
|
118
|
+
this.resources = null;
|
|
119
|
+
this.nextResourceGenerationId = 1;
|
|
110
120
|
this.metaCache = null;
|
|
111
121
|
this.server = null;
|
|
122
|
+
this.connections = new Set();
|
|
112
123
|
this.releaseLock = null;
|
|
113
124
|
this.lastActivity = Date.now();
|
|
114
125
|
this.startTime = Date.now();
|
|
@@ -123,6 +134,7 @@ class Daemon {
|
|
|
123
134
|
this.ready = false;
|
|
124
135
|
this.processManager = new process_manager_1.ProcessManager({
|
|
125
136
|
getShuttingDown: () => this.shuttingDown,
|
|
137
|
+
getWorkerPids: () => { var _a; return (_a = this.workerPool) === null || _a === void 0 ? void 0 : _a.getWorkerPids(); },
|
|
126
138
|
});
|
|
127
139
|
this.mlxServerManager = new mlx_server_manager_1.MlxServerManager({
|
|
128
140
|
getShuttingDown: () => this.shuttingDown,
|
|
@@ -132,6 +144,7 @@ class Daemon {
|
|
|
132
144
|
subscriptions: this.subscriptions,
|
|
133
145
|
getVectorDb: () => this.vectorDb,
|
|
134
146
|
getMetaCache: () => this.metaCache,
|
|
147
|
+
getWorkerPool: () => this.workerPool,
|
|
135
148
|
getShuttingDown: () => this.shuttingDown,
|
|
136
149
|
touchActivity: () => {
|
|
137
150
|
this.lastActivity = Date.now();
|
|
@@ -139,20 +152,63 @@ class Daemon {
|
|
|
139
152
|
evictSearcher: (root) => {
|
|
140
153
|
this.searchers.delete(root);
|
|
141
154
|
},
|
|
155
|
+
runProjectOperation: (root, name, signal, fn) => this.withProjectLock(root, signal, () => this.runSharedOperation(name, signal, fn)),
|
|
142
156
|
});
|
|
143
|
-
this.
|
|
157
|
+
this.projectMutex = new keyed_mutex_1.KeyedMutex();
|
|
158
|
+
this.operations = new operation_coordinator_1.OperationCoordinator();
|
|
159
|
+
this.shutdownPromise = null;
|
|
144
160
|
// Full-index progress per root while initialSync runs (--reset / initial
|
|
145
161
|
// index). Presence = a full index is in flight; value drives the partial-
|
|
146
162
|
// result pending count (Phase 6). Cleared in the indexProject finally.
|
|
147
163
|
this.indexProgress = new Map();
|
|
148
164
|
this.shutdownAbortControllers = new Set();
|
|
165
|
+
this.pendingIndexRetryTimers = new Map();
|
|
166
|
+
this.pendingIndexRetryCounts = new Map();
|
|
149
167
|
this.llmServer = null;
|
|
150
168
|
}
|
|
169
|
+
assertStartupActive() {
|
|
170
|
+
if (this.shuttingDown)
|
|
171
|
+
throw new operation_coordinator_1.OperationClosedError();
|
|
172
|
+
}
|
|
173
|
+
createWorkerPool(generation, embedMode) {
|
|
174
|
+
return new pool_1.WorkerPool(generation, embedMode);
|
|
175
|
+
}
|
|
176
|
+
createVectorDb(vectorDim, lease) {
|
|
177
|
+
return new vector_db_1.VectorDB(config_1.PATHS.lancedbDir, vectorDim, lease);
|
|
178
|
+
}
|
|
179
|
+
mlxMode(config) {
|
|
180
|
+
if (config.embedMode !== "gpu")
|
|
181
|
+
return "cpu";
|
|
182
|
+
const state = this.mlxServerManager.getStatus().state;
|
|
183
|
+
return state === "owned-ready"
|
|
184
|
+
? "owned"
|
|
185
|
+
: state === "adopted-ready"
|
|
186
|
+
? "adopted"
|
|
187
|
+
: "cpu";
|
|
188
|
+
}
|
|
189
|
+
publishResourceGeneration(config, embedding, vectorDb, workerPool, mlx) {
|
|
190
|
+
const resources = Object.freeze({
|
|
191
|
+
id: this.nextResourceGenerationId++,
|
|
192
|
+
config: Object.freeze(Object.assign({}, config)),
|
|
193
|
+
embedding,
|
|
194
|
+
vectorDb,
|
|
195
|
+
workerPool,
|
|
196
|
+
mlx,
|
|
197
|
+
});
|
|
198
|
+
this.activeConfig = resources.config;
|
|
199
|
+
this.activeGeneration = resources.embedding;
|
|
200
|
+
this.vectorDb = resources.vectorDb;
|
|
201
|
+
this.workerPool = resources.workerPool;
|
|
202
|
+
this.resources = resources;
|
|
203
|
+
return resources;
|
|
204
|
+
}
|
|
151
205
|
start() {
|
|
152
206
|
return __awaiter(this, void 0, void 0, function* () {
|
|
207
|
+
var _a;
|
|
153
208
|
process.title = "gmax-daemon";
|
|
154
209
|
// 0. Singleton enforcement: find and kill ALL stale daemon/worker processes
|
|
155
210
|
yield this.processManager.killStaleProcesses();
|
|
211
|
+
this.assertStartupActive();
|
|
156
212
|
// 1. Acquire exclusive lock — kernel-enforced, atomic, auto-released on death
|
|
157
213
|
fs.mkdirSync(path.dirname(config_1.PATHS.daemonLockFile), { recursive: true });
|
|
158
214
|
fs.writeFileSync(config_1.PATHS.daemonLockFile, "", { flag: "a" }); // ensure file exists
|
|
@@ -169,6 +225,7 @@ class Daemon {
|
|
|
169
225
|
this.shutdown().finally(() => process.exit(0));
|
|
170
226
|
},
|
|
171
227
|
});
|
|
228
|
+
this.assertStartupActive();
|
|
172
229
|
(0, logger_1.debug)("daemon", "lock acquired");
|
|
173
230
|
}
|
|
174
231
|
catch (err) {
|
|
@@ -187,9 +244,11 @@ class Daemon {
|
|
|
187
244
|
try {
|
|
188
245
|
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
189
246
|
}
|
|
190
|
-
catch (
|
|
247
|
+
catch (_b) { }
|
|
191
248
|
this.server = net.createServer((conn) => {
|
|
192
249
|
(0, logger_1.debug)("daemon", "client connected");
|
|
250
|
+
this.connections.add(conn);
|
|
251
|
+
conn.once("close", () => this.connections.delete(conn));
|
|
193
252
|
let buf = "";
|
|
194
253
|
conn.on("data", (chunk) => {
|
|
195
254
|
buf += chunk.toString();
|
|
@@ -239,6 +298,7 @@ class Daemon {
|
|
|
239
298
|
});
|
|
240
299
|
this.server.listen(config_1.PATHS.daemonSocket, () => resolve());
|
|
241
300
|
});
|
|
301
|
+
this.assertStartupActive();
|
|
242
302
|
// 3. Write PID file AFTER socket is listening — ensures any process that
|
|
243
303
|
// reads the PID can immediately ping this daemon and get a response.
|
|
244
304
|
fs.writeFileSync(config_1.PATHS.daemonPidFile, String(process.pid));
|
|
@@ -247,6 +307,7 @@ class Daemon {
|
|
|
247
307
|
for (const w of existing) {
|
|
248
308
|
console.log(`[daemon] Taking over from per-project watcher (PID: ${w.pid}, ${path.basename(w.projectRoot)})`);
|
|
249
309
|
yield (0, process_1.killProcess)(w.pid);
|
|
310
|
+
this.assertStartupActive();
|
|
250
311
|
(0, watcher_store_1.unregisterWatcher)(w.pid);
|
|
251
312
|
}
|
|
252
313
|
// 5. Open shared resources
|
|
@@ -254,10 +315,14 @@ class Daemon {
|
|
|
254
315
|
fs.mkdirSync(config_1.PATHS.cacheDir, { recursive: true });
|
|
255
316
|
fs.mkdirSync(config_1.PATHS.lancedbDir, { recursive: true });
|
|
256
317
|
console.log("[daemon] Opening LanceDB:", config_1.PATHS.lancedbDir);
|
|
257
|
-
this.
|
|
258
|
-
this.
|
|
318
|
+
this.activeConfig = (0, index_config_1.readGlobalConfig)();
|
|
319
|
+
this.activeGeneration = (0, embedding_generation_1.resolveEmbeddingGeneration)(this.activeConfig);
|
|
320
|
+
this.vectorDb = new vector_db_1.VectorDB(config_1.PATHS.lancedbDir, this.activeGeneration.vectorDim);
|
|
321
|
+
this.workerPool = this.createWorkerPool(this.activeGeneration, this.activeConfig.embedMode);
|
|
322
|
+
this.vectorDb.startMaintenanceLoop((fn) => this.runSharedOperation("store-maintenance", undefined, () => fn()));
|
|
259
323
|
console.log("[daemon] Opening MetaCache:", config_1.PATHS.lmdbPath);
|
|
260
324
|
this.metaCache = new meta_cache_1.MetaCache(config_1.PATHS.lmdbPath);
|
|
325
|
+
this.assertStartupActive();
|
|
261
326
|
// Resources are open — only now may resource-dependent IPC commands run.
|
|
262
327
|
this.ready = true;
|
|
263
328
|
}
|
|
@@ -268,11 +333,13 @@ class Daemon {
|
|
|
268
333
|
// 6. LLM server manager (constructed, not started — starts on first request)
|
|
269
334
|
this.llmServer = new server_1.LlmServer();
|
|
270
335
|
// 6b. MLX embed server — start if GPU mode is active
|
|
271
|
-
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
336
|
+
const globalConfig = (_a = this.activeConfig) !== null && _a !== void 0 ? _a : (0, index_config_1.readGlobalConfig)();
|
|
272
337
|
const isAppleSilicon = process.arch === "arm64" && process.platform === "darwin";
|
|
273
338
|
if (isAppleSilicon && globalConfig.embedMode === "gpu") {
|
|
274
339
|
yield this.mlxServerManager.ensureMlxServer(globalConfig.mlxModel);
|
|
340
|
+
this.assertStartupActive();
|
|
275
341
|
}
|
|
342
|
+
this.publishResourceGeneration(globalConfig, this.activeGeneration, this.vectorDb, this.workerPool, this.mlxMode(globalConfig));
|
|
276
343
|
// 7. Register daemon (only after resources are open)
|
|
277
344
|
(0, watcher_store_1.registerDaemon)(process.pid);
|
|
278
345
|
// 8. Subscribe to all registered projects (skip missing directories)
|
|
@@ -285,6 +352,7 @@ class Daemon {
|
|
|
285
352
|
}
|
|
286
353
|
try {
|
|
287
354
|
yield this.watchProject(p.root);
|
|
355
|
+
this.assertStartupActive();
|
|
288
356
|
}
|
|
289
357
|
catch (err) {
|
|
290
358
|
console.error(`[daemon] Failed to watch ${path.basename(p.root)}:`, err);
|
|
@@ -297,6 +365,7 @@ class Daemon {
|
|
|
297
365
|
// snapshot, so a new project op kicked off after the snapshot would race
|
|
298
366
|
// with vectorDb.close() and fail with "VectorDB connection is closed".
|
|
299
367
|
const pending = allProjects.filter((p) => (p.status === "pending" || p.status === "error") &&
|
|
368
|
+
!p.rebuildId &&
|
|
300
369
|
fs.existsSync(p.root));
|
|
301
370
|
void (() => __awaiter(this, void 0, void 0, function* () {
|
|
302
371
|
for (const p of pending) {
|
|
@@ -365,15 +434,16 @@ class Daemon {
|
|
|
365
434
|
setTimeout(() => {
|
|
366
435
|
if (this.shuttingDown)
|
|
367
436
|
return;
|
|
368
|
-
void (() => __awaiter(this, void 0, void 0, function* () {
|
|
437
|
+
void this.runSharedOperation("search-warmup", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
369
438
|
const t0 = Date.now();
|
|
370
439
|
try {
|
|
371
440
|
if (this.vectorDb) {
|
|
372
441
|
yield this.vectorDb.ensureTable();
|
|
373
442
|
yield this.vectorDb.createFTSIndex();
|
|
374
443
|
}
|
|
375
|
-
const
|
|
376
|
-
|
|
444
|
+
const pool = this.workerPool;
|
|
445
|
+
if (!pool)
|
|
446
|
+
throw new Error("worker pool not ready");
|
|
377
447
|
// Two parallel encodes force the pool to spawn two workers and
|
|
378
448
|
// warm both (each worker loads Granite + ColBERT lazily on first
|
|
379
449
|
// encode — once warmed, subsequent encodes are ~13ms).
|
|
@@ -386,64 +456,154 @@ class Daemon {
|
|
|
386
456
|
catch (err) {
|
|
387
457
|
console.log(`[daemon] Search warmup failed (non-fatal): ${err}`);
|
|
388
458
|
}
|
|
389
|
-
}))()
|
|
459
|
+
})).catch((err) => {
|
|
460
|
+
if (err instanceof operation_coordinator_1.OperationClosedError)
|
|
461
|
+
return;
|
|
462
|
+
console.log(`[daemon] Search warmup skipped: ${err}`);
|
|
463
|
+
});
|
|
390
464
|
}, 5000).unref();
|
|
391
465
|
});
|
|
392
466
|
}
|
|
393
|
-
watchProject(root) {
|
|
394
|
-
return
|
|
395
|
-
|
|
396
|
-
|
|
467
|
+
watchProject(root, signal) {
|
|
468
|
+
return this.withProjectLock(root, signal, () => this.runSharedOperation("watch", signal, () => this.watchProjectWithinOperation(root)));
|
|
469
|
+
}
|
|
470
|
+
watchProjectWithinOperation(root) {
|
|
471
|
+
return this.watcherManager.watchProject(root);
|
|
472
|
+
}
|
|
473
|
+
runSharedOperation(name, signal, fn) {
|
|
474
|
+
return this.operations.runShared(name, signal, fn);
|
|
475
|
+
}
|
|
476
|
+
operationStatus() {
|
|
477
|
+
return this.operations.status;
|
|
478
|
+
}
|
|
479
|
+
resourceGenerationId() {
|
|
480
|
+
var _a, _b;
|
|
481
|
+
return (_b = (_a = this.resources) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
|
|
482
|
+
}
|
|
483
|
+
hasUnfinishedRebuild() {
|
|
484
|
+
try {
|
|
485
|
+
return (0, project_registry_1.hasUnfinishedProjectRebuild)();
|
|
486
|
+
}
|
|
487
|
+
catch (_a) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
schedulePendingIndexRetry(root) {
|
|
492
|
+
var _a;
|
|
493
|
+
if (this.shuttingDown || this.pendingIndexRetryTimers.has(root))
|
|
494
|
+
return;
|
|
495
|
+
const attempts = (_a = this.pendingIndexRetryCounts.get(root)) !== null && _a !== void 0 ? _a : 0;
|
|
496
|
+
if (attempts >= 5)
|
|
497
|
+
return;
|
|
498
|
+
this.pendingIndexRetryCounts.set(root, attempts + 1);
|
|
499
|
+
const timer = setTimeout(() => {
|
|
500
|
+
this.pendingIndexRetryTimers.delete(root);
|
|
501
|
+
if (!this.shuttingDown)
|
|
502
|
+
void this.indexPendingProject(root);
|
|
503
|
+
}, 30000);
|
|
504
|
+
timer.unref();
|
|
505
|
+
this.pendingIndexRetryTimers.set(root, timer);
|
|
506
|
+
}
|
|
507
|
+
clearPendingIndexRetry(root) {
|
|
508
|
+
const timer = this.pendingIndexRetryTimers.get(root);
|
|
509
|
+
if (timer)
|
|
510
|
+
clearTimeout(timer);
|
|
511
|
+
this.pendingIndexRetryTimers.delete(root);
|
|
512
|
+
this.pendingIndexRetryCounts.delete(root);
|
|
397
513
|
}
|
|
398
514
|
indexPendingProject(root) {
|
|
399
515
|
return __awaiter(this, void 0, void 0, function* () {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
516
|
+
const ac = new AbortController();
|
|
517
|
+
this.shutdownAbortControllers.add(ac);
|
|
518
|
+
try {
|
|
519
|
+
yield this.withProjectLock(root, ac.signal, () => __awaiter(this, void 0, void 0, function* () {
|
|
520
|
+
return this.runSharedOperation("index-pending", ac.signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
521
|
+
var _a, _b, _c, _d;
|
|
522
|
+
// Bail if shutdown raced ahead of us between iteration and lock
|
|
523
|
+
// acquisition. Starting now would race the store close below.
|
|
524
|
+
if (this.shuttingDown)
|
|
525
|
+
return;
|
|
526
|
+
if (!this.vectorDb || !this.metaCache)
|
|
527
|
+
return;
|
|
528
|
+
const current = (0, project_registry_1.getProject)(root);
|
|
529
|
+
if ((current === null || current === void 0 ? void 0 : current.status) !== "pending" && (current === null || current === void 0 ? void 0 : current.status) !== "error")
|
|
530
|
+
return;
|
|
531
|
+
const name = path.basename(root);
|
|
532
|
+
const start = Date.now();
|
|
533
|
+
(0, logger_1.log)("daemon", `indexPendingProject start: ${name} (${root})`);
|
|
534
|
+
this.vectorDb.pauseMaintenanceLoop();
|
|
535
|
+
try {
|
|
536
|
+
if (this.processors.has(root))
|
|
537
|
+
yield this.unwatchProjectWithinOperation(root);
|
|
538
|
+
const result = yield (0, syncer_1.initialSync)({
|
|
539
|
+
projectRoot: root,
|
|
540
|
+
vectorDb: this.vectorDb,
|
|
541
|
+
metaCache: this.metaCache,
|
|
542
|
+
signal: operationSignal,
|
|
543
|
+
generation: (_a = this.activeGeneration) !== null && _a !== void 0 ? _a : undefined,
|
|
544
|
+
embedMode: (_b = this.activeConfig) === null || _b === void 0 ? void 0 : _b.embedMode,
|
|
545
|
+
workerPool: (_c = this.workerPool) !== null && _c !== void 0 ? _c : undefined,
|
|
546
|
+
onProgress: () => {
|
|
547
|
+
this.resetActivity();
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
551
|
+
const chunkCount = yield this.vectorDb.countRowsForPath(prefix);
|
|
552
|
+
const proj = (0, project_registry_1.getProject)(root);
|
|
553
|
+
if (proj) {
|
|
554
|
+
if (result.degraded) {
|
|
555
|
+
(0, project_registry_1.registerProject)(Object.assign(Object.assign({}, proj), { status: "pending" }));
|
|
556
|
+
this.schedulePendingIndexRetry(root);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
this.clearPendingIndexRetry(root);
|
|
560
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
561
|
+
root,
|
|
562
|
+
name: proj.name,
|
|
563
|
+
generation: result.generation,
|
|
564
|
+
embedMode: result.embedMode,
|
|
565
|
+
chunkCount,
|
|
566
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
567
|
+
expectedFingerprint: result.registryExpectation.embeddingFingerprint,
|
|
568
|
+
expectedRebuildId: result.registryExpectation.rebuildId,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
(0, logger_1.log)("daemon", `indexPendingProject done: ${name} — ${result.total} files, ${result.indexed} chunks, ${Date.now() - start}ms`);
|
|
573
|
+
}
|
|
574
|
+
catch (err) {
|
|
575
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
576
|
+
console.error(`[daemon] indexPendingProject failed for ${name} after ${Date.now() - start}ms: ${msg}`);
|
|
577
|
+
const proj = (0, project_registry_1.getProject)(root);
|
|
578
|
+
if (proj && !(err instanceof project_registry_1.ProjectRegistryConflictError)) {
|
|
579
|
+
(0, project_registry_1.registerProject)(Object.assign(Object.assign({}, proj), { status: "error" }));
|
|
580
|
+
this.schedulePendingIndexRetry(root);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
finally {
|
|
584
|
+
(_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.resumeMaintenanceLoop();
|
|
585
|
+
if (!this.shuttingDown) {
|
|
586
|
+
try {
|
|
587
|
+
yield this.watchProjectWithinOperation(root);
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
console.error(`[daemon] Failed to re-watch ${name}:`, err);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}));
|
|
595
|
+
}));
|
|
596
|
+
}
|
|
597
|
+
finally {
|
|
598
|
+
this.shutdownAbortControllers.delete(ac);
|
|
599
|
+
}
|
|
441
600
|
});
|
|
442
601
|
}
|
|
443
|
-
unwatchProject(root) {
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
|
|
602
|
+
unwatchProject(root, signal) {
|
|
603
|
+
return this.withProjectLock(root, signal, () => this.runSharedOperation("unwatch", signal, () => this.unwatchProjectWithinOperation(root)));
|
|
604
|
+
}
|
|
605
|
+
unwatchProjectWithinOperation(root) {
|
|
606
|
+
return this.watcherManager.unwatchProject(root);
|
|
447
607
|
}
|
|
448
608
|
/**
|
|
449
609
|
* Run a search inside the daemon, reusing the warm VectorDB connection,
|
|
@@ -485,16 +645,20 @@ class Daemon {
|
|
|
485
645
|
// Search handling lives in search-handler.ts (Phase 12 split). The daemon
|
|
486
646
|
// supplies its warm VectorDB + watcher/index bookkeeping; the handler runs
|
|
487
647
|
// the query and assembles the response.
|
|
488
|
-
return (0,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
648
|
+
return this.runSharedOperation("search", signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
649
|
+
return (0, search_handler_1.handleDaemonSearch)({
|
|
650
|
+
vectorDb: this.vectorDb,
|
|
651
|
+
processors: this.processors,
|
|
652
|
+
indexProgress: this.indexProgress,
|
|
653
|
+
searchers: this.searchers,
|
|
654
|
+
getIndexState: (root) => this.indexState(root),
|
|
655
|
+
touchActivity: () => {
|
|
656
|
+
this.lastActivity = Date.now();
|
|
657
|
+
},
|
|
658
|
+
generation: this.activeGeneration,
|
|
659
|
+
workerPool: this.workerPool,
|
|
660
|
+
}, payload, operationSignal);
|
|
661
|
+
}));
|
|
498
662
|
});
|
|
499
663
|
}
|
|
500
664
|
listProjects() {
|
|
@@ -514,90 +678,250 @@ class Daemon {
|
|
|
514
678
|
var _a, _b;
|
|
515
679
|
return (_b = (_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.diskPressure) !== null && _b !== void 0 ? _b : "unknown";
|
|
516
680
|
}
|
|
681
|
+
getMlxStatus() {
|
|
682
|
+
return this.mlxServerManager.getStatus();
|
|
683
|
+
}
|
|
517
684
|
/** Reset idle timer — call during long-running operations. */
|
|
518
685
|
resetActivity() {
|
|
519
686
|
this.lastActivity = Date.now();
|
|
520
687
|
}
|
|
521
688
|
// --- Per-project operation serialization ---
|
|
522
|
-
withProjectLock(root, fn) {
|
|
689
|
+
withProjectLock(root, signal, fn) {
|
|
523
690
|
return __awaiter(this, void 0, void 0, function* () {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
691
|
+
return this.projectMutex.run(root, signal, fn);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
// --- Streaming write operations (IPC) ---
|
|
695
|
+
activeConfigurationError() {
|
|
696
|
+
const activeConfig = this.activeConfig;
|
|
697
|
+
const activeGeneration = this.activeGeneration;
|
|
698
|
+
if (!activeConfig || !activeGeneration)
|
|
699
|
+
return "daemon resources not ready";
|
|
700
|
+
const currentConfig = (0, index_config_1.readGlobalConfig)();
|
|
701
|
+
let currentGeneration;
|
|
702
|
+
try {
|
|
703
|
+
currentGeneration = (0, embedding_generation_1.resolveEmbeddingGeneration)(currentConfig);
|
|
704
|
+
}
|
|
705
|
+
catch (error) {
|
|
706
|
+
return error instanceof Error ? error.message : String(error);
|
|
707
|
+
}
|
|
708
|
+
return currentGeneration.fingerprint !== activeGeneration.fingerprint ||
|
|
709
|
+
currentConfig.embedMode !== activeConfig.embedMode ||
|
|
710
|
+
currentConfig.mlxModel !== activeConfig.mlxModel
|
|
711
|
+
? "daemon configuration is stale; restart the gmax daemon"
|
|
712
|
+
: null;
|
|
713
|
+
}
|
|
714
|
+
ensureProject(root, conn) {
|
|
715
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
716
|
+
const ac = new AbortController();
|
|
717
|
+
const onClose = () => ac.abort();
|
|
718
|
+
conn.once("close", onClose);
|
|
719
|
+
this.shutdownAbortControllers.add(ac);
|
|
532
720
|
try {
|
|
533
|
-
|
|
721
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("ensure-project", ac.signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
722
|
+
var _a, _b;
|
|
723
|
+
if (operationSignal.aborted || this.shuttingDown)
|
|
724
|
+
return;
|
|
725
|
+
const activeConfig = this.activeConfig;
|
|
726
|
+
const configurationError = this.activeConfigurationError();
|
|
727
|
+
if (!activeConfig || configurationError) {
|
|
728
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
729
|
+
ok: false,
|
|
730
|
+
error: configurationError !== null && configurationError !== void 0 ? configurationError : "daemon resources not ready",
|
|
731
|
+
});
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
735
|
+
if ((project === null || project === void 0 ? void 0 : project.status) === "indexed") {
|
|
736
|
+
if (!this.activeGeneration ||
|
|
737
|
+
(0, embedding_generation_1.compareEmbeddingGeneration)(project, this.activeGeneration)
|
|
738
|
+
.state === "stale") {
|
|
739
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
740
|
+
ok: false,
|
|
741
|
+
error: "project embedding generation is stale; rebuild the project",
|
|
742
|
+
});
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
yield this.watchProjectWithinOperation(root);
|
|
746
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: true, status: "indexed", watched: true });
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
(0, project_registry_1.registerProject)({
|
|
750
|
+
root,
|
|
751
|
+
name: (_a = project === null || project === void 0 ? void 0 : project.name) !== null && _a !== void 0 ? _a : path.basename(root),
|
|
752
|
+
vectorDim: activeConfig.vectorDim,
|
|
753
|
+
modelTier: activeConfig.modelTier,
|
|
754
|
+
embedMode: activeConfig.embedMode,
|
|
755
|
+
lastIndexed: (_b = project === null || project === void 0 ? void 0 : project.lastIndexed) !== null && _b !== void 0 ? _b : "",
|
|
756
|
+
chunkCount: project === null || project === void 0 ? void 0 : project.chunkCount,
|
|
757
|
+
status: "pending",
|
|
758
|
+
chunkerVersion: project === null || project === void 0 ? void 0 : project.chunkerVersion,
|
|
759
|
+
});
|
|
760
|
+
yield this.addProjectLocked(root, conn, operationSignal, project);
|
|
761
|
+
})));
|
|
534
762
|
}
|
|
535
763
|
finally {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
this.projectLocks.delete(root);
|
|
539
|
-
}
|
|
764
|
+
conn.off("close", onClose);
|
|
765
|
+
this.shutdownAbortControllers.delete(ac);
|
|
540
766
|
}
|
|
541
767
|
});
|
|
542
768
|
}
|
|
543
|
-
// --- Streaming write operations (IPC) ---
|
|
544
769
|
addProject(root, conn) {
|
|
545
770
|
return __awaiter(this, void 0, void 0, function* () {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
try {
|
|
559
|
-
const result = yield (0, syncer_1.initialSync)({
|
|
560
|
-
projectRoot: root,
|
|
561
|
-
vectorDb: this.vectorDb,
|
|
562
|
-
metaCache: this.metaCache,
|
|
563
|
-
signal: ac.signal,
|
|
564
|
-
onProgress: (info) => {
|
|
565
|
-
this.resetActivity();
|
|
566
|
-
const now = Date.now();
|
|
567
|
-
if (now - lastProgressTime < 100)
|
|
568
|
-
return;
|
|
569
|
-
lastProgressTime = now;
|
|
570
|
-
(0, ipc_handler_1.writeProgress)(conn, {
|
|
571
|
-
processed: info.processed,
|
|
572
|
-
indexed: info.indexed,
|
|
573
|
-
total: info.total,
|
|
574
|
-
filePath: info.filePath,
|
|
575
|
-
});
|
|
576
|
-
},
|
|
577
|
-
});
|
|
578
|
-
if (!this.shuttingDown) {
|
|
579
|
-
yield this.watchProject(root);
|
|
771
|
+
const ac = new AbortController();
|
|
772
|
+
const onClose = () => ac.abort();
|
|
773
|
+
conn.once("close", onClose);
|
|
774
|
+
this.shutdownAbortControllers.add(ac);
|
|
775
|
+
try {
|
|
776
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("add-project", ac.signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
777
|
+
if (operationSignal.aborted || this.shuttingDown)
|
|
778
|
+
return;
|
|
779
|
+
const configurationError = this.activeConfigurationError();
|
|
780
|
+
if (configurationError) {
|
|
781
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: configurationError });
|
|
782
|
+
return;
|
|
580
783
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
784
|
+
yield this.addProjectLocked(root, conn, operationSignal, (0, project_registry_1.getProject)(root));
|
|
785
|
+
})));
|
|
786
|
+
}
|
|
787
|
+
finally {
|
|
788
|
+
conn.off("close", onClose);
|
|
789
|
+
this.shutdownAbortControllers.delete(ac);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
addProjectLocked(root, conn, signal, previousProject) {
|
|
794
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
795
|
+
var _a, _b, _c, _d;
|
|
796
|
+
if (!this.vectorDb || !this.metaCache || !this.activeConfig) {
|
|
797
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (!(0, project_registry_1.getProject)(root)) {
|
|
801
|
+
(0, project_registry_1.registerProject)({
|
|
802
|
+
root,
|
|
803
|
+
name: path.basename(root),
|
|
804
|
+
vectorDim: this.activeConfig.vectorDim,
|
|
805
|
+
modelTier: this.activeConfig.modelTier,
|
|
806
|
+
embedMode: this.activeConfig.embedMode,
|
|
807
|
+
lastIndexed: "",
|
|
808
|
+
status: "pending",
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
this.vectorDb.pauseMaintenanceLoop();
|
|
812
|
+
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
813
|
+
let lastProgressTime = 0;
|
|
814
|
+
try {
|
|
815
|
+
const result = yield (0, syncer_1.initialSync)({
|
|
816
|
+
projectRoot: root,
|
|
817
|
+
vectorDb: this.vectorDb,
|
|
818
|
+
metaCache: this.metaCache,
|
|
819
|
+
signal,
|
|
820
|
+
generation: (_a = this.activeGeneration) !== null && _a !== void 0 ? _a : undefined,
|
|
821
|
+
embedMode: this.activeConfig.embedMode,
|
|
822
|
+
workerPool: (_b = this.workerPool) !== null && _b !== void 0 ? _b : undefined,
|
|
823
|
+
onProgress: (info) => {
|
|
824
|
+
this.resetActivity();
|
|
825
|
+
const now = Date.now();
|
|
826
|
+
if (now - lastProgressTime < 100)
|
|
827
|
+
return;
|
|
828
|
+
lastProgressTime = now;
|
|
829
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
830
|
+
processed: info.processed,
|
|
831
|
+
indexed: info.indexed,
|
|
832
|
+
total: info.total,
|
|
833
|
+
filePath: info.filePath,
|
|
834
|
+
});
|
|
835
|
+
},
|
|
836
|
+
});
|
|
837
|
+
if (signal.aborted || this.shuttingDown) {
|
|
838
|
+
const abortError = new Error("Aborted");
|
|
839
|
+
abortError.name = "AbortError";
|
|
840
|
+
throw abortError;
|
|
841
|
+
}
|
|
842
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
843
|
+
const chunkCount = yield this.vectorDb.countRowsForPath(prefix);
|
|
844
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
845
|
+
if (result.degraded) {
|
|
846
|
+
if (previousProject)
|
|
847
|
+
(0, project_registry_1.registerProject)(previousProject);
|
|
848
|
+
this.schedulePendingIndexRetry(root);
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
this.clearPendingIndexRetry(root);
|
|
852
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
853
|
+
root,
|
|
854
|
+
name: (_c = project === null || project === void 0 ? void 0 : project.name) !== null && _c !== void 0 ? _c : path.basename(root),
|
|
855
|
+
generation: result.generation,
|
|
856
|
+
embedMode: result.embedMode,
|
|
857
|
+
chunkCount,
|
|
858
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
859
|
+
expectedFingerprint: result.registryExpectation.embeddingFingerprint,
|
|
860
|
+
expectedRebuildId: result.registryExpectation.rebuildId,
|
|
588
861
|
});
|
|
589
862
|
}
|
|
590
|
-
|
|
863
|
+
yield this.watchProjectWithinOperation(root);
|
|
864
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
865
|
+
ok: true,
|
|
866
|
+
processed: result.processed,
|
|
867
|
+
indexed: result.indexed,
|
|
868
|
+
total: result.total,
|
|
869
|
+
failedFiles: result.failedFiles,
|
|
870
|
+
degraded: result.degraded,
|
|
871
|
+
scanErrors: result.scanErrors,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
catch (err) {
|
|
875
|
+
const aborted = signal.aborted || (err === null || err === void 0 ? void 0 : err.name) === "AbortError";
|
|
876
|
+
if (aborted) {
|
|
877
|
+
if (previousProject)
|
|
878
|
+
(0, project_registry_1.registerProject)(previousProject);
|
|
879
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "aborted" });
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
591
882
|
const msg = err instanceof Error ? err.message : String(err);
|
|
592
883
|
console.error(`[daemon] addProject failed for ${path.basename(root)}:`, msg);
|
|
593
|
-
|
|
884
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
885
|
+
if (project && !(err instanceof project_registry_1.ProjectRegistryConflictError)) {
|
|
886
|
+
(0, project_registry_1.registerProject)(Object.assign(Object.assign({}, project), { status: "error" }));
|
|
887
|
+
}
|
|
594
888
|
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
595
889
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
890
|
+
}
|
|
891
|
+
finally {
|
|
892
|
+
stopHeartbeat();
|
|
893
|
+
(_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.resumeMaintenanceLoop();
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
projectStats(root) {
|
|
898
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
899
|
+
return this.runSharedOperation("project-stats", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
900
|
+
if (!this.vectorDb)
|
|
901
|
+
throw new Error("daemon resources not ready");
|
|
902
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
903
|
+
if (!project)
|
|
904
|
+
throw new Error("project not registered");
|
|
905
|
+
if (!this.activeConfig)
|
|
906
|
+
throw new Error("daemon resources not ready");
|
|
907
|
+
const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, this.activeConfig);
|
|
908
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
909
|
+
const [chunks, files] = yield Promise.all([
|
|
910
|
+
this.vectorDb.countRowsForPath(prefix),
|
|
911
|
+
this.vectorDb.countDistinctFilesForPath(prefix),
|
|
912
|
+
]);
|
|
913
|
+
return {
|
|
914
|
+
files,
|
|
915
|
+
chunks,
|
|
916
|
+
vectorDim: project.vectorDim,
|
|
917
|
+
modelTier: project.modelTier,
|
|
918
|
+
embedMode: project.embedMode,
|
|
919
|
+
indexedAt: project.lastIndexed,
|
|
920
|
+
watching: this.processors.has(root),
|
|
921
|
+
configuredEmbedding: identity.configured,
|
|
922
|
+
builtEmbedding: identity.built,
|
|
923
|
+
embeddingState: identity.state,
|
|
924
|
+
};
|
|
601
925
|
}));
|
|
602
926
|
});
|
|
603
927
|
}
|
|
@@ -612,20 +936,13 @@ class Daemon {
|
|
|
612
936
|
*/
|
|
613
937
|
reindexOneProject(root, opts, signal, onProgress) {
|
|
614
938
|
return __awaiter(this, void 0, void 0, function* () {
|
|
939
|
+
var _a, _b, _c;
|
|
615
940
|
if (!this.vectorDb || !this.metaCache) {
|
|
616
941
|
throw new Error("daemon resources not ready");
|
|
617
942
|
}
|
|
618
|
-
//
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
yield processor.close();
|
|
622
|
-
this.processors.delete(root);
|
|
623
|
-
}
|
|
624
|
-
const sub = this.subscriptions.get(root);
|
|
625
|
-
if (sub) {
|
|
626
|
-
yield sub.unsubscribe();
|
|
627
|
-
this.subscriptions.delete(root);
|
|
628
|
-
}
|
|
943
|
+
// Quiesce the subscription, processor, and any catchup generation before
|
|
944
|
+
// full sync takes deletion authority for this project.
|
|
945
|
+
yield this.unwatchProjectWithinOperation(root);
|
|
629
946
|
// Mark this root as full-indexing so concurrent searches get a
|
|
630
947
|
// partial-result footer (Phase 6); seeded at 0/0 until the first tick.
|
|
631
948
|
this.indexProgress.set(root, { processed: 0, total: 0 });
|
|
@@ -637,6 +954,9 @@ class Daemon {
|
|
|
637
954
|
vectorDb: this.vectorDb,
|
|
638
955
|
metaCache: this.metaCache,
|
|
639
956
|
signal,
|
|
957
|
+
generation: (_a = this.activeGeneration) !== null && _a !== void 0 ? _a : undefined,
|
|
958
|
+
embedMode: (_b = this.activeConfig) === null || _b === void 0 ? void 0 : _b.embedMode,
|
|
959
|
+
workerPool: (_c = this.workerPool) !== null && _c !== void 0 ? _c : undefined,
|
|
640
960
|
onProgress: (info) => {
|
|
641
961
|
this.resetActivity();
|
|
642
962
|
this.indexProgress.set(root, {
|
|
@@ -650,9 +970,9 @@ class Daemon {
|
|
|
650
970
|
finally {
|
|
651
971
|
this.indexProgress.delete(root);
|
|
652
972
|
// Re-enable watcher (skip if shutting down)
|
|
653
|
-
if (!this.shuttingDown) {
|
|
973
|
+
if (!this.shuttingDown && opts.rewatch !== false) {
|
|
654
974
|
try {
|
|
655
|
-
yield this.
|
|
975
|
+
yield this.watchProjectWithinOperation(root);
|
|
656
976
|
}
|
|
657
977
|
catch (err) {
|
|
658
978
|
console.error(`[daemon] Failed to re-watch ${path.basename(root)}:`, err);
|
|
@@ -663,52 +983,78 @@ class Daemon {
|
|
|
663
983
|
}
|
|
664
984
|
indexProject(root, conn, opts) {
|
|
665
985
|
return __awaiter(this, void 0, void 0, function* () {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
let lastProgressTime = 0;
|
|
678
|
-
try {
|
|
679
|
-
const result = yield this.reindexOneProject(root, opts, ac.signal, (info) => {
|
|
680
|
-
const now = Date.now();
|
|
681
|
-
if (now - lastProgressTime < 100)
|
|
682
|
-
return;
|
|
683
|
-
lastProgressTime = now;
|
|
684
|
-
(0, ipc_handler_1.writeProgress)(conn, {
|
|
685
|
-
processed: info.processed,
|
|
686
|
-
indexed: info.indexed,
|
|
687
|
-
total: info.total,
|
|
688
|
-
filePath: info.filePath,
|
|
986
|
+
const ac = new AbortController();
|
|
987
|
+
const onClose = () => ac.abort();
|
|
988
|
+
conn.once("close", onClose);
|
|
989
|
+
this.shutdownAbortControllers.add(ac);
|
|
990
|
+
try {
|
|
991
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("index-project", ac.signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
992
|
+
var _a, _b;
|
|
993
|
+
if (!this.vectorDb || !this.metaCache) {
|
|
994
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
995
|
+
ok: false,
|
|
996
|
+
error: "daemon resources not ready",
|
|
689
997
|
});
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
this.vectorDb.pauseMaintenanceLoop();
|
|
1001
|
+
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
1002
|
+
let lastProgressTime = 0;
|
|
1003
|
+
try {
|
|
1004
|
+
const result = yield this.reindexOneProject(root, opts, operationSignal, (info) => {
|
|
1005
|
+
const now = Date.now();
|
|
1006
|
+
if (now - lastProgressTime < 100)
|
|
1007
|
+
return;
|
|
1008
|
+
lastProgressTime = now;
|
|
1009
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
1010
|
+
processed: info.processed,
|
|
1011
|
+
indexed: info.indexed,
|
|
1012
|
+
total: info.total,
|
|
1013
|
+
filePath: info.filePath,
|
|
1014
|
+
});
|
|
1015
|
+
});
|
|
1016
|
+
if (!opts.dryRun && !result.degraded) {
|
|
1017
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1018
|
+
const chunkCount = yield this.vectorDb.countRowsForPath(prefix);
|
|
1019
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
1020
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
1021
|
+
root,
|
|
1022
|
+
generation: result.generation,
|
|
1023
|
+
embedMode: result.embedMode,
|
|
1024
|
+
chunkCount,
|
|
1025
|
+
chunkerVersion: opts.reset
|
|
1026
|
+
? config_1.CONFIG.CHUNKER_VERSION
|
|
1027
|
+
: ((_a = project === null || project === void 0 ? void 0 : project.chunkerVersion) !== null && _a !== void 0 ? _a : 1),
|
|
1028
|
+
expectedFingerprint: result.registryExpectation.embeddingFingerprint,
|
|
1029
|
+
expectedRebuildId: result.registryExpectation.rebuildId,
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1033
|
+
ok: true,
|
|
1034
|
+
processed: result.processed,
|
|
1035
|
+
indexed: result.indexed,
|
|
1036
|
+
total: result.total,
|
|
1037
|
+
failedFiles: result.failedFiles,
|
|
1038
|
+
degraded: result.degraded,
|
|
1039
|
+
scanErrors: result.scanErrors,
|
|
1040
|
+
embeddingFingerprint: result.generation.fingerprint,
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
catch (err) {
|
|
1044
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1045
|
+
console.error(`[daemon] indexProject failed for ${path.basename(root)}:`, msg);
|
|
1046
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
1047
|
+
}
|
|
1048
|
+
finally {
|
|
1049
|
+
stopHeartbeat();
|
|
1050
|
+
(_b = this.vectorDb) === null || _b === void 0 ? void 0 : _b.resumeMaintenanceLoop();
|
|
1051
|
+
}
|
|
1052
|
+
})));
|
|
1053
|
+
}
|
|
1054
|
+
finally {
|
|
1055
|
+
conn.off("close", onClose);
|
|
1056
|
+
this.shutdownAbortControllers.delete(ac);
|
|
1057
|
+
}
|
|
712
1058
|
});
|
|
713
1059
|
}
|
|
714
1060
|
/**
|
|
@@ -724,179 +1070,508 @@ class Daemon {
|
|
|
724
1070
|
repairRebuild(conn) {
|
|
725
1071
|
return __awaiter(this, void 0, void 0, function* () {
|
|
726
1072
|
var _a;
|
|
727
|
-
|
|
1073
|
+
const oldResources = this.resources;
|
|
1074
|
+
const metaCache = this.metaCache;
|
|
1075
|
+
if (!oldResources || !metaCache) {
|
|
728
1076
|
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
729
1077
|
return;
|
|
730
1078
|
}
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1079
|
+
const targetConfig = (0, index_config_1.readGlobalConfig)();
|
|
1080
|
+
const targetGeneration = (0, embedding_generation_1.resolveEmbeddingGeneration)(targetConfig);
|
|
1081
|
+
const clientAbort = new AbortController();
|
|
1082
|
+
let dropCommitted = false;
|
|
1083
|
+
let desiredWatchRoots = [];
|
|
1084
|
+
let watchersRestored = false;
|
|
1085
|
+
let oldPoolDestroyed = false;
|
|
1086
|
+
let oldDbClosed = false;
|
|
1087
|
+
const failClosedOldResources = (lease) => __awaiter(this, void 0, void 0, function* () {
|
|
1088
|
+
this.ready = false;
|
|
1089
|
+
this.resources = null;
|
|
1090
|
+
this.vectorDb = null;
|
|
1091
|
+
this.workerPool = null;
|
|
1092
|
+
if (!oldPoolDestroyed) {
|
|
1093
|
+
yield oldResources.workerPool
|
|
1094
|
+
.destroy({ requireExit: true })
|
|
1095
|
+
.catch(() => { });
|
|
1096
|
+
oldPoolDestroyed = true;
|
|
1097
|
+
}
|
|
1098
|
+
if (!oldDbClosed) {
|
|
1099
|
+
yield oldResources.vectorDb.close().catch(() => { });
|
|
1100
|
+
oldDbClosed = true;
|
|
1101
|
+
}
|
|
1102
|
+
yield lease.release().catch(() => { });
|
|
1103
|
+
});
|
|
1104
|
+
const onClose = () => {
|
|
1105
|
+
if (!dropCommitted)
|
|
1106
|
+
clientAbort.abort(new Error("client disconnected"));
|
|
1107
|
+
};
|
|
1108
|
+
conn.once("close", onClose);
|
|
735
1109
|
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1110
|
+
const throwIfPreDropCancelled = (operationSignal) => {
|
|
1111
|
+
if (operationSignal.aborted)
|
|
1112
|
+
throw operationSignal.reason;
|
|
1113
|
+
if (!dropCommitted && clientAbort.signal.aborted) {
|
|
1114
|
+
throw clientAbort.signal.reason;
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
742
1117
|
try {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1118
|
+
const result = yield this.operations.runExclusive("repair", () => __awaiter(this, void 0, void 0, function* () {
|
|
1119
|
+
desiredWatchRoots = yield this.watcherManager.quiesceAll();
|
|
1120
|
+
}), (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
1121
|
+
throwIfPreDropCancelled(operationSignal);
|
|
1122
|
+
oldResources.vectorDb.pauseMaintenanceLoop();
|
|
1123
|
+
this.searchers.clear();
|
|
1124
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
1125
|
+
phase: "lease",
|
|
1126
|
+
message: "waiting for exclusive store ownership",
|
|
1127
|
+
});
|
|
1128
|
+
let exclusiveLease = null;
|
|
1129
|
+
try {
|
|
1130
|
+
exclusiveLease = yield oldResources.vectorDb.upgradeStoreLease(AbortSignal.any([operationSignal, clientAbort.signal]));
|
|
1131
|
+
throwIfPreDropCancelled(operationSignal);
|
|
1132
|
+
}
|
|
1133
|
+
catch (error) {
|
|
1134
|
+
if (exclusiveLease) {
|
|
1135
|
+
try {
|
|
1136
|
+
yield oldResources.vectorDb.downgradeStoreLease();
|
|
1137
|
+
}
|
|
1138
|
+
catch (downgradeError) {
|
|
1139
|
+
yield failClosedOldResources(exclusiveLease);
|
|
1140
|
+
throw new Error(`Failed to restore shared store ownership: ${String(downgradeError)}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
oldResources.vectorDb.resumeMaintenanceLoop();
|
|
1144
|
+
throw error;
|
|
1145
|
+
}
|
|
1146
|
+
if (!exclusiveLease) {
|
|
1147
|
+
throw new Error("Exclusive store lease acquisition returned no lease");
|
|
1148
|
+
}
|
|
1149
|
+
let reservation;
|
|
1150
|
+
try {
|
|
1151
|
+
reservation = (0, project_registry_1.reserveProjectsForRebuild)(targetGeneration);
|
|
1152
|
+
}
|
|
1153
|
+
catch (error) {
|
|
1154
|
+
try {
|
|
1155
|
+
yield oldResources.vectorDb.downgradeStoreLease();
|
|
1156
|
+
}
|
|
1157
|
+
catch (downgradeError) {
|
|
1158
|
+
yield failClosedOldResources(exclusiveLease);
|
|
1159
|
+
throw new Error(`Failed to restore shared store ownership: ${String(downgradeError)}`);
|
|
1160
|
+
}
|
|
1161
|
+
oldResources.vectorDb.resumeMaintenanceLoop();
|
|
1162
|
+
throw error;
|
|
1163
|
+
}
|
|
1164
|
+
let targetDb = null;
|
|
1165
|
+
let targetPool = null;
|
|
1166
|
+
let published = false;
|
|
1167
|
+
try {
|
|
1168
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
1169
|
+
phase: "prepare",
|
|
1170
|
+
rebuildId: reservation.rebuildId,
|
|
1171
|
+
projects: reservation.reserved.length,
|
|
1172
|
+
});
|
|
1173
|
+
yield oldResources.workerPool.destroy({ requireExit: true });
|
|
1174
|
+
oldPoolDestroyed = true;
|
|
1175
|
+
if (oldResources.mlx === "owned") {
|
|
1176
|
+
yield this.mlxServerManager.stopMlxServer();
|
|
1177
|
+
}
|
|
1178
|
+
throwIfPreDropCancelled(operationSignal);
|
|
1179
|
+
yield oldResources.vectorDb.close({
|
|
1180
|
+
releaseLease: false,
|
|
1181
|
+
requireClosed: true,
|
|
1182
|
+
});
|
|
1183
|
+
oldDbClosed = true;
|
|
1184
|
+
throwIfPreDropCancelled(operationSignal);
|
|
1185
|
+
targetDb = this.createVectorDb(targetGeneration.vectorDim, exclusiveLease);
|
|
1186
|
+
(0, project_registry_1.markProjectRebuildDropping)(reservation);
|
|
1187
|
+
try {
|
|
1188
|
+
yield targetDb.drop();
|
|
1189
|
+
dropCommitted = true;
|
|
1190
|
+
conn.off("close", onClose);
|
|
1191
|
+
}
|
|
1192
|
+
catch (error) {
|
|
1193
|
+
// LanceDB does not expose a commit token. Inspect physical state:
|
|
1194
|
+
// an absent table means the destructive commit happened; an
|
|
1195
|
+
// unreadable state is treated as uncertain and therefore post-drop.
|
|
1196
|
+
try {
|
|
1197
|
+
dropCommitted = (yield targetDb.getSchemaVectorDim()) === null;
|
|
1198
|
+
}
|
|
1199
|
+
catch (_a) {
|
|
1200
|
+
dropCommitted = true;
|
|
1201
|
+
}
|
|
1202
|
+
if (dropCommitted)
|
|
1203
|
+
conn.off("close", onClose);
|
|
1204
|
+
throw error;
|
|
1205
|
+
}
|
|
1206
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
1207
|
+
phase: "schema",
|
|
1208
|
+
message: "creating target table",
|
|
1209
|
+
});
|
|
1210
|
+
yield targetDb.ensureTable();
|
|
1211
|
+
const physicalDim = yield targetDb.getSchemaVectorDim();
|
|
1212
|
+
if (physicalDim !== targetGeneration.vectorDim) {
|
|
1213
|
+
throw new Error(`rebuilt table dimension ${physicalDim !== null && physicalDim !== void 0 ? physicalDim : "missing"} does not match target ${targetGeneration.vectorDim}`);
|
|
1214
|
+
}
|
|
1215
|
+
if (targetConfig.embedMode === "gpu") {
|
|
1216
|
+
yield this.mlxServerManager.ensureMlxServer(targetGeneration.mlxModel);
|
|
1217
|
+
}
|
|
1218
|
+
const targetMlx = this.mlxMode(targetConfig);
|
|
1219
|
+
targetPool = this.createWorkerPool(targetGeneration, targetConfig.embedMode);
|
|
1220
|
+
this.publishResourceGeneration(targetConfig, targetGeneration, targetDb, targetPool, targetMlx);
|
|
1221
|
+
published = true;
|
|
1222
|
+
let completed = 0;
|
|
1223
|
+
const failures = [];
|
|
1224
|
+
for (const project of reservation.reserved) {
|
|
1225
|
+
if (operationSignal.aborted)
|
|
1226
|
+
throw operationSignal.reason;
|
|
757
1227
|
(0, ipc_handler_1.writeProgress)(conn, {
|
|
758
|
-
phase: "
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
indexed: info.indexed,
|
|
764
|
-
total: info.total,
|
|
765
|
-
filePath: info.filePath,
|
|
1228
|
+
phase: "index",
|
|
1229
|
+
root: project.root,
|
|
1230
|
+
project: project.name,
|
|
1231
|
+
completed,
|
|
1232
|
+
totalProjects: reservation.reserved.length,
|
|
766
1233
|
});
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1234
|
+
try {
|
|
1235
|
+
const sync = yield this.reindexOneProject(project.root, { reset: true, rewatch: false }, operationSignal, (info) => (0, ipc_handler_1.writeProgress)(conn, {
|
|
1236
|
+
phase: "index",
|
|
1237
|
+
root: project.root,
|
|
1238
|
+
project: project.name,
|
|
1239
|
+
processed: info.processed,
|
|
1240
|
+
indexed: info.indexed,
|
|
1241
|
+
total: info.total,
|
|
1242
|
+
filePath: info.filePath,
|
|
1243
|
+
}));
|
|
1244
|
+
if (sync.degraded) {
|
|
1245
|
+
throw new Error(`degraded scan (${sync.failedFiles} failed files)`);
|
|
1246
|
+
}
|
|
1247
|
+
const prefix = project.root.endsWith("/")
|
|
1248
|
+
? project.root
|
|
1249
|
+
: `${project.root}/`;
|
|
1250
|
+
const chunkCount = yield targetDb.countRowsForPath(prefix);
|
|
1251
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
1252
|
+
root: project.root,
|
|
1253
|
+
name: project.name,
|
|
1254
|
+
generation: sync.generation,
|
|
1255
|
+
embedMode: sync.embedMode,
|
|
1256
|
+
chunkCount,
|
|
1257
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
1258
|
+
expectedFingerprint: targetGeneration.fingerprint,
|
|
1259
|
+
expectedRebuildId: reservation.rebuildId,
|
|
1260
|
+
});
|
|
1261
|
+
completed++;
|
|
1262
|
+
}
|
|
1263
|
+
catch (error) {
|
|
1264
|
+
failures.push({
|
|
1265
|
+
root: project.root,
|
|
1266
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
772
1269
|
}
|
|
773
|
-
|
|
774
|
-
|
|
1270
|
+
yield targetDb.downgradeStoreLease();
|
|
1271
|
+
targetDb.startMaintenanceLoop((fn) => this.runSharedOperation("store-maintenance", undefined, () => fn()));
|
|
1272
|
+
const currentGeneration = (0, embedding_generation_1.resolveEmbeddingGeneration)((0, index_config_1.readGlobalConfig)());
|
|
1273
|
+
const configChanged = currentGeneration.fingerprint !== targetGeneration.fingerprint;
|
|
1274
|
+
if (failures.length === 0) {
|
|
1275
|
+
(0, project_registry_1.completeProjectRebuild)(reservation.rebuildId);
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
completed,
|
|
1279
|
+
total: reservation.reserved.length,
|
|
1280
|
+
failures,
|
|
1281
|
+
configChanged,
|
|
1282
|
+
generation: targetGeneration.fingerprint,
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
catch (error) {
|
|
1286
|
+
if (!dropCommitted) {
|
|
1287
|
+
this.ready = false;
|
|
1288
|
+
this.resources = null;
|
|
1289
|
+
let recoveryError;
|
|
1290
|
+
yield (targetPool === null || targetPool === void 0 ? void 0 : targetPool.destroy({ requireExit: true }).catch((cause) => {
|
|
1291
|
+
recoveryError !== null && recoveryError !== void 0 ? recoveryError : (recoveryError = cause);
|
|
1292
|
+
}));
|
|
1293
|
+
if (targetDb) {
|
|
1294
|
+
yield targetDb
|
|
1295
|
+
.close({ releaseLease: false, requireClosed: true })
|
|
1296
|
+
.catch((cause) => {
|
|
1297
|
+
recoveryError !== null && recoveryError !== void 0 ? recoveryError : (recoveryError = cause);
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
try {
|
|
1301
|
+
(0, project_registry_1.restoreProjectsAfterRebuild)(reservation);
|
|
1302
|
+
}
|
|
1303
|
+
catch (cause) {
|
|
1304
|
+
recoveryError !== null && recoveryError !== void 0 ? recoveryError : (recoveryError = cause);
|
|
1305
|
+
}
|
|
1306
|
+
if (!recoveryError && !oldPoolDestroyed) {
|
|
1307
|
+
try {
|
|
1308
|
+
yield oldResources.vectorDb.downgradeStoreLease();
|
|
1309
|
+
oldResources.vectorDb.resumeMaintenanceLoop();
|
|
1310
|
+
this.resources = oldResources;
|
|
1311
|
+
this.activeConfig = oldResources.config;
|
|
1312
|
+
this.activeGeneration = oldResources.embedding;
|
|
1313
|
+
this.vectorDb = oldResources.vectorDb;
|
|
1314
|
+
this.workerPool = oldResources.workerPool;
|
|
1315
|
+
this.ready = true;
|
|
1316
|
+
}
|
|
1317
|
+
catch (cause) {
|
|
1318
|
+
recoveryError = cause;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
else if (!recoveryError) {
|
|
1322
|
+
let restoredDb = null;
|
|
1323
|
+
let restoredPool = null;
|
|
1324
|
+
try {
|
|
1325
|
+
restoredDb = oldDbClosed
|
|
1326
|
+
? this.createVectorDb(oldResources.embedding.vectorDim, exclusiveLease)
|
|
1327
|
+
: oldResources.vectorDb;
|
|
1328
|
+
if (oldResources.config.embedMode === "gpu" &&
|
|
1329
|
+
oldResources.mlx === "owned") {
|
|
1330
|
+
yield this.mlxServerManager.ensureMlxServer(oldResources.embedding.mlxModel);
|
|
1331
|
+
}
|
|
1332
|
+
restoredPool = this.createWorkerPool(oldResources.embedding, oldResources.config.embedMode);
|
|
1333
|
+
yield restoredDb.downgradeStoreLease();
|
|
1334
|
+
restoredDb.startMaintenanceLoop((fn) => this.runSharedOperation("store-maintenance", undefined, () => fn()));
|
|
1335
|
+
this.publishResourceGeneration(oldResources.config, oldResources.embedding, restoredDb, restoredPool, this.mlxMode(oldResources.config));
|
|
1336
|
+
this.ready = true;
|
|
1337
|
+
}
|
|
1338
|
+
catch (cause) {
|
|
1339
|
+
recoveryError = cause;
|
|
1340
|
+
yield (restoredPool === null || restoredPool === void 0 ? void 0 : restoredPool.destroy({ requireExit: true }).catch(() => { }));
|
|
1341
|
+
if (restoredDb) {
|
|
1342
|
+
yield restoredDb.close().catch(() => { });
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
yield exclusiveLease.release().catch(() => { });
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (recoveryError) {
|
|
1350
|
+
if (!oldPoolDestroyed) {
|
|
1351
|
+
yield oldResources.workerPool
|
|
1352
|
+
.destroy({ requireExit: true })
|
|
1353
|
+
.catch((cause) => {
|
|
1354
|
+
recoveryError = new Error(`Pre-drop rebuild pool cleanup failed: ${String(recoveryError)}; ${String(cause)}`);
|
|
1355
|
+
});
|
|
1356
|
+
oldPoolDestroyed = true;
|
|
1357
|
+
}
|
|
1358
|
+
if (!oldDbClosed) {
|
|
1359
|
+
yield oldResources.vectorDb.close().catch((cause) => {
|
|
1360
|
+
recoveryError = new Error(`Pre-drop rebuild DB cleanup failed: ${String(recoveryError)}; ${String(cause)}`);
|
|
1361
|
+
});
|
|
1362
|
+
oldDbClosed = true;
|
|
1363
|
+
}
|
|
1364
|
+
yield exclusiveLease.release().catch((cause) => {
|
|
1365
|
+
recoveryError = new Error(`Pre-drop rebuild cleanup failed: ${String(recoveryError)}; ${String(cause)}`);
|
|
1366
|
+
});
|
|
1367
|
+
this.resources = null;
|
|
1368
|
+
this.vectorDb = null;
|
|
1369
|
+
this.workerPool = null;
|
|
1370
|
+
this.ready = false;
|
|
1371
|
+
throw new Error(`Pre-drop rebuild recovery failed: ${String(error)}; ${String(recoveryError)}`);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
else {
|
|
1375
|
+
this.ready = false;
|
|
1376
|
+
this.resources = null;
|
|
1377
|
+
this.activeConfig = targetConfig;
|
|
1378
|
+
this.activeGeneration = targetGeneration;
|
|
1379
|
+
this.vectorDb = targetDb;
|
|
1380
|
+
this.workerPool = published ? targetPool : null;
|
|
1381
|
+
if (targetDb) {
|
|
1382
|
+
yield targetDb.downgradeStoreLease().catch(() => { });
|
|
1383
|
+
}
|
|
1384
|
+
else {
|
|
1385
|
+
yield exclusiveLease.release().catch(() => { });
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
throw error;
|
|
1389
|
+
}
|
|
1390
|
+
}));
|
|
1391
|
+
for (const root of desiredWatchRoots) {
|
|
1392
|
+
if (((_a = (0, project_registry_1.getProject)(root)) === null || _a === void 0 ? void 0 : _a.status) !== "indexed")
|
|
1393
|
+
continue;
|
|
1394
|
+
try {
|
|
1395
|
+
yield this.watcherManager.watchProject(root, { catchup: false });
|
|
1396
|
+
}
|
|
1397
|
+
catch (error) {
|
|
1398
|
+
result.failures.push({
|
|
1399
|
+
root,
|
|
1400
|
+
error: `watch restore failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
775
1403
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1404
|
+
watchersRestored = true;
|
|
1405
|
+
try {
|
|
1406
|
+
yield this.watcherManager.catchupAll(desiredWatchRoots);
|
|
1407
|
+
}
|
|
1408
|
+
catch (error) {
|
|
1409
|
+
result.failures.push({
|
|
1410
|
+
root: "*",
|
|
1411
|
+
error: `watch catchup failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
(0, ipc_handler_1.writeDone)(conn, Object.assign(Object.assign({ ok: result.failures.length === 0 }, result), (result.configChanged
|
|
1415
|
+
? {
|
|
1416
|
+
warning: "configured embedding changed during rebuild; another rebuild is required",
|
|
1417
|
+
}
|
|
1418
|
+
: {})));
|
|
783
1419
|
}
|
|
784
|
-
catch (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
let reportedError = error;
|
|
1422
|
+
if (!dropCommitted && !watchersRestored && this.ready && this.resources) {
|
|
1423
|
+
try {
|
|
1424
|
+
yield this.watcherManager.resumeAll(desiredWatchRoots, {
|
|
1425
|
+
catchup: false,
|
|
1426
|
+
});
|
|
1427
|
+
watchersRestored = true;
|
|
1428
|
+
yield this.watcherManager.catchupAll(desiredWatchRoots);
|
|
1429
|
+
}
|
|
1430
|
+
catch (watchError) {
|
|
1431
|
+
this.ready = false;
|
|
1432
|
+
this.resources = null;
|
|
1433
|
+
reportedError = new Error(`Pre-drop rebuild watcher restoration failed: ${String(error)}; ${String(watchError)}`);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
const details = reportedError instanceof store_lease_1.StoreLeaseTimeoutError
|
|
1437
|
+
? { blockers: reportedError.blockers }
|
|
1438
|
+
: {};
|
|
1439
|
+
(0, ipc_handler_1.writeDone)(conn, Object.assign({ ok: false, error: reportedError instanceof Error
|
|
1440
|
+
? reportedError.message
|
|
1441
|
+
: String(reportedError), degraded: dropCommitted }, details));
|
|
794
1442
|
}
|
|
795
1443
|
finally {
|
|
1444
|
+
conn.off("close", onClose);
|
|
796
1445
|
stopHeartbeat();
|
|
797
|
-
this.
|
|
798
|
-
|
|
1446
|
+
if (!this.ready) {
|
|
1447
|
+
setImmediate(() => void this.shutdown());
|
|
1448
|
+
}
|
|
799
1449
|
}
|
|
800
1450
|
});
|
|
801
1451
|
}
|
|
802
1452
|
removeProject(root, conn) {
|
|
803
1453
|
return __awaiter(this, void 0, void 0, function* () {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
this.metaCache.delete(key);
|
|
1454
|
+
const ac = new AbortController();
|
|
1455
|
+
const onClose = () => ac.abort();
|
|
1456
|
+
conn.once("close", onClose);
|
|
1457
|
+
this.shutdownAbortControllers.add(ac);
|
|
1458
|
+
try {
|
|
1459
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("remove-project", ac.signal, () => __awaiter(this, void 0, void 0, function* () {
|
|
1460
|
+
if (!this.vectorDb || !this.metaCache) {
|
|
1461
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1462
|
+
ok: false,
|
|
1463
|
+
error: "daemon resources not ready",
|
|
1464
|
+
});
|
|
1465
|
+
return;
|
|
817
1466
|
}
|
|
818
|
-
stopHeartbeat();
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1467
|
+
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
1468
|
+
try {
|
|
1469
|
+
yield this.unwatchProjectWithinOperation(root);
|
|
1470
|
+
const rootPrefix = root.endsWith("/") ? root : `${root}/`;
|
|
1471
|
+
yield this.vectorDb.deletePathsWithPrefix(rootPrefix);
|
|
1472
|
+
const keys = yield this.metaCache.getKeysWithPrefix(rootPrefix);
|
|
1473
|
+
for (const key of keys)
|
|
1474
|
+
this.metaCache.delete(key);
|
|
1475
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: true });
|
|
1476
|
+
}
|
|
1477
|
+
catch (err) {
|
|
1478
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1479
|
+
console.error(`[daemon] removeProject failed for ${path.basename(root)}:`, msg);
|
|
1480
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
1481
|
+
}
|
|
1482
|
+
finally {
|
|
1483
|
+
stopHeartbeat();
|
|
1484
|
+
}
|
|
1485
|
+
})));
|
|
1486
|
+
}
|
|
1487
|
+
finally {
|
|
1488
|
+
conn.off("close", onClose);
|
|
1489
|
+
this.shutdownAbortControllers.delete(ac);
|
|
1490
|
+
}
|
|
831
1491
|
});
|
|
832
1492
|
}
|
|
833
1493
|
summarizeProject(root, conn, opts) {
|
|
834
1494
|
return __awaiter(this, void 0, void 0, function* () {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1495
|
+
const ac = new AbortController();
|
|
1496
|
+
const onClose = () => ac.abort();
|
|
1497
|
+
conn.once("close", onClose);
|
|
1498
|
+
this.shutdownAbortControllers.add(ac);
|
|
1499
|
+
try {
|
|
1500
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("summarize-project", ac.signal, () => __awaiter(this, void 0, void 0, function* () {
|
|
1501
|
+
var _a;
|
|
1502
|
+
if (!this.vectorDb) {
|
|
1503
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1504
|
+
ok: false,
|
|
1505
|
+
error: "daemon resources not ready",
|
|
1506
|
+
});
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const rootPrefix = (_a = opts.pathPrefix) !== null && _a !== void 0 ? _a : (root.endsWith("/") ? root : `${root}/`);
|
|
1510
|
+
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
1511
|
+
let lastProgressTime = 0;
|
|
1512
|
+
try {
|
|
1513
|
+
const result = yield (0, syncer_1.generateSummaries)(this.vectorDb, rootPrefix, (done, total) => {
|
|
1514
|
+
this.resetActivity();
|
|
1515
|
+
const now = Date.now();
|
|
1516
|
+
if (now - lastProgressTime < 100)
|
|
1517
|
+
return;
|
|
1518
|
+
lastProgressTime = now;
|
|
1519
|
+
(0, ipc_handler_1.writeProgress)(conn, { summarized: done, total });
|
|
1520
|
+
}, opts.limit);
|
|
1521
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1522
|
+
ok: true,
|
|
1523
|
+
summarized: result.summarized,
|
|
1524
|
+
remaining: result.remaining,
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
catch (err) {
|
|
1528
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1529
|
+
console.error(`[daemon] summarizeProject failed for ${path.basename(root)}:`, msg);
|
|
1530
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
1531
|
+
}
|
|
1532
|
+
finally {
|
|
1533
|
+
stopHeartbeat();
|
|
1534
|
+
}
|
|
1535
|
+
})));
|
|
1536
|
+
}
|
|
1537
|
+
finally {
|
|
1538
|
+
conn.off("close", onClose);
|
|
1539
|
+
this.shutdownAbortControllers.delete(ac);
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
// --- LLM server management ---
|
|
1544
|
+
llmStart() {
|
|
1545
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1546
|
+
return this.runSharedOperation("llm-start", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
1547
|
+
if (!this.llmServer)
|
|
1548
|
+
return { ok: false, error: "daemon not initialized" };
|
|
844
1549
|
try {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
if (now - lastProgressTime < 100)
|
|
849
|
-
return;
|
|
850
|
-
lastProgressTime = now;
|
|
851
|
-
(0, ipc_handler_1.writeProgress)(conn, { summarized: done, total });
|
|
852
|
-
}, opts.limit);
|
|
853
|
-
stopHeartbeat();
|
|
854
|
-
(0, ipc_handler_1.writeDone)(conn, {
|
|
855
|
-
ok: true,
|
|
856
|
-
summarized: result.summarized,
|
|
857
|
-
remaining: result.remaining,
|
|
858
|
-
});
|
|
1550
|
+
yield this.llmServer.start();
|
|
1551
|
+
this.resetActivity();
|
|
1552
|
+
return Object.assign({ ok: true }, this.llmServer.getStatus());
|
|
859
1553
|
}
|
|
860
1554
|
catch (err) {
|
|
861
1555
|
const msg = err instanceof Error ? err.message : String(err);
|
|
862
|
-
|
|
863
|
-
stopHeartbeat();
|
|
864
|
-
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
865
|
-
}
|
|
866
|
-
finally {
|
|
867
|
-
stopHeartbeat();
|
|
1556
|
+
return { ok: false, error: msg };
|
|
868
1557
|
}
|
|
869
1558
|
}));
|
|
870
1559
|
});
|
|
871
1560
|
}
|
|
872
|
-
// --- LLM server management ---
|
|
873
|
-
llmStart() {
|
|
874
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
875
|
-
if (!this.llmServer)
|
|
876
|
-
return { ok: false, error: "daemon not initialized" };
|
|
877
|
-
try {
|
|
878
|
-
yield this.llmServer.start();
|
|
879
|
-
this.resetActivity();
|
|
880
|
-
return Object.assign({ ok: true }, this.llmServer.getStatus());
|
|
881
|
-
}
|
|
882
|
-
catch (err) {
|
|
883
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
884
|
-
return { ok: false, error: msg };
|
|
885
|
-
}
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
1561
|
llmStop() {
|
|
889
1562
|
return __awaiter(this, void 0, void 0, function* () {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1563
|
+
return this.runSharedOperation("llm-stop", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
1564
|
+
if (!this.llmServer)
|
|
1565
|
+
return { ok: false, error: "daemon not initialized" };
|
|
1566
|
+
try {
|
|
1567
|
+
yield this.llmServer.stop();
|
|
1568
|
+
return { ok: true };
|
|
1569
|
+
}
|
|
1570
|
+
catch (err) {
|
|
1571
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1572
|
+
return { ok: false, error: msg };
|
|
1573
|
+
}
|
|
1574
|
+
}));
|
|
900
1575
|
});
|
|
901
1576
|
}
|
|
902
1577
|
llmStatus() {
|
|
@@ -910,20 +1585,22 @@ class Daemon {
|
|
|
910
1585
|
}
|
|
911
1586
|
reviewCommit(root, commitRef) {
|
|
912
1587
|
return __awaiter(this, void 0, void 0, function* () {
|
|
913
|
-
this.
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1588
|
+
return this.runSharedOperation("review", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
1589
|
+
this.resetActivity();
|
|
1590
|
+
try {
|
|
1591
|
+
if (!this.llmServer) {
|
|
1592
|
+
console.log("[review] daemon not initialized, skipping");
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
yield this.llmServer.ensure();
|
|
1596
|
+
const { reviewCommit } = yield Promise.resolve().then(() => __importStar(require("../llm/review")));
|
|
1597
|
+
const result = yield reviewCommit({ commitRef, projectRoot: root });
|
|
1598
|
+
console.log(`[review] ${result.commit} — ${result.findingCount} finding(s) in ${result.duration}s`);
|
|
918
1599
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
catch (err) {
|
|
925
|
-
console.error(`[review] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
926
|
-
}
|
|
1600
|
+
catch (err) {
|
|
1601
|
+
console.error(`[review] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1602
|
+
}
|
|
1603
|
+
}));
|
|
927
1604
|
});
|
|
928
1605
|
}
|
|
929
1606
|
/**
|
|
@@ -945,7 +1622,7 @@ class Daemon {
|
|
|
945
1622
|
// Defer while busy; we'll re-check next tick.
|
|
946
1623
|
if ((_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.isMaintenanceActive())
|
|
947
1624
|
return;
|
|
948
|
-
if (this.
|
|
1625
|
+
if (this.projectMutex.pending > 0 || this.operations.activeCount > 0)
|
|
949
1626
|
return;
|
|
950
1627
|
const reason = ageExceeded
|
|
951
1628
|
? `age ${(ageMs / 3600000).toFixed(1)}h > ${(MAX_LIFETIME_MS / 3600000).toFixed(1)}h`
|
|
@@ -954,22 +1631,43 @@ class Daemon {
|
|
|
954
1631
|
this.recycling = true;
|
|
955
1632
|
void this.shutdown({ relaunch: true }).finally(() => process.exit(0));
|
|
956
1633
|
}
|
|
957
|
-
shutdown() {
|
|
958
|
-
|
|
1634
|
+
shutdown(opts = {}) {
|
|
1635
|
+
if (!this.shutdownPromise) {
|
|
1636
|
+
this.shutdownPromise = this.performShutdown(opts);
|
|
1637
|
+
}
|
|
1638
|
+
return this.shutdownPromise;
|
|
1639
|
+
}
|
|
1640
|
+
performShutdown(opts) {
|
|
1641
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
959
1642
|
var _a, _b, _c, _d;
|
|
960
|
-
if (this.shuttingDown)
|
|
961
|
-
return;
|
|
962
1643
|
this.shuttingDown = true;
|
|
1644
|
+
this.ready = false;
|
|
1645
|
+
const strictResourceShutdown = this.hasUnfinishedRebuild();
|
|
963
1646
|
console.log("[daemon] Shutting down...");
|
|
964
1647
|
// Announce graceful shutdown BEFORE dropping the liveness markers below, so a
|
|
965
1648
|
// successor spawned during the (possibly long) drain sees the draining marker
|
|
966
1649
|
// and defers instead of SIGKILLing us mid-cleanup.
|
|
967
1650
|
(0, daemon_client_1.writeDrainingMarker)(process.pid);
|
|
968
|
-
//
|
|
969
|
-
//
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1651
|
+
// Stop accepting new IPC before draining admitted operations. Existing
|
|
1652
|
+
// sockets are destroyed below so their close handlers cancel queued work.
|
|
1653
|
+
const server = this.server;
|
|
1654
|
+
this.server = null;
|
|
1655
|
+
const serverClosed = new Promise((resolve) => {
|
|
1656
|
+
if (!(server === null || server === void 0 ? void 0 : server.listening)) {
|
|
1657
|
+
resolve();
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
server.close(() => resolve());
|
|
1661
|
+
});
|
|
1662
|
+
for (const connection of this.connections)
|
|
1663
|
+
connection.end();
|
|
1664
|
+
const forceCloseConnections = setTimeout(() => {
|
|
1665
|
+
for (const connection of this.connections)
|
|
1666
|
+
connection.destroy();
|
|
1667
|
+
}, 1000);
|
|
1668
|
+
forceCloseConnections.unref();
|
|
1669
|
+
// Drop external liveness markers now so interrupted cleanup cannot leave a
|
|
1670
|
+
// fresh-looking daemon that silently blocks its successor.
|
|
973
1671
|
try {
|
|
974
1672
|
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
975
1673
|
}
|
|
@@ -981,44 +1679,60 @@ class Daemon {
|
|
|
981
1679
|
if (this.releaseLock) {
|
|
982
1680
|
const release = this.releaseLock;
|
|
983
1681
|
this.releaseLock = null;
|
|
984
|
-
|
|
1682
|
+
try {
|
|
1683
|
+
yield release();
|
|
1684
|
+
}
|
|
1685
|
+
catch (_g) { }
|
|
985
1686
|
}
|
|
986
1687
|
if (this.heartbeatInterval)
|
|
987
1688
|
clearInterval(this.heartbeatInterval);
|
|
988
1689
|
if (this.idleInterval)
|
|
989
1690
|
clearInterval(this.idleInterval);
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
ac.abort();
|
|
993
|
-
}
|
|
994
|
-
// Wait for in-flight project operations to finish (they check shuttingDown/signal)
|
|
995
|
-
const pendingLocks = [...this.projectLocks.values()];
|
|
996
|
-
if (pendingLocks.length > 0) {
|
|
997
|
-
console.log(`[daemon] Waiting for ${pendingLocks.length} in-flight operation(s)...`);
|
|
998
|
-
yield Promise.allSettled(pendingLocks);
|
|
1691
|
+
for (const timer of this.pendingIndexRetryTimers.values()) {
|
|
1692
|
+
clearTimeout(timer);
|
|
999
1693
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1694
|
+
this.pendingIndexRetryTimers.clear();
|
|
1695
|
+
this.pendingIndexRetryCounts.clear();
|
|
1696
|
+
// Abort explicit operation controllers, close coordinator admission, and
|
|
1697
|
+
// reject queued project waiters. Start these drains before watcher teardown
|
|
1698
|
+
// so no timer or socket can admit replacement work.
|
|
1699
|
+
for (const ac of this.shutdownAbortControllers) {
|
|
1700
|
+
ac.abort(new operation_coordinator_1.OperationClosedError());
|
|
1003
1701
|
}
|
|
1702
|
+
const operationDrain = this.operations.close();
|
|
1703
|
+
const projectDrain = this.projectMutex.close();
|
|
1704
|
+
// Abort catchup/recovery and remove each processor before worker teardown.
|
|
1705
|
+
yield this.watcherManager.quiesceAll();
|
|
1706
|
+
yield Promise.all([operationDrain, projectDrain]);
|
|
1004
1707
|
// Stop LLM server if running
|
|
1005
1708
|
try {
|
|
1006
1709
|
yield ((_a = this.llmServer) === null || _a === void 0 ? void 0 : _a.stop());
|
|
1007
1710
|
}
|
|
1008
|
-
catch (
|
|
1009
|
-
// Stop MLX embed server if we started it
|
|
1010
|
-
this.mlxServerManager.stopMlxServer();
|
|
1711
|
+
catch (_h) { }
|
|
1011
1712
|
// Destroy worker pool to prevent orphaned child processes
|
|
1713
|
+
if (this.workerPool) {
|
|
1714
|
+
if (strictResourceShutdown) {
|
|
1715
|
+
yield this.workerPool.destroy({
|
|
1716
|
+
requireExit: true,
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
else {
|
|
1720
|
+
try {
|
|
1721
|
+
yield this.workerPool.destroy();
|
|
1722
|
+
}
|
|
1723
|
+
catch (_j) { }
|
|
1724
|
+
}
|
|
1725
|
+
this.workerPool = null;
|
|
1726
|
+
this.resources = null;
|
|
1727
|
+
}
|
|
1012
1728
|
if ((0, pool_1.isWorkerPoolInitialized)()) {
|
|
1013
1729
|
try {
|
|
1014
1730
|
yield (0, pool_1.destroyWorkerPool)();
|
|
1015
1731
|
}
|
|
1016
|
-
catch (
|
|
1732
|
+
catch (_k) { }
|
|
1017
1733
|
}
|
|
1018
|
-
// Stop
|
|
1019
|
-
yield this.
|
|
1020
|
-
// Close server (socket/pid/lock already dropped at the top of shutdown)
|
|
1021
|
-
(_b = this.server) === null || _b === void 0 ? void 0 : _b.close();
|
|
1734
|
+
// Stop MLX embed server only after worker requests have drained.
|
|
1735
|
+
yield this.mlxServerManager.stopMlxServer();
|
|
1022
1736
|
// Unregister all
|
|
1023
1737
|
for (const root of this.processors.keys()) {
|
|
1024
1738
|
(0, watcher_store_1.unregisterWatcherByRoot)(root);
|
|
@@ -1027,19 +1741,27 @@ class Daemon {
|
|
|
1027
1741
|
this.processors.clear();
|
|
1028
1742
|
// Close shared resources
|
|
1029
1743
|
try {
|
|
1030
|
-
yield ((
|
|
1744
|
+
yield ((_b = this.metaCache) === null || _b === void 0 ? void 0 : _b.close());
|
|
1031
1745
|
}
|
|
1032
|
-
catch (
|
|
1033
|
-
|
|
1034
|
-
yield ((
|
|
1746
|
+
catch (_l) { }
|
|
1747
|
+
if (strictResourceShutdown) {
|
|
1748
|
+
yield ((_c = this.vectorDb) === null || _c === void 0 ? void 0 : _c.close({ requireClosed: true }));
|
|
1749
|
+
}
|
|
1750
|
+
else {
|
|
1751
|
+
try {
|
|
1752
|
+
yield ((_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.close());
|
|
1753
|
+
}
|
|
1754
|
+
catch (_m) { }
|
|
1035
1755
|
}
|
|
1036
|
-
|
|
1756
|
+
yield serverClosed;
|
|
1757
|
+
clearTimeout(forceCloseConnections);
|
|
1758
|
+
this.connections.clear();
|
|
1037
1759
|
// Hand off to a successor only after every resource is released and the
|
|
1038
1760
|
// liveness markers (socket/pid/lock) are already gone — so the fresh
|
|
1039
1761
|
// daemon's singleton check sees a clean slate and opens LanceDB/LMDB
|
|
1040
1762
|
// without contending with this exiting process.
|
|
1041
1763
|
if (opts.relaunch) {
|
|
1042
|
-
const pid = (0, daemon_launcher_1.spawnDaemon)();
|
|
1764
|
+
const pid = yield (0, daemon_launcher_1.spawnDaemon)();
|
|
1043
1765
|
console.log(`[daemon] Spawned successor daemon${pid ? ` (PID: ${pid})` : " (spawn failed)"}`);
|
|
1044
1766
|
}
|
|
1045
1767
|
// Cleanly drained — drop the marker so a later start doesn't defer to a
|