docdex 0.2.28 → 0.2.29
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/assets/agents.md +1 -1
- package/lib/postinstall_setup.js +180 -16
- package/package.json +1 -1
package/assets/agents.md
CHANGED
package/lib/postinstall_setup.js
CHANGED
|
@@ -18,6 +18,8 @@ const DAEMON_HEALTH_TIMEOUT_MS = 8000;
|
|
|
18
18
|
const DAEMON_HEALTH_REQUEST_TIMEOUT_MS = 1000;
|
|
19
19
|
const DAEMON_HEALTH_POLL_INTERVAL_MS = 200;
|
|
20
20
|
const DAEMON_HEALTH_PATH = "/healthz";
|
|
21
|
+
const DAEMON_INFO_PATH = "/ai-help";
|
|
22
|
+
const DAEMON_PORT_RELEASE_TIMEOUT_MS = 5000;
|
|
21
23
|
const STARTUP_FAILURE_MARKER = "startup_registration_failed.json";
|
|
22
24
|
const DEFAULT_OLLAMA_MODEL = "nomic-embed-text";
|
|
23
25
|
const DEFAULT_OLLAMA_CHAT_MODEL = "phi3.5:3.8b";
|
|
@@ -120,6 +122,139 @@ async function waitForDaemonHealthy({ host, port, timeoutMs = DAEMON_HEALTH_TIME
|
|
|
120
122
|
return false;
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
async function waitForPortAvailable({
|
|
126
|
+
host,
|
|
127
|
+
port,
|
|
128
|
+
timeoutMs = DAEMON_PORT_RELEASE_TIMEOUT_MS
|
|
129
|
+
}) {
|
|
130
|
+
const deadline = Date.now() + timeoutMs;
|
|
131
|
+
while (Date.now() < deadline) {
|
|
132
|
+
if (await isPortAvailable(port, host)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
await sleep(DAEMON_HEALTH_POLL_INTERVAL_MS);
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isPidRunning(pid) {
|
|
141
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
142
|
+
try {
|
|
143
|
+
process.kill(pid, 0);
|
|
144
|
+
return true;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return err?.code === "EPERM";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function readDaemonLockMetadataForPort(port) {
|
|
151
|
+
for (const lockPath of daemonLockPaths()) {
|
|
152
|
+
if (!lockPath || !fs.existsSync(lockPath)) continue;
|
|
153
|
+
try {
|
|
154
|
+
const raw = fs.readFileSync(lockPath, "utf8");
|
|
155
|
+
if (!raw.trim()) continue;
|
|
156
|
+
const payload = JSON.parse(raw);
|
|
157
|
+
const lockPort = Number(payload?.port);
|
|
158
|
+
const pid = Number(payload?.pid);
|
|
159
|
+
if (!Number.isFinite(lockPort) || lockPort !== port) continue;
|
|
160
|
+
return { pid, port: lockPort };
|
|
161
|
+
} catch {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeVersion(value) {
|
|
169
|
+
return String(value || "")
|
|
170
|
+
.trim()
|
|
171
|
+
.replace(/^v/i, "");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function fetchDaemonInfo({ host, port, timeoutMs = DAEMON_HEALTH_REQUEST_TIMEOUT_MS }) {
|
|
175
|
+
return new Promise((resolve) => {
|
|
176
|
+
const req = http.request(
|
|
177
|
+
{
|
|
178
|
+
host,
|
|
179
|
+
port,
|
|
180
|
+
path: DAEMON_INFO_PATH,
|
|
181
|
+
method: "GET",
|
|
182
|
+
timeout: timeoutMs
|
|
183
|
+
},
|
|
184
|
+
(res) => {
|
|
185
|
+
let body = "";
|
|
186
|
+
res.setEncoding("utf8");
|
|
187
|
+
res.on("data", (chunk) => {
|
|
188
|
+
body += chunk;
|
|
189
|
+
});
|
|
190
|
+
res.on("end", () => {
|
|
191
|
+
if (res.statusCode !== 200) return resolve(null);
|
|
192
|
+
try {
|
|
193
|
+
const payload = JSON.parse(body);
|
|
194
|
+
resolve(payload);
|
|
195
|
+
} catch {
|
|
196
|
+
resolve(null);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
req.on("timeout", () => {
|
|
202
|
+
req.destroy();
|
|
203
|
+
resolve(null);
|
|
204
|
+
});
|
|
205
|
+
req.on("error", () => resolve(null));
|
|
206
|
+
req.end();
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function checkDocdexIdentity({ host, port, timeoutMs = DAEMON_HEALTH_REQUEST_TIMEOUT_MS }) {
|
|
211
|
+
return fetchDaemonInfo({ host, port, timeoutMs }).then((payload) => payload?.product === "Docdex");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function resolveDaemonPortState({ host, port, logger, deps } = {}) {
|
|
215
|
+
const log = logger || console;
|
|
216
|
+
const helpers = {
|
|
217
|
+
isPortAvailable,
|
|
218
|
+
checkDaemonHealth,
|
|
219
|
+
checkDocdexIdentity,
|
|
220
|
+
stopDaemonService,
|
|
221
|
+
stopDaemonFromLock,
|
|
222
|
+
stopDaemonByName,
|
|
223
|
+
clearDaemonLocks,
|
|
224
|
+
sleep,
|
|
225
|
+
readDaemonLockMetadataForPort,
|
|
226
|
+
isPidRunning,
|
|
227
|
+
normalizeVersion
|
|
228
|
+
};
|
|
229
|
+
if (deps && typeof deps === "object") {
|
|
230
|
+
Object.assign(helpers, deps);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let available = await helpers.isPortAvailable(port, host);
|
|
234
|
+
if (available) return { available: true, reuseExisting: false, stopped: false };
|
|
235
|
+
|
|
236
|
+
helpers.stopDaemonService({ logger: log });
|
|
237
|
+
helpers.stopDaemonFromLock({ logger: log });
|
|
238
|
+
helpers.stopDaemonByName({ logger: log });
|
|
239
|
+
await helpers.sleep(DAEMON_HEALTH_POLL_INTERVAL_MS);
|
|
240
|
+
|
|
241
|
+
available = await helpers.isPortAvailable(port, host);
|
|
242
|
+
if (available) {
|
|
243
|
+
helpers.clearDaemonLocks();
|
|
244
|
+
return { available: true, reuseExisting: false, stopped: true };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const lockMeta = helpers.readDaemonLockMetadataForPort(port);
|
|
248
|
+
const lockRunning = lockMeta ? helpers.isPidRunning(lockMeta.pid) : false;
|
|
249
|
+
const healthy = await helpers.checkDaemonHealth({ host, port });
|
|
250
|
+
const identity = lockRunning ? true : await helpers.checkDocdexIdentity({ host, port });
|
|
251
|
+
const reuseExisting = Boolean(lockRunning || healthy || identity);
|
|
252
|
+
if (reuseExisting) {
|
|
253
|
+
log.warn?.(`[docdex] ${host}:${port} already in use by a running docdex daemon; reusing it.`);
|
|
254
|
+
}
|
|
255
|
+
return { available: false, reuseExisting, stopped: false };
|
|
256
|
+
}
|
|
257
|
+
|
|
123
258
|
function parseServerBind(contents) {
|
|
124
259
|
let inServer = false;
|
|
125
260
|
const lines = contents.split(/\r?\n/);
|
|
@@ -2254,8 +2389,12 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
|
|
|
2254
2389
|
existingConfig = fs.readFileSync(configPath, "utf8");
|
|
2255
2390
|
}
|
|
2256
2391
|
const port = DEFAULT_DAEMON_PORT;
|
|
2257
|
-
const
|
|
2258
|
-
|
|
2392
|
+
const portState = await resolveDaemonPortState({
|
|
2393
|
+
host: DEFAULT_HOST,
|
|
2394
|
+
port,
|
|
2395
|
+
logger: log
|
|
2396
|
+
});
|
|
2397
|
+
if (!portState.available && !portState.reuseExisting) {
|
|
2259
2398
|
log.error?.(
|
|
2260
2399
|
`[docdex] ${DEFAULT_HOST}:${port} is already in use; docdex requires a fixed port. Stop the process using this port and re-run the install.`
|
|
2261
2400
|
);
|
|
@@ -2268,19 +2407,42 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
|
|
|
2268
2407
|
binaryPath: resolvedBinary,
|
|
2269
2408
|
logger: log
|
|
2270
2409
|
});
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2410
|
+
let reuseExisting = portState.reuseExisting;
|
|
2411
|
+
if (reuseExisting) {
|
|
2412
|
+
const daemonInfo = await fetchDaemonInfo({ host: DEFAULT_HOST, port });
|
|
2413
|
+
const daemonVersion = normalizeVersion(daemonInfo?.version);
|
|
2414
|
+
const packageVersion = normalizeVersion(resolvePackageVersion());
|
|
2415
|
+
if (daemonInfo?.product === "Docdex" && daemonVersion && packageVersion) {
|
|
2416
|
+
if (daemonVersion !== packageVersion) {
|
|
2417
|
+
log.warn?.(
|
|
2418
|
+
`[docdex] daemon version ${daemonVersion} differs from package ${packageVersion}; restarting daemon.`
|
|
2419
|
+
);
|
|
2420
|
+
stopDaemonService({ logger: log });
|
|
2421
|
+
stopDaemonFromLock({ logger: log });
|
|
2422
|
+
stopDaemonByName({ logger: log });
|
|
2423
|
+
clearDaemonLocks();
|
|
2424
|
+
const released = await waitForPortAvailable({
|
|
2425
|
+
host: DEFAULT_HOST,
|
|
2426
|
+
port
|
|
2427
|
+
});
|
|
2428
|
+
if (!released) {
|
|
2429
|
+
throw new Error("docdex daemon restart failed; port still in use");
|
|
2430
|
+
}
|
|
2431
|
+
reuseExisting = false;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
if (!reuseExisting) {
|
|
2436
|
+
const result = await startDaemonWithHealthCheck({
|
|
2437
|
+
binaryPath: startupBinaries.binaryPath,
|
|
2438
|
+
port,
|
|
2439
|
+
host: DEFAULT_HOST,
|
|
2440
|
+
logger: log
|
|
2441
|
+
});
|
|
2442
|
+
if (!result.ok) {
|
|
2443
|
+
log.warn?.(`[docdex] daemon failed to start on ${DEFAULT_HOST}:${port}.`);
|
|
2444
|
+
throw new Error("docdex daemon failed to start");
|
|
2445
|
+
}
|
|
2284
2446
|
}
|
|
2285
2447
|
|
|
2286
2448
|
const httpBindAddr = `${DEFAULT_HOST}:${port}`;
|
|
@@ -2345,5 +2507,7 @@ module.exports = {
|
|
|
2345
2507
|
shouldSkipSetup,
|
|
2346
2508
|
launchSetupWizard,
|
|
2347
2509
|
applyAgentInstructions,
|
|
2348
|
-
buildDaemonEnv
|
|
2510
|
+
buildDaemonEnv,
|
|
2511
|
+
resolveDaemonPortState,
|
|
2512
|
+
normalizeVersion
|
|
2349
2513
|
};
|