@juspay/neurolink 10.8.19 → 10.8.21

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 CHANGED
@@ -1,3 +1,11 @@
1
+ ## [10.8.21](https://github.com/juspay/neurolink/compare/v10.8.20...v10.8.21) (2026-08-05)
2
+
3
+ ## [10.8.20](https://github.com/juspay/neurolink/compare/v10.8.19...v10.8.20) (2026-08-05)
4
+
5
+ ### Bug Fixes
6
+
7
+ - **(proxy):** retry transient updater installs and expose worker exits ([756d04e](https://github.com/juspay/neurolink/commit/756d04ed2b39005c9b49917abdad6df78a4e251b))
8
+
1
9
  ## [10.8.19](https://github.com/juspay/neurolink/compare/v10.8.18...v10.8.19) (2026-08-05)
2
10
 
3
11
  ### Bug Fixes
@@ -25,7 +25,7 @@ import { ProxyRuntimeConfigStore } from "../../lib/proxy/runtimeConfig.js";
25
25
  import { anthropicAccountKeysEqual, createAccountAllowlist, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../lib/proxy/accountSelection.js";
26
26
  import { beginProxyRequest, getProxyActivitySnapshot, trackProxyResponse, } from "../../lib/proxy/proxyActivity.js";
27
27
  import { flushProxyLifecycleEvents, getProxyLifecycleLoggerSnapshot, hashProxyLifecycleSessionId, logProxyLifecycleEvent, } from "../../lib/proxy/proxyLifecycle.js";
28
- import { describeInstallFailure, getGlobalInstallArgs, resolveGlobalInstaller, validateInstalledVersion, } from "../../lib/proxy/globalInstaller.js";
28
+ import { describeInstallFailure, getGlobalInstallArgs, isTransientInstallFailure, resolveGlobalInstaller, validateInstalledVersion, } from "../../lib/proxy/globalInstaller.js";
29
29
  import { startUpdaterWorkerSupervisor } from "../../lib/proxy/updaterSupervisor.js";
30
30
  import { openProxyWorkerLog } from "../../lib/proxy/workerLog.js";
31
31
  import { startRollingProxyServer } from "../../lib/proxy/rollingProxyServer.js";
@@ -671,7 +671,22 @@ async function getRollingActivationFailure(host, port, expectedVersion) {
671
671
  typeof failure.message !== "string") {
672
672
  return null;
673
673
  }
674
- return `${failure.phase}: ${failure.message}`;
674
+ const workerDetail = [
675
+ typeof failure.workerPid === "number" ? `pid=${failure.workerPid}` : null,
676
+ typeof failure.workerExitCode === "number" ||
677
+ failure.workerExitCode === null
678
+ ? `exitCode=${failure.workerExitCode ?? "none"}`
679
+ : null,
680
+ typeof failure.workerExitSignal === "string"
681
+ ? `exitSignal=${failure.workerExitSignal}`
682
+ : null,
683
+ typeof failure.supervisorAction === "string"
684
+ ? `supervisorAction=${failure.supervisorAction}`
685
+ : null,
686
+ ]
687
+ .filter(Boolean)
688
+ .join(" ");
689
+ return `${failure.phase}: ${failure.message}${workerDetail ? ` (${workerDetail})` : ""}`;
675
690
  }
676
691
  catch {
677
692
  return null;
@@ -3252,6 +3267,8 @@ export const proxyGuardCommand = {
3252
3267
  const NATURAL_WINDOW_WAIT_MS = 10 * 60 * 1000; // prefer no admission pause
3253
3268
  const UPDATE_DRAIN_TIMEOUT_MS = 30 * 60 * 1000; // preserve long streams
3254
3269
  const UPDATE_RETRY_DELAY_MS = 5 * 60 * 1000;
3270
+ const UPDATE_RETRY_MAX_DELAY_MS = 30 * 60 * 1000;
3271
+ const UPDATE_RETRY_MAX_ATTEMPTS = 4;
3255
3272
  const UPDATE_ACTIVITY_POLL_MS = 10 * 1000;
3256
3273
  const UPDATE_TIMEOUT_MS = 30 * 1000; // 30 seconds to come healthy
3257
3274
  // Get running version from /health endpoint (with timeout to avoid hanging)
@@ -3276,6 +3293,8 @@ export const proxyGuardCommand = {
3276
3293
  let updateCheckTimeout;
3277
3294
  let updateCheckInterval;
3278
3295
  let updateRetryTimeout;
3296
+ let updateRetryAttempts = 0;
3297
+ let updateRetryVersion = null;
3279
3298
  const stopUpdateChecks = () => {
3280
3299
  guardStopping = true;
3281
3300
  if (updateCheckTimeout) {
@@ -3317,6 +3336,8 @@ export const proxyGuardCommand = {
3317
3336
  updateVersion = result.latestVersion;
3318
3337
  persistUpdaterState("record update check", () => recordCheck(result.latestVersion));
3319
3338
  if (!result.updateAvailable) {
3339
+ updateRetryAttempts = 0;
3340
+ updateRetryVersion = null;
3320
3341
  persistUpdaterState("clear update deferral", () => clearUpdateDeferral());
3321
3342
  return;
3322
3343
  }
@@ -3368,7 +3389,7 @@ export const proxyGuardCommand = {
3368
3389
  if (updateWindow.reason === "drain_failed") {
3369
3390
  persistUpdaterState("record unavailable update drain", () => recordUpdateDeferred(result.latestVersion, "drain_unavailable", null));
3370
3391
  }
3371
- scheduleUpdateRetry();
3392
+ scheduleUpdateRetry("update window unavailable", result.latestVersion);
3372
3393
  return;
3373
3394
  }
3374
3395
  // Hold admission closed through install and restart after a quiet
@@ -3376,7 +3397,7 @@ export const proxyGuardCommand = {
3376
3397
  if (!drainActive) {
3377
3398
  updateWindow = await waitForWindow(0);
3378
3399
  if (!updateWindow.ready) {
3379
- scheduleUpdateRetry();
3400
+ scheduleUpdateRetry("update drain unavailable", result.latestVersion);
3380
3401
  return;
3381
3402
  }
3382
3403
  }
@@ -3437,6 +3458,9 @@ export const proxyGuardCommand = {
3437
3458
  const detail = describeInstallFailure(installErr);
3438
3459
  logger.always(`[updater] WARNING: global install failed:\n${detail}`);
3439
3460
  persistUpdaterState("record update failure", () => recordUpdateFailure(result.latestVersion, "install", detail));
3461
+ if (isTransientInstallFailure(installErr)) {
3462
+ scheduleUpdateRetry("transient install failure", result.latestVersion);
3463
+ }
3440
3464
  return;
3441
3465
  }
3442
3466
  }
@@ -3459,6 +3483,8 @@ export const proxyGuardCommand = {
3459
3483
  return;
3460
3484
  }
3461
3485
  persistUpdaterState("record installed update", () => recordUpdateInstalled(result.latestVersion));
3486
+ updateRetryAttempts = 0;
3487
+ updateRetryVersion = null;
3462
3488
  logger.always(`[updater] trampoline validated at v${validation.version} after ${validation.attempts} attempt(s)`);
3463
3489
  }
3464
3490
  catch (trampolineError) {
@@ -3610,14 +3636,25 @@ export const proxyGuardCommand = {
3610
3636
  updateInProgress = false;
3611
3637
  }
3612
3638
  };
3613
- const scheduleUpdateRetry = () => {
3639
+ const scheduleUpdateRetry = (reason, version) => {
3614
3640
  if (guardStopping || updateRetryTimeout) {
3615
3641
  return;
3616
3642
  }
3643
+ if (updateRetryVersion !== version) {
3644
+ updateRetryVersion = version;
3645
+ updateRetryAttempts = 0;
3646
+ }
3647
+ if (updateRetryAttempts >= UPDATE_RETRY_MAX_ATTEMPTS) {
3648
+ logger.always(`[updater] retry budget exhausted after ${updateRetryAttempts} attempt(s); waiting for the periodic check`);
3649
+ return;
3650
+ }
3651
+ updateRetryAttempts += 1;
3652
+ const delay = Math.min(UPDATE_RETRY_MAX_DELAY_MS, UPDATE_RETRY_DELAY_MS * 2 ** (updateRetryAttempts - 1));
3653
+ logger.always(`[updater] retry scheduled reason=${reason} attempt=${updateRetryAttempts}/${UPDATE_RETRY_MAX_ATTEMPTS} delayMs=${delay}`);
3617
3654
  updateRetryTimeout = setTimeout(() => {
3618
3655
  updateRetryTimeout = undefined;
3619
3656
  void runUpdateCheck();
3620
- }, UPDATE_RETRY_DELAY_MS);
3657
+ }, delay);
3621
3658
  updateRetryTimeout.unref?.();
3622
3659
  };
3623
3660
  // Run first check after a short delay, then on interval
@@ -2,6 +2,12 @@ import type { GlobalInstallerKind, GlobalInstallerResolution, InstalledVersionVa
2
2
  /** Resolve a package manager that can update the installation currently running. */
3
3
  export declare function resolveGlobalInstaller(options?: ResolveGlobalInstallerOptions): GlobalInstallerResolution;
4
4
  export declare function getGlobalInstallArgs(kind: GlobalInstallerKind, packageSpec: string): string[];
5
+ /**
6
+ * Return whether a global package-manager failure is likely environmental and
7
+ * worth retrying. Configuration and permission failures deliberately return
8
+ * false so the updater does not repeatedly mutate a broken installation.
9
+ */
10
+ export declare function isTransientInstallFailure(error: unknown): boolean;
5
11
  /**
6
12
  * Validate a freshly installed CLI with retries. Global package replacement
7
13
  * can leave executable shims briefly unavailable while filesystem metadata and
@@ -136,6 +136,36 @@ export function getGlobalInstallArgs(kind, packageSpec) {
136
136
  ? ["add", "-g", packageSpec]
137
137
  : ["install", "--global", "--no-audit", "--no-fund", packageSpec];
138
138
  }
139
+ const TRANSIENT_INSTALL_FAILURE_CODES = new Set([
140
+ "ECONNRESET",
141
+ "ECONNREFUSED",
142
+ "EAI_AGAIN",
143
+ "EHOSTUNREACH",
144
+ "ENETUNREACH",
145
+ "ENOTFOUND",
146
+ "EPIPE",
147
+ "ETIMEDOUT",
148
+ ]);
149
+ /**
150
+ * Return whether a global package-manager failure is likely environmental and
151
+ * worth retrying. Configuration and permission failures deliberately return
152
+ * false so the updater does not repeatedly mutate a broken installation.
153
+ */
154
+ export function isTransientInstallFailure(error) {
155
+ let current = error;
156
+ for (let depth = 0; depth < 5 && current; depth++) {
157
+ if (typeof current !== "object") {
158
+ return false;
159
+ }
160
+ const candidate = current;
161
+ if (typeof candidate.code === "string" &&
162
+ TRANSIENT_INSTALL_FAILURE_CODES.has(candidate.code)) {
163
+ return true;
164
+ }
165
+ current = candidate.cause;
166
+ }
167
+ return false;
168
+ }
139
169
  /**
140
170
  * Validate a freshly installed CLI with retries. Global package replacement
141
171
  * can leave executable shims briefly unavailable while filesystem metadata and
@@ -36,6 +36,7 @@ export declare class RollingWorkerSupervisor {
36
36
  private handleTransferFailure;
37
37
  private rejectSocket;
38
38
  private describeTransferError;
39
+ private extractLifecycleFailureDetails;
39
40
  private recordFailure;
40
41
  private publishState;
41
42
  }
@@ -200,7 +200,7 @@ export class RollingWorkerSupervisor {
200
200
  }
201
201
  return new Promise((resolve, reject) => {
202
202
  let settled = false;
203
- const finish = (error, phase = "startup") => {
203
+ const finish = (error, phase = "startup", details) => {
204
204
  if (settled) {
205
205
  return;
206
206
  }
@@ -208,7 +208,7 @@ export class RollingWorkerSupervisor {
208
208
  clearTimeout(readyTimeout);
209
209
  if (error) {
210
210
  if (!this.closed) {
211
- this.recordFailure(generation, expectedVersion, phase, error.message);
211
+ this.recordFailure(generation, expectedVersion, phase, error.message, details);
212
212
  }
213
213
  if (this.candidate?.generation === generation) {
214
214
  this.candidate = null;
@@ -307,14 +307,22 @@ export class RollingWorkerSupervisor {
307
307
  });
308
308
  const offExit = handle.onExit((code, signal) => {
309
309
  if (this.candidate?.generation === generation) {
310
- finish(new Error(`worker ${handle.pid} exited before readiness (code=${code ?? "none"}, signal=${signal ?? "none"})`));
310
+ finish(new Error(`worker ${handle.pid} exited before readiness (code=${code ?? "none"}, signal=${signal ?? "none"})`), "startup", {
311
+ workerPid: handle.pid,
312
+ workerExitCode: code,
313
+ workerExitSignal: signal,
314
+ });
311
315
  return;
312
316
  }
313
317
  if (this.active?.generation === generation) {
314
318
  this.active.dispose();
315
319
  this.active = null;
316
320
  if (!this.closed) {
317
- this.recordFailure(generation, expectedVersion, "runtime", `worker exited (code=${code ?? "none"}, signal=${signal ?? "none"})`);
321
+ this.recordFailure(generation, expectedVersion, "runtime", `worker exited (code=${code ?? "none"}, signal=${signal ?? "none"})`, {
322
+ workerPid: handle.pid,
323
+ workerExitCode: code,
324
+ workerExitSignal: signal,
325
+ });
318
326
  }
319
327
  this.options.log?.(`[proxy-supervisor] active worker exited generation=${generation} pid=${handle.pid} code=${code ?? "none"} signal=${signal ?? "none"}`);
320
328
  }
@@ -402,7 +410,16 @@ export class RollingWorkerSupervisor {
402
410
  handleTransferFailure(worker, socket, error) {
403
411
  this.failedTransfers += 1;
404
412
  const detail = this.describeTransferError(error);
405
- this.recordFailure(worker.generation, worker.version, "transfer", `worker ${worker.handle.pid} failed to accept a transferred socket: ${detail}`);
413
+ const lifecycle = this.extractLifecycleFailureDetails(error, worker.handle.pid);
414
+ this.recordFailure(worker.generation, worker.version, "transfer", `worker ${worker.handle.pid} failed to accept a transferred socket: ${detail}`, {
415
+ ...lifecycle.details,
416
+ // If the error already records an exit, the supervisor did not cause
417
+ // that exit. Otherwise this captures the deliberate cleanup following
418
+ // the failed transfer, not a claimed root cause for the failure.
419
+ supervisorAction: lifecycle.observedExit
420
+ ? "none"
421
+ : "sigkill_after_transfer_failure",
422
+ });
406
423
  this.options.log?.(`[proxy-supervisor] socket transfer failed generation=${worker.generation} pid=${worker.handle.pid}: ${detail}`);
407
424
  if (this.active?.generation === worker.generation && !this.closed) {
408
425
  this.active = null;
@@ -427,13 +444,38 @@ export class RollingWorkerSupervisor {
427
444
  const code = error.code;
428
445
  return code ? `${code}: ${error.message}` : error.message;
429
446
  }
430
- recordFailure(generation, version, phase, message) {
447
+ extractLifecycleFailureDetails(error, fallbackPid) {
448
+ const context = error &&
449
+ typeof error === "object" &&
450
+ "context" in error &&
451
+ error.context &&
452
+ typeof error.context === "object"
453
+ ? (error.context ?? {})
454
+ : {};
455
+ const workerPid = typeof context.workerPid === "number" ? context.workerPid : fallbackPid;
456
+ const hasExitCode = typeof context.exitCode === "number" || context.exitCode === null;
457
+ const hasExitSignal = typeof context.signal === "string" || context.signal === null;
458
+ return {
459
+ details: {
460
+ workerPid,
461
+ ...(hasExitCode
462
+ ? { workerExitCode: context.exitCode }
463
+ : {}),
464
+ ...(hasExitSignal
465
+ ? { workerExitSignal: context.signal }
466
+ : {}),
467
+ },
468
+ observedExit: hasExitCode || hasExitSignal,
469
+ };
470
+ }
471
+ recordFailure(generation, version, phase, message, details = {}) {
431
472
  this.lastFailure = {
432
473
  at: new Date().toISOString(),
433
474
  generation,
434
475
  version,
435
476
  phase,
436
477
  message: message.slice(0, 1_000),
478
+ ...details,
437
479
  };
438
480
  }
439
481
  publishState() {
@@ -863,6 +863,10 @@ export type ProxyRollingState = {
863
863
  version: string;
864
864
  phase: "startup" | "activation" | "runtime" | "transfer";
865
865
  message: string;
866
+ workerPid?: number;
867
+ workerExitCode?: number | null;
868
+ workerExitSignal?: string | null;
869
+ supervisorAction?: "none" | "sigkill_after_transfer_failure";
866
870
  } | null;
867
871
  };
868
872
  export type ProxySupervisorState = {
@@ -1906,6 +1906,12 @@ export type SpawnProxySocketWorkerOptions = {
1906
1906
  stderr?: "inherit" | "ignore";
1907
1907
  socketAckTimeoutMs?: number;
1908
1908
  };
1909
+ export type RollingWorkerFailureDetails = {
1910
+ workerPid?: number;
1911
+ workerExitCode?: number | null;
1912
+ workerExitSignal?: string | null;
1913
+ supervisorAction?: "none" | "sigkill_after_transfer_failure";
1914
+ };
1909
1915
  export type RollingWorkerSupervisorSnapshot = {
1910
1916
  generation: number;
1911
1917
  active: {
@@ -1926,13 +1932,13 @@ export type RollingWorkerSupervisorSnapshot = {
1926
1932
  queuedSockets: number;
1927
1933
  rejectedSockets: number;
1928
1934
  failedTransfers: number;
1929
- lastFailure: {
1935
+ lastFailure: ({
1930
1936
  at: string;
1931
1937
  generation: number;
1932
1938
  version: string;
1933
1939
  phase: "startup" | "activation" | "runtime" | "transfer";
1934
1940
  message: string;
1935
- } | null;
1941
+ } & RollingWorkerFailureDetails) | null;
1936
1942
  };
1937
1943
  export type RollingWorkerSupervisorOptions = {
1938
1944
  spawnWorker: (generation: number, expectedVersion: string) => RollingWorkerHandle;
@@ -2,6 +2,12 @@ import type { GlobalInstallerKind, GlobalInstallerResolution, InstalledVersionVa
2
2
  /** Resolve a package manager that can update the installation currently running. */
3
3
  export declare function resolveGlobalInstaller(options?: ResolveGlobalInstallerOptions): GlobalInstallerResolution;
4
4
  export declare function getGlobalInstallArgs(kind: GlobalInstallerKind, packageSpec: string): string[];
5
+ /**
6
+ * Return whether a global package-manager failure is likely environmental and
7
+ * worth retrying. Configuration and permission failures deliberately return
8
+ * false so the updater does not repeatedly mutate a broken installation.
9
+ */
10
+ export declare function isTransientInstallFailure(error: unknown): boolean;
5
11
  /**
6
12
  * Validate a freshly installed CLI with retries. Global package replacement
7
13
  * can leave executable shims briefly unavailable while filesystem metadata and
@@ -136,6 +136,36 @@ export function getGlobalInstallArgs(kind, packageSpec) {
136
136
  ? ["add", "-g", packageSpec]
137
137
  : ["install", "--global", "--no-audit", "--no-fund", packageSpec];
138
138
  }
139
+ const TRANSIENT_INSTALL_FAILURE_CODES = new Set([
140
+ "ECONNRESET",
141
+ "ECONNREFUSED",
142
+ "EAI_AGAIN",
143
+ "EHOSTUNREACH",
144
+ "ENETUNREACH",
145
+ "ENOTFOUND",
146
+ "EPIPE",
147
+ "ETIMEDOUT",
148
+ ]);
149
+ /**
150
+ * Return whether a global package-manager failure is likely environmental and
151
+ * worth retrying. Configuration and permission failures deliberately return
152
+ * false so the updater does not repeatedly mutate a broken installation.
153
+ */
154
+ export function isTransientInstallFailure(error) {
155
+ let current = error;
156
+ for (let depth = 0; depth < 5 && current; depth++) {
157
+ if (typeof current !== "object") {
158
+ return false;
159
+ }
160
+ const candidate = current;
161
+ if (typeof candidate.code === "string" &&
162
+ TRANSIENT_INSTALL_FAILURE_CODES.has(candidate.code)) {
163
+ return true;
164
+ }
165
+ current = candidate.cause;
166
+ }
167
+ return false;
168
+ }
139
169
  /**
140
170
  * Validate a freshly installed CLI with retries. Global package replacement
141
171
  * can leave executable shims briefly unavailable while filesystem metadata and
@@ -36,6 +36,7 @@ export declare class RollingWorkerSupervisor {
36
36
  private handleTransferFailure;
37
37
  private rejectSocket;
38
38
  private describeTransferError;
39
+ private extractLifecycleFailureDetails;
39
40
  private recordFailure;
40
41
  private publishState;
41
42
  }
@@ -200,7 +200,7 @@ export class RollingWorkerSupervisor {
200
200
  }
201
201
  return new Promise((resolve, reject) => {
202
202
  let settled = false;
203
- const finish = (error, phase = "startup") => {
203
+ const finish = (error, phase = "startup", details) => {
204
204
  if (settled) {
205
205
  return;
206
206
  }
@@ -208,7 +208,7 @@ export class RollingWorkerSupervisor {
208
208
  clearTimeout(readyTimeout);
209
209
  if (error) {
210
210
  if (!this.closed) {
211
- this.recordFailure(generation, expectedVersion, phase, error.message);
211
+ this.recordFailure(generation, expectedVersion, phase, error.message, details);
212
212
  }
213
213
  if (this.candidate?.generation === generation) {
214
214
  this.candidate = null;
@@ -307,14 +307,22 @@ export class RollingWorkerSupervisor {
307
307
  });
308
308
  const offExit = handle.onExit((code, signal) => {
309
309
  if (this.candidate?.generation === generation) {
310
- finish(new Error(`worker ${handle.pid} exited before readiness (code=${code ?? "none"}, signal=${signal ?? "none"})`));
310
+ finish(new Error(`worker ${handle.pid} exited before readiness (code=${code ?? "none"}, signal=${signal ?? "none"})`), "startup", {
311
+ workerPid: handle.pid,
312
+ workerExitCode: code,
313
+ workerExitSignal: signal,
314
+ });
311
315
  return;
312
316
  }
313
317
  if (this.active?.generation === generation) {
314
318
  this.active.dispose();
315
319
  this.active = null;
316
320
  if (!this.closed) {
317
- this.recordFailure(generation, expectedVersion, "runtime", `worker exited (code=${code ?? "none"}, signal=${signal ?? "none"})`);
321
+ this.recordFailure(generation, expectedVersion, "runtime", `worker exited (code=${code ?? "none"}, signal=${signal ?? "none"})`, {
322
+ workerPid: handle.pid,
323
+ workerExitCode: code,
324
+ workerExitSignal: signal,
325
+ });
318
326
  }
319
327
  this.options.log?.(`[proxy-supervisor] active worker exited generation=${generation} pid=${handle.pid} code=${code ?? "none"} signal=${signal ?? "none"}`);
320
328
  }
@@ -402,7 +410,16 @@ export class RollingWorkerSupervisor {
402
410
  handleTransferFailure(worker, socket, error) {
403
411
  this.failedTransfers += 1;
404
412
  const detail = this.describeTransferError(error);
405
- this.recordFailure(worker.generation, worker.version, "transfer", `worker ${worker.handle.pid} failed to accept a transferred socket: ${detail}`);
413
+ const lifecycle = this.extractLifecycleFailureDetails(error, worker.handle.pid);
414
+ this.recordFailure(worker.generation, worker.version, "transfer", `worker ${worker.handle.pid} failed to accept a transferred socket: ${detail}`, {
415
+ ...lifecycle.details,
416
+ // If the error already records an exit, the supervisor did not cause
417
+ // that exit. Otherwise this captures the deliberate cleanup following
418
+ // the failed transfer, not a claimed root cause for the failure.
419
+ supervisorAction: lifecycle.observedExit
420
+ ? "none"
421
+ : "sigkill_after_transfer_failure",
422
+ });
406
423
  this.options.log?.(`[proxy-supervisor] socket transfer failed generation=${worker.generation} pid=${worker.handle.pid}: ${detail}`);
407
424
  if (this.active?.generation === worker.generation && !this.closed) {
408
425
  this.active = null;
@@ -427,13 +444,38 @@ export class RollingWorkerSupervisor {
427
444
  const code = error.code;
428
445
  return code ? `${code}: ${error.message}` : error.message;
429
446
  }
430
- recordFailure(generation, version, phase, message) {
447
+ extractLifecycleFailureDetails(error, fallbackPid) {
448
+ const context = error &&
449
+ typeof error === "object" &&
450
+ "context" in error &&
451
+ error.context &&
452
+ typeof error.context === "object"
453
+ ? (error.context ?? {})
454
+ : {};
455
+ const workerPid = typeof context.workerPid === "number" ? context.workerPid : fallbackPid;
456
+ const hasExitCode = typeof context.exitCode === "number" || context.exitCode === null;
457
+ const hasExitSignal = typeof context.signal === "string" || context.signal === null;
458
+ return {
459
+ details: {
460
+ workerPid,
461
+ ...(hasExitCode
462
+ ? { workerExitCode: context.exitCode }
463
+ : {}),
464
+ ...(hasExitSignal
465
+ ? { workerExitSignal: context.signal }
466
+ : {}),
467
+ },
468
+ observedExit: hasExitCode || hasExitSignal,
469
+ };
470
+ }
471
+ recordFailure(generation, version, phase, message, details = {}) {
431
472
  this.lastFailure = {
432
473
  at: new Date().toISOString(),
433
474
  generation,
434
475
  version,
435
476
  phase,
436
477
  message: message.slice(0, 1_000),
478
+ ...details,
437
479
  };
438
480
  }
439
481
  publishState() {
@@ -863,6 +863,10 @@ export type ProxyRollingState = {
863
863
  version: string;
864
864
  phase: "startup" | "activation" | "runtime" | "transfer";
865
865
  message: string;
866
+ workerPid?: number;
867
+ workerExitCode?: number | null;
868
+ workerExitSignal?: string | null;
869
+ supervisorAction?: "none" | "sigkill_after_transfer_failure";
866
870
  } | null;
867
871
  };
868
872
  export type ProxySupervisorState = {
@@ -1906,6 +1906,12 @@ export type SpawnProxySocketWorkerOptions = {
1906
1906
  stderr?: "inherit" | "ignore";
1907
1907
  socketAckTimeoutMs?: number;
1908
1908
  };
1909
+ export type RollingWorkerFailureDetails = {
1910
+ workerPid?: number;
1911
+ workerExitCode?: number | null;
1912
+ workerExitSignal?: string | null;
1913
+ supervisorAction?: "none" | "sigkill_after_transfer_failure";
1914
+ };
1909
1915
  export type RollingWorkerSupervisorSnapshot = {
1910
1916
  generation: number;
1911
1917
  active: {
@@ -1926,13 +1932,13 @@ export type RollingWorkerSupervisorSnapshot = {
1926
1932
  queuedSockets: number;
1927
1933
  rejectedSockets: number;
1928
1934
  failedTransfers: number;
1929
- lastFailure: {
1935
+ lastFailure: ({
1930
1936
  at: string;
1931
1937
  generation: number;
1932
1938
  version: string;
1933
1939
  phase: "startup" | "activation" | "runtime" | "transfer";
1934
1940
  message: string;
1935
- } | null;
1941
+ } & RollingWorkerFailureDetails) | null;
1936
1942
  };
1937
1943
  export type RollingWorkerSupervisorOptions = {
1938
1944
  spawnWorker: (generation: number, expectedVersion: string) => RollingWorkerHandle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.8.19",
3
+ "version": "10.8.21",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {
@@ -143,7 +143,7 @@
143
143
  "test:model-pool": "npx tsx test/continuous-test-suite-model-pool.ts",
144
144
  "test:model-not-found-retryable": "npx tsx test/continuous-test-suite-model-not-found-retryable.ts",
145
145
  "test:model-capabilities": "npx tsx test/continuous-test-suite-model-capabilities.ts",
146
- "test:agent-runtime:vitest": "pnpm exec vitest run test/agentRuntime.test.ts test/agentDelegation.test.ts test/agentPlumbing.test.ts test/toolExecutionRecorder.test.ts",
146
+ "test:agent-runtime:vitest": "pnpm exec vitest run test/agentRuntime.test.ts",
147
147
  "test:retry-after:vitest": "pnpm exec vitest run test/retryAfter.test.ts",
148
148
  "test:ci": "pnpm run test && pnpm run test:client && pnpm run test:hitl",
149
149
  "// CI tier — fast, no live AI calls, safe for every commit": "",
@@ -152,7 +152,7 @@
152
152
  "test:system-messages": "npx tsx test/continuous-test-suite-system-messages.ts",
153
153
  "test:test-stubs": "npx tsx test/continuous-test-suite-test-stubs.ts",
154
154
  "test:tool-routing-semantic": "npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
155
- "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:litellm-context && pnpm run test:dedup-execute-map && pnpm run test:step-budget-guard:vitest && pnpm run test:system-messages && pnpm run test:tool-routing-semantic && pnpm run test:anthropic-tools-policy && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop && pnpm run test:model-capabilities && pnpm run test:agent-runtime:vitest && pnpm run test:retry-after:vitest && pnpm run test:sampling-params && pnpm run test:structured-recovery && pnpm run test:prompt-redaction && pnpm run test:mcp-result-cache && pnpm run test:test-stubs && pnpm run test:model-not-found-retryable",
155
+ "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:litellm-context && pnpm run test:dedup-execute-map && pnpm run test:step-budget-guard && pnpm run test:agent-plumbing && pnpm run test:tool-execution-recorder && pnpm run test:proxy-terminal-errors && pnpm run test:system-messages && pnpm run test:tool-routing-semantic && pnpm run test:anthropic-tools-policy && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop && pnpm run test:model-capabilities && pnpm run test:agent-runtime:vitest && pnpm run test:agent-delegation && pnpm run test:retry-after:vitest && pnpm run test:sampling-params && pnpm run test:structured-recovery && pnpm run test:prompt-redaction && pnpm run test:mcp-result-cache && pnpm run test:test-stubs && pnpm run test:model-not-found-retryable",
156
156
  "// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit)": "",
157
157
  "test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic",
158
158
  "// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",
@@ -216,7 +216,11 @@
216
216
  "check:all": "pnpm run lint && pnpm run format --check && pnpm run validate && pnpm run validate:commit",
217
217
  "test:litellm-context": "npx tsx test/continuous-test-suite-litellm-context-windows.ts",
218
218
  "test:dedup-execute-map": "npx tsx test/continuous-test-suite-dedup-execute-map.ts",
219
- "test:step-budget-guard:vitest": "pnpm exec vitest run test/stepBudgetGuard.test.ts",
219
+ "test:agent-delegation": "npx tsx test/continuous-test-suite-agent-delegation.ts",
220
+ "test:step-budget-guard": "npx tsx test/continuous-test-suite-step-budget-guard.ts",
221
+ "test:agent-plumbing": "npx tsx test/continuous-test-suite-agent-plumbing.ts",
222
+ "test:tool-execution-recorder": "npx tsx test/continuous-test-suite-tool-execution-recorder.ts",
223
+ "test:proxy-terminal-errors": "npx tsx test/continuous-test-suite-proxy-terminal-errors.ts",
220
224
  "test:audio": "npx tsx test/continuous-test-suite-audio.ts",
221
225
  "test:office": "npx tsx test/continuous-test-suite-office.ts",
222
226
  "test:tts:unit": "npx tsx test/continuous-test-suite-tts-unit.ts",