@juspay/neurolink 12.17.1 → 12.17.2
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/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +421 -412
- package/dist/cli/commands/proxy.js +455 -259
- package/dist/constants/contextWindows.d.ts +2 -0
- package/dist/constants/contextWindows.js +4 -0
- package/dist/proxy/anthropicBudgetResponse.d.ts +3 -0
- package/dist/proxy/anthropicBudgetResponse.js +153 -0
- package/dist/proxy/bodyCaptureProcessing.d.ts +1 -0
- package/dist/proxy/bodyCaptureProcessing.js +23 -4
- package/dist/proxy/bodyCaptureWorker.js +20 -2
- package/dist/proxy/codexFallback.d.ts +10 -0
- package/dist/proxy/codexFallback.js +108 -24
- package/dist/proxy/codexUsage.js +2 -0
- package/dist/proxy/globalInstaller.d.ts +32 -1
- package/dist/proxy/globalInstaller.js +386 -3
- package/dist/proxy/otelLogSink.d.ts +3 -3
- package/dist/proxy/otelLogSink.js +137 -144
- package/dist/proxy/otlpLogTransport.d.ts +3 -0
- package/dist/proxy/otlpLogTransport.js +334 -0
- package/dist/proxy/proxyActivity.d.ts +22 -2
- package/dist/proxy/proxyActivity.js +114 -9
- package/dist/proxy/proxyAnalysis.js +82 -52
- package/dist/proxy/proxyClientCancellation.d.ts +7 -0
- package/dist/proxy/proxyClientCancellation.js +23 -0
- package/dist/proxy/proxyContextPreflight.d.ts +25 -0
- package/dist/proxy/proxyContextPreflight.js +337 -0
- package/dist/proxy/proxyFailureDetails.d.ts +19 -0
- package/dist/proxy/proxyFailureDetails.js +96 -0
- package/dist/proxy/proxyLifecycle.js +7 -0
- package/dist/proxy/proxyRouteBodyCapture.d.ts +13 -0
- package/dist/proxy/proxyRouteBodyCapture.js +112 -0
- package/dist/proxy/proxyTokenBudget.d.ts +40 -0
- package/dist/proxy/proxyTokenBudget.js +523 -0
- package/dist/proxy/proxyTracer.d.ts +3 -2
- package/dist/proxy/proxyTracer.js +46 -20
- package/dist/proxy/proxyTranslationEngine.d.ts +8 -10
- package/dist/proxy/proxyTranslationEngine.js +736 -376
- package/dist/proxy/requestLogger.d.ts +1 -0
- package/dist/proxy/requestLogger.js +107 -5
- package/dist/proxy/restartControl.js +9 -4
- package/dist/proxy/rollingWorkerProcess.js +65 -0
- package/dist/proxy/rollingWorkerSupervisor.js +7 -0
- package/dist/proxy/updateCoordinator.d.ts +14 -1
- package/dist/proxy/updateCoordinator.js +57 -2
- package/dist/server/routes/claudeProxyRoutes.d.ts +4 -5
- package/dist/server/routes/claudeProxyRoutes.js +809 -577
- package/dist/server/routes/codexProxyRoutes.js +191 -9
- package/dist/server/routes/openaiProxyRoutes.d.ts +1 -1
- package/dist/server/routes/openaiProxyRoutes.js +261 -69
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/codex.d.ts +5 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/proxy.d.ts +148 -3
- package/dist/types/proxy.js +0 -15
- package/dist/types/proxyBudget.d.ts +74 -0
- package/dist/types/proxyBudget.js +1 -0
- package/dist/types/proxyContext.d.ts +35 -0
- package/dist/types/proxyContext.js +1 -0
- package/docs/assets/dashboards/README.md +37 -0
- package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +197 -89
- package/docs-site/static/search-index.json +1 -1
- package/package.json +3 -1
- package/scripts/observability/NATIVE-DURABLE.md +63 -0
- package/scripts/observability/otel-collector.proxy-native-durable.yaml +93 -0
- package/scripts/observability/proxy-telemetry-backend.mjs +32 -5
- package/scripts/observability/proxy-telemetry-check.mjs +396 -61
- package/scripts/observability/query-proxy-history.mjs +34 -5
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { execFileSync as nodeExecFileSync } from "node:child_process";
|
|
2
|
-
import { accessSync, constants, existsSync, realpathSync } from "node:fs";
|
|
1
|
+
import { execFileSync as nodeExecFileSync, spawn as nodeSpawn, } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, existsSync, realpathSync, readFileSync, mkdirSync, writeFileSync, renameSync, chmodSync, rmSync, } from "node:fs";
|
|
3
|
+
import { mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
|
-
import {
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { basename, dirname, isAbsolute, join, resolve, relative, } from "node:path";
|
|
5
7
|
function runText(execFileSync, bin, args) {
|
|
6
8
|
return String(execFileSync(bin, args, {
|
|
7
9
|
encoding: "utf8",
|
|
@@ -246,3 +248,384 @@ export function describeInstallFailure(error) {
|
|
|
246
248
|
.filter(Boolean)
|
|
247
249
|
.join("\n");
|
|
248
250
|
}
|
|
251
|
+
/** Read package metadata without importing the CLI/provider/telemetry graph. */
|
|
252
|
+
export function inspectProxyPackage(entryScript, nodePath = process.execPath) {
|
|
253
|
+
const entry = realpathSync(entryScript);
|
|
254
|
+
let directory = dirname(entry);
|
|
255
|
+
while (true) {
|
|
256
|
+
const manifestPath = join(directory, "package.json");
|
|
257
|
+
if (existsSync(manifestPath)) {
|
|
258
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
259
|
+
if (manifest.name === "@juspay/neurolink") {
|
|
260
|
+
if (typeof manifest.version !== "string" ||
|
|
261
|
+
!/^\d+\.\d+\.\d+$/.test(manifest.version)) {
|
|
262
|
+
throw new Error("Invalid proxy package version");
|
|
263
|
+
}
|
|
264
|
+
return { version: manifest.version, entryScript: entry, nodePath };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const parent = dirname(directory);
|
|
268
|
+
if (parent === directory) {
|
|
269
|
+
throw new Error("Proxy entrypoint has no package manifest");
|
|
270
|
+
}
|
|
271
|
+
directory = parent;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function validateProxyPackageSelection(value) {
|
|
275
|
+
try {
|
|
276
|
+
if (!value || typeof value !== "object") {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
const selection = value;
|
|
280
|
+
if (typeof selection.entryScript !== "string" ||
|
|
281
|
+
typeof selection.nodePath !== "string" ||
|
|
282
|
+
!isAbsolute(selection.entryScript) ||
|
|
283
|
+
!isAbsolute(selection.nodePath)) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const actual = inspectProxyPackage(selection.entryScript, selection.nodePath);
|
|
287
|
+
return actual.version === selection.version ? actual : null;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** Read active and rollback selections from one committed generation. */
|
|
294
|
+
function readProxyPackageSelections(packagesDir) {
|
|
295
|
+
let value;
|
|
296
|
+
try {
|
|
297
|
+
value = JSON.parse(readFileSync(join(packagesDir, "selections.json"), "utf8"));
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
// Legacy migration is allowed only when no atomic record has ever been
|
|
301
|
+
// published. Invalid/unreadable committed state must not revive stale files.
|
|
302
|
+
if (error.code !== "ENOENT") {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
const readLegacy = (file) => {
|
|
306
|
+
try {
|
|
307
|
+
return validateProxyPackageSelection(JSON.parse(readFileSync(join(packagesDir, file), "utf8")));
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
schemaVersion: 1,
|
|
315
|
+
active: readLegacy("active.json"),
|
|
316
|
+
previous: readLegacy("previous.json"),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (!value || typeof value !== "object") {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
const state = value;
|
|
323
|
+
if (state.schemaVersion !== 1 ||
|
|
324
|
+
!("active" in state) ||
|
|
325
|
+
!("previous" in state)) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
schemaVersion: 1,
|
|
330
|
+
active: validateProxyPackageSelection(state.active),
|
|
331
|
+
previous: validateProxyPackageSelection(state.previous),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
export function readProxyPackageSelection(packagesDir, previous = false) {
|
|
335
|
+
const state = readProxyPackageSelections(packagesDir);
|
|
336
|
+
return (previous ? state?.previous : state?.active) ?? null;
|
|
337
|
+
}
|
|
338
|
+
/** Resolve immutable selections before consulting an older mutable global entry. */
|
|
339
|
+
export function resolveProxyWorkerPackage(options) {
|
|
340
|
+
const state = readProxyPackageSelections(options.packagesDir);
|
|
341
|
+
for (const selection of [state?.active, state?.previous]) {
|
|
342
|
+
if (selection?.version === options.expectedVersion) {
|
|
343
|
+
return selection;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const original = inspectProxyPackage(options.entryScript);
|
|
348
|
+
if (original.version === options.expectedVersion) {
|
|
349
|
+
return original;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// A global upgrade/removal may invalidate the original entry while an
|
|
354
|
+
// already selected private package remains the valid recovery target.
|
|
355
|
+
}
|
|
356
|
+
throw new Error(`No validated package selection for worker v${options.expectedVersion}`);
|
|
357
|
+
}
|
|
358
|
+
function assertPackageSelectionOwner(isCurrentOwner) {
|
|
359
|
+
if (isCurrentOwner && !isCurrentOwner()) {
|
|
360
|
+
throw new Error("Proxy update owner changed; package selection was not modified");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** Persist only package selection; runtime env, config and launchd settings are untouched. */
|
|
364
|
+
export function selectProxyPackage(packagesDir, selection, isCurrentOwner) {
|
|
365
|
+
assertPackageSelectionOwner(isCurrentOwner);
|
|
366
|
+
mkdirSync(packagesDir, { recursive: true, mode: 0o700 });
|
|
367
|
+
const current = readProxyPackageSelections(packagesDir);
|
|
368
|
+
if (!current) {
|
|
369
|
+
throw new Error("Proxy package selection state is unreadable or invalid");
|
|
370
|
+
}
|
|
371
|
+
// Manifest reads can outlive a service owner, even before private staging.
|
|
372
|
+
assertPackageSelectionOwner(isCurrentOwner);
|
|
373
|
+
const state = {
|
|
374
|
+
schemaVersion: 1,
|
|
375
|
+
active: selection,
|
|
376
|
+
previous: current.active && current.active.version !== selection.version
|
|
377
|
+
? current.active
|
|
378
|
+
: current.previous,
|
|
379
|
+
};
|
|
380
|
+
const temporary = join(packagesDir, `selections-${process.pid}-${randomUUID()}.tmp`);
|
|
381
|
+
try {
|
|
382
|
+
writeFileSync(temporary, JSON.stringify(state), {
|
|
383
|
+
mode: 0o600,
|
|
384
|
+
flag: "wx",
|
|
385
|
+
});
|
|
386
|
+
assertPackageSelectionOwner(isCurrentOwner);
|
|
387
|
+
// One rename publishes both versions. A stale owner never mutates rollback
|
|
388
|
+
// state independently, and must never attempt to restore a newer owner's state.
|
|
389
|
+
renameSync(temporary, join(packagesDir, "selections.json"));
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
rmSync(temporary, { force: true });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/** No startup probes: IPC descriptors and OTel initialization belong to the real process. */
|
|
396
|
+
export function writeProxyPackageLauncher(path, selection, isCurrentOwner) {
|
|
397
|
+
assertPackageSelectionOwner(isCurrentOwner);
|
|
398
|
+
const quote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
399
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
400
|
+
// --version reads the manifest in a tiny isolated Node process. This proves
|
|
401
|
+
// the selected files still exist without cold-loading every provider twice.
|
|
402
|
+
const probe = `const fs=require('node:fs');let p=${JSON.stringify(dirname(selection.entryScript))};for(;;){const f=p+'/package.json';if(fs.existsSync(f)){const m=JSON.parse(fs.readFileSync(f,'utf8'));if(m.name==='@juspay/neurolink'){if(m.version!==${JSON.stringify(selection.version)})process.exit(1);console.log(m.version);break;}}const q=require('node:path').dirname(p);if(q===p)process.exit(1);p=q;}`;
|
|
403
|
+
const script = `#!/bin/sh\n# Generated proxy package selection. Service environment is inherited.\n[ -f ${quote(selection.entryScript)} ] || exit 127\nif [ "$#" = "1" ] && [ "$1" = "--version" ]; then\n exec ${quote(selection.nodePath)} -e ${quote(probe)}\nfi\nexec ${quote(selection.nodePath)} ${quote(selection.entryScript)} "$@"\n`;
|
|
404
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
405
|
+
writeFileSync(temporary, script, { mode: 0o755 });
|
|
406
|
+
chmodSync(temporary, 0o755);
|
|
407
|
+
assertPackageSelectionOwner(isCurrentOwner);
|
|
408
|
+
renameSync(temporary, path);
|
|
409
|
+
}
|
|
410
|
+
/** Install in a private tree, then publish the immutable directory after validation. */
|
|
411
|
+
export async function installStagedProxyPackage(options) {
|
|
412
|
+
if (!/^\d+\.\d+\.\d+$/.test(options.version)) {
|
|
413
|
+
throw new Error("Invalid proxy package version");
|
|
414
|
+
}
|
|
415
|
+
await mkdir(options.packagesDir, { recursive: true, mode: 0o700 });
|
|
416
|
+
const destination = join(options.packagesDir, options.version);
|
|
417
|
+
const validate = (root) => {
|
|
418
|
+
const packageRoot = join(root, "node_modules", "@juspay", "neurolink");
|
|
419
|
+
const manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
420
|
+
const bin = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.neurolink;
|
|
421
|
+
if (!bin) {
|
|
422
|
+
throw new Error("Candidate package is missing its neurolink executable");
|
|
423
|
+
}
|
|
424
|
+
const entry = realpathSync(resolve(packageRoot, bin));
|
|
425
|
+
const rel = relative(realpathSync(packageRoot), entry);
|
|
426
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
427
|
+
throw new Error("Candidate executable escapes its package");
|
|
428
|
+
}
|
|
429
|
+
const selection = inspectProxyPackage(entry);
|
|
430
|
+
if (selection.version !== options.version) {
|
|
431
|
+
throw new Error(`Candidate version ${selection.version} does not match ${options.version}`);
|
|
432
|
+
}
|
|
433
|
+
(options.execFileSync ?? nodeExecFileSync)(selection.nodePath, ["--check", selection.entryScript], { timeout: 10_000, stdio: "pipe" });
|
|
434
|
+
return selection;
|
|
435
|
+
};
|
|
436
|
+
if (existsSync(destination)) {
|
|
437
|
+
return validate(destination);
|
|
438
|
+
}
|
|
439
|
+
const staging = await mkdtemp(join(options.packagesDir, `.install-${options.version}-`));
|
|
440
|
+
try {
|
|
441
|
+
await writeFile(join(staging, "package.json"), JSON.stringify({ private: true }), { mode: 0o600 });
|
|
442
|
+
const spec = `@juspay/neurolink@${options.version}`;
|
|
443
|
+
const args = options.installer.kind === "npm"
|
|
444
|
+
? [
|
|
445
|
+
"install",
|
|
446
|
+
"--prefix",
|
|
447
|
+
staging,
|
|
448
|
+
"--no-save",
|
|
449
|
+
"--package-lock=false",
|
|
450
|
+
"--no-audit",
|
|
451
|
+
"--no-fund",
|
|
452
|
+
"--omit=dev",
|
|
453
|
+
"--loglevel=http",
|
|
454
|
+
spec,
|
|
455
|
+
]
|
|
456
|
+
: [
|
|
457
|
+
"add",
|
|
458
|
+
"--dir",
|
|
459
|
+
staging,
|
|
460
|
+
"--no-lockfile",
|
|
461
|
+
"--prod",
|
|
462
|
+
"--reporter=ndjson",
|
|
463
|
+
spec,
|
|
464
|
+
];
|
|
465
|
+
await new Promise((resolveInstall, rejectInstall) => {
|
|
466
|
+
const started = Date.now();
|
|
467
|
+
let lastProgress = started;
|
|
468
|
+
let bytes = 0;
|
|
469
|
+
let failureCode;
|
|
470
|
+
let failure;
|
|
471
|
+
let killTimer;
|
|
472
|
+
const child = (options.spawn ?? nodeSpawn)(options.installer.bin, args, {
|
|
473
|
+
cwd: staging,
|
|
474
|
+
env: { ...process.env, npm_config_update_notifier: "false" },
|
|
475
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
476
|
+
detached: process.platform !== "win32",
|
|
477
|
+
});
|
|
478
|
+
const progress = (chunk) => {
|
|
479
|
+
bytes += Buffer.byteLength(chunk);
|
|
480
|
+
lastProgress = Date.now();
|
|
481
|
+
// Retain a machine code only: npm output can include authenticated URLs.
|
|
482
|
+
const text = String(chunk);
|
|
483
|
+
const code = text.match(/npm (?:ERR!|error) code ([A-Z][A-Z0-9_]+)/)?.[1] ??
|
|
484
|
+
text.match(/\b(?:ECONNRESET|ECONNREFUSED|EAI_AGAIN|EHOSTUNREACH|ENETUNREACH|ENOTFOUND|EPIPE|ETIMEDOUT)\b/)?.[0];
|
|
485
|
+
if (code) {
|
|
486
|
+
failureCode = code;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
child.stdout?.on("data", progress);
|
|
490
|
+
child.stderr?.on("data", progress);
|
|
491
|
+
const signal = (value) => {
|
|
492
|
+
try {
|
|
493
|
+
if (process.platform !== "win32" && child.pid) {
|
|
494
|
+
process.kill(-child.pid, value);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
child.kill(value);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
/* child already exited */
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
const interval = setInterval(() => {
|
|
505
|
+
const now = Date.now();
|
|
506
|
+
options.onProgress?.({
|
|
507
|
+
elapsedMs: now - started,
|
|
508
|
+
outputBytes: bytes,
|
|
509
|
+
});
|
|
510
|
+
if (!failure &&
|
|
511
|
+
(now - started >= (options.maxDurationMs ?? 15 * 60_000) ||
|
|
512
|
+
now - lastProgress >= (options.idleTimeoutMs ?? 120_000))) {
|
|
513
|
+
failure = Object.assign(new Error(`Staged install timed out after ${now - started}ms (${bytes} output bytes, idle ${now - lastProgress}ms)`), { code: "ETIMEDOUT" });
|
|
514
|
+
signal("SIGTERM");
|
|
515
|
+
killTimer = setTimeout(() => signal("SIGKILL"), options.killGraceMs ?? 5_000);
|
|
516
|
+
}
|
|
517
|
+
}, Math.min(5_000, Math.max(10, (options.idleTimeoutMs ?? 120_000) / 4)));
|
|
518
|
+
const cleanup = () => {
|
|
519
|
+
clearInterval(interval);
|
|
520
|
+
clearTimeout(killTimer);
|
|
521
|
+
};
|
|
522
|
+
child.once("error", (error) => {
|
|
523
|
+
cleanup();
|
|
524
|
+
rejectInstall(error);
|
|
525
|
+
});
|
|
526
|
+
child.once("close", (code, signalName) => {
|
|
527
|
+
cleanup();
|
|
528
|
+
if (failure) {
|
|
529
|
+
rejectInstall(failure);
|
|
530
|
+
}
|
|
531
|
+
else if (code !== 0) {
|
|
532
|
+
rejectInstall(Object.assign(new Error(`Staged install exited code=${code} signal=${signalName ?? "none"} errorCode=${failureCode ?? "unknown"}`), {
|
|
533
|
+
code: failureCode ??
|
|
534
|
+
(code === null ? "ECONNRESET" : "INSTALL_FAILED"),
|
|
535
|
+
}));
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
resolveInstall();
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
validate(staging);
|
|
543
|
+
await rename(staging, destination);
|
|
544
|
+
return validate(destination);
|
|
545
|
+
}
|
|
546
|
+
finally {
|
|
547
|
+
await rm(staging, { recursive: true, force: true });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/** Only operator runtime settings survive reinstall, never worker IPC identity. */
|
|
551
|
+
export function isProxyServiceEnvironmentKey(name) {
|
|
552
|
+
return ((name.startsWith("OTEL_") ||
|
|
553
|
+
name.startsWith("NEUROLINK_PROXY_") ||
|
|
554
|
+
[
|
|
555
|
+
"NEUROLINK_PACKAGE_MANAGER",
|
|
556
|
+
"NEUROLINK_PACKAGE_MANAGER_PATH",
|
|
557
|
+
"NEUROLINK_PNPM_PATH",
|
|
558
|
+
"PNPM_HOME",
|
|
559
|
+
].includes(name)) &&
|
|
560
|
+
![
|
|
561
|
+
"NEUROLINK_PROXY_SOCKET_WORKER",
|
|
562
|
+
"NEUROLINK_PROXY_WORKER_GENERATION",
|
|
563
|
+
"NEUROLINK_PROXY_WORKER_EXPECTED_VERSION",
|
|
564
|
+
"NEUROLINK_PROXY_UPDATE_CONTROL_TOKEN",
|
|
565
|
+
"NEUROLINK_PROXY_ROLLING_SUPERVISOR",
|
|
566
|
+
"NEUROLINK_PROXY_TRAMPOLINE_EXEC_ONLY",
|
|
567
|
+
"NEUROLINK_PROXY_TEST_ISOLATED",
|
|
568
|
+
].includes(name));
|
|
569
|
+
}
|
|
570
|
+
/** Parse plutil JSON without exposing credentials or invoking the saved launcher. */
|
|
571
|
+
export function parseProxyServiceInstallSettings(value) {
|
|
572
|
+
const settings = { environment: {} };
|
|
573
|
+
if (!value || typeof value !== "object") {
|
|
574
|
+
return settings;
|
|
575
|
+
}
|
|
576
|
+
const plist = value;
|
|
577
|
+
const args = Array.isArray(plist.ProgramArguments)
|
|
578
|
+
? plist.ProgramArguments
|
|
579
|
+
: [];
|
|
580
|
+
const option = (name) => {
|
|
581
|
+
const index = args.indexOf(name);
|
|
582
|
+
return index >= 0 && typeof args[index + 1] === "string"
|
|
583
|
+
? args[index + 1]
|
|
584
|
+
: undefined;
|
|
585
|
+
};
|
|
586
|
+
settings.envFile = option("--env-file");
|
|
587
|
+
settings.configFile = option("--config");
|
|
588
|
+
settings.host = option("--host");
|
|
589
|
+
const port = Number(option("--port"));
|
|
590
|
+
if (Number.isInteger(port) && port > 0 && port <= 65535) {
|
|
591
|
+
settings.port = port;
|
|
592
|
+
}
|
|
593
|
+
if (plist.EnvironmentVariables &&
|
|
594
|
+
typeof plist.EnvironmentVariables === "object") {
|
|
595
|
+
for (const [name, entry] of Object.entries(plist.EnvironmentVariables)) {
|
|
596
|
+
if (isProxyServiceEnvironmentKey(name) && typeof entry === "string") {
|
|
597
|
+
settings.environment[name] = entry;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return settings;
|
|
602
|
+
}
|
|
603
|
+
/** Distinguish an absent launchd job from permission failures without logging plist/env data. */
|
|
604
|
+
export function probeProxyLaunchdPresence(options) {
|
|
605
|
+
try {
|
|
606
|
+
(options.execFileSync ?? nodeExecFileSync)("launchctl", ["print", `gui/${options.uid}/${options.label}`], { encoding: "utf8", timeout: 5_000, stdio: ["ignore", "pipe", "pipe"] });
|
|
607
|
+
// A loaded but currently idle KeepAlive job can start immediately. It must
|
|
608
|
+
// be explicitly unloaded before regenerating its launcher and environment.
|
|
609
|
+
return "loaded";
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
if (error &&
|
|
613
|
+
typeof error === "object" &&
|
|
614
|
+
"status" in error &&
|
|
615
|
+
error.status === 113 &&
|
|
616
|
+
"stderr" in error &&
|
|
617
|
+
/could not find service/i.test(String(error.stderr))) {
|
|
618
|
+
return "not_loaded";
|
|
619
|
+
}
|
|
620
|
+
return "unknown";
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/** Read-only guard: callers must run this before every reinstall mutation. */
|
|
624
|
+
export function assertProxyServiceInstallIdle(options) {
|
|
625
|
+
if (options.launchdPresence !== "not_loaded" ||
|
|
626
|
+
options.pids.some((pid) => Number.isSafeInteger(pid) &&
|
|
627
|
+
pid > 0 &&
|
|
628
|
+
options.getProcessStatus(pid) !== "not_running")) {
|
|
629
|
+
throw new Error("Proxy service is running or could still run. Reinstall was refused before changing files or processes. Use 'neurolink proxy restart' for a safe rolling replacement, or explicitly stop and unload the service before an intended reinstall.");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
@@ -3,9 +3,9 @@ import type { ProxyBodyChunkEmitter, ProxyBodyDeliveryResult } from "../types/in
|
|
|
3
3
|
/** Explicit opt-in; configuration never silently falls back to file logging. */
|
|
4
4
|
export declare function isProxyOtelOnly(): boolean;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Publish bounded captures concurrently into a shared batch queue. Transport
|
|
7
|
+
* callbacks settle each chunk once; a flush is a process fence, not a per-body
|
|
8
|
+
* network round trip. Metadata has a separate queue and transport.
|
|
9
9
|
*/
|
|
10
10
|
export declare function publishProxyOtelBody(captureId: string, body: string, emit: ProxyBodyChunkEmitter): Promise<ProxyBodyDeliveryResult>;
|
|
11
11
|
/** Initialize a log-only provider in every proxy process, including the supervisor. */
|