@agent-api/sdk 1.2.2 → 1.3.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 +20 -0
- package/README.md +8 -0
- package/dist/local/core.js +38 -7
- package/dist/local/shell.d.ts +76 -0
- package/dist/local/shell.js +347 -13
- package/dist/node.d.ts +2 -2
- package/dist/node.js +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/dist-cjs/local/core.js +37 -6
- package/dist-cjs/local/shell.js +348 -13
- package/dist-cjs/node.js +2 -1
- package/dist-cjs/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog — @agent-api/sdk
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added SDK support for `local_shell` isolation modes: `none`, `auto`, and `required`.
|
|
8
|
+
- Added `IsolatorLocalShellRunner`, a Node-local runner that delegates shell execution to the shared `agent-isolator` binary.
|
|
9
|
+
- Added model-facing `shell_isolation` metadata for approval previews and execution results.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- `local_shell` auto isolation now tries an explicitly configured `agent-isolator` path first and falls back to direct host execution when isolation is unavailable.
|
|
14
|
+
- SDKs no longer assume `agent-isolator` is discoverable on `PATH`; pass `isolator.executablePath` or set `AGENT_ISOLATOR_PATH`.
|
|
15
|
+
- Direct host execution remains the default/fallback path and reports its actual non-isolated guarantees.
|
|
16
|
+
|
|
17
|
+
## 1.2.3
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Hardened local workdir scans so broken symlinks and files that disappear during recursive list, grep, and summarize operations do not abort the scan.
|
|
22
|
+
|
|
3
23
|
## 1.2.2
|
|
4
24
|
|
|
5
25
|
### Added
|
package/README.md
CHANGED
|
@@ -199,6 +199,14 @@ Keep UI and OS interaction policy in your host framework. Electron, Tauri, Qt, o
|
|
|
199
199
|
|
|
200
200
|
`local_shell` executes commands through a pluggable command runner. The default `HostLocalShellRunner` runs on the user's local machine, bounds captured output, enforces a timeout, and confines relative `workdir` overrides under the configured cwd. It is not a security sandbox; use approval mode unless your application intentionally grants full local command access.
|
|
201
201
|
|
|
202
|
+
For OS-level isolation, install the standalone `agent-isolator` binary from the
|
|
203
|
+
GitHub Release artifacts and pass its explicit path through
|
|
204
|
+
`isolator.executablePath` or `AGENT_ISOLATOR_PATH`. `isolation: "auto"` tries
|
|
205
|
+
that configured isolator first and falls back to direct execution if it is
|
|
206
|
+
missing or unavailable. `isolation: "required"` fails closed when an isolating
|
|
207
|
+
runner cannot be selected. The SDK does not search `PATH`, run postinstall
|
|
208
|
+
scripts, or build native binaries during package installation.
|
|
209
|
+
|
|
202
210
|
```typescript
|
|
203
211
|
const workdir = local.data.child("workdirs/demo");
|
|
204
212
|
|
package/dist/local/core.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { constants as fsConstants, watch as watchFS } from "node:fs";
|
|
3
|
-
import { access, copyFile, mkdir, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, copyFile, lstat, mkdir, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { localSkillFromDirectory } from "../local-skills.js";
|
|
@@ -322,12 +322,15 @@ export class LocalFileStore {
|
|
|
322
322
|
if (item.type !== "file" || item.size > maxBytesPerFile || !isLikelyTextFile(item.path)) {
|
|
323
323
|
continue;
|
|
324
324
|
}
|
|
325
|
-
const raw = await
|
|
325
|
+
const raw = await readOptionalFile(item.fullPath);
|
|
326
|
+
if (!raw) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
326
329
|
if (looksBinary(raw)) {
|
|
327
330
|
continue;
|
|
328
331
|
}
|
|
329
332
|
filesScanned++;
|
|
330
|
-
const lines = splitLines(raw.toString("utf8"));
|
|
333
|
+
const lines = splitLines(Buffer.from(raw).toString("utf8"));
|
|
331
334
|
for (let i = 0; i < lines.length; i++) {
|
|
332
335
|
if (!lines[i].includes(pattern)) {
|
|
333
336
|
continue;
|
|
@@ -363,7 +366,10 @@ export class LocalFileStore {
|
|
|
363
366
|
if (!isLikelyTextFile(item.path) || item.size > previewBytes * 4) {
|
|
364
367
|
continue;
|
|
365
368
|
}
|
|
366
|
-
const raw = await
|
|
369
|
+
const raw = await readOptionalFile(item.fullPath);
|
|
370
|
+
if (!raw) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
367
373
|
if (looksBinary(raw)) {
|
|
368
374
|
continue;
|
|
369
375
|
}
|
|
@@ -371,7 +377,7 @@ export class LocalFileStore {
|
|
|
371
377
|
previews.push({
|
|
372
378
|
path: item.path,
|
|
373
379
|
size: item.size,
|
|
374
|
-
preview: raw.subarray(0, previewBytes).toString("utf8"),
|
|
380
|
+
preview: Buffer.from(raw.subarray(0, previewBytes)).toString("utf8"),
|
|
375
381
|
preview_truncated: truncated || undefined,
|
|
376
382
|
});
|
|
377
383
|
}
|
|
@@ -393,7 +399,10 @@ export class LocalFileStore {
|
|
|
393
399
|
if (ignored(relativePath, options.ignore)) {
|
|
394
400
|
continue;
|
|
395
401
|
}
|
|
396
|
-
const info = await
|
|
402
|
+
const info = await lstatOptional(fullPath);
|
|
403
|
+
if (!info) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
397
406
|
const item = {
|
|
398
407
|
path: relativePath,
|
|
399
408
|
fullPath,
|
|
@@ -401,7 +410,7 @@ export class LocalFileStore {
|
|
|
401
410
|
size: info.size,
|
|
402
411
|
modifiedAt: info.mtime,
|
|
403
412
|
};
|
|
404
|
-
if (
|
|
413
|
+
if (info.isDirectory()) {
|
|
405
414
|
if (options.includeDirectories) {
|
|
406
415
|
out.push(item);
|
|
407
416
|
}
|
|
@@ -813,6 +822,28 @@ async function fileExists(fullPath) {
|
|
|
813
822
|
return false;
|
|
814
823
|
}
|
|
815
824
|
}
|
|
825
|
+
async function lstatOptional(fullPath) {
|
|
826
|
+
try {
|
|
827
|
+
return await lstat(fullPath);
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function readOptionalFile(fullPath) {
|
|
837
|
+
try {
|
|
838
|
+
return await readFile(fullPath);
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR" || error?.code === "EISDIR") {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
816
847
|
function normalizeAppName(appName) {
|
|
817
848
|
const trimmed = appName.trim();
|
|
818
849
|
if (!trimmed) {
|
package/dist/local/shell.d.ts
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
import type { FunctionTool } from "../types/tools.js";
|
|
2
2
|
import type { LocalWorkdir } from "./core.js";
|
|
3
3
|
export type LocalShellAccessMode = "approval" | "full";
|
|
4
|
+
/**
|
|
5
|
+
* Requested isolation policy for local shell execution.
|
|
6
|
+
* - none: direct host execution.
|
|
7
|
+
* - auto: use an isolating runner when the host app provides one; otherwise fall back to direct execution.
|
|
8
|
+
* - required: fail closed unless the configured runner reports real isolation.
|
|
9
|
+
*/
|
|
10
|
+
export type LocalShellIsolationMode = "none" | "auto" | "required";
|
|
11
|
+
export interface LocalShellIsolationGuarantees {
|
|
12
|
+
filesystem: "none" | "workdir-mounted" | "policy-enforced";
|
|
13
|
+
network: "allowed" | "blocked" | "configurable";
|
|
14
|
+
user: "host-user" | "unprivileged-user" | "namespace-user";
|
|
15
|
+
process: "host-process-tree" | "child-contained" | "pid-namespace";
|
|
16
|
+
resources: "none" | "timeout-only" | "cpu-memory-limits";
|
|
17
|
+
}
|
|
18
|
+
export interface LocalShellIsolationResourceOptions {
|
|
19
|
+
memoryMb?: number;
|
|
20
|
+
cpuCount?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface LocalShellIsolationOptions {
|
|
23
|
+
filesystem?: "host" | "workdir-readonly" | "workdir-readwrite";
|
|
24
|
+
network?: "allowed" | "blocked";
|
|
25
|
+
env?: "inherit" | "minimal";
|
|
26
|
+
resources?: LocalShellIsolationResourceOptions;
|
|
27
|
+
}
|
|
28
|
+
export interface LocalShellIsolationStatus {
|
|
29
|
+
executor: "direct" | "isolator";
|
|
30
|
+
driver: string;
|
|
31
|
+
isolated: boolean;
|
|
32
|
+
fallback: boolean;
|
|
33
|
+
requested: Required<LocalShellIsolationOptions> & {
|
|
34
|
+
resources: LocalShellIsolationResourceOptions;
|
|
35
|
+
};
|
|
36
|
+
guarantees: LocalShellIsolationGuarantees;
|
|
37
|
+
warnings: string[];
|
|
38
|
+
}
|
|
39
|
+
export interface LocalShellIsolatorStatusResult {
|
|
40
|
+
version?: string;
|
|
41
|
+
driver: string;
|
|
42
|
+
status: LocalShellIsolationStatus;
|
|
43
|
+
drivers?: Array<{
|
|
44
|
+
name: string;
|
|
45
|
+
platform: string;
|
|
46
|
+
available: boolean;
|
|
47
|
+
warnings?: string[];
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
4
50
|
export interface LocalShellRequest {
|
|
5
51
|
command: string;
|
|
6
52
|
description?: string;
|
|
@@ -24,8 +70,10 @@ export interface LocalShellResult {
|
|
|
24
70
|
duration_ms: number;
|
|
25
71
|
timed_out: boolean;
|
|
26
72
|
truncated: boolean;
|
|
73
|
+
shell_isolation: LocalShellIsolationStatus;
|
|
27
74
|
}
|
|
28
75
|
export interface LocalCommandRunner {
|
|
76
|
+
readonly isolationStatus?: LocalShellIsolationStatus;
|
|
29
77
|
run(request: LocalShellRequest, context?: LocalShellContext): Promise<LocalShellResult>;
|
|
30
78
|
}
|
|
31
79
|
export interface HostLocalShellRunnerOptions {
|
|
@@ -34,6 +82,15 @@ export interface HostLocalShellRunnerOptions {
|
|
|
34
82
|
timeoutMs?: number;
|
|
35
83
|
maxOutputBytes?: number;
|
|
36
84
|
env?: NodeJS.ProcessEnv;
|
|
85
|
+
isolationOptions?: LocalShellIsolationOptions;
|
|
86
|
+
}
|
|
87
|
+
export interface IsolatorLocalShellRunnerOptions {
|
|
88
|
+
executablePath?: string;
|
|
89
|
+
driver?: "auto" | "direct" | "bwrap" | "sandbox-exec" | "windows-job" | string;
|
|
90
|
+
cwd?: string;
|
|
91
|
+
timeoutMs?: number;
|
|
92
|
+
maxOutputBytes?: number;
|
|
93
|
+
isolationOptions?: LocalShellIsolationOptions;
|
|
37
94
|
}
|
|
38
95
|
export declare class HostLocalShellRunner implements LocalCommandRunner {
|
|
39
96
|
readonly cwd: string;
|
|
@@ -41,11 +98,27 @@ export declare class HostLocalShellRunner implements LocalCommandRunner {
|
|
|
41
98
|
readonly timeoutMs: number;
|
|
42
99
|
readonly maxOutputBytes: number;
|
|
43
100
|
readonly env: NodeJS.ProcessEnv;
|
|
101
|
+
readonly isolationStatus: LocalShellIsolationStatus;
|
|
44
102
|
constructor(options?: HostLocalShellRunnerOptions);
|
|
45
103
|
run(request: LocalShellRequest, context?: LocalShellContext): Promise<LocalShellResult>;
|
|
46
104
|
}
|
|
105
|
+
export declare class IsolatorLocalShellRunner implements LocalCommandRunner {
|
|
106
|
+
readonly executablePath: string;
|
|
107
|
+
readonly driver: string;
|
|
108
|
+
readonly cwd: string;
|
|
109
|
+
readonly timeoutMs: number;
|
|
110
|
+
readonly maxOutputBytes: number;
|
|
111
|
+
readonly isolationOptions: LocalShellIsolationOptions;
|
|
112
|
+
readonly isolationStatus: LocalShellIsolationStatus;
|
|
113
|
+
readonly statusResult: LocalShellIsolatorStatusResult;
|
|
114
|
+
constructor(options?: IsolatorLocalShellRunnerOptions);
|
|
115
|
+
run(request: LocalShellRequest, context?: LocalShellContext): Promise<LocalShellResult>;
|
|
116
|
+
}
|
|
47
117
|
export interface LocalShellToolRegistryOptions {
|
|
48
118
|
accessMode?: LocalShellAccessMode;
|
|
119
|
+
isolation?: LocalShellIsolationMode;
|
|
120
|
+
isolationOptions?: LocalShellIsolationOptions;
|
|
121
|
+
isolator?: IsolatorLocalShellRunnerOptions | boolean;
|
|
49
122
|
toolName?: string;
|
|
50
123
|
runner?: LocalCommandRunner;
|
|
51
124
|
cwd?: string;
|
|
@@ -56,6 +129,8 @@ export interface LocalShellToolRegistryOptions {
|
|
|
56
129
|
}
|
|
57
130
|
export interface LocalShellToolPresentationOptions {
|
|
58
131
|
accessMode?: LocalShellAccessMode;
|
|
132
|
+
isolationStatus?: LocalShellIsolationStatus;
|
|
133
|
+
isolationOptions?: LocalShellIsolationOptions;
|
|
59
134
|
cwd?: string;
|
|
60
135
|
shell?: string | boolean;
|
|
61
136
|
platform?: NodeJS.Platform;
|
|
@@ -73,6 +148,7 @@ export interface LocalShellToolRegistry {
|
|
|
73
148
|
export declare class LocalShellDriver {
|
|
74
149
|
readonly accessMode: LocalShellAccessMode;
|
|
75
150
|
readonly runner: LocalCommandRunner;
|
|
151
|
+
readonly isolationStatus: LocalShellIsolationStatus;
|
|
76
152
|
constructor(options?: LocalShellToolRegistryOptions);
|
|
77
153
|
dispatch(args: Record<string, unknown>, context?: LocalShellContext): Promise<Record<string, unknown>>;
|
|
78
154
|
requiresApproval(): boolean;
|
package/dist/local/shell.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export class HostLocalShellRunner {
|
|
4
4
|
cwd;
|
|
@@ -6,12 +6,14 @@ export class HostLocalShellRunner {
|
|
|
6
6
|
timeoutMs;
|
|
7
7
|
maxOutputBytes;
|
|
8
8
|
env;
|
|
9
|
+
isolationStatus;
|
|
9
10
|
constructor(options = {}) {
|
|
10
11
|
this.cwd = path.resolve(options.cwd ?? process.cwd());
|
|
11
12
|
this.shell = options.shell ?? true;
|
|
12
13
|
this.timeoutMs = positiveInteger(options.timeoutMs, 2 * 60 * 1000, "timeoutMs");
|
|
13
14
|
this.maxOutputBytes = positiveInteger(options.maxOutputBytes, 128 * 1024, "maxOutputBytes");
|
|
14
15
|
this.env = { ...process.env, ...(options.env ?? {}) };
|
|
16
|
+
this.isolationStatus = directIsolationStatus(false, options.isolationOptions);
|
|
15
17
|
}
|
|
16
18
|
async run(request, context = {}) {
|
|
17
19
|
const command = requiredString(request.command, "command");
|
|
@@ -54,6 +56,7 @@ export class HostLocalShellRunner {
|
|
|
54
56
|
duration_ms: Date.now() - started,
|
|
55
57
|
timed_out: timedOut,
|
|
56
58
|
truncated: stdout.truncated || stderr.truncated,
|
|
59
|
+
shell_isolation: this.isolationStatus,
|
|
57
60
|
});
|
|
58
61
|
};
|
|
59
62
|
const kill = () => {
|
|
@@ -93,23 +96,61 @@ export class HostLocalShellRunner {
|
|
|
93
96
|
});
|
|
94
97
|
}
|
|
95
98
|
}
|
|
99
|
+
export class IsolatorLocalShellRunner {
|
|
100
|
+
executablePath;
|
|
101
|
+
driver;
|
|
102
|
+
cwd;
|
|
103
|
+
timeoutMs;
|
|
104
|
+
maxOutputBytes;
|
|
105
|
+
isolationOptions;
|
|
106
|
+
isolationStatus;
|
|
107
|
+
statusResult;
|
|
108
|
+
constructor(options = {}) {
|
|
109
|
+
this.executablePath = resolveIsolatorExecutablePath(options.executablePath);
|
|
110
|
+
this.driver = options.driver ?? "auto";
|
|
111
|
+
this.cwd = path.resolve(options.cwd ?? process.cwd());
|
|
112
|
+
this.timeoutMs = positiveInteger(options.timeoutMs, 2 * 60 * 1000, "timeoutMs");
|
|
113
|
+
this.maxOutputBytes = positiveInteger(options.maxOutputBytes, 128 * 1024, "maxOutputBytes");
|
|
114
|
+
this.isolationOptions = options.isolationOptions ?? {};
|
|
115
|
+
this.statusResult = isolatorStatusSync(this.executablePath, this.driver);
|
|
116
|
+
this.isolationStatus = this.statusResult.status;
|
|
117
|
+
}
|
|
118
|
+
async run(request, context = {}) {
|
|
119
|
+
const command = requiredString(request.command, "command");
|
|
120
|
+
const cwd = resolveContainedPath(this.cwd, request.workdir);
|
|
121
|
+
const timeoutMs = request.timeoutMs == null
|
|
122
|
+
? this.timeoutMs
|
|
123
|
+
: positiveInteger(request.timeoutMs, this.timeoutMs, "timeoutMs");
|
|
124
|
+
const response = await isolatorRequest(this.executablePath, this.driver, {
|
|
125
|
+
id: `run_${Date.now().toString(36)}`,
|
|
126
|
+
method: "run",
|
|
127
|
+
params: {
|
|
128
|
+
command,
|
|
129
|
+
description: request.description,
|
|
130
|
+
cwd,
|
|
131
|
+
timeout_ms: timeoutMs,
|
|
132
|
+
max_output_bytes: this.maxOutputBytes,
|
|
133
|
+
env: request.env,
|
|
134
|
+
isolation: this.isolationOptions,
|
|
135
|
+
},
|
|
136
|
+
}, context);
|
|
137
|
+
return isolatorRunResult(response.result);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
96
140
|
export class LocalShellDriver {
|
|
97
141
|
accessMode;
|
|
98
142
|
runner;
|
|
143
|
+
isolationStatus;
|
|
99
144
|
constructor(options = {}) {
|
|
100
145
|
const cwd = options.workdir?.root ?? options.cwd;
|
|
101
146
|
this.accessMode = options.accessMode ?? "approval";
|
|
102
|
-
this.runner = options
|
|
103
|
-
|
|
104
|
-
shell: options.shell,
|
|
105
|
-
timeoutMs: options.timeoutMs,
|
|
106
|
-
maxOutputBytes: options.maxOutputBytes,
|
|
107
|
-
});
|
|
147
|
+
this.runner = resolveShellRunner(options, cwd);
|
|
148
|
+
this.isolationStatus = this.runner.isolationStatus ?? directIsolationStatus(options.isolation === "auto", options.isolationOptions);
|
|
108
149
|
}
|
|
109
150
|
async dispatch(args, context = {}) {
|
|
110
151
|
const request = shellRequest(args);
|
|
111
152
|
if (this.accessMode !== "full") {
|
|
112
|
-
return shellApprovalRequired(request);
|
|
153
|
+
return shellApprovalRequired(request, this.isolationStatus);
|
|
113
154
|
}
|
|
114
155
|
return shellToolResult(await this.runner.run(request, context));
|
|
115
156
|
}
|
|
@@ -117,16 +158,80 @@ export class LocalShellDriver {
|
|
|
117
158
|
return this.accessMode !== "full";
|
|
118
159
|
}
|
|
119
160
|
}
|
|
161
|
+
function resolveShellRunner(options, cwd) {
|
|
162
|
+
if (options.runner) {
|
|
163
|
+
if (options.isolation === "required" && options.runner.isolationStatus?.isolated !== true) {
|
|
164
|
+
throw new Error("local_shell isolation is required, but the configured runner does not report isolation");
|
|
165
|
+
}
|
|
166
|
+
return options.runner;
|
|
167
|
+
}
|
|
168
|
+
let isolatorFallbackWarning;
|
|
169
|
+
if (options.isolation === "auto" || options.isolation === "required" || options.isolator) {
|
|
170
|
+
const isolatorOptions = typeof options.isolator === "object" ? options.isolator : {};
|
|
171
|
+
try {
|
|
172
|
+
const runner = new IsolatorLocalShellRunner({
|
|
173
|
+
cwd,
|
|
174
|
+
timeoutMs: options.timeoutMs,
|
|
175
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
176
|
+
isolationOptions: options.isolationOptions,
|
|
177
|
+
...isolatorOptions,
|
|
178
|
+
});
|
|
179
|
+
if (options.isolation === "required" && runner.isolationStatus.isolated !== true) {
|
|
180
|
+
throw new Error(`local_shell isolation is required, but agent-isolator selected non-isolated driver ${runner.isolationStatus.driver}`);
|
|
181
|
+
}
|
|
182
|
+
if (options.isolation !== "none" || options.isolator) {
|
|
183
|
+
return runner;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
if (options.isolation === "required") {
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
isolatorFallbackWarning = errorMessage(error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const runner = new HostLocalShellRunner({
|
|
194
|
+
cwd,
|
|
195
|
+
shell: options.shell,
|
|
196
|
+
timeoutMs: options.timeoutMs,
|
|
197
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
198
|
+
isolationOptions: options.isolationOptions,
|
|
199
|
+
});
|
|
200
|
+
if (options.isolation === "auto") {
|
|
201
|
+
let status = directIsolationStatus(true, options.isolationOptions);
|
|
202
|
+
if (isolatorFallbackWarning) {
|
|
203
|
+
status = {
|
|
204
|
+
...status,
|
|
205
|
+
warnings: [...status.warnings, `Isolator unavailable: ${isolatorFallbackWarning}`],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return withIsolationStatus(runner, status);
|
|
209
|
+
}
|
|
210
|
+
return runner;
|
|
211
|
+
}
|
|
212
|
+
function resolveIsolatorExecutablePath(explicitPath) {
|
|
213
|
+
const configured = (explicitPath?.trim() || process.env.AGENT_ISOLATOR_PATH?.trim() || "");
|
|
214
|
+
if (!configured) {
|
|
215
|
+
throw new Error("agent-isolator executable path is not configured; pass isolator.executablePath or set AGENT_ISOLATOR_PATH");
|
|
216
|
+
}
|
|
217
|
+
return configured;
|
|
218
|
+
}
|
|
219
|
+
function errorMessage(error) {
|
|
220
|
+
return error instanceof Error ? error.message : String(error);
|
|
221
|
+
}
|
|
120
222
|
export function createLocalShellToolRegistry(options = {}) {
|
|
121
223
|
const toolName = options.toolName ?? "local_shell";
|
|
122
224
|
const driver = new LocalShellDriver(options);
|
|
123
225
|
const hostRunner = driver.runner instanceof HostLocalShellRunner ? driver.runner : undefined;
|
|
226
|
+
const isolatorRunner = driver.runner instanceof IsolatorLocalShellRunner ? driver.runner : undefined;
|
|
124
227
|
const definition = localShellToolDefinition(toolName, {
|
|
125
228
|
accessMode: driver.accessMode,
|
|
126
|
-
cwd: options.workdir?.root ?? hostRunner?.cwd ?? options.cwd,
|
|
229
|
+
cwd: options.workdir?.root ?? hostRunner?.cwd ?? isolatorRunner?.cwd ?? options.cwd,
|
|
127
230
|
shell: hostRunner?.shell ?? options.shell,
|
|
128
|
-
timeoutMs: hostRunner?.timeoutMs ?? options.timeoutMs,
|
|
129
|
-
maxOutputBytes: hostRunner?.maxOutputBytes ?? options.maxOutputBytes,
|
|
231
|
+
timeoutMs: hostRunner?.timeoutMs ?? isolatorRunner?.timeoutMs ?? options.timeoutMs,
|
|
232
|
+
maxOutputBytes: hostRunner?.maxOutputBytes ?? isolatorRunner?.maxOutputBytes ?? options.maxOutputBytes,
|
|
233
|
+
isolationStatus: driver.isolationStatus,
|
|
234
|
+
isolationOptions: options.isolationOptions,
|
|
130
235
|
});
|
|
131
236
|
return {
|
|
132
237
|
accessMode: driver.accessMode,
|
|
@@ -158,13 +263,16 @@ function localShellToolDescription(options = {}) {
|
|
|
158
263
|
const platform = options.platform ?? process.platform;
|
|
159
264
|
const shell = shellDisplayName(options.shell);
|
|
160
265
|
const accessMode = options.accessMode ?? "approval";
|
|
266
|
+
const isolation = options.isolationStatus ?? directIsolationStatus(false, options.isolationOptions);
|
|
161
267
|
const cwd = options.cwd ? path.resolve(options.cwd) : undefined;
|
|
162
268
|
const timeoutMs = options.timeoutMs ?? 2 * 60 * 1000;
|
|
163
269
|
const maxOutputBytes = options.maxOutputBytes ?? 128 * 1024;
|
|
164
270
|
return [
|
|
165
271
|
"Run a local shell command through one model-facing primitive.",
|
|
166
272
|
"Prefer local_workdir for file discovery, reading, and editing. Use local_shell for package managers, tests, build commands, git, and other process-level tasks.",
|
|
167
|
-
`Execution environment: platform=${platform}; shell=${shell}; access_mode=${accessMode}; default_timeout_ms=${timeoutMs}; max_output_bytes=${maxOutputBytes}.`,
|
|
273
|
+
`Execution environment: platform=${platform}; shell=${shell}; access_mode=${accessMode}; isolation_driver=${isolation.driver}; isolated=${isolation.isolated}; fallback=${isolation.fallback}; default_timeout_ms=${timeoutMs}; max_output_bytes=${maxOutputBytes}.`,
|
|
274
|
+
isolationRequestDescription(isolation.requested),
|
|
275
|
+
isolation.warnings.length > 0 ? `Isolation warning: ${isolation.warnings.join(" ")}` : "",
|
|
168
276
|
cwd ? `Default cwd: ${cwd}. Relative command paths resolve from this cwd unless workdir is set.` : "Relative command paths resolve from the configured cwd unless workdir is set.",
|
|
169
277
|
"The workdir parameter must be a relative child path of the configured cwd. Use workdir instead of cd when possible.",
|
|
170
278
|
"Absolute paths inside the command are permitted by the host OS if the user/process has permission; this tool is not a filesystem sandbox or security sandbox.",
|
|
@@ -206,7 +314,7 @@ function shellRequest(args) {
|
|
|
206
314
|
timeoutMs: optionalNumber(args.timeoutMs ?? args.timeout_ms, "timeout_ms"),
|
|
207
315
|
};
|
|
208
316
|
}
|
|
209
|
-
function shellApprovalRequired(request) {
|
|
317
|
+
function shellApprovalRequired(request, isolationStatus) {
|
|
210
318
|
return {
|
|
211
319
|
ok: false,
|
|
212
320
|
requires_approval: true,
|
|
@@ -215,9 +323,235 @@ function shellApprovalRequired(request) {
|
|
|
215
323
|
description: request.description,
|
|
216
324
|
workdir: request.workdir,
|
|
217
325
|
timeout_ms: request.timeoutMs,
|
|
326
|
+
shell_isolation: isolationStatus,
|
|
218
327
|
message: "local_shell command execution requires approval",
|
|
219
328
|
};
|
|
220
329
|
}
|
|
330
|
+
function directIsolationStatus(fallback, options = {}) {
|
|
331
|
+
const requested = normalizeIsolationOptions(options);
|
|
332
|
+
const warnings = directIsolationWarnings(fallback, requested);
|
|
333
|
+
return {
|
|
334
|
+
executor: "direct",
|
|
335
|
+
driver: "direct",
|
|
336
|
+
isolated: false,
|
|
337
|
+
fallback,
|
|
338
|
+
requested,
|
|
339
|
+
guarantees: {
|
|
340
|
+
filesystem: "none",
|
|
341
|
+
network: "allowed",
|
|
342
|
+
user: "host-user",
|
|
343
|
+
process: "host-process-tree",
|
|
344
|
+
resources: "timeout-only",
|
|
345
|
+
},
|
|
346
|
+
warnings,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function normalizeIsolationOptions(options = {}) {
|
|
350
|
+
return {
|
|
351
|
+
filesystem: options.filesystem ?? "host",
|
|
352
|
+
network: options.network ?? "allowed",
|
|
353
|
+
env: options.env ?? "inherit",
|
|
354
|
+
resources: {
|
|
355
|
+
memoryMb: options.resources?.memoryMb,
|
|
356
|
+
cpuCount: options.resources?.cpuCount,
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function directIsolationWarnings(fallback, requested) {
|
|
361
|
+
const warnings = [
|
|
362
|
+
fallback
|
|
363
|
+
? "No local shell isolator is configured; falling back to direct host execution."
|
|
364
|
+
: "Direct host execution has no OS-level isolation.",
|
|
365
|
+
];
|
|
366
|
+
if (requested.filesystem !== "host") {
|
|
367
|
+
warnings.push(`Requested filesystem isolation (${requested.filesystem}) is not enforced by direct execution.`);
|
|
368
|
+
}
|
|
369
|
+
if (requested.network === "blocked") {
|
|
370
|
+
warnings.push("Requested network blocking is not enforced by direct execution.");
|
|
371
|
+
}
|
|
372
|
+
if (requested.env === "minimal") {
|
|
373
|
+
warnings.push("Requested minimal environment is not enforced by direct execution.");
|
|
374
|
+
}
|
|
375
|
+
if (requested.resources.memoryMb != null || requested.resources.cpuCount != null) {
|
|
376
|
+
warnings.push("Requested CPU or memory limits are not enforced by direct execution.");
|
|
377
|
+
}
|
|
378
|
+
return warnings;
|
|
379
|
+
}
|
|
380
|
+
function isolationRequestDescription(options) {
|
|
381
|
+
const resources = [
|
|
382
|
+
options.resources.memoryMb == null ? "" : `memory_mb=${options.resources.memoryMb}`,
|
|
383
|
+
options.resources.cpuCount == null ? "" : `cpu_count=${options.resources.cpuCount}`,
|
|
384
|
+
].filter(Boolean).join("; ");
|
|
385
|
+
return `Requested isolation: filesystem=${options.filesystem}; network=${options.network}; env=${options.env}${resources ? `; ${resources}` : ""}.`;
|
|
386
|
+
}
|
|
387
|
+
function withIsolationStatus(runner, status) {
|
|
388
|
+
Object.defineProperty(runner, "isolationStatus", {
|
|
389
|
+
configurable: true,
|
|
390
|
+
enumerable: true,
|
|
391
|
+
value: status,
|
|
392
|
+
});
|
|
393
|
+
return runner;
|
|
394
|
+
}
|
|
395
|
+
function isolatorStatusSync(executablePath, driver) {
|
|
396
|
+
const request = JSON.stringify({ id: "status", method: "status", params: {} });
|
|
397
|
+
const result = spawnSync(executablePath, ["--once", `--driver=${driver}`], {
|
|
398
|
+
input: request,
|
|
399
|
+
encoding: "utf8",
|
|
400
|
+
maxBuffer: 1024 * 1024,
|
|
401
|
+
windowsHide: true,
|
|
402
|
+
});
|
|
403
|
+
if (result.error) {
|
|
404
|
+
throw result.error;
|
|
405
|
+
}
|
|
406
|
+
if (result.status !== 0) {
|
|
407
|
+
throw new Error((result.stderr || result.stdout || `agent-isolator exited with status ${result.status}`).trim());
|
|
408
|
+
}
|
|
409
|
+
const envelope = parseIsolatorEnvelope(result.stdout);
|
|
410
|
+
if (envelope.error) {
|
|
411
|
+
throw new Error(envelope.error.message || envelope.error.code || "agent-isolator status failed");
|
|
412
|
+
}
|
|
413
|
+
return isolatorStatusResult(envelope.result);
|
|
414
|
+
}
|
|
415
|
+
async function isolatorRequest(executablePath, driver, envelope, context) {
|
|
416
|
+
return await new Promise((resolve, reject) => {
|
|
417
|
+
const child = spawn(executablePath, ["--once", `--driver=${driver}`], {
|
|
418
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
419
|
+
windowsHide: true,
|
|
420
|
+
});
|
|
421
|
+
const stdout = new BoundedText(1024 * 1024);
|
|
422
|
+
const stderr = new BoundedText(1024 * 1024);
|
|
423
|
+
let settled = false;
|
|
424
|
+
const finish = (error) => {
|
|
425
|
+
if (settled)
|
|
426
|
+
return;
|
|
427
|
+
settled = true;
|
|
428
|
+
context.signal?.removeEventListener("abort", abort);
|
|
429
|
+
if (error) {
|
|
430
|
+
reject(error);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
const parsed = parseIsolatorEnvelope(stdout.text());
|
|
435
|
+
if (parsed.error) {
|
|
436
|
+
reject(new Error(parsed.error.message || parsed.error.code || "agent-isolator request failed"));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
resolve({ result: parsed.result });
|
|
440
|
+
}
|
|
441
|
+
catch (parseError) {
|
|
442
|
+
const details = stderr.text();
|
|
443
|
+
reject(new Error(`${parseError instanceof Error ? parseError.message : String(parseError)}${details ? `: ${details}` : ""}`));
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const abort = () => {
|
|
447
|
+
if (!child.killed)
|
|
448
|
+
child.kill(process.platform === "win32" ? undefined : "SIGTERM");
|
|
449
|
+
};
|
|
450
|
+
if (context.signal?.aborted) {
|
|
451
|
+
abort();
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
context.signal?.addEventListener("abort", abort, { once: true });
|
|
455
|
+
}
|
|
456
|
+
child.stdout?.on("data", (chunk) => stdout.append(chunk));
|
|
457
|
+
child.stderr?.on("data", (chunk) => stderr.append(chunk));
|
|
458
|
+
child.on("error", finish);
|
|
459
|
+
child.on("close", (code) => {
|
|
460
|
+
if (code !== 0) {
|
|
461
|
+
finish(new Error((stderr.text() || stdout.text() || `agent-isolator exited with status ${code}`).trim()));
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
finish();
|
|
465
|
+
});
|
|
466
|
+
child.stdin?.end(JSON.stringify(envelope));
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function parseIsolatorEnvelope(text) {
|
|
470
|
+
const trimmed = text.trim();
|
|
471
|
+
if (!trimmed) {
|
|
472
|
+
throw new Error("agent-isolator returned an empty response");
|
|
473
|
+
}
|
|
474
|
+
return JSON.parse(trimmed);
|
|
475
|
+
}
|
|
476
|
+
function isolatorStatusResult(value) {
|
|
477
|
+
if (!isRecord(value)) {
|
|
478
|
+
throw new Error("agent-isolator status result must be an object");
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
version: typeof value.version === "string" ? value.version : undefined,
|
|
482
|
+
driver: requiredString(value.driver, "driver"),
|
|
483
|
+
status: isolationStatusFromUnknown(value.status),
|
|
484
|
+
drivers: Array.isArray(value.drivers)
|
|
485
|
+
? value.drivers.filter(isRecord).map((item) => ({
|
|
486
|
+
name: String(item.name ?? ""),
|
|
487
|
+
platform: String(item.platform ?? ""),
|
|
488
|
+
available: item.available === true,
|
|
489
|
+
warnings: Array.isArray(item.warnings) ? item.warnings.map(String) : undefined,
|
|
490
|
+
}))
|
|
491
|
+
: undefined,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function isolatorRunResult(value) {
|
|
495
|
+
if (!isRecord(value)) {
|
|
496
|
+
throw new Error("agent-isolator run result must be an object");
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
ok: value.ok === true,
|
|
500
|
+
command: requiredString(value.command, "command"),
|
|
501
|
+
description: optionalString(value.description, "description"),
|
|
502
|
+
cwd: requiredString(value.cwd, "cwd"),
|
|
503
|
+
exit_code: typeof value.exit_code === "number" ? value.exit_code : null,
|
|
504
|
+
signal: typeof value.signal === "string" ? value.signal : null,
|
|
505
|
+
stdout: typeof value.stdout === "string" ? value.stdout : "",
|
|
506
|
+
stderr: typeof value.stderr === "string" ? value.stderr : "",
|
|
507
|
+
output: typeof value.output === "string" ? value.output : "(no output)",
|
|
508
|
+
duration_ms: typeof value.duration_ms === "number" ? value.duration_ms : 0,
|
|
509
|
+
timed_out: value.timed_out === true,
|
|
510
|
+
truncated: value.truncated === true,
|
|
511
|
+
shell_isolation: isolationStatusFromUnknown(value.shell_isolation),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function isolationStatusFromUnknown(value) {
|
|
515
|
+
if (!isRecord(value)) {
|
|
516
|
+
throw new Error("shell isolation status must be an object");
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
executor: value.executor === "isolator" ? "isolator" : "direct",
|
|
520
|
+
driver: requiredString(value.driver, "shell_isolation.driver"),
|
|
521
|
+
isolated: value.isolated === true,
|
|
522
|
+
fallback: value.fallback === true,
|
|
523
|
+
requested: normalizeIsolationOptions(isRecord(value.requested) ? isolationOptionsFromUnknown(value.requested) : {}),
|
|
524
|
+
guarantees: isolationGuaranteesFromUnknown(value.guarantees),
|
|
525
|
+
warnings: Array.isArray(value.warnings) ? value.warnings.map(String) : [],
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function isolationOptionsFromUnknown(value) {
|
|
529
|
+
const resources = isRecord(value.resources) ? value.resources : {};
|
|
530
|
+
return {
|
|
531
|
+
filesystem: value.filesystem === "workdir-readonly" || value.filesystem === "workdir-readwrite" || value.filesystem === "host"
|
|
532
|
+
? value.filesystem
|
|
533
|
+
: undefined,
|
|
534
|
+
network: value.network === "blocked" || value.network === "allowed" ? value.network : undefined,
|
|
535
|
+
env: value.env === "minimal" || value.env === "inherit" ? value.env : undefined,
|
|
536
|
+
resources: {
|
|
537
|
+
memoryMb: typeof resources.memoryMb === "number" ? resources.memoryMb : undefined,
|
|
538
|
+
cpuCount: typeof resources.cpuCount === "number" ? resources.cpuCount : undefined,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function isolationGuaranteesFromUnknown(value) {
|
|
543
|
+
const record = isRecord(value) ? value : {};
|
|
544
|
+
return {
|
|
545
|
+
filesystem: record.filesystem === "workdir-mounted" || record.filesystem === "policy-enforced" ? record.filesystem : "none",
|
|
546
|
+
network: record.network === "blocked" || record.network === "configurable" ? record.network : "allowed",
|
|
547
|
+
user: record.user === "unprivileged-user" || record.user === "namespace-user" ? record.user : "host-user",
|
|
548
|
+
process: record.process === "child-contained" || record.process === "pid-namespace" ? record.process : "host-process-tree",
|
|
549
|
+
resources: record.resources === "cpu-memory-limits" ? "cpu-memory-limits" : record.resources === "timeout-only" ? "timeout-only" : "none",
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function isRecord(value) {
|
|
553
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
554
|
+
}
|
|
221
555
|
function shellToolResult(result) {
|
|
222
556
|
return {
|
|
223
557
|
...result,
|
package/dist/node.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * from "./index.js";
|
|
2
2
|
export { NodeAgentAPI } from "./node-client.js";
|
|
3
3
|
export { NodeSkillsResource } from "./resources/skills-node.js";
|
|
4
|
-
export { createLocalShellToolRegistry, LocalWorkdirDriver, HostLocalShellRunner, LocalShellDriver, localShellToolDefinition, localShellToolInstructions, createLocalWorkdirToolRegistry, localWorkdirToolDefinition, localWorkdirToolInstructions, } from "./local/index.js";
|
|
5
|
-
export type { LocalCommandRunner, LocalShellAccessMode, LocalShellContext, LocalShellRequest, LocalShellResult, LocalShellToolRegistry, LocalShellToolRegistryOptions, LocalWorkdirAction, LocalWorkdirAccessMode, LocalWorkdirToolRegistry, LocalWorkdirToolRegistryOptions, } from "./local/index.js";
|
|
4
|
+
export { createLocalShellToolRegistry, LocalWorkdirDriver, HostLocalShellRunner, IsolatorLocalShellRunner, LocalShellDriver, localShellToolDefinition, localShellToolInstructions, createLocalWorkdirToolRegistry, localWorkdirToolDefinition, localWorkdirToolInstructions, } from "./local/index.js";
|
|
5
|
+
export type { LocalCommandRunner, LocalShellAccessMode, LocalShellContext, LocalShellIsolationGuarantees, LocalShellIsolationMode, LocalShellIsolationOptions, LocalShellIsolationResourceOptions, LocalShellIsolationStatus, LocalShellIsolatorStatusResult, LocalShellRequest, LocalShellResult, LocalShellToolRegistry, LocalShellToolRegistryOptions, IsolatorLocalShellRunnerOptions, LocalWorkdirAction, LocalWorkdirAccessMode, LocalWorkdirToolRegistry, LocalWorkdirToolRegistryOptions, } from "./local/index.js";
|
|
6
6
|
export { localSkillFromDirectory, pendingLocalSkillCalls, runLocalSkillHandlers } from "./local-skills.js";
|
|
7
7
|
export type { LocalSkillDirectoryOptions } from "./local-skills.js";
|
|
8
8
|
export * from "./local/index.js";
|
package/dist/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "./index.js";
|
|
2
2
|
export { NodeAgentAPI } from "./node-client.js";
|
|
3
3
|
export { NodeSkillsResource } from "./resources/skills-node.js";
|
|
4
|
-
export { createLocalShellToolRegistry, LocalWorkdirDriver, HostLocalShellRunner, LocalShellDriver, localShellToolDefinition, localShellToolInstructions, createLocalWorkdirToolRegistry, localWorkdirToolDefinition, localWorkdirToolInstructions, } from "./local/index.js";
|
|
4
|
+
export { createLocalShellToolRegistry, LocalWorkdirDriver, HostLocalShellRunner, IsolatorLocalShellRunner, LocalShellDriver, localShellToolDefinition, localShellToolInstructions, createLocalWorkdirToolRegistry, localWorkdirToolDefinition, localWorkdirToolInstructions, } from "./local/index.js";
|
|
5
5
|
export { localSkillFromDirectory, pendingLocalSkillCalls, runLocalSkillHandlers } from "./local-skills.js";
|
|
6
6
|
export * from "./local/index.js";
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
2
|
-
export declare const USER_AGENT = "@agent-api/sdk/1.
|
|
1
|
+
export declare const VERSION = "1.3.0";
|
|
2
|
+
export declare const USER_AGENT = "@agent-api/sdk/1.3.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "1.
|
|
1
|
+
export const VERSION = "1.3.0";
|
|
2
2
|
export const USER_AGENT = `@agent-api/sdk/${VERSION}`;
|
package/dist-cjs/local/core.js
CHANGED
|
@@ -337,12 +337,15 @@ class LocalFileStore {
|
|
|
337
337
|
if (item.type !== "file" || item.size > maxBytesPerFile || !isLikelyTextFile(item.path)) {
|
|
338
338
|
continue;
|
|
339
339
|
}
|
|
340
|
-
const raw = await (
|
|
340
|
+
const raw = await readOptionalFile(item.fullPath);
|
|
341
|
+
if (!raw) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
341
344
|
if (looksBinary(raw)) {
|
|
342
345
|
continue;
|
|
343
346
|
}
|
|
344
347
|
filesScanned++;
|
|
345
|
-
const lines = splitLines(raw.toString("utf8"));
|
|
348
|
+
const lines = splitLines(Buffer.from(raw).toString("utf8"));
|
|
346
349
|
for (let i = 0; i < lines.length; i++) {
|
|
347
350
|
if (!lines[i].includes(pattern)) {
|
|
348
351
|
continue;
|
|
@@ -378,7 +381,10 @@ class LocalFileStore {
|
|
|
378
381
|
if (!isLikelyTextFile(item.path) || item.size > previewBytes * 4) {
|
|
379
382
|
continue;
|
|
380
383
|
}
|
|
381
|
-
const raw = await (
|
|
384
|
+
const raw = await readOptionalFile(item.fullPath);
|
|
385
|
+
if (!raw) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
382
388
|
if (looksBinary(raw)) {
|
|
383
389
|
continue;
|
|
384
390
|
}
|
|
@@ -386,7 +392,7 @@ class LocalFileStore {
|
|
|
386
392
|
previews.push({
|
|
387
393
|
path: item.path,
|
|
388
394
|
size: item.size,
|
|
389
|
-
preview: raw.subarray(0, previewBytes).toString("utf8"),
|
|
395
|
+
preview: Buffer.from(raw.subarray(0, previewBytes)).toString("utf8"),
|
|
390
396
|
preview_truncated: truncated || undefined,
|
|
391
397
|
});
|
|
392
398
|
}
|
|
@@ -408,7 +414,10 @@ class LocalFileStore {
|
|
|
408
414
|
if (ignored(relativePath, options.ignore)) {
|
|
409
415
|
continue;
|
|
410
416
|
}
|
|
411
|
-
const info = await (
|
|
417
|
+
const info = await lstatOptional(fullPath);
|
|
418
|
+
if (!info) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
412
421
|
const item = {
|
|
413
422
|
path: relativePath,
|
|
414
423
|
fullPath,
|
|
@@ -416,7 +425,7 @@ class LocalFileStore {
|
|
|
416
425
|
size: info.size,
|
|
417
426
|
modifiedAt: info.mtime,
|
|
418
427
|
};
|
|
419
|
-
if (
|
|
428
|
+
if (info.isDirectory()) {
|
|
420
429
|
if (options.includeDirectories) {
|
|
421
430
|
out.push(item);
|
|
422
431
|
}
|
|
@@ -833,6 +842,28 @@ async function fileExists(fullPath) {
|
|
|
833
842
|
return false;
|
|
834
843
|
}
|
|
835
844
|
}
|
|
845
|
+
async function lstatOptional(fullPath) {
|
|
846
|
+
try {
|
|
847
|
+
return await (0, promises_1.lstat)(fullPath);
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
throw error;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
async function readOptionalFile(fullPath) {
|
|
857
|
+
try {
|
|
858
|
+
return await (0, promises_1.readFile)(fullPath);
|
|
859
|
+
}
|
|
860
|
+
catch (error) {
|
|
861
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR" || error?.code === "EISDIR") {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
836
867
|
function normalizeAppName(appName) {
|
|
837
868
|
const trimmed = appName.trim();
|
|
838
869
|
if (!trimmed) {
|
package/dist-cjs/local/shell.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.LocalShellDriver = exports.HostLocalShellRunner = void 0;
|
|
6
|
+
exports.LocalShellDriver = exports.IsolatorLocalShellRunner = exports.HostLocalShellRunner = void 0;
|
|
7
7
|
exports.createLocalShellToolRegistry = createLocalShellToolRegistry;
|
|
8
8
|
exports.localShellToolDefinition = localShellToolDefinition;
|
|
9
9
|
exports.localShellToolInstructions = localShellToolInstructions;
|
|
@@ -15,12 +15,14 @@ class HostLocalShellRunner {
|
|
|
15
15
|
timeoutMs;
|
|
16
16
|
maxOutputBytes;
|
|
17
17
|
env;
|
|
18
|
+
isolationStatus;
|
|
18
19
|
constructor(options = {}) {
|
|
19
20
|
this.cwd = node_path_1.default.resolve(options.cwd ?? process.cwd());
|
|
20
21
|
this.shell = options.shell ?? true;
|
|
21
22
|
this.timeoutMs = positiveInteger(options.timeoutMs, 2 * 60 * 1000, "timeoutMs");
|
|
22
23
|
this.maxOutputBytes = positiveInteger(options.maxOutputBytes, 128 * 1024, "maxOutputBytes");
|
|
23
24
|
this.env = { ...process.env, ...(options.env ?? {}) };
|
|
25
|
+
this.isolationStatus = directIsolationStatus(false, options.isolationOptions);
|
|
24
26
|
}
|
|
25
27
|
async run(request, context = {}) {
|
|
26
28
|
const command = requiredString(request.command, "command");
|
|
@@ -63,6 +65,7 @@ class HostLocalShellRunner {
|
|
|
63
65
|
duration_ms: Date.now() - started,
|
|
64
66
|
timed_out: timedOut,
|
|
65
67
|
truncated: stdout.truncated || stderr.truncated,
|
|
68
|
+
shell_isolation: this.isolationStatus,
|
|
66
69
|
});
|
|
67
70
|
};
|
|
68
71
|
const kill = () => {
|
|
@@ -103,23 +106,62 @@ class HostLocalShellRunner {
|
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
exports.HostLocalShellRunner = HostLocalShellRunner;
|
|
109
|
+
class IsolatorLocalShellRunner {
|
|
110
|
+
executablePath;
|
|
111
|
+
driver;
|
|
112
|
+
cwd;
|
|
113
|
+
timeoutMs;
|
|
114
|
+
maxOutputBytes;
|
|
115
|
+
isolationOptions;
|
|
116
|
+
isolationStatus;
|
|
117
|
+
statusResult;
|
|
118
|
+
constructor(options = {}) {
|
|
119
|
+
this.executablePath = resolveIsolatorExecutablePath(options.executablePath);
|
|
120
|
+
this.driver = options.driver ?? "auto";
|
|
121
|
+
this.cwd = node_path_1.default.resolve(options.cwd ?? process.cwd());
|
|
122
|
+
this.timeoutMs = positiveInteger(options.timeoutMs, 2 * 60 * 1000, "timeoutMs");
|
|
123
|
+
this.maxOutputBytes = positiveInteger(options.maxOutputBytes, 128 * 1024, "maxOutputBytes");
|
|
124
|
+
this.isolationOptions = options.isolationOptions ?? {};
|
|
125
|
+
this.statusResult = isolatorStatusSync(this.executablePath, this.driver);
|
|
126
|
+
this.isolationStatus = this.statusResult.status;
|
|
127
|
+
}
|
|
128
|
+
async run(request, context = {}) {
|
|
129
|
+
const command = requiredString(request.command, "command");
|
|
130
|
+
const cwd = resolveContainedPath(this.cwd, request.workdir);
|
|
131
|
+
const timeoutMs = request.timeoutMs == null
|
|
132
|
+
? this.timeoutMs
|
|
133
|
+
: positiveInteger(request.timeoutMs, this.timeoutMs, "timeoutMs");
|
|
134
|
+
const response = await isolatorRequest(this.executablePath, this.driver, {
|
|
135
|
+
id: `run_${Date.now().toString(36)}`,
|
|
136
|
+
method: "run",
|
|
137
|
+
params: {
|
|
138
|
+
command,
|
|
139
|
+
description: request.description,
|
|
140
|
+
cwd,
|
|
141
|
+
timeout_ms: timeoutMs,
|
|
142
|
+
max_output_bytes: this.maxOutputBytes,
|
|
143
|
+
env: request.env,
|
|
144
|
+
isolation: this.isolationOptions,
|
|
145
|
+
},
|
|
146
|
+
}, context);
|
|
147
|
+
return isolatorRunResult(response.result);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.IsolatorLocalShellRunner = IsolatorLocalShellRunner;
|
|
106
151
|
class LocalShellDriver {
|
|
107
152
|
accessMode;
|
|
108
153
|
runner;
|
|
154
|
+
isolationStatus;
|
|
109
155
|
constructor(options = {}) {
|
|
110
156
|
const cwd = options.workdir?.root ?? options.cwd;
|
|
111
157
|
this.accessMode = options.accessMode ?? "approval";
|
|
112
|
-
this.runner = options
|
|
113
|
-
|
|
114
|
-
shell: options.shell,
|
|
115
|
-
timeoutMs: options.timeoutMs,
|
|
116
|
-
maxOutputBytes: options.maxOutputBytes,
|
|
117
|
-
});
|
|
158
|
+
this.runner = resolveShellRunner(options, cwd);
|
|
159
|
+
this.isolationStatus = this.runner.isolationStatus ?? directIsolationStatus(options.isolation === "auto", options.isolationOptions);
|
|
118
160
|
}
|
|
119
161
|
async dispatch(args, context = {}) {
|
|
120
162
|
const request = shellRequest(args);
|
|
121
163
|
if (this.accessMode !== "full") {
|
|
122
|
-
return shellApprovalRequired(request);
|
|
164
|
+
return shellApprovalRequired(request, this.isolationStatus);
|
|
123
165
|
}
|
|
124
166
|
return shellToolResult(await this.runner.run(request, context));
|
|
125
167
|
}
|
|
@@ -128,16 +170,80 @@ class LocalShellDriver {
|
|
|
128
170
|
}
|
|
129
171
|
}
|
|
130
172
|
exports.LocalShellDriver = LocalShellDriver;
|
|
173
|
+
function resolveShellRunner(options, cwd) {
|
|
174
|
+
if (options.runner) {
|
|
175
|
+
if (options.isolation === "required" && options.runner.isolationStatus?.isolated !== true) {
|
|
176
|
+
throw new Error("local_shell isolation is required, but the configured runner does not report isolation");
|
|
177
|
+
}
|
|
178
|
+
return options.runner;
|
|
179
|
+
}
|
|
180
|
+
let isolatorFallbackWarning;
|
|
181
|
+
if (options.isolation === "auto" || options.isolation === "required" || options.isolator) {
|
|
182
|
+
const isolatorOptions = typeof options.isolator === "object" ? options.isolator : {};
|
|
183
|
+
try {
|
|
184
|
+
const runner = new IsolatorLocalShellRunner({
|
|
185
|
+
cwd,
|
|
186
|
+
timeoutMs: options.timeoutMs,
|
|
187
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
188
|
+
isolationOptions: options.isolationOptions,
|
|
189
|
+
...isolatorOptions,
|
|
190
|
+
});
|
|
191
|
+
if (options.isolation === "required" && runner.isolationStatus.isolated !== true) {
|
|
192
|
+
throw new Error(`local_shell isolation is required, but agent-isolator selected non-isolated driver ${runner.isolationStatus.driver}`);
|
|
193
|
+
}
|
|
194
|
+
if (options.isolation !== "none" || options.isolator) {
|
|
195
|
+
return runner;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (options.isolation === "required") {
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
isolatorFallbackWarning = errorMessage(error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const runner = new HostLocalShellRunner({
|
|
206
|
+
cwd,
|
|
207
|
+
shell: options.shell,
|
|
208
|
+
timeoutMs: options.timeoutMs,
|
|
209
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
210
|
+
isolationOptions: options.isolationOptions,
|
|
211
|
+
});
|
|
212
|
+
if (options.isolation === "auto") {
|
|
213
|
+
let status = directIsolationStatus(true, options.isolationOptions);
|
|
214
|
+
if (isolatorFallbackWarning) {
|
|
215
|
+
status = {
|
|
216
|
+
...status,
|
|
217
|
+
warnings: [...status.warnings, `Isolator unavailable: ${isolatorFallbackWarning}`],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return withIsolationStatus(runner, status);
|
|
221
|
+
}
|
|
222
|
+
return runner;
|
|
223
|
+
}
|
|
224
|
+
function resolveIsolatorExecutablePath(explicitPath) {
|
|
225
|
+
const configured = (explicitPath?.trim() || process.env.AGENT_ISOLATOR_PATH?.trim() || "");
|
|
226
|
+
if (!configured) {
|
|
227
|
+
throw new Error("agent-isolator executable path is not configured; pass isolator.executablePath or set AGENT_ISOLATOR_PATH");
|
|
228
|
+
}
|
|
229
|
+
return configured;
|
|
230
|
+
}
|
|
231
|
+
function errorMessage(error) {
|
|
232
|
+
return error instanceof Error ? error.message : String(error);
|
|
233
|
+
}
|
|
131
234
|
function createLocalShellToolRegistry(options = {}) {
|
|
132
235
|
const toolName = options.toolName ?? "local_shell";
|
|
133
236
|
const driver = new LocalShellDriver(options);
|
|
134
237
|
const hostRunner = driver.runner instanceof HostLocalShellRunner ? driver.runner : undefined;
|
|
238
|
+
const isolatorRunner = driver.runner instanceof IsolatorLocalShellRunner ? driver.runner : undefined;
|
|
135
239
|
const definition = localShellToolDefinition(toolName, {
|
|
136
240
|
accessMode: driver.accessMode,
|
|
137
|
-
cwd: options.workdir?.root ?? hostRunner?.cwd ?? options.cwd,
|
|
241
|
+
cwd: options.workdir?.root ?? hostRunner?.cwd ?? isolatorRunner?.cwd ?? options.cwd,
|
|
138
242
|
shell: hostRunner?.shell ?? options.shell,
|
|
139
|
-
timeoutMs: hostRunner?.timeoutMs ?? options.timeoutMs,
|
|
140
|
-
maxOutputBytes: hostRunner?.maxOutputBytes ?? options.maxOutputBytes,
|
|
243
|
+
timeoutMs: hostRunner?.timeoutMs ?? isolatorRunner?.timeoutMs ?? options.timeoutMs,
|
|
244
|
+
maxOutputBytes: hostRunner?.maxOutputBytes ?? isolatorRunner?.maxOutputBytes ?? options.maxOutputBytes,
|
|
245
|
+
isolationStatus: driver.isolationStatus,
|
|
246
|
+
isolationOptions: options.isolationOptions,
|
|
141
247
|
});
|
|
142
248
|
return {
|
|
143
249
|
accessMode: driver.accessMode,
|
|
@@ -169,13 +275,16 @@ function localShellToolDescription(options = {}) {
|
|
|
169
275
|
const platform = options.platform ?? process.platform;
|
|
170
276
|
const shell = shellDisplayName(options.shell);
|
|
171
277
|
const accessMode = options.accessMode ?? "approval";
|
|
278
|
+
const isolation = options.isolationStatus ?? directIsolationStatus(false, options.isolationOptions);
|
|
172
279
|
const cwd = options.cwd ? node_path_1.default.resolve(options.cwd) : undefined;
|
|
173
280
|
const timeoutMs = options.timeoutMs ?? 2 * 60 * 1000;
|
|
174
281
|
const maxOutputBytes = options.maxOutputBytes ?? 128 * 1024;
|
|
175
282
|
return [
|
|
176
283
|
"Run a local shell command through one model-facing primitive.",
|
|
177
284
|
"Prefer local_workdir for file discovery, reading, and editing. Use local_shell for package managers, tests, build commands, git, and other process-level tasks.",
|
|
178
|
-
`Execution environment: platform=${platform}; shell=${shell}; access_mode=${accessMode}; default_timeout_ms=${timeoutMs}; max_output_bytes=${maxOutputBytes}.`,
|
|
285
|
+
`Execution environment: platform=${platform}; shell=${shell}; access_mode=${accessMode}; isolation_driver=${isolation.driver}; isolated=${isolation.isolated}; fallback=${isolation.fallback}; default_timeout_ms=${timeoutMs}; max_output_bytes=${maxOutputBytes}.`,
|
|
286
|
+
isolationRequestDescription(isolation.requested),
|
|
287
|
+
isolation.warnings.length > 0 ? `Isolation warning: ${isolation.warnings.join(" ")}` : "",
|
|
179
288
|
cwd ? `Default cwd: ${cwd}. Relative command paths resolve from this cwd unless workdir is set.` : "Relative command paths resolve from the configured cwd unless workdir is set.",
|
|
180
289
|
"The workdir parameter must be a relative child path of the configured cwd. Use workdir instead of cd when possible.",
|
|
181
290
|
"Absolute paths inside the command are permitted by the host OS if the user/process has permission; this tool is not a filesystem sandbox or security sandbox.",
|
|
@@ -217,7 +326,7 @@ function shellRequest(args) {
|
|
|
217
326
|
timeoutMs: optionalNumber(args.timeoutMs ?? args.timeout_ms, "timeout_ms"),
|
|
218
327
|
};
|
|
219
328
|
}
|
|
220
|
-
function shellApprovalRequired(request) {
|
|
329
|
+
function shellApprovalRequired(request, isolationStatus) {
|
|
221
330
|
return {
|
|
222
331
|
ok: false,
|
|
223
332
|
requires_approval: true,
|
|
@@ -226,9 +335,235 @@ function shellApprovalRequired(request) {
|
|
|
226
335
|
description: request.description,
|
|
227
336
|
workdir: request.workdir,
|
|
228
337
|
timeout_ms: request.timeoutMs,
|
|
338
|
+
shell_isolation: isolationStatus,
|
|
229
339
|
message: "local_shell command execution requires approval",
|
|
230
340
|
};
|
|
231
341
|
}
|
|
342
|
+
function directIsolationStatus(fallback, options = {}) {
|
|
343
|
+
const requested = normalizeIsolationOptions(options);
|
|
344
|
+
const warnings = directIsolationWarnings(fallback, requested);
|
|
345
|
+
return {
|
|
346
|
+
executor: "direct",
|
|
347
|
+
driver: "direct",
|
|
348
|
+
isolated: false,
|
|
349
|
+
fallback,
|
|
350
|
+
requested,
|
|
351
|
+
guarantees: {
|
|
352
|
+
filesystem: "none",
|
|
353
|
+
network: "allowed",
|
|
354
|
+
user: "host-user",
|
|
355
|
+
process: "host-process-tree",
|
|
356
|
+
resources: "timeout-only",
|
|
357
|
+
},
|
|
358
|
+
warnings,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function normalizeIsolationOptions(options = {}) {
|
|
362
|
+
return {
|
|
363
|
+
filesystem: options.filesystem ?? "host",
|
|
364
|
+
network: options.network ?? "allowed",
|
|
365
|
+
env: options.env ?? "inherit",
|
|
366
|
+
resources: {
|
|
367
|
+
memoryMb: options.resources?.memoryMb,
|
|
368
|
+
cpuCount: options.resources?.cpuCount,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function directIsolationWarnings(fallback, requested) {
|
|
373
|
+
const warnings = [
|
|
374
|
+
fallback
|
|
375
|
+
? "No local shell isolator is configured; falling back to direct host execution."
|
|
376
|
+
: "Direct host execution has no OS-level isolation.",
|
|
377
|
+
];
|
|
378
|
+
if (requested.filesystem !== "host") {
|
|
379
|
+
warnings.push(`Requested filesystem isolation (${requested.filesystem}) is not enforced by direct execution.`);
|
|
380
|
+
}
|
|
381
|
+
if (requested.network === "blocked") {
|
|
382
|
+
warnings.push("Requested network blocking is not enforced by direct execution.");
|
|
383
|
+
}
|
|
384
|
+
if (requested.env === "minimal") {
|
|
385
|
+
warnings.push("Requested minimal environment is not enforced by direct execution.");
|
|
386
|
+
}
|
|
387
|
+
if (requested.resources.memoryMb != null || requested.resources.cpuCount != null) {
|
|
388
|
+
warnings.push("Requested CPU or memory limits are not enforced by direct execution.");
|
|
389
|
+
}
|
|
390
|
+
return warnings;
|
|
391
|
+
}
|
|
392
|
+
function isolationRequestDescription(options) {
|
|
393
|
+
const resources = [
|
|
394
|
+
options.resources.memoryMb == null ? "" : `memory_mb=${options.resources.memoryMb}`,
|
|
395
|
+
options.resources.cpuCount == null ? "" : `cpu_count=${options.resources.cpuCount}`,
|
|
396
|
+
].filter(Boolean).join("; ");
|
|
397
|
+
return `Requested isolation: filesystem=${options.filesystem}; network=${options.network}; env=${options.env}${resources ? `; ${resources}` : ""}.`;
|
|
398
|
+
}
|
|
399
|
+
function withIsolationStatus(runner, status) {
|
|
400
|
+
Object.defineProperty(runner, "isolationStatus", {
|
|
401
|
+
configurable: true,
|
|
402
|
+
enumerable: true,
|
|
403
|
+
value: status,
|
|
404
|
+
});
|
|
405
|
+
return runner;
|
|
406
|
+
}
|
|
407
|
+
function isolatorStatusSync(executablePath, driver) {
|
|
408
|
+
const request = JSON.stringify({ id: "status", method: "status", params: {} });
|
|
409
|
+
const result = (0, node_child_process_1.spawnSync)(executablePath, ["--once", `--driver=${driver}`], {
|
|
410
|
+
input: request,
|
|
411
|
+
encoding: "utf8",
|
|
412
|
+
maxBuffer: 1024 * 1024,
|
|
413
|
+
windowsHide: true,
|
|
414
|
+
});
|
|
415
|
+
if (result.error) {
|
|
416
|
+
throw result.error;
|
|
417
|
+
}
|
|
418
|
+
if (result.status !== 0) {
|
|
419
|
+
throw new Error((result.stderr || result.stdout || `agent-isolator exited with status ${result.status}`).trim());
|
|
420
|
+
}
|
|
421
|
+
const envelope = parseIsolatorEnvelope(result.stdout);
|
|
422
|
+
if (envelope.error) {
|
|
423
|
+
throw new Error(envelope.error.message || envelope.error.code || "agent-isolator status failed");
|
|
424
|
+
}
|
|
425
|
+
return isolatorStatusResult(envelope.result);
|
|
426
|
+
}
|
|
427
|
+
async function isolatorRequest(executablePath, driver, envelope, context) {
|
|
428
|
+
return await new Promise((resolve, reject) => {
|
|
429
|
+
const child = (0, node_child_process_1.spawn)(executablePath, ["--once", `--driver=${driver}`], {
|
|
430
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
431
|
+
windowsHide: true,
|
|
432
|
+
});
|
|
433
|
+
const stdout = new BoundedText(1024 * 1024);
|
|
434
|
+
const stderr = new BoundedText(1024 * 1024);
|
|
435
|
+
let settled = false;
|
|
436
|
+
const finish = (error) => {
|
|
437
|
+
if (settled)
|
|
438
|
+
return;
|
|
439
|
+
settled = true;
|
|
440
|
+
context.signal?.removeEventListener("abort", abort);
|
|
441
|
+
if (error) {
|
|
442
|
+
reject(error);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
const parsed = parseIsolatorEnvelope(stdout.text());
|
|
447
|
+
if (parsed.error) {
|
|
448
|
+
reject(new Error(parsed.error.message || parsed.error.code || "agent-isolator request failed"));
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
resolve({ result: parsed.result });
|
|
452
|
+
}
|
|
453
|
+
catch (parseError) {
|
|
454
|
+
const details = stderr.text();
|
|
455
|
+
reject(new Error(`${parseError instanceof Error ? parseError.message : String(parseError)}${details ? `: ${details}` : ""}`));
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
const abort = () => {
|
|
459
|
+
if (!child.killed)
|
|
460
|
+
child.kill(process.platform === "win32" ? undefined : "SIGTERM");
|
|
461
|
+
};
|
|
462
|
+
if (context.signal?.aborted) {
|
|
463
|
+
abort();
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
context.signal?.addEventListener("abort", abort, { once: true });
|
|
467
|
+
}
|
|
468
|
+
child.stdout?.on("data", (chunk) => stdout.append(chunk));
|
|
469
|
+
child.stderr?.on("data", (chunk) => stderr.append(chunk));
|
|
470
|
+
child.on("error", finish);
|
|
471
|
+
child.on("close", (code) => {
|
|
472
|
+
if (code !== 0) {
|
|
473
|
+
finish(new Error((stderr.text() || stdout.text() || `agent-isolator exited with status ${code}`).trim()));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
finish();
|
|
477
|
+
});
|
|
478
|
+
child.stdin?.end(JSON.stringify(envelope));
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function parseIsolatorEnvelope(text) {
|
|
482
|
+
const trimmed = text.trim();
|
|
483
|
+
if (!trimmed) {
|
|
484
|
+
throw new Error("agent-isolator returned an empty response");
|
|
485
|
+
}
|
|
486
|
+
return JSON.parse(trimmed);
|
|
487
|
+
}
|
|
488
|
+
function isolatorStatusResult(value) {
|
|
489
|
+
if (!isRecord(value)) {
|
|
490
|
+
throw new Error("agent-isolator status result must be an object");
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
version: typeof value.version === "string" ? value.version : undefined,
|
|
494
|
+
driver: requiredString(value.driver, "driver"),
|
|
495
|
+
status: isolationStatusFromUnknown(value.status),
|
|
496
|
+
drivers: Array.isArray(value.drivers)
|
|
497
|
+
? value.drivers.filter(isRecord).map((item) => ({
|
|
498
|
+
name: String(item.name ?? ""),
|
|
499
|
+
platform: String(item.platform ?? ""),
|
|
500
|
+
available: item.available === true,
|
|
501
|
+
warnings: Array.isArray(item.warnings) ? item.warnings.map(String) : undefined,
|
|
502
|
+
}))
|
|
503
|
+
: undefined,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function isolatorRunResult(value) {
|
|
507
|
+
if (!isRecord(value)) {
|
|
508
|
+
throw new Error("agent-isolator run result must be an object");
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
ok: value.ok === true,
|
|
512
|
+
command: requiredString(value.command, "command"),
|
|
513
|
+
description: optionalString(value.description, "description"),
|
|
514
|
+
cwd: requiredString(value.cwd, "cwd"),
|
|
515
|
+
exit_code: typeof value.exit_code === "number" ? value.exit_code : null,
|
|
516
|
+
signal: typeof value.signal === "string" ? value.signal : null,
|
|
517
|
+
stdout: typeof value.stdout === "string" ? value.stdout : "",
|
|
518
|
+
stderr: typeof value.stderr === "string" ? value.stderr : "",
|
|
519
|
+
output: typeof value.output === "string" ? value.output : "(no output)",
|
|
520
|
+
duration_ms: typeof value.duration_ms === "number" ? value.duration_ms : 0,
|
|
521
|
+
timed_out: value.timed_out === true,
|
|
522
|
+
truncated: value.truncated === true,
|
|
523
|
+
shell_isolation: isolationStatusFromUnknown(value.shell_isolation),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function isolationStatusFromUnknown(value) {
|
|
527
|
+
if (!isRecord(value)) {
|
|
528
|
+
throw new Error("shell isolation status must be an object");
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
executor: value.executor === "isolator" ? "isolator" : "direct",
|
|
532
|
+
driver: requiredString(value.driver, "shell_isolation.driver"),
|
|
533
|
+
isolated: value.isolated === true,
|
|
534
|
+
fallback: value.fallback === true,
|
|
535
|
+
requested: normalizeIsolationOptions(isRecord(value.requested) ? isolationOptionsFromUnknown(value.requested) : {}),
|
|
536
|
+
guarantees: isolationGuaranteesFromUnknown(value.guarantees),
|
|
537
|
+
warnings: Array.isArray(value.warnings) ? value.warnings.map(String) : [],
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function isolationOptionsFromUnknown(value) {
|
|
541
|
+
const resources = isRecord(value.resources) ? value.resources : {};
|
|
542
|
+
return {
|
|
543
|
+
filesystem: value.filesystem === "workdir-readonly" || value.filesystem === "workdir-readwrite" || value.filesystem === "host"
|
|
544
|
+
? value.filesystem
|
|
545
|
+
: undefined,
|
|
546
|
+
network: value.network === "blocked" || value.network === "allowed" ? value.network : undefined,
|
|
547
|
+
env: value.env === "minimal" || value.env === "inherit" ? value.env : undefined,
|
|
548
|
+
resources: {
|
|
549
|
+
memoryMb: typeof resources.memoryMb === "number" ? resources.memoryMb : undefined,
|
|
550
|
+
cpuCount: typeof resources.cpuCount === "number" ? resources.cpuCount : undefined,
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function isolationGuaranteesFromUnknown(value) {
|
|
555
|
+
const record = isRecord(value) ? value : {};
|
|
556
|
+
return {
|
|
557
|
+
filesystem: record.filesystem === "workdir-mounted" || record.filesystem === "policy-enforced" ? record.filesystem : "none",
|
|
558
|
+
network: record.network === "blocked" || record.network === "configurable" ? record.network : "allowed",
|
|
559
|
+
user: record.user === "unprivileged-user" || record.user === "namespace-user" ? record.user : "host-user",
|
|
560
|
+
process: record.process === "child-contained" || record.process === "pid-namespace" ? record.process : "host-process-tree",
|
|
561
|
+
resources: record.resources === "cpu-memory-limits" ? "cpu-memory-limits" : record.resources === "timeout-only" ? "timeout-only" : "none",
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function isRecord(value) {
|
|
565
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
566
|
+
}
|
|
232
567
|
function shellToolResult(result) {
|
|
233
568
|
return {
|
|
234
569
|
...result,
|
package/dist-cjs/node.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.runLocalSkillHandlers = exports.pendingLocalSkillCalls = exports.localSkillFromDirectory = exports.localWorkdirToolInstructions = exports.localWorkdirToolDefinition = exports.createLocalWorkdirToolRegistry = exports.localShellToolInstructions = exports.localShellToolDefinition = exports.LocalShellDriver = exports.HostLocalShellRunner = exports.LocalWorkdirDriver = exports.createLocalShellToolRegistry = exports.NodeSkillsResource = exports.NodeAgentAPI = void 0;
|
|
17
|
+
exports.runLocalSkillHandlers = exports.pendingLocalSkillCalls = exports.localSkillFromDirectory = exports.localWorkdirToolInstructions = exports.localWorkdirToolDefinition = exports.createLocalWorkdirToolRegistry = exports.localShellToolInstructions = exports.localShellToolDefinition = exports.LocalShellDriver = exports.IsolatorLocalShellRunner = exports.HostLocalShellRunner = exports.LocalWorkdirDriver = exports.createLocalShellToolRegistry = exports.NodeSkillsResource = exports.NodeAgentAPI = void 0;
|
|
18
18
|
__exportStar(require("./index.js"), exports);
|
|
19
19
|
var node_client_js_1 = require("./node-client.js");
|
|
20
20
|
Object.defineProperty(exports, "NodeAgentAPI", { enumerable: true, get: function () { return node_client_js_1.NodeAgentAPI; } });
|
|
@@ -24,6 +24,7 @@ var index_js_1 = require("./local/index.js");
|
|
|
24
24
|
Object.defineProperty(exports, "createLocalShellToolRegistry", { enumerable: true, get: function () { return index_js_1.createLocalShellToolRegistry; } });
|
|
25
25
|
Object.defineProperty(exports, "LocalWorkdirDriver", { enumerable: true, get: function () { return index_js_1.LocalWorkdirDriver; } });
|
|
26
26
|
Object.defineProperty(exports, "HostLocalShellRunner", { enumerable: true, get: function () { return index_js_1.HostLocalShellRunner; } });
|
|
27
|
+
Object.defineProperty(exports, "IsolatorLocalShellRunner", { enumerable: true, get: function () { return index_js_1.IsolatorLocalShellRunner; } });
|
|
27
28
|
Object.defineProperty(exports, "LocalShellDriver", { enumerable: true, get: function () { return index_js_1.LocalShellDriver; } });
|
|
28
29
|
Object.defineProperty(exports, "localShellToolDefinition", { enumerable: true, get: function () { return index_js_1.localShellToolDefinition; } });
|
|
29
30
|
Object.defineProperty(exports, "localShellToolInstructions", { enumerable: true, get: function () { return index_js_1.localShellToolInstructions; } });
|
package/dist-cjs/version.js
CHANGED