@agent-api/sdk 1.2.3 → 1.3.1
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 +21 -0
- package/README.md +8 -0
- package/dist/local/core.d.ts +1 -0
- package/dist/local/core.js +22 -1
- package/dist/local/shell.d.ts +76 -0
- package/dist/local/shell.js +347 -13
- package/dist/local/tools.d.ts +1 -0
- package/dist/local/tools.js +38 -2
- 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 +22 -1
- package/dist-cjs/local/shell.js +348 -13
- package/dist-cjs/local/tools.js +38 -2
- package/dist-cjs/node.js +2 -1
- package/dist-cjs/version.js +1 -1
- package/package.json +1 -1
package/dist/local/tools.js
CHANGED
|
@@ -7,6 +7,15 @@ export class LocalWorkdirDriver {
|
|
|
7
7
|
this.accessMode = options.accessMode ?? "approval";
|
|
8
8
|
}
|
|
9
9
|
async dispatch(args) {
|
|
10
|
+
const action = safeWorkdirAction(args);
|
|
11
|
+
try {
|
|
12
|
+
return await this.dispatchUnsafe(args);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return localToolErrorResult(action, error);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async dispatchUnsafe(args) {
|
|
10
19
|
const action = workdirAction(args);
|
|
11
20
|
switch (action) {
|
|
12
21
|
case "summarize":
|
|
@@ -45,7 +54,8 @@ export class LocalWorkdirDriver {
|
|
|
45
54
|
if (this.accessMode === "full") {
|
|
46
55
|
return false;
|
|
47
56
|
}
|
|
48
|
-
|
|
57
|
+
const action = safeWorkdirAction(args);
|
|
58
|
+
return action !== "unknown" && mutatingLocalWorkdirActions.has(action);
|
|
49
59
|
}
|
|
50
60
|
async dispatchApplyEdits(args) {
|
|
51
61
|
const edits = editsArg(args);
|
|
@@ -129,6 +139,7 @@ const mutatingLocalWorkdirActions = new Set([
|
|
|
129
139
|
const localWorkdirToolDescription = [
|
|
130
140
|
"Inspect and modify the selected local workdir through one model-facing primitive.",
|
|
131
141
|
"Use action=list/search/grep/summarize/context to discover files, read/read_lines for file content, preview_edits before edits, and apply_edits/write/mkdir/delete only when mutation is intended.",
|
|
142
|
+
"grep searches file contents for a literal substring; path may be omitted, a file path, or a directory subtree.",
|
|
132
143
|
"In approval mode, mutating actions return requires_approval with a safe preview instead of changing files. In full mode, mutating actions execute immediately.",
|
|
133
144
|
"Paths are relative to the selected local workdir; never use absolute paths.",
|
|
134
145
|
].join(" ");
|
|
@@ -139,7 +150,7 @@ function localWorkdirToolParameters() {
|
|
|
139
150
|
enum: localWorkdirActions,
|
|
140
151
|
description: "Workdir operation. Prefer summarize/list/search/grep before reading or editing. Prefer read_lines and apply_edits for source changes.",
|
|
141
152
|
},
|
|
142
|
-
path: stringSchema("Relative path. File path for read/write/delete/edit actions
|
|
153
|
+
path: stringSchema("Relative path. File path for read/write/delete/edit actions. For grep, path may be a file or a directory subtree. For list/search/summarize/context/snapshot, path is a directory base."),
|
|
143
154
|
query: stringSchema("Path/name query for search, or optional context query."),
|
|
144
155
|
pattern: stringSchema("Literal text pattern for grep."),
|
|
145
156
|
content: stringSchema("Text content for write."),
|
|
@@ -182,6 +193,10 @@ function workdirAction(args) {
|
|
|
182
193
|
}
|
|
183
194
|
return value;
|
|
184
195
|
}
|
|
196
|
+
function safeWorkdirAction(args) {
|
|
197
|
+
const value = typeof args.action === "string" ? args.action.trim().toLowerCase() : "";
|
|
198
|
+
return localWorkdirActions.includes(value) ? value : "unknown";
|
|
199
|
+
}
|
|
185
200
|
function summaryArgs(args) {
|
|
186
201
|
return {
|
|
187
202
|
path: optionalStringArg(args, "path"),
|
|
@@ -277,6 +292,27 @@ function localToolResult(action, value) {
|
|
|
277
292
|
}
|
|
278
293
|
return { ok: true, action, result };
|
|
279
294
|
}
|
|
295
|
+
function localToolErrorResult(action, error) {
|
|
296
|
+
const details = errorDetails(error);
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
action,
|
|
300
|
+
error: details.message,
|
|
301
|
+
code: details.code,
|
|
302
|
+
path: details.path,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function errorDetails(error) {
|
|
306
|
+
if (error instanceof Error) {
|
|
307
|
+
const record = error;
|
|
308
|
+
return {
|
|
309
|
+
code: typeof record.code === "string" ? record.code : undefined,
|
|
310
|
+
message: error.message || "local_workdir action failed",
|
|
311
|
+
path: typeof record.path === "string" ? record.path : undefined,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return { message: String(error || "local_workdir action failed") };
|
|
315
|
+
}
|
|
280
316
|
function stringArg(args, key, alternateKey) {
|
|
281
317
|
const direct = args[key] ?? (alternateKey ? args[alternateKey] : undefined);
|
|
282
318
|
const fromOptions = args.options && typeof args.options === "object"
|
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.1";
|
|
2
|
+
export declare const USER_AGENT = "@agent-api/sdk/1.3.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "1.
|
|
1
|
+
export const VERSION = "1.3.1";
|
|
2
2
|
export const USER_AGENT = `@agent-api/sdk/${VERSION}`;
|
package/dist-cjs/local/core.js
CHANGED
|
@@ -325,7 +325,7 @@ class LocalFileStore {
|
|
|
325
325
|
const maxFiles = positiveInt(params.maxFiles, 500);
|
|
326
326
|
const maxBytesPerFile = positiveInt(params.maxBytesPerFile, 512 * 1024);
|
|
327
327
|
const maxLineLength = positiveInt(params.maxLineLength, 500);
|
|
328
|
-
const stats = await this.
|
|
328
|
+
const stats = await this.grepCandidates(params.path ?? ".", params.ignore);
|
|
329
329
|
const matches = [];
|
|
330
330
|
let filesScanned = 0;
|
|
331
331
|
let scanTruncated = false;
|
|
@@ -363,6 +363,27 @@ class LocalFileStore {
|
|
|
363
363
|
}
|
|
364
364
|
return { object: "list", matches, files_scanned: filesScanned, scan_truncated: scanTruncated };
|
|
365
365
|
}
|
|
366
|
+
async grepCandidates(relativePath, ignore) {
|
|
367
|
+
const fullPath = this.resolvePath(relativePath);
|
|
368
|
+
const info = await (0, promises_1.stat)(fullPath);
|
|
369
|
+
const portablePath = toPortablePath(node_path_1.default.relative(this.root, fullPath)) || ".";
|
|
370
|
+
if (ignored(portablePath, ignore)) {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
if (info.isFile()) {
|
|
374
|
+
return [{
|
|
375
|
+
path: portablePath,
|
|
376
|
+
fullPath,
|
|
377
|
+
type: "file",
|
|
378
|
+
size: info.size,
|
|
379
|
+
modifiedAt: info.mtime,
|
|
380
|
+
}];
|
|
381
|
+
}
|
|
382
|
+
if (info.isDirectory()) {
|
|
383
|
+
return await this.list(relativePath, { recursive: true, ignore });
|
|
384
|
+
}
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
366
387
|
async summarize(params = {}) {
|
|
367
388
|
const maxFiles = positiveInt(params.maxFiles, 2000);
|
|
368
389
|
const maxPreviews = positiveInt(params.maxPreviews, 20);
|
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,
|