@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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog — @agent-api/sdk
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Fixed
6
+
7
+ - Made `local_workdir` grep behave like familiar grep: `path` may be omitted, a file path, or a directory subtree.
8
+ - Model-facing `local_workdir` execution errors now return structured `{ ok: false, error }` tool results instead of aborting agent runs.
9
+
10
+ ## 1.3.0
11
+
12
+ ### Added
13
+
14
+ - Added SDK support for `local_shell` isolation modes: `none`, `auto`, and `required`.
15
+ - Added `IsolatorLocalShellRunner`, a Node-local runner that delegates shell execution to the shared `agent-isolator` binary.
16
+ - Added model-facing `shell_isolation` metadata for approval previews and execution results.
17
+
18
+ ### Changed
19
+
20
+ - `local_shell` auto isolation now tries an explicitly configured `agent-isolator` path first and falls back to direct host execution when isolation is unavailable.
21
+ - SDKs no longer assume `agent-isolator` is discoverable on `PATH`; pass `isolator.executablePath` or set `AGENT_ISOLATOR_PATH`.
22
+ - Direct host execution remains the default/fallback path and reports its actual non-isolated guarantees.
23
+
3
24
  ## 1.2.3
4
25
 
5
26
  ### Fixed
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
 
@@ -309,6 +309,7 @@ export declare class LocalFileStore {
309
309
  readLines(relativePath: string, params: LocalReadLinesParams): Promise<LocalFileLines>;
310
310
  patchLines(relativePath: string, params: LocalPatchLinesParams): Promise<LocalFileLinesPatch>;
311
311
  grep(params: LocalGrepParams): Promise<LocalGrepResponse>;
312
+ private grepCandidates;
312
313
  summarize(params?: LocalSummarizeParams): Promise<LocalSummary>;
313
314
  private walk;
314
315
  }
@@ -310,7 +310,7 @@ export class LocalFileStore {
310
310
  const maxFiles = positiveInt(params.maxFiles, 500);
311
311
  const maxBytesPerFile = positiveInt(params.maxBytesPerFile, 512 * 1024);
312
312
  const maxLineLength = positiveInt(params.maxLineLength, 500);
313
- const stats = await this.list(params.path ?? ".", { recursive: true, ignore: params.ignore });
313
+ const stats = await this.grepCandidates(params.path ?? ".", params.ignore);
314
314
  const matches = [];
315
315
  let filesScanned = 0;
316
316
  let scanTruncated = false;
@@ -348,6 +348,27 @@ export class LocalFileStore {
348
348
  }
349
349
  return { object: "list", matches, files_scanned: filesScanned, scan_truncated: scanTruncated };
350
350
  }
351
+ async grepCandidates(relativePath, ignore) {
352
+ const fullPath = this.resolvePath(relativePath);
353
+ const info = await stat(fullPath);
354
+ const portablePath = toPortablePath(path.relative(this.root, fullPath)) || ".";
355
+ if (ignored(portablePath, ignore)) {
356
+ return [];
357
+ }
358
+ if (info.isFile()) {
359
+ return [{
360
+ path: portablePath,
361
+ fullPath,
362
+ type: "file",
363
+ size: info.size,
364
+ modifiedAt: info.mtime,
365
+ }];
366
+ }
367
+ if (info.isDirectory()) {
368
+ return await this.list(relativePath, { recursive: true, ignore });
369
+ }
370
+ return [];
371
+ }
351
372
  async summarize(params = {}) {
352
373
  const maxFiles = positiveInt(params.maxFiles, 2000);
353
374
  const maxPreviews = positiveInt(params.maxPreviews, 20);
@@ -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;
@@ -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.runner ?? new HostLocalShellRunner({
103
- cwd,
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,
@@ -23,6 +23,7 @@ export declare class LocalWorkdirDriver {
23
23
  readonly accessMode: LocalWorkdirAccessMode;
24
24
  constructor(workdir: LocalWorkdir, options?: LocalWorkdirDriverOptions);
25
25
  dispatch(args: Record<string, unknown>): Promise<Record<string, unknown>>;
26
+ private dispatchUnsafe;
26
27
  requiresApproval(args: Record<string, unknown>): boolean;
27
28
  private dispatchApplyEdits;
28
29
  private dispatchWrite;