grepmax 0.24.0 → 0.25.1
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 +15 -6
- package/dist/commands/add.js +38 -31
- package/dist/commands/config.js +22 -15
- package/dist/commands/context.js +38 -13
- package/dist/commands/doctor.js +86 -83
- package/dist/commands/extract.js +12 -1
- package/dist/commands/index.js +23 -24
- package/dist/commands/list.js +31 -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 +11 -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 +1108 -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 +37 -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,161 @@ 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
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
472
|
+
if ((project === null || project === void 0 ? void 0 : project.status) === "indexed" &&
|
|
473
|
+
this.activeGeneration &&
|
|
474
|
+
(0, embedding_generation_1.compareEmbeddingGeneration)(project, this.activeGeneration).state ===
|
|
475
|
+
"stale") {
|
|
476
|
+
throw new Error("project embedding generation is stale; run gmax repair --rebuild to rebuild the whole corpus");
|
|
477
|
+
}
|
|
478
|
+
return this.watcherManager.watchProject(root);
|
|
479
|
+
}
|
|
480
|
+
runSharedOperation(name, signal, fn) {
|
|
481
|
+
return this.operations.runShared(name, signal, fn);
|
|
482
|
+
}
|
|
483
|
+
operationStatus() {
|
|
484
|
+
return this.operations.status;
|
|
485
|
+
}
|
|
486
|
+
resourceGenerationId() {
|
|
487
|
+
var _a, _b;
|
|
488
|
+
return (_b = (_a = this.resources) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
|
|
489
|
+
}
|
|
490
|
+
hasUnfinishedRebuild() {
|
|
491
|
+
try {
|
|
492
|
+
return (0, project_registry_1.hasUnfinishedProjectRebuild)();
|
|
493
|
+
}
|
|
494
|
+
catch (_a) {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
schedulePendingIndexRetry(root) {
|
|
499
|
+
var _a;
|
|
500
|
+
if (this.shuttingDown || this.pendingIndexRetryTimers.has(root))
|
|
501
|
+
return;
|
|
502
|
+
const attempts = (_a = this.pendingIndexRetryCounts.get(root)) !== null && _a !== void 0 ? _a : 0;
|
|
503
|
+
if (attempts >= 5)
|
|
504
|
+
return;
|
|
505
|
+
this.pendingIndexRetryCounts.set(root, attempts + 1);
|
|
506
|
+
const timer = setTimeout(() => {
|
|
507
|
+
this.pendingIndexRetryTimers.delete(root);
|
|
508
|
+
if (!this.shuttingDown)
|
|
509
|
+
void this.indexPendingProject(root);
|
|
510
|
+
}, 30000);
|
|
511
|
+
timer.unref();
|
|
512
|
+
this.pendingIndexRetryTimers.set(root, timer);
|
|
513
|
+
}
|
|
514
|
+
clearPendingIndexRetry(root) {
|
|
515
|
+
const timer = this.pendingIndexRetryTimers.get(root);
|
|
516
|
+
if (timer)
|
|
517
|
+
clearTimeout(timer);
|
|
518
|
+
this.pendingIndexRetryTimers.delete(root);
|
|
519
|
+
this.pendingIndexRetryCounts.delete(root);
|
|
397
520
|
}
|
|
398
521
|
indexPendingProject(root) {
|
|
399
522
|
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
|
-
|
|
523
|
+
const ac = new AbortController();
|
|
524
|
+
this.shutdownAbortControllers.add(ac);
|
|
525
|
+
try {
|
|
526
|
+
yield this.withProjectLock(root, ac.signal, () => __awaiter(this, void 0, void 0, function* () {
|
|
527
|
+
return this.runSharedOperation("index-pending", ac.signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
528
|
+
var _a, _b, _c, _d;
|
|
529
|
+
// Bail if shutdown raced ahead of us between iteration and lock
|
|
530
|
+
// acquisition. Starting now would race the store close below.
|
|
531
|
+
if (this.shuttingDown)
|
|
532
|
+
return;
|
|
533
|
+
if (!this.vectorDb || !this.metaCache)
|
|
534
|
+
return;
|
|
535
|
+
const current = (0, project_registry_1.getProject)(root);
|
|
536
|
+
if ((current === null || current === void 0 ? void 0 : current.status) !== "pending" && (current === null || current === void 0 ? void 0 : current.status) !== "error")
|
|
537
|
+
return;
|
|
538
|
+
const name = path.basename(root);
|
|
539
|
+
const start = Date.now();
|
|
540
|
+
(0, logger_1.log)("daemon", `indexPendingProject start: ${name} (${root})`);
|
|
541
|
+
this.vectorDb.pauseMaintenanceLoop();
|
|
542
|
+
try {
|
|
543
|
+
if (this.processors.has(root))
|
|
544
|
+
yield this.unwatchProjectWithinOperation(root);
|
|
545
|
+
const result = yield (0, syncer_1.initialSync)({
|
|
546
|
+
projectRoot: root,
|
|
547
|
+
vectorDb: this.vectorDb,
|
|
548
|
+
metaCache: this.metaCache,
|
|
549
|
+
signal: operationSignal,
|
|
550
|
+
generation: (_a = this.activeGeneration) !== null && _a !== void 0 ? _a : undefined,
|
|
551
|
+
embedMode: (_b = this.activeConfig) === null || _b === void 0 ? void 0 : _b.embedMode,
|
|
552
|
+
workerPool: (_c = this.workerPool) !== null && _c !== void 0 ? _c : undefined,
|
|
553
|
+
onProgress: () => {
|
|
554
|
+
this.resetActivity();
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
558
|
+
const chunkCount = yield this.vectorDb.countRowsForPath(prefix);
|
|
559
|
+
const proj = (0, project_registry_1.getProject)(root);
|
|
560
|
+
if (proj) {
|
|
561
|
+
if (result.degraded) {
|
|
562
|
+
(0, project_registry_1.registerProject)(Object.assign(Object.assign({}, proj), { status: "pending" }));
|
|
563
|
+
this.schedulePendingIndexRetry(root);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
this.clearPendingIndexRetry(root);
|
|
567
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
568
|
+
root,
|
|
569
|
+
name: proj.name,
|
|
570
|
+
generation: result.generation,
|
|
571
|
+
embedMode: result.embedMode,
|
|
572
|
+
chunkCount,
|
|
573
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
574
|
+
expectedFingerprint: result.registryExpectation.embeddingFingerprint,
|
|
575
|
+
expectedRebuildId: result.registryExpectation.rebuildId,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
(0, logger_1.log)("daemon", `indexPendingProject done: ${name} — ${result.total} files, ${result.indexed} chunks, ${Date.now() - start}ms`);
|
|
580
|
+
}
|
|
581
|
+
catch (err) {
|
|
582
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
583
|
+
console.error(`[daemon] indexPendingProject failed for ${name} after ${Date.now() - start}ms: ${msg}`);
|
|
584
|
+
const proj = (0, project_registry_1.getProject)(root);
|
|
585
|
+
if (proj && !(err instanceof project_registry_1.ProjectRegistryConflictError)) {
|
|
586
|
+
(0, project_registry_1.registerProject)(Object.assign(Object.assign({}, proj), { status: "error" }));
|
|
587
|
+
this.schedulePendingIndexRetry(root);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
finally {
|
|
591
|
+
(_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.resumeMaintenanceLoop();
|
|
592
|
+
if (!this.shuttingDown) {
|
|
593
|
+
try {
|
|
594
|
+
yield this.watchProjectWithinOperation(root);
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
console.error(`[daemon] Failed to re-watch ${name}:`, err);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}));
|
|
602
|
+
}));
|
|
603
|
+
}
|
|
604
|
+
finally {
|
|
605
|
+
this.shutdownAbortControllers.delete(ac);
|
|
606
|
+
}
|
|
441
607
|
});
|
|
442
608
|
}
|
|
443
|
-
unwatchProject(root) {
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
|
|
609
|
+
unwatchProject(root, signal) {
|
|
610
|
+
return this.withProjectLock(root, signal, () => this.runSharedOperation("unwatch", signal, () => this.unwatchProjectWithinOperation(root)));
|
|
611
|
+
}
|
|
612
|
+
unwatchProjectWithinOperation(root) {
|
|
613
|
+
return this.watcherManager.unwatchProject(root);
|
|
447
614
|
}
|
|
448
615
|
/**
|
|
449
616
|
* Run a search inside the daemon, reusing the warm VectorDB connection,
|
|
@@ -485,16 +652,20 @@ class Daemon {
|
|
|
485
652
|
// Search handling lives in search-handler.ts (Phase 12 split). The daemon
|
|
486
653
|
// supplies its warm VectorDB + watcher/index bookkeeping; the handler runs
|
|
487
654
|
// the query and assembles the response.
|
|
488
|
-
return (0,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
655
|
+
return this.runSharedOperation("search", signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
656
|
+
return (0, search_handler_1.handleDaemonSearch)({
|
|
657
|
+
vectorDb: this.vectorDb,
|
|
658
|
+
processors: this.processors,
|
|
659
|
+
indexProgress: this.indexProgress,
|
|
660
|
+
searchers: this.searchers,
|
|
661
|
+
getIndexState: (root) => this.indexState(root),
|
|
662
|
+
touchActivity: () => {
|
|
663
|
+
this.lastActivity = Date.now();
|
|
664
|
+
},
|
|
665
|
+
generation: this.activeGeneration,
|
|
666
|
+
workerPool: this.workerPool,
|
|
667
|
+
}, payload, operationSignal);
|
|
668
|
+
}));
|
|
498
669
|
});
|
|
499
670
|
}
|
|
500
671
|
listProjects() {
|
|
@@ -514,90 +685,250 @@ class Daemon {
|
|
|
514
685
|
var _a, _b;
|
|
515
686
|
return (_b = (_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.diskPressure) !== null && _b !== void 0 ? _b : "unknown";
|
|
516
687
|
}
|
|
688
|
+
getMlxStatus() {
|
|
689
|
+
return this.mlxServerManager.getStatus();
|
|
690
|
+
}
|
|
517
691
|
/** Reset idle timer — call during long-running operations. */
|
|
518
692
|
resetActivity() {
|
|
519
693
|
this.lastActivity = Date.now();
|
|
520
694
|
}
|
|
521
695
|
// --- Per-project operation serialization ---
|
|
522
|
-
withProjectLock(root, fn) {
|
|
696
|
+
withProjectLock(root, signal, fn) {
|
|
523
697
|
return __awaiter(this, void 0, void 0, function* () {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
698
|
+
return this.projectMutex.run(root, signal, fn);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
// --- Streaming write operations (IPC) ---
|
|
702
|
+
activeConfigurationError() {
|
|
703
|
+
const activeConfig = this.activeConfig;
|
|
704
|
+
const activeGeneration = this.activeGeneration;
|
|
705
|
+
if (!activeConfig || !activeGeneration)
|
|
706
|
+
return "daemon resources not ready";
|
|
707
|
+
const currentConfig = (0, index_config_1.readGlobalConfig)();
|
|
708
|
+
let currentGeneration;
|
|
709
|
+
try {
|
|
710
|
+
currentGeneration = (0, embedding_generation_1.resolveEmbeddingGeneration)(currentConfig);
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
return error instanceof Error ? error.message : String(error);
|
|
714
|
+
}
|
|
715
|
+
return currentGeneration.fingerprint !== activeGeneration.fingerprint ||
|
|
716
|
+
currentConfig.embedMode !== activeConfig.embedMode ||
|
|
717
|
+
currentConfig.mlxModel !== activeConfig.mlxModel
|
|
718
|
+
? "daemon configuration is stale; restart the gmax daemon"
|
|
719
|
+
: null;
|
|
720
|
+
}
|
|
721
|
+
ensureProject(root, conn) {
|
|
722
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
723
|
+
const ac = new AbortController();
|
|
724
|
+
const onClose = () => ac.abort();
|
|
725
|
+
conn.once("close", onClose);
|
|
726
|
+
this.shutdownAbortControllers.add(ac);
|
|
532
727
|
try {
|
|
533
|
-
|
|
728
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("ensure-project", ac.signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
729
|
+
var _a, _b;
|
|
730
|
+
if (operationSignal.aborted || this.shuttingDown)
|
|
731
|
+
return;
|
|
732
|
+
const activeConfig = this.activeConfig;
|
|
733
|
+
const configurationError = this.activeConfigurationError();
|
|
734
|
+
if (!activeConfig || configurationError) {
|
|
735
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
736
|
+
ok: false,
|
|
737
|
+
error: configurationError !== null && configurationError !== void 0 ? configurationError : "daemon resources not ready",
|
|
738
|
+
});
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
742
|
+
if ((project === null || project === void 0 ? void 0 : project.status) === "indexed") {
|
|
743
|
+
if (!this.activeGeneration ||
|
|
744
|
+
(0, embedding_generation_1.compareEmbeddingGeneration)(project, this.activeGeneration)
|
|
745
|
+
.state === "stale") {
|
|
746
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
747
|
+
ok: false,
|
|
748
|
+
error: "project embedding generation is stale; run gmax repair --rebuild to rebuild the whole corpus",
|
|
749
|
+
});
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
yield this.watchProjectWithinOperation(root);
|
|
753
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: true, status: "indexed", watched: true });
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
(0, project_registry_1.registerProject)({
|
|
757
|
+
root,
|
|
758
|
+
name: (_a = project === null || project === void 0 ? void 0 : project.name) !== null && _a !== void 0 ? _a : path.basename(root),
|
|
759
|
+
vectorDim: activeConfig.vectorDim,
|
|
760
|
+
modelTier: activeConfig.modelTier,
|
|
761
|
+
embedMode: activeConfig.embedMode,
|
|
762
|
+
lastIndexed: (_b = project === null || project === void 0 ? void 0 : project.lastIndexed) !== null && _b !== void 0 ? _b : "",
|
|
763
|
+
chunkCount: project === null || project === void 0 ? void 0 : project.chunkCount,
|
|
764
|
+
status: "pending",
|
|
765
|
+
chunkerVersion: project === null || project === void 0 ? void 0 : project.chunkerVersion,
|
|
766
|
+
});
|
|
767
|
+
yield this.addProjectLocked(root, conn, operationSignal, project);
|
|
768
|
+
})));
|
|
534
769
|
}
|
|
535
770
|
finally {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
this.projectLocks.delete(root);
|
|
539
|
-
}
|
|
771
|
+
conn.off("close", onClose);
|
|
772
|
+
this.shutdownAbortControllers.delete(ac);
|
|
540
773
|
}
|
|
541
774
|
});
|
|
542
775
|
}
|
|
543
|
-
// --- Streaming write operations (IPC) ---
|
|
544
776
|
addProject(root, conn) {
|
|
545
777
|
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);
|
|
778
|
+
const ac = new AbortController();
|
|
779
|
+
const onClose = () => ac.abort();
|
|
780
|
+
conn.once("close", onClose);
|
|
781
|
+
this.shutdownAbortControllers.add(ac);
|
|
782
|
+
try {
|
|
783
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("add-project", ac.signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
784
|
+
if (operationSignal.aborted || this.shuttingDown)
|
|
785
|
+
return;
|
|
786
|
+
const configurationError = this.activeConfigurationError();
|
|
787
|
+
if (configurationError) {
|
|
788
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: configurationError });
|
|
789
|
+
return;
|
|
580
790
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
791
|
+
yield this.addProjectLocked(root, conn, operationSignal, (0, project_registry_1.getProject)(root));
|
|
792
|
+
})));
|
|
793
|
+
}
|
|
794
|
+
finally {
|
|
795
|
+
conn.off("close", onClose);
|
|
796
|
+
this.shutdownAbortControllers.delete(ac);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
addProjectLocked(root, conn, signal, previousProject) {
|
|
801
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
802
|
+
var _a, _b, _c, _d;
|
|
803
|
+
if (!this.vectorDb || !this.metaCache || !this.activeConfig) {
|
|
804
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (!(0, project_registry_1.getProject)(root)) {
|
|
808
|
+
(0, project_registry_1.registerProject)({
|
|
809
|
+
root,
|
|
810
|
+
name: path.basename(root),
|
|
811
|
+
vectorDim: this.activeConfig.vectorDim,
|
|
812
|
+
modelTier: this.activeConfig.modelTier,
|
|
813
|
+
embedMode: this.activeConfig.embedMode,
|
|
814
|
+
lastIndexed: "",
|
|
815
|
+
status: "pending",
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
this.vectorDb.pauseMaintenanceLoop();
|
|
819
|
+
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
820
|
+
let lastProgressTime = 0;
|
|
821
|
+
try {
|
|
822
|
+
const result = yield (0, syncer_1.initialSync)({
|
|
823
|
+
projectRoot: root,
|
|
824
|
+
vectorDb: this.vectorDb,
|
|
825
|
+
metaCache: this.metaCache,
|
|
826
|
+
signal,
|
|
827
|
+
generation: (_a = this.activeGeneration) !== null && _a !== void 0 ? _a : undefined,
|
|
828
|
+
embedMode: this.activeConfig.embedMode,
|
|
829
|
+
workerPool: (_b = this.workerPool) !== null && _b !== void 0 ? _b : undefined,
|
|
830
|
+
onProgress: (info) => {
|
|
831
|
+
this.resetActivity();
|
|
832
|
+
const now = Date.now();
|
|
833
|
+
if (now - lastProgressTime < 100)
|
|
834
|
+
return;
|
|
835
|
+
lastProgressTime = now;
|
|
836
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
837
|
+
processed: info.processed,
|
|
838
|
+
indexed: info.indexed,
|
|
839
|
+
total: info.total,
|
|
840
|
+
filePath: info.filePath,
|
|
841
|
+
});
|
|
842
|
+
},
|
|
843
|
+
});
|
|
844
|
+
if (signal.aborted || this.shuttingDown) {
|
|
845
|
+
const abortError = new Error("Aborted");
|
|
846
|
+
abortError.name = "AbortError";
|
|
847
|
+
throw abortError;
|
|
848
|
+
}
|
|
849
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
850
|
+
const chunkCount = yield this.vectorDb.countRowsForPath(prefix);
|
|
851
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
852
|
+
if (result.degraded) {
|
|
853
|
+
if (previousProject)
|
|
854
|
+
(0, project_registry_1.registerProject)(previousProject);
|
|
855
|
+
this.schedulePendingIndexRetry(root);
|
|
856
|
+
}
|
|
857
|
+
else {
|
|
858
|
+
this.clearPendingIndexRetry(root);
|
|
859
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
860
|
+
root,
|
|
861
|
+
name: (_c = project === null || project === void 0 ? void 0 : project.name) !== null && _c !== void 0 ? _c : path.basename(root),
|
|
862
|
+
generation: result.generation,
|
|
863
|
+
embedMode: result.embedMode,
|
|
864
|
+
chunkCount,
|
|
865
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
866
|
+
expectedFingerprint: result.registryExpectation.embeddingFingerprint,
|
|
867
|
+
expectedRebuildId: result.registryExpectation.rebuildId,
|
|
588
868
|
});
|
|
589
869
|
}
|
|
590
|
-
|
|
870
|
+
yield this.watchProjectWithinOperation(root);
|
|
871
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
872
|
+
ok: true,
|
|
873
|
+
processed: result.processed,
|
|
874
|
+
indexed: result.indexed,
|
|
875
|
+
total: result.total,
|
|
876
|
+
failedFiles: result.failedFiles,
|
|
877
|
+
degraded: result.degraded,
|
|
878
|
+
scanErrors: result.scanErrors,
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
catch (err) {
|
|
882
|
+
const aborted = signal.aborted || (err === null || err === void 0 ? void 0 : err.name) === "AbortError";
|
|
883
|
+
if (aborted) {
|
|
884
|
+
if (previousProject)
|
|
885
|
+
(0, project_registry_1.registerProject)(previousProject);
|
|
886
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "aborted" });
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
591
889
|
const msg = err instanceof Error ? err.message : String(err);
|
|
592
890
|
console.error(`[daemon] addProject failed for ${path.basename(root)}:`, msg);
|
|
593
|
-
|
|
891
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
892
|
+
if (project && !(err instanceof project_registry_1.ProjectRegistryConflictError)) {
|
|
893
|
+
(0, project_registry_1.registerProject)(Object.assign(Object.assign({}, project), { status: "error" }));
|
|
894
|
+
}
|
|
594
895
|
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
595
896
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
897
|
+
}
|
|
898
|
+
finally {
|
|
899
|
+
stopHeartbeat();
|
|
900
|
+
(_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.resumeMaintenanceLoop();
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
projectStats(root) {
|
|
905
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
906
|
+
return this.runSharedOperation("project-stats", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
907
|
+
if (!this.vectorDb)
|
|
908
|
+
throw new Error("daemon resources not ready");
|
|
909
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
910
|
+
if (!project)
|
|
911
|
+
throw new Error("project not registered");
|
|
912
|
+
if (!this.activeConfig)
|
|
913
|
+
throw new Error("daemon resources not ready");
|
|
914
|
+
const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, this.activeConfig);
|
|
915
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
916
|
+
const [chunks, files] = yield Promise.all([
|
|
917
|
+
this.vectorDb.countRowsForPath(prefix),
|
|
918
|
+
this.vectorDb.countDistinctFilesForPath(prefix),
|
|
919
|
+
]);
|
|
920
|
+
return {
|
|
921
|
+
files,
|
|
922
|
+
chunks,
|
|
923
|
+
vectorDim: project.vectorDim,
|
|
924
|
+
modelTier: project.modelTier,
|
|
925
|
+
embedMode: project.embedMode,
|
|
926
|
+
indexedAt: project.lastIndexed,
|
|
927
|
+
watching: this.processors.has(root),
|
|
928
|
+
configuredEmbedding: identity.configured,
|
|
929
|
+
builtEmbedding: identity.built,
|
|
930
|
+
embeddingState: identity.state,
|
|
931
|
+
};
|
|
601
932
|
}));
|
|
602
933
|
});
|
|
603
934
|
}
|
|
@@ -612,20 +943,13 @@ class Daemon {
|
|
|
612
943
|
*/
|
|
613
944
|
reindexOneProject(root, opts, signal, onProgress) {
|
|
614
945
|
return __awaiter(this, void 0, void 0, function* () {
|
|
946
|
+
var _a, _b, _c;
|
|
615
947
|
if (!this.vectorDb || !this.metaCache) {
|
|
616
948
|
throw new Error("daemon resources not ready");
|
|
617
949
|
}
|
|
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
|
-
}
|
|
950
|
+
// Quiesce the subscription, processor, and any catchup generation before
|
|
951
|
+
// full sync takes deletion authority for this project.
|
|
952
|
+
yield this.unwatchProjectWithinOperation(root);
|
|
629
953
|
// Mark this root as full-indexing so concurrent searches get a
|
|
630
954
|
// partial-result footer (Phase 6); seeded at 0/0 until the first tick.
|
|
631
955
|
this.indexProgress.set(root, { processed: 0, total: 0 });
|
|
@@ -637,6 +961,9 @@ class Daemon {
|
|
|
637
961
|
vectorDb: this.vectorDb,
|
|
638
962
|
metaCache: this.metaCache,
|
|
639
963
|
signal,
|
|
964
|
+
generation: (_a = this.activeGeneration) !== null && _a !== void 0 ? _a : undefined,
|
|
965
|
+
embedMode: (_b = this.activeConfig) === null || _b === void 0 ? void 0 : _b.embedMode,
|
|
966
|
+
workerPool: (_c = this.workerPool) !== null && _c !== void 0 ? _c : undefined,
|
|
640
967
|
onProgress: (info) => {
|
|
641
968
|
this.resetActivity();
|
|
642
969
|
this.indexProgress.set(root, {
|
|
@@ -650,9 +977,9 @@ class Daemon {
|
|
|
650
977
|
finally {
|
|
651
978
|
this.indexProgress.delete(root);
|
|
652
979
|
// Re-enable watcher (skip if shutting down)
|
|
653
|
-
if (!this.shuttingDown) {
|
|
980
|
+
if (!this.shuttingDown && opts.rewatch !== false) {
|
|
654
981
|
try {
|
|
655
|
-
yield this.
|
|
982
|
+
yield this.watchProjectWithinOperation(root);
|
|
656
983
|
}
|
|
657
984
|
catch (err) {
|
|
658
985
|
console.error(`[daemon] Failed to re-watch ${path.basename(root)}:`, err);
|
|
@@ -663,52 +990,78 @@ class Daemon {
|
|
|
663
990
|
}
|
|
664
991
|
indexProject(root, conn, opts) {
|
|
665
992
|
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,
|
|
993
|
+
const ac = new AbortController();
|
|
994
|
+
const onClose = () => ac.abort();
|
|
995
|
+
conn.once("close", onClose);
|
|
996
|
+
this.shutdownAbortControllers.add(ac);
|
|
997
|
+
try {
|
|
998
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("index-project", ac.signal, (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
999
|
+
var _a, _b;
|
|
1000
|
+
if (!this.vectorDb || !this.metaCache) {
|
|
1001
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1002
|
+
ok: false,
|
|
1003
|
+
error: "daemon resources not ready",
|
|
689
1004
|
});
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
this.vectorDb.pauseMaintenanceLoop();
|
|
1008
|
+
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
1009
|
+
let lastProgressTime = 0;
|
|
1010
|
+
try {
|
|
1011
|
+
const result = yield this.reindexOneProject(root, opts, operationSignal, (info) => {
|
|
1012
|
+
const now = Date.now();
|
|
1013
|
+
if (now - lastProgressTime < 100)
|
|
1014
|
+
return;
|
|
1015
|
+
lastProgressTime = now;
|
|
1016
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
1017
|
+
processed: info.processed,
|
|
1018
|
+
indexed: info.indexed,
|
|
1019
|
+
total: info.total,
|
|
1020
|
+
filePath: info.filePath,
|
|
1021
|
+
});
|
|
1022
|
+
});
|
|
1023
|
+
if (!opts.dryRun && !result.degraded) {
|
|
1024
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1025
|
+
const chunkCount = yield this.vectorDb.countRowsForPath(prefix);
|
|
1026
|
+
const project = (0, project_registry_1.getProject)(root);
|
|
1027
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
1028
|
+
root,
|
|
1029
|
+
generation: result.generation,
|
|
1030
|
+
embedMode: result.embedMode,
|
|
1031
|
+
chunkCount,
|
|
1032
|
+
chunkerVersion: opts.reset
|
|
1033
|
+
? config_1.CONFIG.CHUNKER_VERSION
|
|
1034
|
+
: ((_a = project === null || project === void 0 ? void 0 : project.chunkerVersion) !== null && _a !== void 0 ? _a : 1),
|
|
1035
|
+
expectedFingerprint: result.registryExpectation.embeddingFingerprint,
|
|
1036
|
+
expectedRebuildId: result.registryExpectation.rebuildId,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1040
|
+
ok: true,
|
|
1041
|
+
processed: result.processed,
|
|
1042
|
+
indexed: result.indexed,
|
|
1043
|
+
total: result.total,
|
|
1044
|
+
failedFiles: result.failedFiles,
|
|
1045
|
+
degraded: result.degraded,
|
|
1046
|
+
scanErrors: result.scanErrors,
|
|
1047
|
+
embeddingFingerprint: result.generation.fingerprint,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
catch (err) {
|
|
1051
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1052
|
+
console.error(`[daemon] indexProject failed for ${path.basename(root)}:`, msg);
|
|
1053
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
1054
|
+
}
|
|
1055
|
+
finally {
|
|
1056
|
+
stopHeartbeat();
|
|
1057
|
+
(_b = this.vectorDb) === null || _b === void 0 ? void 0 : _b.resumeMaintenanceLoop();
|
|
1058
|
+
}
|
|
1059
|
+
})));
|
|
1060
|
+
}
|
|
1061
|
+
finally {
|
|
1062
|
+
conn.off("close", onClose);
|
|
1063
|
+
this.shutdownAbortControllers.delete(ac);
|
|
1064
|
+
}
|
|
712
1065
|
});
|
|
713
1066
|
}
|
|
714
1067
|
/**
|
|
@@ -724,179 +1077,508 @@ class Daemon {
|
|
|
724
1077
|
repairRebuild(conn) {
|
|
725
1078
|
return __awaiter(this, void 0, void 0, function* () {
|
|
726
1079
|
var _a;
|
|
727
|
-
|
|
1080
|
+
const oldResources = this.resources;
|
|
1081
|
+
const metaCache = this.metaCache;
|
|
1082
|
+
if (!oldResources || !metaCache) {
|
|
728
1083
|
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
729
1084
|
return;
|
|
730
1085
|
}
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1086
|
+
const targetConfig = (0, index_config_1.readGlobalConfig)();
|
|
1087
|
+
const targetGeneration = (0, embedding_generation_1.resolveEmbeddingGeneration)(targetConfig);
|
|
1088
|
+
const clientAbort = new AbortController();
|
|
1089
|
+
let dropCommitted = false;
|
|
1090
|
+
let desiredWatchRoots = [];
|
|
1091
|
+
let watchersRestored = false;
|
|
1092
|
+
let oldPoolDestroyed = false;
|
|
1093
|
+
let oldDbClosed = false;
|
|
1094
|
+
const failClosedOldResources = (lease) => __awaiter(this, void 0, void 0, function* () {
|
|
1095
|
+
this.ready = false;
|
|
1096
|
+
this.resources = null;
|
|
1097
|
+
this.vectorDb = null;
|
|
1098
|
+
this.workerPool = null;
|
|
1099
|
+
if (!oldPoolDestroyed) {
|
|
1100
|
+
yield oldResources.workerPool
|
|
1101
|
+
.destroy({ requireExit: true })
|
|
1102
|
+
.catch(() => { });
|
|
1103
|
+
oldPoolDestroyed = true;
|
|
1104
|
+
}
|
|
1105
|
+
if (!oldDbClosed) {
|
|
1106
|
+
yield oldResources.vectorDb.close().catch(() => { });
|
|
1107
|
+
oldDbClosed = true;
|
|
1108
|
+
}
|
|
1109
|
+
yield lease.release().catch(() => { });
|
|
1110
|
+
});
|
|
1111
|
+
const onClose = () => {
|
|
1112
|
+
if (!dropCommitted)
|
|
1113
|
+
clientAbort.abort(new Error("client disconnected"));
|
|
1114
|
+
};
|
|
1115
|
+
conn.once("close", onClose);
|
|
735
1116
|
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1117
|
+
const throwIfPreDropCancelled = (operationSignal) => {
|
|
1118
|
+
if (operationSignal.aborted)
|
|
1119
|
+
throw operationSignal.reason;
|
|
1120
|
+
if (!dropCommitted && clientAbort.signal.aborted) {
|
|
1121
|
+
throw clientAbort.signal.reason;
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
742
1124
|
try {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1125
|
+
const result = yield this.operations.runExclusive("repair", () => __awaiter(this, void 0, void 0, function* () {
|
|
1126
|
+
desiredWatchRoots = yield this.watcherManager.quiesceAll();
|
|
1127
|
+
}), (operationSignal) => __awaiter(this, void 0, void 0, function* () {
|
|
1128
|
+
throwIfPreDropCancelled(operationSignal);
|
|
1129
|
+
oldResources.vectorDb.pauseMaintenanceLoop();
|
|
1130
|
+
this.searchers.clear();
|
|
1131
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
1132
|
+
phase: "lease",
|
|
1133
|
+
message: "waiting for exclusive store ownership",
|
|
1134
|
+
});
|
|
1135
|
+
let exclusiveLease = null;
|
|
1136
|
+
try {
|
|
1137
|
+
exclusiveLease = yield oldResources.vectorDb.upgradeStoreLease(AbortSignal.any([operationSignal, clientAbort.signal]));
|
|
1138
|
+
throwIfPreDropCancelled(operationSignal);
|
|
1139
|
+
}
|
|
1140
|
+
catch (error) {
|
|
1141
|
+
if (exclusiveLease) {
|
|
1142
|
+
try {
|
|
1143
|
+
yield oldResources.vectorDb.downgradeStoreLease();
|
|
1144
|
+
}
|
|
1145
|
+
catch (downgradeError) {
|
|
1146
|
+
yield failClosedOldResources(exclusiveLease);
|
|
1147
|
+
throw new Error(`Failed to restore shared store ownership: ${String(downgradeError)}`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
oldResources.vectorDb.resumeMaintenanceLoop();
|
|
1151
|
+
throw error;
|
|
1152
|
+
}
|
|
1153
|
+
if (!exclusiveLease) {
|
|
1154
|
+
throw new Error("Exclusive store lease acquisition returned no lease");
|
|
1155
|
+
}
|
|
1156
|
+
let reservation;
|
|
1157
|
+
try {
|
|
1158
|
+
reservation = (0, project_registry_1.reserveProjectsForRebuild)(targetGeneration);
|
|
1159
|
+
}
|
|
1160
|
+
catch (error) {
|
|
1161
|
+
try {
|
|
1162
|
+
yield oldResources.vectorDb.downgradeStoreLease();
|
|
1163
|
+
}
|
|
1164
|
+
catch (downgradeError) {
|
|
1165
|
+
yield failClosedOldResources(exclusiveLease);
|
|
1166
|
+
throw new Error(`Failed to restore shared store ownership: ${String(downgradeError)}`);
|
|
1167
|
+
}
|
|
1168
|
+
oldResources.vectorDb.resumeMaintenanceLoop();
|
|
1169
|
+
throw error;
|
|
1170
|
+
}
|
|
1171
|
+
let targetDb = null;
|
|
1172
|
+
let targetPool = null;
|
|
1173
|
+
let published = false;
|
|
1174
|
+
try {
|
|
1175
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
1176
|
+
phase: "prepare",
|
|
1177
|
+
rebuildId: reservation.rebuildId,
|
|
1178
|
+
projects: reservation.reserved.length,
|
|
1179
|
+
});
|
|
1180
|
+
yield oldResources.workerPool.destroy({ requireExit: true });
|
|
1181
|
+
oldPoolDestroyed = true;
|
|
1182
|
+
if (oldResources.mlx === "owned") {
|
|
1183
|
+
yield this.mlxServerManager.stopMlxServer();
|
|
1184
|
+
}
|
|
1185
|
+
throwIfPreDropCancelled(operationSignal);
|
|
1186
|
+
yield oldResources.vectorDb.close({
|
|
1187
|
+
releaseLease: false,
|
|
1188
|
+
requireClosed: true,
|
|
1189
|
+
});
|
|
1190
|
+
oldDbClosed = true;
|
|
1191
|
+
throwIfPreDropCancelled(operationSignal);
|
|
1192
|
+
targetDb = this.createVectorDb(targetGeneration.vectorDim, exclusiveLease);
|
|
1193
|
+
(0, project_registry_1.markProjectRebuildDropping)(reservation);
|
|
1194
|
+
try {
|
|
1195
|
+
yield targetDb.drop();
|
|
1196
|
+
dropCommitted = true;
|
|
1197
|
+
conn.off("close", onClose);
|
|
1198
|
+
}
|
|
1199
|
+
catch (error) {
|
|
1200
|
+
// LanceDB does not expose a commit token. Inspect physical state:
|
|
1201
|
+
// an absent table means the destructive commit happened; an
|
|
1202
|
+
// unreadable state is treated as uncertain and therefore post-drop.
|
|
1203
|
+
try {
|
|
1204
|
+
dropCommitted = (yield targetDb.getSchemaVectorDim()) === null;
|
|
1205
|
+
}
|
|
1206
|
+
catch (_a) {
|
|
1207
|
+
dropCommitted = true;
|
|
1208
|
+
}
|
|
1209
|
+
if (dropCommitted)
|
|
1210
|
+
conn.off("close", onClose);
|
|
1211
|
+
throw error;
|
|
1212
|
+
}
|
|
1213
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
1214
|
+
phase: "schema",
|
|
1215
|
+
message: "creating target table",
|
|
1216
|
+
});
|
|
1217
|
+
yield targetDb.ensureTable();
|
|
1218
|
+
const physicalDim = yield targetDb.getSchemaVectorDim();
|
|
1219
|
+
if (physicalDim !== targetGeneration.vectorDim) {
|
|
1220
|
+
throw new Error(`rebuilt table dimension ${physicalDim !== null && physicalDim !== void 0 ? physicalDim : "missing"} does not match target ${targetGeneration.vectorDim}`);
|
|
1221
|
+
}
|
|
1222
|
+
if (targetConfig.embedMode === "gpu") {
|
|
1223
|
+
yield this.mlxServerManager.ensureMlxServer(targetGeneration.mlxModel);
|
|
1224
|
+
}
|
|
1225
|
+
const targetMlx = this.mlxMode(targetConfig);
|
|
1226
|
+
targetPool = this.createWorkerPool(targetGeneration, targetConfig.embedMode);
|
|
1227
|
+
this.publishResourceGeneration(targetConfig, targetGeneration, targetDb, targetPool, targetMlx);
|
|
1228
|
+
published = true;
|
|
1229
|
+
let completed = 0;
|
|
1230
|
+
const failures = [];
|
|
1231
|
+
for (const project of reservation.reserved) {
|
|
1232
|
+
if (operationSignal.aborted)
|
|
1233
|
+
throw operationSignal.reason;
|
|
757
1234
|
(0, ipc_handler_1.writeProgress)(conn, {
|
|
758
|
-
phase: "
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
indexed: info.indexed,
|
|
764
|
-
total: info.total,
|
|
765
|
-
filePath: info.filePath,
|
|
1235
|
+
phase: "index",
|
|
1236
|
+
root: project.root,
|
|
1237
|
+
project: project.name,
|
|
1238
|
+
completed,
|
|
1239
|
+
totalProjects: reservation.reserved.length,
|
|
766
1240
|
});
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1241
|
+
try {
|
|
1242
|
+
const sync = yield this.reindexOneProject(project.root, { reset: true, rewatch: false }, operationSignal, (info) => (0, ipc_handler_1.writeProgress)(conn, {
|
|
1243
|
+
phase: "index",
|
|
1244
|
+
root: project.root,
|
|
1245
|
+
project: project.name,
|
|
1246
|
+
processed: info.processed,
|
|
1247
|
+
indexed: info.indexed,
|
|
1248
|
+
total: info.total,
|
|
1249
|
+
filePath: info.filePath,
|
|
1250
|
+
}));
|
|
1251
|
+
if (sync.degraded) {
|
|
1252
|
+
throw new Error(`degraded scan (${sync.failedFiles} failed files)`);
|
|
1253
|
+
}
|
|
1254
|
+
const prefix = project.root.endsWith("/")
|
|
1255
|
+
? project.root
|
|
1256
|
+
: `${project.root}/`;
|
|
1257
|
+
const chunkCount = yield targetDb.countRowsForPath(prefix);
|
|
1258
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
1259
|
+
root: project.root,
|
|
1260
|
+
name: project.name,
|
|
1261
|
+
generation: sync.generation,
|
|
1262
|
+
embedMode: sync.embedMode,
|
|
1263
|
+
chunkCount,
|
|
1264
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
1265
|
+
expectedFingerprint: targetGeneration.fingerprint,
|
|
1266
|
+
expectedRebuildId: reservation.rebuildId,
|
|
1267
|
+
});
|
|
1268
|
+
completed++;
|
|
1269
|
+
}
|
|
1270
|
+
catch (error) {
|
|
1271
|
+
failures.push({
|
|
1272
|
+
root: project.root,
|
|
1273
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
772
1276
|
}
|
|
773
|
-
|
|
774
|
-
|
|
1277
|
+
yield targetDb.downgradeStoreLease();
|
|
1278
|
+
targetDb.startMaintenanceLoop((fn) => this.runSharedOperation("store-maintenance", undefined, () => fn()));
|
|
1279
|
+
const currentGeneration = (0, embedding_generation_1.resolveEmbeddingGeneration)((0, index_config_1.readGlobalConfig)());
|
|
1280
|
+
const configChanged = currentGeneration.fingerprint !== targetGeneration.fingerprint;
|
|
1281
|
+
if (failures.length === 0) {
|
|
1282
|
+
(0, project_registry_1.completeProjectRebuild)(reservation.rebuildId);
|
|
1283
|
+
}
|
|
1284
|
+
return {
|
|
1285
|
+
completed,
|
|
1286
|
+
total: reservation.reserved.length,
|
|
1287
|
+
failures,
|
|
1288
|
+
configChanged,
|
|
1289
|
+
generation: targetGeneration.fingerprint,
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
catch (error) {
|
|
1293
|
+
if (!dropCommitted) {
|
|
1294
|
+
this.ready = false;
|
|
1295
|
+
this.resources = null;
|
|
1296
|
+
let recoveryError;
|
|
1297
|
+
yield (targetPool === null || targetPool === void 0 ? void 0 : targetPool.destroy({ requireExit: true }).catch((cause) => {
|
|
1298
|
+
recoveryError !== null && recoveryError !== void 0 ? recoveryError : (recoveryError = cause);
|
|
1299
|
+
}));
|
|
1300
|
+
if (targetDb) {
|
|
1301
|
+
yield targetDb
|
|
1302
|
+
.close({ releaseLease: false, requireClosed: true })
|
|
1303
|
+
.catch((cause) => {
|
|
1304
|
+
recoveryError !== null && recoveryError !== void 0 ? recoveryError : (recoveryError = cause);
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
try {
|
|
1308
|
+
(0, project_registry_1.restoreProjectsAfterRebuild)(reservation);
|
|
1309
|
+
}
|
|
1310
|
+
catch (cause) {
|
|
1311
|
+
recoveryError !== null && recoveryError !== void 0 ? recoveryError : (recoveryError = cause);
|
|
1312
|
+
}
|
|
1313
|
+
if (!recoveryError && !oldPoolDestroyed) {
|
|
1314
|
+
try {
|
|
1315
|
+
yield oldResources.vectorDb.downgradeStoreLease();
|
|
1316
|
+
oldResources.vectorDb.resumeMaintenanceLoop();
|
|
1317
|
+
this.resources = oldResources;
|
|
1318
|
+
this.activeConfig = oldResources.config;
|
|
1319
|
+
this.activeGeneration = oldResources.embedding;
|
|
1320
|
+
this.vectorDb = oldResources.vectorDb;
|
|
1321
|
+
this.workerPool = oldResources.workerPool;
|
|
1322
|
+
this.ready = true;
|
|
1323
|
+
}
|
|
1324
|
+
catch (cause) {
|
|
1325
|
+
recoveryError = cause;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
else if (!recoveryError) {
|
|
1329
|
+
let restoredDb = null;
|
|
1330
|
+
let restoredPool = null;
|
|
1331
|
+
try {
|
|
1332
|
+
restoredDb = oldDbClosed
|
|
1333
|
+
? this.createVectorDb(oldResources.embedding.vectorDim, exclusiveLease)
|
|
1334
|
+
: oldResources.vectorDb;
|
|
1335
|
+
if (oldResources.config.embedMode === "gpu" &&
|
|
1336
|
+
oldResources.mlx === "owned") {
|
|
1337
|
+
yield this.mlxServerManager.ensureMlxServer(oldResources.embedding.mlxModel);
|
|
1338
|
+
}
|
|
1339
|
+
restoredPool = this.createWorkerPool(oldResources.embedding, oldResources.config.embedMode);
|
|
1340
|
+
yield restoredDb.downgradeStoreLease();
|
|
1341
|
+
restoredDb.startMaintenanceLoop((fn) => this.runSharedOperation("store-maintenance", undefined, () => fn()));
|
|
1342
|
+
this.publishResourceGeneration(oldResources.config, oldResources.embedding, restoredDb, restoredPool, this.mlxMode(oldResources.config));
|
|
1343
|
+
this.ready = true;
|
|
1344
|
+
}
|
|
1345
|
+
catch (cause) {
|
|
1346
|
+
recoveryError = cause;
|
|
1347
|
+
yield (restoredPool === null || restoredPool === void 0 ? void 0 : restoredPool.destroy({ requireExit: true }).catch(() => { }));
|
|
1348
|
+
if (restoredDb) {
|
|
1349
|
+
yield restoredDb.close().catch(() => { });
|
|
1350
|
+
}
|
|
1351
|
+
else {
|
|
1352
|
+
yield exclusiveLease.release().catch(() => { });
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (recoveryError) {
|
|
1357
|
+
if (!oldPoolDestroyed) {
|
|
1358
|
+
yield oldResources.workerPool
|
|
1359
|
+
.destroy({ requireExit: true })
|
|
1360
|
+
.catch((cause) => {
|
|
1361
|
+
recoveryError = new Error(`Pre-drop rebuild pool cleanup failed: ${String(recoveryError)}; ${String(cause)}`);
|
|
1362
|
+
});
|
|
1363
|
+
oldPoolDestroyed = true;
|
|
1364
|
+
}
|
|
1365
|
+
if (!oldDbClosed) {
|
|
1366
|
+
yield oldResources.vectorDb.close().catch((cause) => {
|
|
1367
|
+
recoveryError = new Error(`Pre-drop rebuild DB cleanup failed: ${String(recoveryError)}; ${String(cause)}`);
|
|
1368
|
+
});
|
|
1369
|
+
oldDbClosed = true;
|
|
1370
|
+
}
|
|
1371
|
+
yield exclusiveLease.release().catch((cause) => {
|
|
1372
|
+
recoveryError = new Error(`Pre-drop rebuild cleanup failed: ${String(recoveryError)}; ${String(cause)}`);
|
|
1373
|
+
});
|
|
1374
|
+
this.resources = null;
|
|
1375
|
+
this.vectorDb = null;
|
|
1376
|
+
this.workerPool = null;
|
|
1377
|
+
this.ready = false;
|
|
1378
|
+
throw new Error(`Pre-drop rebuild recovery failed: ${String(error)}; ${String(recoveryError)}`);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
this.ready = false;
|
|
1383
|
+
this.resources = null;
|
|
1384
|
+
this.activeConfig = targetConfig;
|
|
1385
|
+
this.activeGeneration = targetGeneration;
|
|
1386
|
+
this.vectorDb = targetDb;
|
|
1387
|
+
this.workerPool = published ? targetPool : null;
|
|
1388
|
+
if (targetDb) {
|
|
1389
|
+
yield targetDb.downgradeStoreLease().catch(() => { });
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
yield exclusiveLease.release().catch(() => { });
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
}));
|
|
1398
|
+
for (const root of desiredWatchRoots) {
|
|
1399
|
+
if (((_a = (0, project_registry_1.getProject)(root)) === null || _a === void 0 ? void 0 : _a.status) !== "indexed")
|
|
1400
|
+
continue;
|
|
1401
|
+
try {
|
|
1402
|
+
yield this.watcherManager.watchProject(root, { catchup: false });
|
|
1403
|
+
}
|
|
1404
|
+
catch (error) {
|
|
1405
|
+
result.failures.push({
|
|
1406
|
+
root,
|
|
1407
|
+
error: `watch restore failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
775
1410
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1411
|
+
watchersRestored = true;
|
|
1412
|
+
try {
|
|
1413
|
+
yield this.watcherManager.catchupAll(desiredWatchRoots);
|
|
1414
|
+
}
|
|
1415
|
+
catch (error) {
|
|
1416
|
+
result.failures.push({
|
|
1417
|
+
root: "*",
|
|
1418
|
+
error: `watch catchup failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
(0, ipc_handler_1.writeDone)(conn, Object.assign(Object.assign({ ok: result.failures.length === 0 }, result), (result.configChanged
|
|
1422
|
+
? {
|
|
1423
|
+
warning: "configured embedding changed during rebuild; another rebuild is required",
|
|
1424
|
+
}
|
|
1425
|
+
: {})));
|
|
783
1426
|
}
|
|
784
|
-
catch (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1427
|
+
catch (error) {
|
|
1428
|
+
let reportedError = error;
|
|
1429
|
+
if (!dropCommitted && !watchersRestored && this.ready && this.resources) {
|
|
1430
|
+
try {
|
|
1431
|
+
yield this.watcherManager.resumeAll(desiredWatchRoots, {
|
|
1432
|
+
catchup: false,
|
|
1433
|
+
});
|
|
1434
|
+
watchersRestored = true;
|
|
1435
|
+
yield this.watcherManager.catchupAll(desiredWatchRoots);
|
|
1436
|
+
}
|
|
1437
|
+
catch (watchError) {
|
|
1438
|
+
this.ready = false;
|
|
1439
|
+
this.resources = null;
|
|
1440
|
+
reportedError = new Error(`Pre-drop rebuild watcher restoration failed: ${String(error)}; ${String(watchError)}`);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
const details = reportedError instanceof store_lease_1.StoreLeaseTimeoutError
|
|
1444
|
+
? { blockers: reportedError.blockers }
|
|
1445
|
+
: {};
|
|
1446
|
+
(0, ipc_handler_1.writeDone)(conn, Object.assign({ ok: false, error: reportedError instanceof Error
|
|
1447
|
+
? reportedError.message
|
|
1448
|
+
: String(reportedError), degraded: dropCommitted }, details));
|
|
794
1449
|
}
|
|
795
1450
|
finally {
|
|
1451
|
+
conn.off("close", onClose);
|
|
796
1452
|
stopHeartbeat();
|
|
797
|
-
this.
|
|
798
|
-
|
|
1453
|
+
if (!this.ready) {
|
|
1454
|
+
setImmediate(() => void this.shutdown());
|
|
1455
|
+
}
|
|
799
1456
|
}
|
|
800
1457
|
});
|
|
801
1458
|
}
|
|
802
1459
|
removeProject(root, conn) {
|
|
803
1460
|
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);
|
|
1461
|
+
const ac = new AbortController();
|
|
1462
|
+
const onClose = () => ac.abort();
|
|
1463
|
+
conn.once("close", onClose);
|
|
1464
|
+
this.shutdownAbortControllers.add(ac);
|
|
1465
|
+
try {
|
|
1466
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("remove-project", ac.signal, () => __awaiter(this, void 0, void 0, function* () {
|
|
1467
|
+
if (!this.vectorDb || !this.metaCache) {
|
|
1468
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1469
|
+
ok: false,
|
|
1470
|
+
error: "daemon resources not ready",
|
|
1471
|
+
});
|
|
1472
|
+
return;
|
|
817
1473
|
}
|
|
818
|
-
stopHeartbeat();
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1474
|
+
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
1475
|
+
try {
|
|
1476
|
+
yield this.unwatchProjectWithinOperation(root);
|
|
1477
|
+
const rootPrefix = root.endsWith("/") ? root : `${root}/`;
|
|
1478
|
+
yield this.vectorDb.deletePathsWithPrefix(rootPrefix);
|
|
1479
|
+
const keys = yield this.metaCache.getKeysWithPrefix(rootPrefix);
|
|
1480
|
+
for (const key of keys)
|
|
1481
|
+
this.metaCache.delete(key);
|
|
1482
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: true });
|
|
1483
|
+
}
|
|
1484
|
+
catch (err) {
|
|
1485
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1486
|
+
console.error(`[daemon] removeProject failed for ${path.basename(root)}:`, msg);
|
|
1487
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
1488
|
+
}
|
|
1489
|
+
finally {
|
|
1490
|
+
stopHeartbeat();
|
|
1491
|
+
}
|
|
1492
|
+
})));
|
|
1493
|
+
}
|
|
1494
|
+
finally {
|
|
1495
|
+
conn.off("close", onClose);
|
|
1496
|
+
this.shutdownAbortControllers.delete(ac);
|
|
1497
|
+
}
|
|
831
1498
|
});
|
|
832
1499
|
}
|
|
833
1500
|
summarizeProject(root, conn, opts) {
|
|
834
1501
|
return __awaiter(this, void 0, void 0, function* () {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1502
|
+
const ac = new AbortController();
|
|
1503
|
+
const onClose = () => ac.abort();
|
|
1504
|
+
conn.once("close", onClose);
|
|
1505
|
+
this.shutdownAbortControllers.add(ac);
|
|
1506
|
+
try {
|
|
1507
|
+
yield this.withProjectLock(root, ac.signal, () => this.runSharedOperation("summarize-project", ac.signal, () => __awaiter(this, void 0, void 0, function* () {
|
|
1508
|
+
var _a;
|
|
1509
|
+
if (!this.vectorDb) {
|
|
1510
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1511
|
+
ok: false,
|
|
1512
|
+
error: "daemon resources not ready",
|
|
1513
|
+
});
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
const rootPrefix = (_a = opts.pathPrefix) !== null && _a !== void 0 ? _a : (root.endsWith("/") ? root : `${root}/`);
|
|
1517
|
+
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
1518
|
+
let lastProgressTime = 0;
|
|
1519
|
+
try {
|
|
1520
|
+
const result = yield (0, syncer_1.generateSummaries)(this.vectorDb, rootPrefix, (done, total) => {
|
|
1521
|
+
this.resetActivity();
|
|
1522
|
+
const now = Date.now();
|
|
1523
|
+
if (now - lastProgressTime < 100)
|
|
1524
|
+
return;
|
|
1525
|
+
lastProgressTime = now;
|
|
1526
|
+
(0, ipc_handler_1.writeProgress)(conn, { summarized: done, total });
|
|
1527
|
+
}, opts.limit);
|
|
1528
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
1529
|
+
ok: true,
|
|
1530
|
+
summarized: result.summarized,
|
|
1531
|
+
remaining: result.remaining,
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
catch (err) {
|
|
1535
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1536
|
+
console.error(`[daemon] summarizeProject failed for ${path.basename(root)}:`, msg);
|
|
1537
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
1538
|
+
}
|
|
1539
|
+
finally {
|
|
1540
|
+
stopHeartbeat();
|
|
1541
|
+
}
|
|
1542
|
+
})));
|
|
1543
|
+
}
|
|
1544
|
+
finally {
|
|
1545
|
+
conn.off("close", onClose);
|
|
1546
|
+
this.shutdownAbortControllers.delete(ac);
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
// --- LLM server management ---
|
|
1551
|
+
llmStart() {
|
|
1552
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1553
|
+
return this.runSharedOperation("llm-start", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
1554
|
+
if (!this.llmServer)
|
|
1555
|
+
return { ok: false, error: "daemon not initialized" };
|
|
844
1556
|
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
|
-
});
|
|
1557
|
+
yield this.llmServer.start();
|
|
1558
|
+
this.resetActivity();
|
|
1559
|
+
return Object.assign({ ok: true }, this.llmServer.getStatus());
|
|
859
1560
|
}
|
|
860
1561
|
catch (err) {
|
|
861
1562
|
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();
|
|
1563
|
+
return { ok: false, error: msg };
|
|
868
1564
|
}
|
|
869
1565
|
}));
|
|
870
1566
|
});
|
|
871
1567
|
}
|
|
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
1568
|
llmStop() {
|
|
889
1569
|
return __awaiter(this, void 0, void 0, function* () {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1570
|
+
return this.runSharedOperation("llm-stop", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
1571
|
+
if (!this.llmServer)
|
|
1572
|
+
return { ok: false, error: "daemon not initialized" };
|
|
1573
|
+
try {
|
|
1574
|
+
yield this.llmServer.stop();
|
|
1575
|
+
return { ok: true };
|
|
1576
|
+
}
|
|
1577
|
+
catch (err) {
|
|
1578
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1579
|
+
return { ok: false, error: msg };
|
|
1580
|
+
}
|
|
1581
|
+
}));
|
|
900
1582
|
});
|
|
901
1583
|
}
|
|
902
1584
|
llmStatus() {
|
|
@@ -910,20 +1592,22 @@ class Daemon {
|
|
|
910
1592
|
}
|
|
911
1593
|
reviewCommit(root, commitRef) {
|
|
912
1594
|
return __awaiter(this, void 0, void 0, function* () {
|
|
913
|
-
this.
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1595
|
+
return this.runSharedOperation("review", undefined, () => __awaiter(this, void 0, void 0, function* () {
|
|
1596
|
+
this.resetActivity();
|
|
1597
|
+
try {
|
|
1598
|
+
if (!this.llmServer) {
|
|
1599
|
+
console.log("[review] daemon not initialized, skipping");
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
yield this.llmServer.ensure();
|
|
1603
|
+
const { reviewCommit } = yield Promise.resolve().then(() => __importStar(require("../llm/review")));
|
|
1604
|
+
const result = yield reviewCommit({ commitRef, projectRoot: root });
|
|
1605
|
+
console.log(`[review] ${result.commit} — ${result.findingCount} finding(s) in ${result.duration}s`);
|
|
918
1606
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
catch (err) {
|
|
925
|
-
console.error(`[review] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
926
|
-
}
|
|
1607
|
+
catch (err) {
|
|
1608
|
+
console.error(`[review] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1609
|
+
}
|
|
1610
|
+
}));
|
|
927
1611
|
});
|
|
928
1612
|
}
|
|
929
1613
|
/**
|
|
@@ -945,7 +1629,7 @@ class Daemon {
|
|
|
945
1629
|
// Defer while busy; we'll re-check next tick.
|
|
946
1630
|
if ((_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.isMaintenanceActive())
|
|
947
1631
|
return;
|
|
948
|
-
if (this.
|
|
1632
|
+
if (this.projectMutex.pending > 0 || this.operations.activeCount > 0)
|
|
949
1633
|
return;
|
|
950
1634
|
const reason = ageExceeded
|
|
951
1635
|
? `age ${(ageMs / 3600000).toFixed(1)}h > ${(MAX_LIFETIME_MS / 3600000).toFixed(1)}h`
|
|
@@ -954,22 +1638,43 @@ class Daemon {
|
|
|
954
1638
|
this.recycling = true;
|
|
955
1639
|
void this.shutdown({ relaunch: true }).finally(() => process.exit(0));
|
|
956
1640
|
}
|
|
957
|
-
shutdown() {
|
|
958
|
-
|
|
1641
|
+
shutdown(opts = {}) {
|
|
1642
|
+
if (!this.shutdownPromise) {
|
|
1643
|
+
this.shutdownPromise = this.performShutdown(opts);
|
|
1644
|
+
}
|
|
1645
|
+
return this.shutdownPromise;
|
|
1646
|
+
}
|
|
1647
|
+
performShutdown(opts) {
|
|
1648
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
959
1649
|
var _a, _b, _c, _d;
|
|
960
|
-
if (this.shuttingDown)
|
|
961
|
-
return;
|
|
962
1650
|
this.shuttingDown = true;
|
|
1651
|
+
this.ready = false;
|
|
1652
|
+
const strictResourceShutdown = this.hasUnfinishedRebuild();
|
|
963
1653
|
console.log("[daemon] Shutting down...");
|
|
964
1654
|
// Announce graceful shutdown BEFORE dropping the liveness markers below, so a
|
|
965
1655
|
// successor spawned during the (possibly long) drain sees the draining marker
|
|
966
1656
|
// and defers instead of SIGKILLing us mid-cleanup.
|
|
967
1657
|
(0, daemon_client_1.writeDrainingMarker)(process.pid);
|
|
968
|
-
//
|
|
969
|
-
//
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1658
|
+
// Stop accepting new IPC before draining admitted operations. Existing
|
|
1659
|
+
// sockets are destroyed below so their close handlers cancel queued work.
|
|
1660
|
+
const server = this.server;
|
|
1661
|
+
this.server = null;
|
|
1662
|
+
const serverClosed = new Promise((resolve) => {
|
|
1663
|
+
if (!(server === null || server === void 0 ? void 0 : server.listening)) {
|
|
1664
|
+
resolve();
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
server.close(() => resolve());
|
|
1668
|
+
});
|
|
1669
|
+
for (const connection of this.connections)
|
|
1670
|
+
connection.end();
|
|
1671
|
+
const forceCloseConnections = setTimeout(() => {
|
|
1672
|
+
for (const connection of this.connections)
|
|
1673
|
+
connection.destroy();
|
|
1674
|
+
}, 1000);
|
|
1675
|
+
forceCloseConnections.unref();
|
|
1676
|
+
// Drop external liveness markers now so interrupted cleanup cannot leave a
|
|
1677
|
+
// fresh-looking daemon that silently blocks its successor.
|
|
973
1678
|
try {
|
|
974
1679
|
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
975
1680
|
}
|
|
@@ -981,44 +1686,60 @@ class Daemon {
|
|
|
981
1686
|
if (this.releaseLock) {
|
|
982
1687
|
const release = this.releaseLock;
|
|
983
1688
|
this.releaseLock = null;
|
|
984
|
-
|
|
1689
|
+
try {
|
|
1690
|
+
yield release();
|
|
1691
|
+
}
|
|
1692
|
+
catch (_g) { }
|
|
985
1693
|
}
|
|
986
1694
|
if (this.heartbeatInterval)
|
|
987
1695
|
clearInterval(this.heartbeatInterval);
|
|
988
1696
|
if (this.idleInterval)
|
|
989
1697
|
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);
|
|
1698
|
+
for (const timer of this.pendingIndexRetryTimers.values()) {
|
|
1699
|
+
clearTimeout(timer);
|
|
999
1700
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1701
|
+
this.pendingIndexRetryTimers.clear();
|
|
1702
|
+
this.pendingIndexRetryCounts.clear();
|
|
1703
|
+
// Abort explicit operation controllers, close coordinator admission, and
|
|
1704
|
+
// reject queued project waiters. Start these drains before watcher teardown
|
|
1705
|
+
// so no timer or socket can admit replacement work.
|
|
1706
|
+
for (const ac of this.shutdownAbortControllers) {
|
|
1707
|
+
ac.abort(new operation_coordinator_1.OperationClosedError());
|
|
1003
1708
|
}
|
|
1709
|
+
const operationDrain = this.operations.close();
|
|
1710
|
+
const projectDrain = this.projectMutex.close();
|
|
1711
|
+
// Abort catchup/recovery and remove each processor before worker teardown.
|
|
1712
|
+
yield this.watcherManager.quiesceAll();
|
|
1713
|
+
yield Promise.all([operationDrain, projectDrain]);
|
|
1004
1714
|
// Stop LLM server if running
|
|
1005
1715
|
try {
|
|
1006
1716
|
yield ((_a = this.llmServer) === null || _a === void 0 ? void 0 : _a.stop());
|
|
1007
1717
|
}
|
|
1008
|
-
catch (
|
|
1009
|
-
// Stop MLX embed server if we started it
|
|
1010
|
-
this.mlxServerManager.stopMlxServer();
|
|
1718
|
+
catch (_h) { }
|
|
1011
1719
|
// Destroy worker pool to prevent orphaned child processes
|
|
1720
|
+
if (this.workerPool) {
|
|
1721
|
+
if (strictResourceShutdown) {
|
|
1722
|
+
yield this.workerPool.destroy({
|
|
1723
|
+
requireExit: true,
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
try {
|
|
1728
|
+
yield this.workerPool.destroy();
|
|
1729
|
+
}
|
|
1730
|
+
catch (_j) { }
|
|
1731
|
+
}
|
|
1732
|
+
this.workerPool = null;
|
|
1733
|
+
this.resources = null;
|
|
1734
|
+
}
|
|
1012
1735
|
if ((0, pool_1.isWorkerPoolInitialized)()) {
|
|
1013
1736
|
try {
|
|
1014
1737
|
yield (0, pool_1.destroyWorkerPool)();
|
|
1015
1738
|
}
|
|
1016
|
-
catch (
|
|
1739
|
+
catch (_k) { }
|
|
1017
1740
|
}
|
|
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();
|
|
1741
|
+
// Stop MLX embed server only after worker requests have drained.
|
|
1742
|
+
yield this.mlxServerManager.stopMlxServer();
|
|
1022
1743
|
// Unregister all
|
|
1023
1744
|
for (const root of this.processors.keys()) {
|
|
1024
1745
|
(0, watcher_store_1.unregisterWatcherByRoot)(root);
|
|
@@ -1027,19 +1748,27 @@ class Daemon {
|
|
|
1027
1748
|
this.processors.clear();
|
|
1028
1749
|
// Close shared resources
|
|
1029
1750
|
try {
|
|
1030
|
-
yield ((
|
|
1751
|
+
yield ((_b = this.metaCache) === null || _b === void 0 ? void 0 : _b.close());
|
|
1031
1752
|
}
|
|
1032
|
-
catch (
|
|
1033
|
-
|
|
1034
|
-
yield ((
|
|
1753
|
+
catch (_l) { }
|
|
1754
|
+
if (strictResourceShutdown) {
|
|
1755
|
+
yield ((_c = this.vectorDb) === null || _c === void 0 ? void 0 : _c.close({ requireClosed: true }));
|
|
1756
|
+
}
|
|
1757
|
+
else {
|
|
1758
|
+
try {
|
|
1759
|
+
yield ((_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.close());
|
|
1760
|
+
}
|
|
1761
|
+
catch (_m) { }
|
|
1035
1762
|
}
|
|
1036
|
-
|
|
1763
|
+
yield serverClosed;
|
|
1764
|
+
clearTimeout(forceCloseConnections);
|
|
1765
|
+
this.connections.clear();
|
|
1037
1766
|
// Hand off to a successor only after every resource is released and the
|
|
1038
1767
|
// liveness markers (socket/pid/lock) are already gone — so the fresh
|
|
1039
1768
|
// daemon's singleton check sees a clean slate and opens LanceDB/LMDB
|
|
1040
1769
|
// without contending with this exiting process.
|
|
1041
1770
|
if (opts.relaunch) {
|
|
1042
|
-
const pid = (0, daemon_launcher_1.spawnDaemon)();
|
|
1771
|
+
const pid = yield (0, daemon_launcher_1.spawnDaemon)();
|
|
1043
1772
|
console.log(`[daemon] Spawned successor daemon${pid ? ` (PID: ${pid})` : " (spawn failed)"}`);
|
|
1044
1773
|
}
|
|
1045
1774
|
// Cleanly drained — drop the marker so a later start doesn't defer to a
|