@agent-api/sdk 1.2.3 → 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 CHANGED
@@ -1,5 +1,19 @@
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
+
3
17
  ## 1.2.3
4
18
 
5
19
  ### 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
 
@@ -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,
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.3";
2
- export declare const USER_AGENT = "@agent-api/sdk/1.2.3";
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.2.3";
1
+ export const VERSION = "1.3.0";
2
2
  export const USER_AGENT = `@agent-api/sdk/${VERSION}`;
@@ -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.runner ?? new HostLocalShellRunner({
113
- cwd,
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; } });
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.USER_AGENT = exports.VERSION = void 0;
4
- exports.VERSION = "1.2.3";
4
+ exports.VERSION = "1.3.0";
5
5
  exports.USER_AGENT = `@agent-api/sdk/${exports.VERSION}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-api/sdk",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Production JavaScript SDK for the Managed Agent API",
5
5
  "license": "MIT",
6
6
  "repository": {