@juspay/neurolink 12.27.1 → 12.28.0

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/agent/directTools.d.ts +118 -94
  3. package/dist/agent/directTools.js +578 -446
  4. package/dist/agent/index.d.ts +1 -0
  5. package/dist/agent/index.js +1 -0
  6. package/dist/agent/prompts/terminalAgentPrompt.d.ts +13 -0
  7. package/dist/agent/prompts/terminalAgentPrompt.js +59 -0
  8. package/dist/browser/neurolink.min.js +382 -378
  9. package/dist/cli/factories/commandFactory.d.ts +25 -0
  10. package/dist/cli/factories/commandFactory.js +94 -8
  11. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  12. package/dist/cli/loop/session.js +6 -0
  13. package/dist/cli/utils/stdinRef.d.ts +16 -0
  14. package/dist/cli/utils/stdinRef.js +21 -0
  15. package/dist/core/baseProvider.d.ts +17 -1
  16. package/dist/core/baseProvider.js +48 -1
  17. package/dist/core/modules/ToolsManager.d.ts +7 -1
  18. package/dist/core/modules/ToolsManager.js +8 -0
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +1 -0
  21. package/dist/mcp/externalServerManager.js +173 -8
  22. package/dist/mcp/mcpCircuitBreaker.d.ts +8 -3
  23. package/dist/mcp/mcpCircuitBreaker.js +25 -9
  24. package/dist/mcp/mcpClientFactory.d.ts +9 -4
  25. package/dist/mcp/mcpClientFactory.js +26 -15
  26. package/dist/mcp/toolDiscoveryService.d.ts +8 -3
  27. package/dist/mcp/toolDiscoveryService.js +17 -7
  28. package/dist/mcp/toolRegistry.d.ts +12 -1
  29. package/dist/mcp/toolRegistry.js +26 -2
  30. package/dist/neurolink.d.ts +13 -0
  31. package/dist/neurolink.js +97 -10
  32. package/dist/session/globalSessionState.d.ts +8 -0
  33. package/dist/session/globalSessionState.js +17 -0
  34. package/dist/types/agentMode.d.ts +9 -0
  35. package/dist/types/agentMode.js +1 -0
  36. package/dist/types/cli.d.ts +6 -0
  37. package/dist/types/config.d.ts +9 -0
  38. package/dist/types/fileToolRoots.d.ts +17 -0
  39. package/dist/types/fileToolRoots.js +7 -0
  40. package/dist/types/generate.d.ts +17 -0
  41. package/dist/types/index.d.ts +2 -0
  42. package/dist/types/index.js +2 -0
  43. package/dist/types/providers.d.ts +6 -0
  44. package/dist/types/stream.d.ts +7 -0
  45. package/dist/types/tools.d.ts +7 -0
  46. package/dist/utils/fileToolRoots.d.ts +34 -0
  47. package/dist/utils/fileToolRoots.js +111 -0
  48. package/dist/utils/pathSandbox.d.ts +9 -6
  49. package/dist/utils/pathSandbox.js +35 -9
  50. package/docs-site/static/search-index.json +6 -5
  51. package/package.json +6 -2
@@ -145,6 +145,11 @@ export declare class CLICommandFactory {
145
145
  description: string;
146
146
  alias: string;
147
147
  };
148
+ toolRoot: {
149
+ type: "string";
150
+ array: boolean;
151
+ description: string;
152
+ };
148
153
  format: {
149
154
  choices: string[];
150
155
  default: string;
@@ -609,6 +614,17 @@ export declare class CLICommandFactory {
609
614
  alias: string;
610
615
  };
611
616
  };
617
+ static readonly agentOptions: {
618
+ agentMode: {
619
+ type: "boolean";
620
+ description: string;
621
+ };
622
+ maxSteps: {
623
+ type: "number";
624
+ description: string;
625
+ coerce: (value: unknown) => number;
626
+ };
627
+ };
612
628
  private static buildOptions;
613
629
  private static processCliImages;
614
630
  private static processCliCSVFiles;
@@ -920,6 +936,10 @@ export declare class CLICommandFactory {
920
936
  * Flush Langfuse traces before exit
921
937
  */
922
938
  private static flushLangfuseTraces;
939
+ /** process.exit discards piped stdout queued past the 64 KiB buffer; resolves false if the reader stalls past the bound. */
940
+ private static flushStdout;
941
+ /** Forced exit that fails loudly when queued output could not be delivered. */
942
+ private static exitAfterStdoutDrain;
923
943
  }
924
944
  /** Re-export of CLICommandFactory's static option definitions for direct import by tests/tooling. */
925
945
  export declare const commonOptions: {
@@ -1048,6 +1068,11 @@ export declare const commonOptions: {
1048
1068
  description: string;
1049
1069
  alias: string;
1050
1070
  };
1071
+ toolRoot: {
1072
+ type: "string";
1073
+ array: boolean;
1074
+ description: string;
1075
+ };
1051
1076
  format: {
1052
1077
  choices: string[];
1053
1078
  default: string;
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import chalk from "chalk";
4
4
  import ora from "ora";
5
+ import { STEP_LIMITS } from "../../core/constants.js";
5
6
  import { ModelResolver } from "../../models/modelResolver.js";
6
7
  import { providerChoicesFor } from "../../factories/mediaHandlerCatalog.js";
7
8
  import { globalSession } from "../../session/globalSessionState.js";
@@ -15,6 +16,7 @@ import { createThinkingConfigFromRecord } from "../../utils/thinkingConfig.js";
15
16
  import { buildToolRoutingConfigFromCli } from "../utils/toolRoutingFlags.js";
16
17
  import { buildClassifierRouterConfigFromCli } from "../utils/classifierRouterFlags.js";
17
18
  import { buildSkillsConfigFromCli } from "../utils/skillsFlags.js";
19
+ import { ensureStdinRef } from "../utils/stdinRef.js";
18
20
  import { SkillsManager } from "../../skills/skillsManager.js";
19
21
  import { configManager } from "../commands/config.js";
20
22
  import { MCPCommandFactory } from "../commands/mcp.js";
@@ -47,6 +49,21 @@ const DERIVED_PROVIDER_CHOICES = [
47
49
  ];
48
50
  /** Space-separated form for the bash-completion script, kept in sync with DERIVED_PROVIDER_CHOICES by construction. */
49
51
  export const BASH_COMPLETION_PROVIDERS = DERIVED_PROVIDER_CHOICES.join(" ");
52
+ /** EX_IOERR (sysexits.h): the result could not be fully written to stdout. */
53
+ const STDOUT_DRAIN_FAILED_EXIT_CODE = 74;
54
+ const DEFAULT_STDOUT_DRAIN_TIMEOUT_MS = 30_000;
55
+ const STDOUT_DRAIN_WARNING = "neurolink: stdout drain timed out; output may be incomplete\n";
56
+ let stdoutDrainTimeoutMs;
57
+ const resolveStdoutDrainTimeoutMs = () => {
58
+ if (stdoutDrainTimeoutMs === undefined) {
59
+ const override = Number(process.env.NEUROLINK_STDOUT_DRAIN_TIMEOUT_MS);
60
+ stdoutDrainTimeoutMs =
61
+ Number.isFinite(override) && override > 0
62
+ ? override
63
+ : DEFAULT_STDOUT_DRAIN_TIMEOUT_MS;
64
+ }
65
+ return stdoutDrainTimeoutMs;
66
+ };
50
67
  /**
51
68
  * CLI Command Factory for generate commands
52
69
  */
@@ -220,6 +237,11 @@ export class CLICommandFactory {
220
237
  description: "System prompt to guide AI behavior",
221
238
  alias: "s",
222
239
  },
240
+ toolRoot: {
241
+ type: "string",
242
+ array: true,
243
+ description: "Directory the built-in file tools may read and write (repeatable). Defaults to the working directory. Constrains bash's working directory, not its commands.",
244
+ },
223
245
  // Output control options
224
246
  format: {
225
247
  choices: ["text", "json", "table"],
@@ -735,6 +757,26 @@ export class CLICommandFactory {
735
757
  alias: "skills-dir",
736
758
  },
737
759
  };
760
+ // Registered on generate and stream only; the flags mean nothing elsewhere.
761
+ static agentOptions = {
762
+ agentMode: {
763
+ type: "boolean",
764
+ description: "Run as an autonomous terminal agent: prepend Neurolink's versioned agent instructions to the system prompt",
765
+ },
766
+ maxSteps: {
767
+ type: "number",
768
+ description: `Maximum tool-calling steps before the turn stops (integer ${STEP_LIMITS.min}-${STEP_LIMITS.max}, default ${STEP_LIMITS.default}; some providers cap lower)`,
769
+ coerce: (value) => {
770
+ if (typeof value !== "number" ||
771
+ !Number.isInteger(value) ||
772
+ value < STEP_LIMITS.min ||
773
+ value > STEP_LIMITS.max) {
774
+ throw new Error(`--max-steps must be an integer between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`);
775
+ }
776
+ return value;
777
+ },
778
+ },
779
+ };
738
780
  // Helper method to build options for commands. `excludeOptions` drops
739
781
  // common flags from registration entirely (not just from validation) —
740
782
  // #1191 round-5: batch uses this to omit `commonOptions.file` so `--file`
@@ -881,6 +923,8 @@ export class CLICommandFactory {
881
923
  stopSequences: argv.stopSequences,
882
924
  enabledToolNames: argv.enabledToolNames,
883
925
  systemPrompt: argv.system,
926
+ agentMode: argv.agentMode,
927
+ maxSteps: argv.maxSteps,
884
928
  timeout: argv.timeout,
885
929
  disableTools: argv.disableTools,
886
930
  enableAnalytics: argv.enableAnalytics,
@@ -1530,7 +1574,8 @@ export class CLICommandFactory {
1530
1574
  .example('$0 generate "Summarize this report" --pdf ./report.pdf', "Analyze a PDF document")
1531
1575
  .example('$0 generate "Key trends?" --csv large-data.csv --csvMaxRows 100', "Analyze a CSV with a row limit")
1532
1576
  .example('$0 generate "Compare the chart and the report" --image ./chart.png --pdf ./report.pdf', "Combine multiple file types in one prompt")
1533
- .example('$0 generate "What is in this file?" --file ./data.json', "Auto-detect a file type with --file"));
1577
+ .example('$0 generate "What is in this file?" --file ./data.json', "Auto-detect a file type with --file")
1578
+ .example('$0 generate "Fix the failing config" --agent-mode --max-steps 60', "Run as an autonomous terminal agent with a step budget"), CLICommandFactory.agentOptions);
1534
1579
  },
1535
1580
  handler: async (argv) => await CLICommandFactory.executeGenerate(argv),
1536
1581
  };
@@ -1555,7 +1600,7 @@ export class CLICommandFactory {
1555
1600
  .example('$0 stream "Narrate this video" --video path/to/video.mp4', "Stream video analysis")
1556
1601
  .example('$0 stream "Describe this image" --image ./photo.jpg', "Stream image analysis (multimodal input)")
1557
1602
  .example('$0 stream "Summarize this document" --pdf ./report.pdf', "Stream PDF analysis")
1558
- .example('$0 stream "Explain this dataset" --csv ./data.csv --csv-format markdown', "Stream CSV analysis"));
1603
+ .example('$0 stream "Explain this dataset" --csv ./data.csv --csv-format markdown', "Stream CSV analysis"), CLICommandFactory.agentOptions);
1559
1604
  },
1560
1605
  handler: async (argv) => await CLICommandFactory.executeStream(argv),
1561
1606
  };
@@ -2260,7 +2305,7 @@ export class CLICommandFactory {
2260
2305
  logger.error("Error during SDK shutdown:", shutdownError);
2261
2306
  }
2262
2307
  if (!globalSession.getCurrentSessionId()) {
2263
- process.exit();
2308
+ await CLICommandFactory.exitAfterStdoutDrain();
2264
2309
  }
2265
2310
  }
2266
2311
  }
@@ -2275,6 +2320,11 @@ export class CLICommandFactory {
2275
2320
  const isSttOnly = !!(argv.stt &&
2276
2321
  argv.inputAudio);
2277
2322
  if (!argv.input && !process.stdin.isTTY) {
2323
+ // The SDK's MCP stdio transport unrefs stdin on import so an idle
2324
+ // process can exit on its own (see externalServerManager.ts). This
2325
+ // path actually reads stdin and must block on it, so ref it back
2326
+ // before consuming.
2327
+ ensureStdinRef();
2278
2328
  let stdinData = "";
2279
2329
  process.stdin.setEncoding("utf8");
2280
2330
  for await (const chunk of process.stdin) {
@@ -2556,7 +2606,7 @@ export class CLICommandFactory {
2556
2606
  }
2557
2607
  if (!globalSession.getCurrentSessionId()) {
2558
2608
  await CLICommandFactory.flushLangfuseTraces();
2559
- process.exit(0);
2609
+ await CLICommandFactory.exitAfterStdoutDrain(0);
2560
2610
  }
2561
2611
  }
2562
2612
  /**
@@ -2621,7 +2671,7 @@ export class CLICommandFactory {
2621
2671
  }
2622
2672
  if (!globalSession.getCurrentSessionId()) {
2623
2673
  await CLICommandFactory.flushLangfuseTraces();
2624
- process.exit(0);
2674
+ await CLICommandFactory.exitAfterStdoutDrain(0);
2625
2675
  }
2626
2676
  return;
2627
2677
  }
@@ -2640,6 +2690,7 @@ export class CLICommandFactory {
2640
2690
  if (skillsConfig) {
2641
2691
  globalSession.setSkillsConfig(skillsConfig);
2642
2692
  }
2693
+ globalSession.setFileToolRoots(argv.toolRoot);
2643
2694
  // Initialize SDK and session
2644
2695
  const sdk = globalSession.getOrCreateNeuroLink();
2645
2696
  const sessionVariables = CLICommandFactory.normalizeLoopSessionVariables(globalSession.getSessionVariables());
@@ -2694,6 +2745,8 @@ export class CLICommandFactory {
2694
2745
  topK: enhancedOptions.topK,
2695
2746
  stopSequences: enhancedOptions.stopSequences,
2696
2747
  systemPrompt: enhancedOptions.systemPrompt,
2748
+ agentMode: enhancedOptions.agentMode,
2749
+ maxSteps: enhancedOptions.maxSteps,
2697
2750
  timeout: enhancedOptions.timeout
2698
2751
  ? enhancedOptions.timeout * 1000
2699
2752
  : undefined,
@@ -2886,7 +2939,7 @@ export class CLICommandFactory {
2886
2939
  }
2887
2940
  if (!globalSession.getCurrentSessionId()) {
2888
2941
  await CLICommandFactory.flushLangfuseTraces();
2889
- process.exit(0);
2942
+ await CLICommandFactory.exitAfterStdoutDrain(0);
2890
2943
  }
2891
2944
  }
2892
2945
  /**
@@ -2908,6 +2961,7 @@ export class CLICommandFactory {
2908
2961
  if (skillsConfig) {
2909
2962
  globalSession.setSkillsConfig(skillsConfig);
2910
2963
  }
2964
+ globalSession.setFileToolRoots(argv.toolRoot);
2911
2965
  const sdk = globalSession.getOrCreateNeuroLink();
2912
2966
  const sessionVariables = CLICommandFactory.normalizeLoopSessionVariables(globalSession.getSessionVariables());
2913
2967
  const enhancedOptions = { ...options, ...sessionVariables };
@@ -2943,6 +2997,8 @@ export class CLICommandFactory {
2943
2997
  topK: enhancedOptions.topK,
2944
2998
  stopSequences: enhancedOptions.stopSequences,
2945
2999
  systemPrompt: enhancedOptions.systemPrompt,
3000
+ agentMode: enhancedOptions.agentMode,
3001
+ maxSteps: enhancedOptions.maxSteps,
2946
3002
  timeout: enhancedOptions.timeout
2947
3003
  ? enhancedOptions.timeout * 1000
2948
3004
  : undefined,
@@ -3322,6 +3378,11 @@ export class CLICommandFactory {
3322
3378
  // as the prompt). Skip the stdin/empty-input rejection in that case.
3323
3379
  const isSttOnly = !!argv.stt && !!argv.inputAudio;
3324
3380
  if (!argv.input && !process.stdin.isTTY) {
3381
+ // The SDK's MCP stdio transport unrefs stdin on import so an idle
3382
+ // process can exit on its own (see externalServerManager.ts). This
3383
+ // path actually reads stdin and must block on it, so ref it back
3384
+ // before consuming.
3385
+ ensureStdinRef();
3325
3386
  let stdinData = "";
3326
3387
  process.stdin.setEncoding("utf8");
3327
3388
  for await (const chunk of process.stdin) {
@@ -3380,7 +3441,7 @@ export class CLICommandFactory {
3380
3441
  await CLICommandFactory.handleStreamOutput(options, fullContent);
3381
3442
  if (!globalSession.getCurrentSessionId()) {
3382
3443
  await CLICommandFactory.flushLangfuseTraces();
3383
- process.exit(0);
3444
+ await CLICommandFactory.exitAfterStdoutDrain(0);
3384
3445
  }
3385
3446
  }
3386
3447
  catch (error) {
@@ -3451,6 +3512,7 @@ export class CLICommandFactory {
3451
3512
  if (skillsConfig) {
3452
3513
  globalSession.setSkillsConfig(skillsConfig);
3453
3514
  }
3515
+ globalSession.setFileToolRoots(argv.toolRoot);
3454
3516
  const sdk = globalSession.getOrCreateNeuroLink();
3455
3517
  const sessionVariables = CLICommandFactory.normalizeLoopSessionVariables(globalSession.getSessionVariables());
3456
3518
  const enhancedOptions = { ...options, ...sessionVariables };
@@ -3584,7 +3646,7 @@ export class CLICommandFactory {
3584
3646
  CLICommandFactory.handleOutput(results, options);
3585
3647
  if (!globalSession.getCurrentSessionId()) {
3586
3648
  await CLICommandFactory.flushLangfuseTraces();
3587
- process.exit(0);
3649
+ await CLICommandFactory.exitAfterStdoutDrain(0);
3588
3650
  }
3589
3651
  }
3590
3652
  catch (error) {
@@ -4320,6 +4382,11 @@ export class CLICommandFactory {
4320
4382
  logger.always(chalk.yellow(`Usage: neurolink memory delete ${argv.sessionId} --force`));
4321
4383
  return;
4322
4384
  }
4385
+ // The SDK's MCP stdio transport unrefs stdin on import so an idle
4386
+ // process can exit on its own (see externalServerManager.ts). This
4387
+ // prompt actually reads stdin and must block on it, so ref it back
4388
+ // before consuming.
4389
+ ensureStdinRef();
4323
4390
  const readline = await import("readline");
4324
4391
  const rl = readline.createInterface({
4325
4392
  input: process.stdin,
@@ -4545,6 +4612,25 @@ export class CLICommandFactory {
4545
4612
  logger.error("[CLI] Error flushing Langfuse traces", { error });
4546
4613
  }
4547
4614
  }
4615
+ /** process.exit discards piped stdout queued past the 64 KiB buffer; resolves false if the reader stalls past the bound. */
4616
+ static flushStdout() {
4617
+ return new Promise((resolve) => {
4618
+ const timer = setTimeout(() => resolve(false), resolveStdoutDrainTimeoutMs());
4619
+ timer.unref();
4620
+ process.stdout.write("", () => {
4621
+ clearTimeout(timer);
4622
+ resolve(true);
4623
+ });
4624
+ });
4625
+ }
4626
+ /** Forced exit that fails loudly when queued output could not be delivered. */
4627
+ static async exitAfterStdoutDrain(exitCode) {
4628
+ if (await CLICommandFactory.flushStdout()) {
4629
+ process.exit(exitCode);
4630
+ }
4631
+ process.stderr.write(STDOUT_DRAIN_WARNING);
4632
+ process.exit(STDOUT_DRAIN_FAILED_EXIT_CODE);
4633
+ }
4548
4634
  }
4549
4635
  /** Re-export of CLICommandFactory's static option definitions for direct import by tests/tooling. */
4550
4636
  export const commonOptions = CLICommandFactory.commonOptions;
@@ -4,4 +4,4 @@ import type { OptionSchema, TextGenerationOptions } from "../../types/index.js";
4
4
  * This object provides metadata for validation and help text in the CLI loop.
5
5
  * It is derived from the main TextGenerationOptions interface to ensure consistency.
6
6
  */
7
- export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "pdfOptions" | "imageOptions" | "tts" | "stt" | "thinkingConfig" | "requestId" | "fileRegistry" | "abortSignal" | "toolFilter" | "excludeTools" | "toolChoice" | "prepareStep" | "credentials" | "onFinish" | "onError" | "processors" | "piiDetection" | "responseValidation" | "inputValidation" | "toolExecutionCapture" | "toolExecutionRecorder">, OptionSchema>;
7
+ export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "pdfOptions" | "imageOptions" | "tts" | "stt" | "thinkingConfig" | "requestId" | "fileRegistry" | "abortSignal" | "toolFilter" | "excludeTools" | "toolChoice" | "prepareStep" | "credentials" | "onFinish" | "onError" | "processors" | "piiDetection" | "responseValidation" | "inputValidation" | "toolExecutionCapture" | "toolExecutionRecorder" | "toolRoots">, OptionSchema>;
@@ -10,6 +10,7 @@ import { tracers } from "../../telemetry/tracers.js";
10
10
  import { handleError } from "../errorHandler.js";
11
11
  import { ConversationSelector } from "./conversationSelector.js";
12
12
  import { textGenerationOptionsSchema } from "./optionsSchema.js";
13
+ import { ensureStdinRef } from "../utils/stdinRef.js";
13
14
  // Banner Art
14
15
  const NEUROLINK_BANNER = `
15
16
  ▗▖ ▗▖▗▄▄▄▖▗▖ ▗▖▗▄▄▖ ▗▄▖ ▗▖ ▗▄▄▄▖▗▖ ▗▖▗▖ ▗▖
@@ -455,6 +456,11 @@ export class LoopSession {
455
456
  * Get command input with history support using readline
456
457
  */
457
458
  async getCommandWithHistory() {
459
+ // The SDK's MCP stdio transport unrefs stdin on import so an idle
460
+ // process can exit on its own (see externalServerManager.ts). The
461
+ // interactive loop reads a line from stdin on every prompt and must
462
+ // block on it, so ref it back before consuming.
463
+ ensureStdinRef();
458
464
  return new Promise((resolve) => {
459
465
  const rl = readline.createInterface({
460
466
  input: process.stdin,
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The SDK unrefs `process.stdin` on import (see externalServerManager.ts) so
3
+ * that a process which never reads stdin can still exit on its own instead
4
+ * of being kept alive forever by a handle it never uses. Any CLI code path
5
+ * that actually reads from stdin — piped generate/stream input, a readline
6
+ * confirmation prompt, the interactive loop's line reader — must ref it
7
+ * back first, or the process can exit (or simply never treat stdin as a
8
+ * reason to stay alive) before the read completes.
9
+ *
10
+ * Centralizing the call here means a new stdin consumer only has to
11
+ * remember to call this, not to rediscover and re-derive the reason why.
12
+ * Three call sites each doing this inline already produced one that forgot
13
+ * to (the memory-delete confirmation prompt) — a fourth is just as likely
14
+ * to repeat that.
15
+ */
16
+ export declare function ensureStdinRef(): void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The SDK unrefs `process.stdin` on import (see externalServerManager.ts) so
3
+ * that a process which never reads stdin can still exit on its own instead
4
+ * of being kept alive forever by a handle it never uses. Any CLI code path
5
+ * that actually reads from stdin — piped generate/stream input, a readline
6
+ * confirmation prompt, the interactive loop's line reader — must ref it
7
+ * back first, or the process can exit (or simply never treat stdin as a
8
+ * reason to stay alive) before the read completes.
9
+ *
10
+ * Centralizing the call here means a new stdin consumer only has to
11
+ * remember to call this, not to rediscover and re-derive the reason why.
12
+ * Three call sites each doing this inline already produced one that forgot
13
+ * to (the memory-delete confirmation prompt) — a fourth is just as likely
14
+ * to repeat that.
15
+ */
16
+ export function ensureStdinRef() {
17
+ if (process.stdin && typeof process.stdin.ref === "function") {
18
+ process.stdin.ref();
19
+ }
20
+ }
21
+ //# sourceMappingURL=stdinRef.js.map
@@ -1,7 +1,7 @@
1
1
  import type { AIProviderName } from "../constants/enums.js";
2
2
  import type { EvaluationData } from "../index.js";
3
3
  import type { NeuroLink } from "../neurolink.js";
4
- import type { UnknownRecord, MiddlewareFactoryOptions, StreamOptions, StreamResult, AIProvider, AnalyticsData, EnhancedGenerateResult, TextGenerationOptions, TextGenerationResult, ValidationSchema, EmbedInput, DecisionRequest, DecisionResult } from "../types/index.js";
4
+ import type { FileToolRootPolicy, UnknownRecord, MiddlewareFactoryOptions, StreamOptions, StreamResult, AIProvider, AnalyticsData, EnhancedGenerateResult, TextGenerationOptions, TextGenerationResult, ValidationSchema, EmbedInput, DecisionRequest, DecisionResult } from "../types/index.js";
5
5
  import { TelemetryHandler } from "./modules/TelemetryHandler.js";
6
6
  import type { LanguageModel, ModelMessage, RawUsageObject, Tool, ToolCallRepairFunction, ToolSet } from "../types/index.js";
7
7
  export declare abstract class BaseProvider implements AIProvider {
@@ -11,6 +11,7 @@ export declare abstract class BaseProvider implements AIProvider {
11
11
  protected middlewareOptions?: MiddlewareFactoryOptions;
12
12
  protected readonly directTools: {};
13
13
  protected mcpTools?: Record<string, Tool>;
14
+ private fileToolRootsBound;
14
15
  protected customTools?: Map<string, unknown>;
15
16
  protected toolExecutor?: (toolName: string, params: unknown) => Promise<unknown>;
16
17
  protected sessionId?: string;
@@ -532,6 +533,21 @@ export declare abstract class BaseProvider implements AIProvider {
532
533
  /**
533
534
  * Get all available tools - delegated to ToolsManager
534
535
  */
536
+ /**
537
+ * Bind the built-in file tools to one request's root policy. NeuroLink
538
+ * calls this before every provider call that carries the caller's options,
539
+ * because several providers override generate()/stream() and assemble
540
+ * their tools without passing through BaseProvider's entry points.
541
+ * Providers are created per request, so a bound policy never reaches
542
+ * another request.
543
+ */
544
+ setFileToolRootPolicy(policy: FileToolRootPolicy | undefined): void;
545
+ /**
546
+ * Resolve roots for a provider called directly, without NeuroLink. A
547
+ * policy NeuroLink already bound is left alone unless these options carry
548
+ * their own. An invalid root throws, before any model request.
549
+ */
550
+ private applyFileToolRoots;
535
551
  protected getAllTools(): Promise<Record<string, Tool>>;
536
552
  /**
537
553
  * Calculate actual cost - delegated to TelemetryHandler
@@ -1,5 +1,6 @@
1
1
  import { context, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
2
- import { directAgentTools } from "../agent/directTools.js";
2
+ import { createDirectAgentTools, directAgentTools, } from "../agent/directTools.js";
3
+ import { boundFileToolRoots, resolveFileToolRootPolicy, } from "../utils/fileToolRoots.js";
3
4
  import { defaultProviderFor } from "../factories/mediaHandlerCatalog.js";
4
5
  import { DECISION_PROVIDERS, PROVIDER_DESCRIPTORS_BY_NAME, } from "../factories/providerDescriptors.js";
5
6
  import { MiddlewareFactory } from "../middleware/factory.js";
@@ -120,6 +121,7 @@ export class BaseProvider {
120
121
  ? {}
121
122
  : directAgentTools;
122
123
  mcpTools; // MCP tools loaded dynamically when available
124
+ fileToolRootsBound = false;
123
125
  customTools; // Custom tools from registerTool()
124
126
  toolExecutor; // Tool executor from setupToolExecutor
125
127
  sessionId;
@@ -225,6 +227,7 @@ export class BaseProvider {
225
227
  // maxTokens (getSafeMaxTokens consults the discovered output ceiling).
226
228
  await this.ensureModelLimits();
227
229
  let options = this.normalizeStreamOptions(optionsOrPrompt);
230
+ this.applyFileToolRoots(options);
228
231
  // Before anything else, and before a single byte leaves the process: an
229
232
  // execution policy this provider cannot honour is an error, and a policy
230
233
  // whose shape could be read two ways is an error. Both are silent bugs at
@@ -1292,6 +1295,7 @@ export class BaseProvider {
1292
1295
  await this.ensureModelLimits();
1293
1296
  const options = this.normalizeTextOptions(optionsOrPrompt);
1294
1297
  this.validateOptions(options);
1298
+ this.applyFileToolRoots(options);
1295
1299
  const startTime = Date.now();
1296
1300
  // One span per attempt. runGenerateInActiveContext ends the span on the
1297
1301
  // way out (success or failure), so a model-fallback retry must not reuse
@@ -2156,7 +2160,50 @@ export class BaseProvider {
2156
2160
  /**
2157
2161
  * Get all available tools - delegated to ToolsManager
2158
2162
  */
2163
+ /**
2164
+ * Bind the built-in file tools to one request's root policy. NeuroLink
2165
+ * calls this before every provider call that carries the caller's options,
2166
+ * because several providers override generate()/stream() and assemble
2167
+ * their tools without passing through BaseProvider's entry points.
2168
+ * Providers are created per request, so a bound policy never reaches
2169
+ * another request.
2170
+ */
2171
+ setFileToolRootPolicy(policy) {
2172
+ if (policy === undefined || Object.keys(this.directTools).length === 0) {
2173
+ return;
2174
+ }
2175
+ this.fileToolRootsBound = true;
2176
+ this.toolsManager.setDirectTools(policy.roots === null ? this.directTools : createDirectAgentTools(policy));
2177
+ }
2178
+ /**
2179
+ * Resolve roots for a provider called directly, without NeuroLink. A
2180
+ * policy NeuroLink already bound is left alone unless these options carry
2181
+ * their own. An invalid root throws, before any model request.
2182
+ */
2183
+ applyFileToolRoots(options) {
2184
+ if (Object.keys(this.directTools).length === 0) {
2185
+ return;
2186
+ }
2187
+ const bound = boundFileToolRoots(options);
2188
+ if (bound !== undefined) {
2189
+ this.setFileToolRootPolicy(bound);
2190
+ return;
2191
+ }
2192
+ if (this.fileToolRootsBound && options.toolRoots === undefined) {
2193
+ return;
2194
+ }
2195
+ this.setFileToolRootPolicy(resolveFileToolRootPolicy({
2196
+ perCall: options.toolRoots,
2197
+ instance: this.neurolink?.getToolsConfig()?.fileRoots,
2198
+ }));
2199
+ }
2159
2200
  async getAllTools() {
2201
+ // Any path that reaches tool assembly without a bound policy — an internal
2202
+ // caller, or a provider whose generate()/stream() skips the base entry
2203
+ // points — gets the instance's roots rather than the working directory.
2204
+ if (!this.fileToolRootsBound) {
2205
+ this.applyFileToolRoots({});
2206
+ }
2160
2207
  return this.toolsManager.getAllTools();
2161
2208
  }
2162
2209
  /**
@@ -20,7 +20,7 @@ export declare function buildMCPSchemaValidator(toolName: string, schema: Record
20
20
  }) | undefined>;
21
21
  export declare class ToolsManager {
22
22
  private readonly providerName;
23
- private readonly directTools;
23
+ private directTools;
24
24
  private readonly neurolink?;
25
25
  private readonly utilities?;
26
26
  protected mcpTools?: Record<string, Tool>;
@@ -112,6 +112,12 @@ export declare class ToolsManager {
112
112
  * Get direct tools (built-in agent tools)
113
113
  */
114
114
  getDirectTools(): Record<string, unknown>;
115
+ /**
116
+ * Replace the direct tools for this provider instance — used to bind the
117
+ * built-in file tools to one request's root policy. Providers are created
118
+ * per request, so this never leaks a boundary into another request.
119
+ */
120
+ setDirectTools(tools: Record<string, unknown>): void;
115
121
  /**
116
122
  * Get MCP tools
117
123
  */
@@ -550,6 +550,14 @@ export class ToolsManager {
550
550
  getDirectTools() {
551
551
  return this.directTools;
552
552
  }
553
+ /**
554
+ * Replace the direct tools for this provider instance — used to bind the
555
+ * built-in file tools to one request's root policy. Providers are created
556
+ * per request, so this never leaks a boundary into another request.
557
+ */
558
+ setDirectTools(tools) {
559
+ this.directTools = tools;
560
+ }
553
561
  /**
554
562
  * Get MCP tools
555
563
  */
package/dist/index.d.ts CHANGED
@@ -504,5 +504,6 @@ export { validateResponse } from "./utils/responseValidator.js";
504
504
  export { TripwireEvaluator, createDefaultTripwireEvaluator, commonTripwires, } from "./utils/tripwireEvaluator.js";
505
505
  export { Agent } from "./agent/agent.js";
506
506
  export { AgentNetwork } from "./agent/agentNetwork.js";
507
+ export { TERMINAL_AGENT_PROMPT_V1, TERMINAL_AGENT_PROMPT_V2, TERMINAL_AGENT_PROMPT_VERSION, } from "./agent/prompts/terminalAgentPrompt.js";
507
508
  export { AgentCoordinator, TaskDistributor, MessageBus, NetworkOrchestrator, NetworkTopology, TopologyBuilder, } from "./agent/index.js";
508
509
  export { createLocalUsageReader, getLocalUsageDescriptors, getRegisteredLocalUsageCliIds, readAllLocalUsage, registerLocalUsageReader, } from "./localUsage/index.js";
package/dist/index.js CHANGED
@@ -823,6 +823,7 @@ export { TripwireEvaluator, createDefaultTripwireEvaluator, commonTripwires, } f
823
823
  // ============================================================================
824
824
  export { Agent } from "./agent/agent.js";
825
825
  export { AgentNetwork } from "./agent/agentNetwork.js";
826
+ export { TERMINAL_AGENT_PROMPT_V1, TERMINAL_AGENT_PROMPT_V2, TERMINAL_AGENT_PROMPT_VERSION, } from "./agent/prompts/terminalAgentPrompt.js";
826
827
  // Advanced agent orchestration exports
827
828
  export {
828
829
  // Coordination