@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.
@@ -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
- return mutatingLocalWorkdirActions.has(workdirAction(args));
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; directory base for list/search/grep/summarize/context/snapshot."),
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.3";
2
- export declare const USER_AGENT = "@agent-api/sdk/1.2.3";
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.2.3";
1
+ export const VERSION = "1.3.1";
2
2
  export const USER_AGENT = `@agent-api/sdk/${VERSION}`;
@@ -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.list(params.path ?? ".", { recursive: true, ignore: params.ignore });
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);
@@ -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,