@juspay/neurolink 12.1.0 → 12.2.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 (35) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/agent/agentToolRegistrar.d.ts +30 -0
  3. package/dist/agent/agentToolRegistrar.js +72 -18
  4. package/dist/agent/backgroundCommands.d.ts +110 -0
  5. package/dist/agent/backgroundCommands.js +914 -0
  6. package/dist/agent/backgroundDelegation.d.ts +87 -0
  7. package/dist/agent/backgroundDelegation.js +753 -0
  8. package/dist/agent/gitTools.d.ts +43 -0
  9. package/dist/agent/gitTools.js +618 -0
  10. package/dist/agent/taskChecklist.d.ts +58 -0
  11. package/dist/agent/taskChecklist.js +322 -0
  12. package/dist/artifacts/artifactBanking.d.ts +57 -0
  13. package/dist/artifacts/artifactBanking.js +123 -0
  14. package/dist/artifacts/artifactStore.d.ts +36 -8
  15. package/dist/artifacts/artifactStore.js +164 -13
  16. package/dist/browser/neurolink.min.js +442 -414
  17. package/dist/neurolink.d.ts +294 -3
  18. package/dist/neurolink.js +447 -4
  19. package/dist/types/artifact.d.ts +54 -0
  20. package/dist/types/backgroundCommand.d.ts +174 -0
  21. package/dist/types/backgroundCommand.js +22 -0
  22. package/dist/types/delegation.d.ts +178 -0
  23. package/dist/types/delegation.js +18 -0
  24. package/dist/types/gitTools.d.ts +69 -0
  25. package/dist/types/gitTools.js +22 -0
  26. package/dist/types/index.d.ts +5 -0
  27. package/dist/types/index.js +8 -0
  28. package/dist/types/pathSandbox.d.ts +23 -0
  29. package/dist/types/pathSandbox.js +12 -0
  30. package/dist/types/tasks.d.ts +85 -0
  31. package/dist/types/tasks.js +14 -0
  32. package/dist/types/tools.d.ts +11 -0
  33. package/dist/utils/pathSandbox.d.ts +49 -0
  34. package/dist/utils/pathSandbox.js +127 -0
  35. package/package.json +5 -1
@@ -153,6 +153,17 @@ export type ToolRegistrationOptions = {
153
153
  * When omitted, the SDK's global default (2 retries) is used.
154
154
  * Set to 0 to disable retries for this tool. */
155
155
  maxRetries?: number;
156
+ /**
157
+ * Whether this tool's result may be served from the tool-result cache
158
+ * (default true).
159
+ *
160
+ * Set to `false` for a tool whose result is NOT a function of its arguments
161
+ * — anything reading or mutating live state. The cache is keyed by tool name
162
+ * plus arguments, so a stateful tool called twice with the same arguments
163
+ * replays its first answer for the whole TTL: a checklist that never updates,
164
+ * a queue that hands out the same item twice.
165
+ */
166
+ cacheable?: boolean;
156
167
  };
157
168
  /**
158
169
  * Tool execution result
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Path containment guard for the sandboxed execution paths.
3
+ *
4
+ * `bashTool` does this check inline; `directTools.resolveWithinCwd` does a
5
+ * weaker, non-symlink-aware version against `process.cwd()`. Neither is
6
+ * reusable, and the background-command runner needs the strong form against a
7
+ * caller-declared root, so it lives here once.
8
+ *
9
+ * The load-bearing detail is **realpath**. A string comparison on resolved
10
+ * paths is defeated by a symlink: `<root>/escape → /etc` resolves lexically to
11
+ * `<root>/escape`, which passes, and then the command runs in `/etc`. Both
12
+ * sides are therefore resolved through the filesystem before they are
13
+ * compared, and a target that cannot be resolved is refused rather than
14
+ * assumed innocent.
15
+ *
16
+ * The `+ sep` suffix on the prefix test is the other one: without it
17
+ * `/home/app-evil` passes a containment check against `/home/app`.
18
+ *
19
+ * @module utils/pathSandbox
20
+ */
21
+ import type { PathSandboxResult } from "../types/index.js";
22
+ /**
23
+ * Resolve `target` and confirm it is `root` or lies inside it, with symlinks
24
+ * followed on both sides.
25
+ *
26
+ * Both must exist and be directories — a containment check on a path that is
27
+ * not there yet cannot be honest, because whatever appears later may be a
28
+ * symlink out.
29
+ *
30
+ * @param target Directory to check. Relative paths resolve against `root`.
31
+ * @param root Sandbox root the target must not escape.
32
+ * @returns The resolved REAL path, or the reason it was refused.
33
+ */
34
+ export declare function resolveWithinRoot(target: string, root: string): PathSandboxResult;
35
+ /**
36
+ * The file twin of {@link resolveWithinRoot}: contain a path that may name a
37
+ * file, or may not exist at all (a path argument to `git log`, say).
38
+ *
39
+ * A path that exists is resolved through its symlinks and checked directly. A
40
+ * path that does not exist is checked on its nearest EXISTING ancestor, which
41
+ * is the deepest point a symlink could redirect — everything below that is a
42
+ * name, not a link. Deleted files therefore stay askable about, and
43
+ * `../../etc/passwd` still does not.
44
+ *
45
+ * @param target Path to check. Relative paths resolve against `root`.
46
+ * @param root Sandbox root the target must not escape.
47
+ * @returns The resolved absolute path, or the reason it was refused.
48
+ */
49
+ export declare function resolvePathWithinRoot(target: string, root: string): PathSandboxResult;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Path containment guard for the sandboxed execution paths.
3
+ *
4
+ * `bashTool` does this check inline; `directTools.resolveWithinCwd` does a
5
+ * weaker, non-symlink-aware version against `process.cwd()`. Neither is
6
+ * reusable, and the background-command runner needs the strong form against a
7
+ * caller-declared root, so it lives here once.
8
+ *
9
+ * The load-bearing detail is **realpath**. A string comparison on resolved
10
+ * paths is defeated by a symlink: `<root>/escape → /etc` resolves lexically to
11
+ * `<root>/escape`, which passes, and then the command runs in `/etc`. Both
12
+ * sides are therefore resolved through the filesystem before they are
13
+ * compared, and a target that cannot be resolved is refused rather than
14
+ * assumed innocent.
15
+ *
16
+ * The `+ sep` suffix on the prefix test is the other one: without it
17
+ * `/home/app-evil` passes a containment check against `/home/app`.
18
+ *
19
+ * @module utils/pathSandbox
20
+ */
21
+ import { realpathSync, statSync } from "node:fs";
22
+ import { basename, isAbsolute, join, resolve, sep } from "node:path";
23
+ /** Real path of an existing directory, or undefined. */
24
+ function realDirectory(candidate) {
25
+ try {
26
+ const real = realpathSync(candidate);
27
+ return statSync(real).isDirectory() ? real : undefined;
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
33
+ /** Real path of anything that exists — file, directory or otherwise. */
34
+ function realEntry(candidate) {
35
+ try {
36
+ return realpathSync(candidate);
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
42
+ function isInside(candidate, root) {
43
+ return candidate === root || candidate.startsWith(root + sep);
44
+ }
45
+ /**
46
+ * Resolve `target` and confirm it is `root` or lies inside it, with symlinks
47
+ * followed on both sides.
48
+ *
49
+ * Both must exist and be directories — a containment check on a path that is
50
+ * not there yet cannot be honest, because whatever appears later may be a
51
+ * symlink out.
52
+ *
53
+ * @param target Directory to check. Relative paths resolve against `root`.
54
+ * @param root Sandbox root the target must not escape.
55
+ * @returns The resolved REAL path, or the reason it was refused.
56
+ */
57
+ export function resolveWithinRoot(target, root) {
58
+ const realRoot = realDirectory(resolve(root));
59
+ if (!realRoot) {
60
+ return {
61
+ error: `Sandbox root "${root}" is not an existing directory. Point the policy's ` +
62
+ "cwdRoot at a directory that exists before starting commands.",
63
+ };
64
+ }
65
+ const requested = isAbsolute(target) ? target : resolve(realRoot, target);
66
+ const realTarget = realDirectory(requested);
67
+ if (!realTarget) {
68
+ return {
69
+ error: `Working directory "${target}" is not an existing directory. Name a directory ` +
70
+ `that exists inside ${realRoot}.`,
71
+ };
72
+ }
73
+ if (!isInside(realTarget, realRoot)) {
74
+ return {
75
+ error: `Access denied: "${target}" resolves to ${realTarget}, which is outside the ` +
76
+ `permitted root ${realRoot}. Run the command inside that root instead.`,
77
+ };
78
+ }
79
+ return { path: realTarget };
80
+ }
81
+ /**
82
+ * The file twin of {@link resolveWithinRoot}: contain a path that may name a
83
+ * file, or may not exist at all (a path argument to `git log`, say).
84
+ *
85
+ * A path that exists is resolved through its symlinks and checked directly. A
86
+ * path that does not exist is checked on its nearest EXISTING ancestor, which
87
+ * is the deepest point a symlink could redirect — everything below that is a
88
+ * name, not a link. Deleted files therefore stay askable about, and
89
+ * `../../etc/passwd` still does not.
90
+ *
91
+ * @param target Path to check. Relative paths resolve against `root`.
92
+ * @param root Sandbox root the target must not escape.
93
+ * @returns The resolved absolute path, or the reason it was refused.
94
+ */
95
+ export function resolvePathWithinRoot(target, root) {
96
+ const realRoot = realDirectory(resolve(root));
97
+ if (!realRoot) {
98
+ return { error: `Sandbox root "${root}" is not an existing directory.` };
99
+ }
100
+ const denied = {
101
+ error: `Access denied: "${target}" resolves outside the permitted root ${realRoot}. ` +
102
+ "Name a path inside it, relative to the root.",
103
+ };
104
+ const requested = isAbsolute(target)
105
+ ? resolve(target)
106
+ : resolve(realRoot, target);
107
+ const direct = realEntry(requested);
108
+ if (direct) {
109
+ return isInside(direct, realRoot) ? { path: direct } : denied;
110
+ }
111
+ let existing = resolve(requested, "..");
112
+ let suffix = basename(requested);
113
+ for (;;) {
114
+ const real = realDirectory(existing);
115
+ if (real) {
116
+ return isInside(real, realRoot)
117
+ ? { path: resolve(real, suffix) }
118
+ : denied;
119
+ }
120
+ const parent = resolve(existing, "..");
121
+ if (parent === existing) {
122
+ return denied;
123
+ }
124
+ suffix = join(basename(existing), suffix);
125
+ existing = parent;
126
+ }
127
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.1.0",
3
+ "version": "12.2.0",
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": {
@@ -69,6 +69,10 @@
69
69
  "convert:specific": "tsx tools/automation/shellConverter.ts --specific",
70
70
  "// Testing (Continuous Test Suites)": "",
71
71
  "test": "pnpm exec tsx test/continuous-test-suite.ts",
72
+ "test:agent-delegation": "pnpm exec tsx test/continuous-test-suite-agent-delegation.ts",
73
+ "test:agent-tasks": "pnpm exec tsx test/continuous-test-suite-agent-tasks.ts",
74
+ "test:artifact-banking": "pnpm exec tsx test/continuous-test-suite-artifact-banking.ts",
75
+ "test:background-commands": "pnpm exec tsx test/continuous-test-suite-background-commands.ts",
72
76
  "test:client": "pnpm exec tsx test/continuous-test-suite-client.ts",
73
77
  "test:context": "pnpm exec tsx test/continuous-test-suite-context.ts",
74
78
  "test:evaluation": "pnpm exec tsx test/continuous-test-suite-evaluation.ts",