@juspay/neurolink 9.35.0 → 9.36.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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [9.36.1](https://github.com/juspay/neurolink/compare/v9.36.0...v9.36.1) (2026-03-28)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **(proxy):** change default strategy from round-robin to fill-first ([067ec34](https://github.com/juspay/neurolink/commit/067ec34044e2bce162e78dca2ebe104aed1560e2))
6
+
7
+ ## [9.36.0](https://github.com/juspay/neurolink/compare/v9.35.0...v9.36.0) (2026-03-28)
8
+
9
+ ### Features
10
+
11
+ - **(proxy):** add auto-update with traffic-aware graceful restart ([4a11a78](https://github.com/juspay/neurolink/commit/4a11a783adb4424d7a303b298c6cf9989cd4ed63))
12
+
1
13
  ## [9.35.0](https://github.com/juspay/neurolink/compare/v9.34.0...v9.35.0) (2026-03-28)
2
14
 
3
15
  ### Features
@@ -287,8 +287,8 @@ export const proxyStartCommand = {
287
287
  .option("strategy", {
288
288
  type: "string",
289
289
  alias: "s",
290
- choices: ["round-robin", "fill-first"],
291
- description: "Account selection strategy for routing requests (default: round-robin)",
290
+ choices: ["fill-first", "round-robin"],
291
+ description: "Account selection strategy for routing requests (default: fill-first)",
292
292
  })
293
293
  .option("health-interval", {
294
294
  type: "number",
@@ -314,8 +314,8 @@ export const proxyStartCommand = {
314
314
  description: "Path to proxy config file (YAML/JSON)",
315
315
  defaultDescription: "~/.neurolink/proxy-config.yaml",
316
316
  })
317
- .example("neurolink proxy start", "Start proxy on default port 55669 with round-robin strategy")
318
- .example("neurolink proxy start -p 8080 -s round-robin", "Start proxy on port 8080 with round-robin")
317
+ .example("neurolink proxy start", "Start proxy on default port 55669 with fill-first strategy")
318
+ .example("neurolink proxy start -p 8080 -s fill-first", "Start proxy on port 8080 with fill-first")
319
319
  .example("neurolink proxy start --health-interval 60", "Start proxy with 60-second health checks");
320
320
  },
321
321
  handler: async (argv) => {
@@ -398,7 +398,7 @@ export const proxyStartCommand = {
398
398
  // -----------------------------------------------------------------
399
399
  // 3. Create ModelRouter from config (if routing configured)
400
400
  // -----------------------------------------------------------------
401
- const strategy = argv.strategy ?? proxyConfig?.routing?.strategy ?? "round-robin";
401
+ const strategy = argv.strategy ?? proxyConfig?.routing?.strategy ?? "fill-first";
402
402
  let modelRouter;
403
403
  if (proxyConfig?.routing) {
404
404
  const { ModelRouter } = await import("../../lib/proxy/modelRouter.js");
@@ -934,16 +934,21 @@ export const proxyGuardCommand = {
934
934
  const UPDATE_CHECK_INTERVAL_MS = 2 * 60 * 60 * 1000; // 2 hours
935
935
  const QUIET_THRESHOLD_MS = 120 * 1000; // 2 minutes of silence
936
936
  const UPDATE_TIMEOUT_MS = 30 * 1000; // 30 seconds to come healthy
937
- // Get running version from /health endpoint
937
+ // Get running version from /health endpoint (with timeout to avoid hanging)
938
938
  let runningVersion = PROXY_VERSION; // fallback
939
939
  try {
940
- const healthResp = await fetch(`http://${host}:${port}/health`);
940
+ const healthResp = await fetch(`http://${host}:${port}/health`, {
941
+ signal: AbortSignal.timeout(5_000),
942
+ });
941
943
  const healthData = (await healthResp.json());
942
944
  runningVersion = healthData.version ?? PROXY_VERSION;
943
945
  }
944
946
  catch {
945
947
  /* use fallback */
946
948
  }
949
+ // Auto-update only works on macOS with launchd. On other platforms,
950
+ // there's no restart mechanism, so skip the update loop entirely.
951
+ const canAutoUpdate = process.platform === "darwin" && (await isLaunchdManaging());
947
952
  let updateInProgress = false;
948
953
  let updateRestartInProgress = false;
949
954
  const runUpdateCheck = async () => {
@@ -1014,7 +1019,7 @@ export const proxyGuardCommand = {
1014
1019
  logger.always(`[guard] restarting proxy via launchctl...`);
1015
1020
  const uid = process.getuid?.() ?? 501;
1016
1021
  try {
1017
- execFileSync("launchctl", ["kickstart", "-k", `gui/${uid}/com.neurolink.proxy`], {
1022
+ execFileSync("launchctl", ["kickstart", "-k", `gui/${uid}/${PLIST_LABEL}`], {
1018
1023
  timeout: 10_000,
1019
1024
  stdio: "pipe",
1020
1025
  });
@@ -1065,8 +1070,10 @@ export const proxyGuardCommand = {
1065
1070
  }
1066
1071
  };
1067
1072
  // Run first check after a short delay, then on interval
1068
- setTimeout(runUpdateCheck, 30_000);
1069
- setInterval(runUpdateCheck, UPDATE_CHECK_INTERVAL_MS);
1073
+ if (canAutoUpdate) {
1074
+ setTimeout(runUpdateCheck, 30_000);
1075
+ setInterval(runUpdateCheck, UPDATE_CHECK_INTERVAL_MS);
1076
+ }
1070
1077
  const startedAt = Date.now();
1071
1078
  let parentStatus = getProcessStatus(parentPid);
1072
1079
  let consecutiveUnhealthy = 0;
@@ -1366,7 +1373,7 @@ export const proxyInstallCommand = {
1366
1373
  pid: Number(pidMatch[1]),
1367
1374
  port,
1368
1375
  host,
1369
- strategy: "round-robin",
1376
+ strategy: "fill-first",
1370
1377
  startTime: new Date().toISOString(),
1371
1378
  managedBy: "launchd",
1372
1379
  });
@@ -12,12 +12,25 @@ const DEFAULT_QUIET_THRESHOLD_MS = 120_000;
12
12
  /** Maximum bytes to read from the tail of the log file. */
13
13
  const TAIL_READ_SIZE = 4096;
14
14
  /**
15
- * Build the path to today's proxy debug log file.
15
+ * Build the path to a proxy debug log file for a given date.
16
16
  * Format: ~/.neurolink/logs/proxy-debug-YYYY-MM-DD.jsonl
17
17
  */
18
- function getTodayLogPath() {
19
- const today = new Date().toISOString().split("T")[0];
20
- return join(homedir(), ".neurolink", "logs", `proxy-debug-${today}.jsonl`);
18
+ function getLogPathForDate(date) {
19
+ const dateStr = date.toISOString().split("T")[0];
20
+ return join(homedir(), ".neurolink", "logs", `proxy-debug-${dateStr}.jsonl`);
21
+ }
22
+ /**
23
+ * Get the most relevant log file path. Uses today's log if it exists,
24
+ * otherwise falls back to yesterday's to handle the midnight rollover case
25
+ * (last request at 23:59, update check at 00:01).
26
+ */
27
+ function getActiveLogPath() {
28
+ const todayPath = getLogPathForDate(new Date());
29
+ if (existsSync(todayPath)) {
30
+ return todayPath;
31
+ }
32
+ const yesterday = new Date(Date.now() - 86_400_000);
33
+ return getLogPathForDate(yesterday);
21
34
  }
22
35
  /**
23
36
  * Read the last complete line(s) from a file efficiently.
@@ -82,7 +95,7 @@ export function checkTrafficQuiet(quietThresholdMs = DEFAULT_QUIET_THRESHOLD_MS)
82
95
  lastActivityAt: null,
83
96
  silenceDurationMs: Infinity,
84
97
  };
85
- const logPath = getTodayLogPath();
98
+ const logPath = getActiveLogPath();
86
99
  if (!existsSync(logPath)) {
87
100
  return noActivityResult;
88
101
  }
@@ -67,9 +67,12 @@ export function loadUpdateState(stateFilePath) {
67
67
  const content = fs.readFileSync(filePath, "utf8");
68
68
  const parsed = JSON.parse(content);
69
69
  // Minimal shape check — reject valid JSON that isn't an UpdateState
70
+ // Note: typeof null === "object", so we check both
70
71
  if (typeof parsed !== "object" ||
71
72
  parsed === null ||
72
73
  typeof parsed.suppressedVersions !== "object" ||
74
+ parsed.suppressedVersions === null ||
75
+ Array.isArray(parsed.suppressedVersions) ||
73
76
  typeof parsed.lastCheckAt !== "string") {
74
77
  return getDefaultUpdateState();
75
78
  }
@@ -12,12 +12,25 @@ const DEFAULT_QUIET_THRESHOLD_MS = 120_000;
12
12
  /** Maximum bytes to read from the tail of the log file. */
13
13
  const TAIL_READ_SIZE = 4096;
14
14
  /**
15
- * Build the path to today's proxy debug log file.
15
+ * Build the path to a proxy debug log file for a given date.
16
16
  * Format: ~/.neurolink/logs/proxy-debug-YYYY-MM-DD.jsonl
17
17
  */
18
- function getTodayLogPath() {
19
- const today = new Date().toISOString().split("T")[0];
20
- return join(homedir(), ".neurolink", "logs", `proxy-debug-${today}.jsonl`);
18
+ function getLogPathForDate(date) {
19
+ const dateStr = date.toISOString().split("T")[0];
20
+ return join(homedir(), ".neurolink", "logs", `proxy-debug-${dateStr}.jsonl`);
21
+ }
22
+ /**
23
+ * Get the most relevant log file path. Uses today's log if it exists,
24
+ * otherwise falls back to yesterday's to handle the midnight rollover case
25
+ * (last request at 23:59, update check at 00:01).
26
+ */
27
+ function getActiveLogPath() {
28
+ const todayPath = getLogPathForDate(new Date());
29
+ if (existsSync(todayPath)) {
30
+ return todayPath;
31
+ }
32
+ const yesterday = new Date(Date.now() - 86_400_000);
33
+ return getLogPathForDate(yesterday);
21
34
  }
22
35
  /**
23
36
  * Read the last complete line(s) from a file efficiently.
@@ -82,7 +95,7 @@ export function checkTrafficQuiet(quietThresholdMs = DEFAULT_QUIET_THRESHOLD_MS)
82
95
  lastActivityAt: null,
83
96
  silenceDurationMs: Infinity,
84
97
  };
85
- const logPath = getTodayLogPath();
98
+ const logPath = getActiveLogPath();
86
99
  if (!existsSync(logPath)) {
87
100
  return noActivityResult;
88
101
  }
@@ -67,9 +67,12 @@ export function loadUpdateState(stateFilePath) {
67
67
  const content = fs.readFileSync(filePath, "utf8");
68
68
  const parsed = JSON.parse(content);
69
69
  // Minimal shape check — reject valid JSON that isn't an UpdateState
70
+ // Note: typeof null === "object", so we check both
70
71
  if (typeof parsed !== "object" ||
71
72
  parsed === null ||
72
73
  typeof parsed.suppressedVersions !== "object" ||
74
+ parsed.suppressedVersions === null ||
75
+ Array.isArray(parsed.suppressedVersions) ||
73
76
  typeof parsed.lastCheckAt !== "string") {
74
77
  return getDefaultUpdateState();
75
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.35.0",
3
+ "version": "9.36.1",
4
4
  "description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 13 providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
5
5
  "author": {
6
6
  "name": "Juspay Technologies",