@juspay/neurolink 13.8.0 → 13.8.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.
@@ -539,9 +539,6 @@ export class RedisConversationMemoryManager {
539
539
  messageCount: conversation.messages.length,
540
540
  });
541
541
  }
542
- if (this.redisConfig.ttl > 0) {
543
- await this.redisClient.expire(redisKey, this.redisConfig.ttl);
544
- }
545
542
  if (options.userId) {
546
543
  await this.addUserSession(options.userId, options.sessionId);
547
544
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { EventEmitter } from "events";
10
10
  import { mcpLogger } from "../utils/logger.js";
11
- import { MCPClientFactory } from "./mcpClientFactory.js";
11
+ import { MCPClientFactory, terminateStartingStdioServers, } from "./mcpClientFactory.js";
12
12
  import { ToolDiscoveryService } from "./toolDiscoveryService.js";
13
13
  import { toolRegistry } from "./toolRegistry.js";
14
14
  import { HITLUserRejectedError, HITLTimeoutError } from "../hitl/hitlErrors.js";
@@ -372,6 +372,9 @@ const shutdownOnSignal = (signal) => {
372
372
  // re-raise and nowhere else.
373
373
  if (!hadOtherListeners) {
374
374
  process.removeListener(signal, shutdownOnSignal);
375
+ // Dying of a signal runs no `exit` handler, and the cleanup above cannot
376
+ // reach a server whose handshake is still pending.
377
+ terminateStartingStdioServers();
375
378
  process.kill(process.pid, signal);
376
379
  }
377
380
  });
@@ -9,6 +9,12 @@ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
9
9
  import type { ClientCapabilities } from "@modelcontextprotocol/sdk/types.js";
10
10
  import type { ChildProcess } from "child_process";
11
11
  import type { MCPTransportType, MCPServerInfo, MCPClientResult } from "../types/index.js";
12
+ /**
13
+ * Synchronous on purpose: it is the one thing that can run inside the
14
+ * `exit` event, and SIGTERM is delivered by the kernel even though this
15
+ * process is already on its way out.
16
+ */
17
+ export declare const terminateStartingStdioServers: () => void;
12
18
  /**
13
19
  * MCPClientFactory
14
20
  * Factory class for creating MCP clients with different transports
@@ -62,6 +62,48 @@ class StderrTail {
62
62
  }
63
63
  }
64
64
  const stderrTails = new WeakMap();
65
+ /**
66
+ * Stdio servers that have been spawned but have not yet answered `initialize`.
67
+ *
68
+ * Nothing that cleans up at exit can reach one: the manager learns of a client
69
+ * and its transport only when `connect()` returns, and `process.exit()` runs
70
+ * no async handler anyway. It is also the state a server is least likely to
71
+ * notice its host going away, because `npx -y <pkg>` does not read its stdin
72
+ * until the package has downloaded. Once a server has answered it is an
73
+ * ordinary MCP server again and ends when its stdin closes, so it leaves this
74
+ * set and the shutdown paths that already exist keep ownership of it.
75
+ */
76
+ const startingStdioTransports = new Set();
77
+ let startingStdioExitHookInstalled = false;
78
+ /**
79
+ * Synchronous on purpose: it is the one thing that can run inside the
80
+ * `exit` event, and SIGTERM is delivered by the kernel even though this
81
+ * process is already on its way out.
82
+ */
83
+ export const terminateStartingStdioServers = () => {
84
+ for (const transport of startingStdioTransports) {
85
+ if ("pid" in transport && typeof transport.pid === "number") {
86
+ try {
87
+ process.kill(transport.pid, "SIGTERM");
88
+ }
89
+ catch {
90
+ // Already gone.
91
+ }
92
+ }
93
+ }
94
+ };
95
+ const trackStartingStdioTransport = (transport) => {
96
+ if (!startingStdioExitHookInstalled) {
97
+ startingStdioExitHookInstalled = true;
98
+ process.on("exit", terminateStartingStdioServers);
99
+ }
100
+ startingStdioTransports.add(transport);
101
+ // Client.connect() chains an onclose that is already set rather than
102
+ // replacing it, and a spawn that fails still closes the transport.
103
+ transport.onclose = () => {
104
+ startingStdioTransports.delete(transport);
105
+ };
106
+ };
65
107
  /**
66
108
  * MCPClientFactory
67
109
  * Factory class for creating MCP clients with different transports
@@ -223,6 +265,7 @@ export class MCPClientFactory {
223
265
  }
224
266
  // Connect with timeout
225
267
  await this.raceWithTimeout(client.connect(transport, { timeout }), timeout, `Client connection timeout for ${config.id}`);
268
+ startingStdioTransports.delete(transport);
226
269
  // Perform handshake to get server capabilities
227
270
  const serverCapabilities = await this.performHandshake(client, timeout);
228
271
  mcpLogger.debug(`[MCPClientFactory] Handshake completed for ${config.id}`, {
@@ -318,6 +361,7 @@ export class MCPClientFactory {
318
361
  cwd: config.cwd,
319
362
  stderr: "pipe",
320
363
  });
364
+ trackStartingStdioTransport(transport);
321
365
  const tail = new StderrTail();
322
366
  stderrTails.set(transport, tail);
323
367
  transport.stderr?.on("data", (chunk) => {
package/dist/neurolink.js CHANGED
@@ -8993,8 +8993,6 @@ Current user's request: ${currentInput}`;
8993
8993
  async prepareStreamOptions(options, streamId, startTime, hrTimeStart) {
8994
8994
  // Initialize conversation memory if needed (for lazy loading)
8995
8995
  await this.initializeConversationMemoryForGeneration(streamId, startTime, hrTimeStart);
8996
- // Initialize MCP
8997
- await this.initializeMCP();
8998
8996
  // Memory retrieval
8999
8997
  if (this.shouldReadMemory(options.memory, options.context?.userId) &&
9000
8998
  options.context?.userId) {
@@ -9042,6 +9040,15 @@ Current user's request: ${currentInput}`;
9042
9040
  }
9043
9041
  // Auto-disable tools for Ollama models that don't support them
9044
9042
  await this.autoDisableOllamaStreamTools(options);
9043
+ // generate() skips MCP when tools are disabled, so stream() must too:
9044
+ // initialization loads the working directory's .mcp-config.json and waits
9045
+ // for every stdio server it names before the request is sent, so an
9046
+ // `npx -y …` entry on a slow npm registry stalls a stream that never
9047
+ // needed a tool. Decided after the Ollama check above, which can turn
9048
+ // tools off. Providers register themselves in AIProviderFactory.
9049
+ if (!options.disableTools) {
9050
+ await this.initializeMCP();
9051
+ }
9045
9052
  // RAG Integration: If rag config is provided, prepare the RAG search tool
9046
9053
  if (options.rag?.files?.length) {
9047
9054
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "13.8.0",
3
+ "version": "13.8.2",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
6
6
  "author": {