@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.
- package/CHANGELOG.md +2 -2
- package/dist/agent/agentToolRegistrar.d.ts +30 -0
- package/dist/agent/agentToolRegistrar.js +72 -18
- package/dist/agent/backgroundCommands.d.ts +110 -0
- package/dist/agent/backgroundCommands.js +914 -0
- package/dist/agent/backgroundDelegation.d.ts +87 -0
- package/dist/agent/backgroundDelegation.js +753 -0
- package/dist/agent/gitTools.d.ts +43 -0
- package/dist/agent/gitTools.js +618 -0
- package/dist/agent/taskChecklist.d.ts +58 -0
- package/dist/agent/taskChecklist.js +322 -0
- package/dist/artifacts/artifactBanking.d.ts +57 -0
- package/dist/artifacts/artifactBanking.js +123 -0
- package/dist/artifacts/artifactStore.d.ts +36 -8
- package/dist/artifacts/artifactStore.js +164 -13
- package/dist/browser/neurolink.min.js +442 -414
- package/dist/neurolink.d.ts +294 -3
- package/dist/neurolink.js +447 -4
- package/dist/types/artifact.d.ts +54 -0
- package/dist/types/backgroundCommand.d.ts +174 -0
- package/dist/types/backgroundCommand.js +22 -0
- package/dist/types/delegation.d.ts +178 -0
- package/dist/types/delegation.js +18 -0
- package/dist/types/gitTools.d.ts +69 -0
- package/dist/types/gitTools.js +22 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +8 -0
- package/dist/types/pathSandbox.d.ts +23 -0
- package/dist/types/pathSandbox.js +12 -0
- package/dist/types/tasks.d.ts +85 -0
- package/dist/types/tasks.js +14 -0
- package/dist/types/tools.d.ts +11 -0
- package/dist/utils/pathSandbox.d.ts +49 -0
- package/dist/utils/pathSandbox.js +127 -0
- package/package.json +5 -1
package/dist/types/tools.d.ts
CHANGED
|
@@ -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.
|
|
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",
|