@deepagents/context 1.0.0 → 2.1.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/dist/index.js CHANGED
@@ -4697,7 +4697,7 @@ async function chat(agent2, options = {}) {
4697
4697
  if (isAborted) {
4698
4698
  normalizedMessage.parts = sanitizeAbortedParts(normalizedMessage.parts);
4699
4699
  }
4700
- const drained = sandbox.drainFileEvents?.() ?? [];
4700
+ const drained = sandbox.drainFileEvents();
4701
4701
  const fileEvents = isAborted ? [] : drained;
4702
4702
  const finalMetadata = await options.finalAssistantMetadata?.(normalizedMessage);
4703
4703
  const mergedMetadata = {
@@ -5454,15 +5454,15 @@ function dateReminder(options) {
5454
5454
  const now = /* @__PURE__ */ new Date();
5455
5455
  const currentDate = formatDateKey(now, tz);
5456
5456
  const currentDay = formatDayOfWeek(now, tz);
5457
- let diff = "";
5457
+ let diff2 = "";
5458
5458
  if (ctx.lastMessageAt !== void 0) {
5459
5459
  const prev = new Date(ctx.lastMessageAt);
5460
- diff = formatDiff([
5460
+ diff2 = formatDiff([
5461
5461
  diffLine("date", formatDateKey(prev, tz), currentDate),
5462
5462
  diffLine("day of week", formatDayOfWeek(prev, tz), currentDay)
5463
5463
  ]);
5464
5464
  }
5465
- return `${diff}Date: ${currentDate}
5465
+ return `${diff2}Date: ${currentDate}
5466
5466
  Day of Week: ${currentDay}`;
5467
5467
  },
5468
5468
  { when: dayChanged(options), asPart: false }
@@ -5474,14 +5474,14 @@ function timeReminder(options) {
5474
5474
  const tz = resolveTz(options, ctx);
5475
5475
  const now = /* @__PURE__ */ new Date();
5476
5476
  const currentTime = formatTime(now, tz);
5477
- let diff = "";
5477
+ let diff2 = "";
5478
5478
  if (ctx.lastMessageAt !== void 0) {
5479
5479
  const prev = new Date(ctx.lastMessageAt);
5480
- diff = formatDiff([
5480
+ diff2 = formatDiff([
5481
5481
  diffLine("hour", formatHour(prev, tz), formatHour(now, tz))
5482
5482
  ]);
5483
5483
  }
5484
- return `${diff}Time: ${currentTime}`;
5484
+ return `${diff2}Time: ${currentTime}`;
5485
5485
  },
5486
5486
  { when: hourChanged(options), asPart: false }
5487
5487
  );
@@ -5492,14 +5492,14 @@ function monthReminder(options) {
5492
5492
  const tz = resolveTz(options, ctx);
5493
5493
  const now = /* @__PURE__ */ new Date();
5494
5494
  const currentMonth = formatMonthName(now, tz);
5495
- let diff = "";
5495
+ let diff2 = "";
5496
5496
  if (ctx.lastMessageAt !== void 0) {
5497
5497
  const prev = new Date(ctx.lastMessageAt);
5498
- diff = formatDiff([
5498
+ diff2 = formatDiff([
5499
5499
  diffLine("month", formatMonthName(prev, tz), currentMonth)
5500
5500
  ]);
5501
5501
  }
5502
- return `${diff}Month: ${currentMonth}`;
5502
+ return `${diff2}Month: ${currentMonth}`;
5503
5503
  },
5504
5504
  { when: monthChanged(options), asPart: false }
5505
5505
  );
@@ -5510,14 +5510,14 @@ function yearReminder(options) {
5510
5510
  const tz = resolveTz(options, ctx);
5511
5511
  const now = /* @__PURE__ */ new Date();
5512
5512
  const currentYear = formatYear(now, tz);
5513
- let diff = "";
5513
+ let diff2 = "";
5514
5514
  if (ctx.lastMessageAt !== void 0) {
5515
5515
  const prev = new Date(ctx.lastMessageAt);
5516
- diff = formatDiff([
5516
+ diff2 = formatDiff([
5517
5517
  diffLine("year", formatYear(prev, tz), currentYear)
5518
5518
  ]);
5519
5519
  }
5520
- return `${diff}Year: ${currentYear}`;
5520
+ return `${diff2}Year: ${currentYear}`;
5521
5521
  },
5522
5522
  { when: yearChanged(options), asPart: false }
5523
5523
  );
@@ -5528,14 +5528,14 @@ function seasonReminder(options) {
5528
5528
  const tz = resolveTz(options, ctx);
5529
5529
  const now = /* @__PURE__ */ new Date();
5530
5530
  const currentSeason = getSeason(getMonthIndex(now, tz));
5531
- let diff = "";
5531
+ let diff2 = "";
5532
5532
  if (ctx.lastMessageAt !== void 0) {
5533
5533
  const prev = new Date(ctx.lastMessageAt);
5534
- diff = formatDiff([
5534
+ diff2 = formatDiff([
5535
5535
  diffLine("season", getSeason(getMonthIndex(prev, tz)), currentSeason)
5536
5536
  ]);
5537
5537
  }
5538
- return `${diff}Season: ${currentSeason}`;
5538
+ return `${diff2}Season: ${currentSeason}`;
5539
5539
  },
5540
5540
  { when: seasonChanged(options), asPart: false }
5541
5541
  );
@@ -5553,11 +5553,11 @@ function localeReminder() {
5553
5553
  const current = getLocaleFromMessage(ctx.currentMessage);
5554
5554
  if (!current) return "";
5555
5555
  const last = getLocaleFromMessage(ctx.lastMessage);
5556
- const diff = last ? formatDiff([
5556
+ const diff2 = last ? formatDiff([
5557
5557
  diffLine("language", last.language, current.language),
5558
5558
  diffLine("timezone", last.timeZone, current.timeZone)
5559
5559
  ]) : "";
5560
- return `${diff}Language: ${current.language}
5560
+ return `${diff2}Language: ${current.language}
5561
5561
  Timezone: ${current.timeZone}`;
5562
5562
  },
5563
5563
  { when: whenFn, asPart: false }
@@ -5829,6 +5829,25 @@ function render(tag, ...fragments) {
5829
5829
  return renderer.render([wrapped]);
5830
5830
  }
5831
5831
 
5832
+ // packages/context/src/lib/sandbox/abort.ts
5833
+ import { AsyncLocalStorage } from "node:async_hooks";
5834
+ var ambientAbortSignal = new AsyncLocalStorage();
5835
+ function runWithAbortSignal(signal, fn) {
5836
+ return ambientAbortSignal.run(signal, fn);
5837
+ }
5838
+ function withAbortSignal(sandbox) {
5839
+ return {
5840
+ ...sandbox,
5841
+ async executeCommand(command, options) {
5842
+ const signal = options?.signal ?? ambientAbortSignal.getStore();
5843
+ return sandbox.executeCommand(
5844
+ command,
5845
+ signal ? { ...options, signal } : options
5846
+ );
5847
+ }
5848
+ };
5849
+ }
5850
+
5832
5851
  // packages/context/src/lib/sandbox/agent-os-sandbox.ts
5833
5852
  import "bash-tool";
5834
5853
  var textDecoder = new TextDecoder();
@@ -5935,8 +5954,8 @@ var BashException = class extends Error {
5935
5954
  };
5936
5955
 
5937
5956
  // packages/context/src/lib/sandbox/bash-meta.ts
5938
- import { AsyncLocalStorage } from "node:async_hooks";
5939
- var store = new AsyncLocalStorage();
5957
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
5958
+ var store = new AsyncLocalStorage2();
5940
5959
  function runWithBashMeta(fn) {
5941
5960
  return store.run({ hidden: {} }, fn);
5942
5961
  }
@@ -5966,6 +5985,103 @@ import {
5966
5985
  } from "bash-tool";
5967
5986
  import z3 from "zod";
5968
5987
 
5988
+ // packages/context/src/lib/sandbox/file-events.ts
5989
+ var SnapshotFailedError = class extends Error {
5990
+ stderr;
5991
+ constructor(message2, stderr) {
5992
+ super(message2);
5993
+ this.name = "SnapshotFailedError";
5994
+ this.stderr = stderr;
5995
+ }
5996
+ };
5997
+ function shellQuote(value) {
5998
+ return `'${value.replace(/'/g, `'\\''`)}'`;
5999
+ }
6000
+ async function snapshot(execute, destination) {
6001
+ const probe = await execute(`[ -d ${shellQuote(destination)} ]`);
6002
+ if (probe.exitCode !== 0) return /* @__PURE__ */ new Map();
6003
+ const list = await execute(
6004
+ `find ${shellQuote(destination)} -type f -print0 | while IFS= read -r -d '' p; do sha256sum "$p"; done`
6005
+ );
6006
+ if (list.exitCode !== 0) {
6007
+ throw new SnapshotFailedError(
6008
+ `snapshot failed for ${destination}`,
6009
+ list.stderr
6010
+ );
6011
+ }
6012
+ const snap = /* @__PURE__ */ new Map();
6013
+ if (!list.stdout) return snap;
6014
+ for (const line of list.stdout.split("\n")) {
6015
+ if (line.length < 66) continue;
6016
+ snap.set(line.slice(66), line.slice(0, 64));
6017
+ }
6018
+ return snap;
6019
+ }
6020
+ function diff(before, after) {
6021
+ const events = [];
6022
+ const ts = Date.now();
6023
+ for (const [path5, hash] of after) {
6024
+ const prior = before.get(path5);
6025
+ if (prior === void 0) {
6026
+ events.push({ path: path5, op: "write", timestamp: ts });
6027
+ } else if (prior !== hash) {
6028
+ events.push({ path: path5, op: "modify", timestamp: ts });
6029
+ }
6030
+ }
6031
+ for (const path5 of before.keys()) {
6032
+ if (!after.has(path5)) {
6033
+ events.push({ path: path5, op: "delete", timestamp: ts });
6034
+ }
6035
+ }
6036
+ return events;
6037
+ }
6038
+ function observeSandboxFileEvents(sandbox, options) {
6039
+ const { destination } = options;
6040
+ if (!destination) {
6041
+ throw new Error("observeSandboxFileEvents: destination is required");
6042
+ }
6043
+ const innerExecute = sandbox.executeCommand.bind(sandbox);
6044
+ const innerReadFile = sandbox.readFile.bind(sandbox);
6045
+ const innerWriteFiles = sandbox.writeFiles.bind(sandbox);
6046
+ const buffer = [];
6047
+ const observe = async (fn) => {
6048
+ const before = await snapshot(innerExecute, destination);
6049
+ const takeAfter = async () => {
6050
+ const after = await snapshot(innerExecute, destination);
6051
+ buffer.push(...diff(before, after));
6052
+ };
6053
+ try {
6054
+ const result = await fn();
6055
+ await takeAfter();
6056
+ return result;
6057
+ } catch (err) {
6058
+ await takeAfter().catch(() => {
6059
+ });
6060
+ throw err;
6061
+ }
6062
+ };
6063
+ const decorated = {
6064
+ async executeCommand(command, options2) {
6065
+ return observe(() => innerExecute(command, options2));
6066
+ },
6067
+ async readFile(path5) {
6068
+ const content = await innerReadFile(path5);
6069
+ buffer.push({ path: path5, op: "read", timestamp: Date.now() });
6070
+ return content;
6071
+ },
6072
+ async writeFiles(files) {
6073
+ await observe(() => innerWriteFiles(files));
6074
+ },
6075
+ dispose: sandbox.dispose.bind(sandbox)
6076
+ };
6077
+ return {
6078
+ sandbox: decorated,
6079
+ drain() {
6080
+ return buffer.splice(0, buffer.length);
6081
+ }
6082
+ };
6083
+ }
6084
+
5969
6085
  // packages/context/src/lib/sandbox/upload-skills.ts
5970
6086
  import * as path3 from "node:path";
5971
6087
 
@@ -6111,22 +6227,38 @@ async function uploadSkills(sandbox, inputs) {
6111
6227
 
6112
6228
  // packages/context/src/lib/sandbox/bash-tool.ts
6113
6229
  var REASONING_INSTRUCTION = 'Every bash tool call must include a brief non-empty "reasoning" input explaining why the command is needed.';
6114
- async function createBashTool(options = {}) {
6115
- const { skills: skillInputs = [], extraInstructions, ...rest } = options;
6230
+ function withBashExceptionCatch(sandbox) {
6231
+ return {
6232
+ ...sandbox,
6233
+ async executeCommand(command, options) {
6234
+ try {
6235
+ return await sandbox.executeCommand(command, options);
6236
+ } catch (err) {
6237
+ if (err instanceof BashException) return err.format();
6238
+ throw err;
6239
+ }
6240
+ }
6241
+ };
6242
+ }
6243
+ async function createBashTool(options) {
6244
+ const {
6245
+ skills: skillInputs = [],
6246
+ extraInstructions,
6247
+ sandbox: backend,
6248
+ destination,
6249
+ ...rest
6250
+ } = options;
6251
+ const observer = observeSandboxFileEvents(backend, {
6252
+ destination: destination ?? "/workspace"
6253
+ });
6254
+ const sandbox = withAbortSignal(withBashExceptionCatch(observer.sandbox));
6116
6255
  const combinedInstructions = [extraInstructions, REASONING_INSTRUCTION].filter(Boolean).join("\n\n");
6117
6256
  const toolkit = await externalCreateBashTool({
6118
6257
  ...rest,
6258
+ sandbox,
6259
+ destination,
6119
6260
  extraInstructions: combinedInstructions
6120
6261
  });
6121
- const innerExecute = toolkit.sandbox.executeCommand.bind(toolkit.sandbox);
6122
- toolkit.sandbox.executeCommand = async (cmd) => {
6123
- try {
6124
- return await innerExecute(cmd);
6125
- } catch (err) {
6126
- if (err instanceof BashException) return err.format();
6127
- throw err;
6128
- }
6129
- };
6130
6262
  const upstreamBash = toolkit.bash;
6131
6263
  const originalExecute = upstreamBash.execute;
6132
6264
  const toolBuilder = tool2;
@@ -6141,197 +6273,43 @@ async function createBashTool(options = {}) {
6141
6273
  if (!originalExecute) {
6142
6274
  throw new Error("bash tool execution is not available");
6143
6275
  }
6144
- return runWithBashMeta(async () => {
6145
- let result;
6146
- try {
6147
- result = await originalExecute({ command }, execOptions);
6148
- } catch (err) {
6149
- if (err instanceof BashException) {
6150
- result = err.format();
6151
- } else {
6152
- throw err;
6153
- }
6154
- }
6155
- const state = readBashMeta();
6156
- if (!state) return result;
6157
- const hasHidden = Object.keys(state.hidden).length > 0;
6158
- const hasReminder = state.reminder !== void 0;
6159
- if (!hasHidden && !hasReminder) return result;
6160
- return {
6161
- ...result,
6162
- ...hasHidden ? { meta: state.hidden } : {},
6163
- ...hasReminder ? { reminder: state.reminder } : {}
6164
- };
6165
- });
6276
+ const { abortSignal } = execOptions;
6277
+ return runWithAbortSignal(
6278
+ abortSignal,
6279
+ () => runWithBashMeta(async () => {
6280
+ const result = await originalExecute({ command }, execOptions);
6281
+ const state = readBashMeta();
6282
+ if (!state) return result;
6283
+ const hasHidden = Object.keys(state.hidden).length > 0;
6284
+ const hasReminder = state.reminder !== void 0;
6285
+ if (!hasHidden && !hasReminder) return result;
6286
+ return {
6287
+ ...result,
6288
+ ...hasHidden ? { meta: state.hidden } : {},
6289
+ ...hasReminder ? { reminder: state.reminder } : {}
6290
+ };
6291
+ })
6292
+ );
6166
6293
  },
6167
6294
  toModelOutput: ({ output }) => {
6168
6295
  if (typeof output !== "object" || output === null) {
6169
6296
  return { type: "json", value: output };
6170
6297
  }
6171
- const record = output;
6172
- if (!("meta" in record)) {
6173
- return { type: "json", value: record };
6174
- }
6175
- const { meta: _meta, ...visible } = record;
6298
+ const { meta: _meta, ...visible } = output;
6176
6299
  return { type: "json", value: visible };
6177
6300
  }
6178
6301
  });
6179
- const skills2 = await uploadSkills(toolkit.sandbox, skillInputs);
6302
+ const skills2 = await uploadSkills(sandbox, skillInputs);
6180
6303
  return {
6181
6304
  ...toolkit,
6305
+ sandbox,
6182
6306
  bash,
6183
6307
  tools: { ...toolkit.tools, bash },
6184
- skills: skills2
6308
+ skills: skills2,
6309
+ drainFileEvents: () => observer.drain()
6185
6310
  };
6186
6311
  }
6187
6312
 
6188
- // packages/context/src/lib/sandbox/file-events.ts
6189
- var ObservedFs = class {
6190
- #base;
6191
- #events = [];
6192
- readdirWithFileTypes;
6193
- constructor(base) {
6194
- this.#base = base;
6195
- if (base.readdirWithFileTypes) {
6196
- this.readdirWithFileTypes = (path5) => base.readdirWithFileTypes(path5);
6197
- }
6198
- }
6199
- drain() {
6200
- const events = this.#events;
6201
- this.#events = [];
6202
- return events;
6203
- }
6204
- #record(op, path5) {
6205
- this.#events.push({ path: path5, op, timestamp: Date.now() });
6206
- }
6207
- async readFile(path5, options) {
6208
- const content = await this.#base.readFile(path5, options);
6209
- this.#record("read", path5);
6210
- return content;
6211
- }
6212
- async readFileBuffer(path5) {
6213
- const content = await this.#base.readFileBuffer(path5);
6214
- this.#record("read", path5);
6215
- return content;
6216
- }
6217
- async writeFile(path5, content, options) {
6218
- const existed = await this.#base.exists(path5);
6219
- await this.#base.writeFile(path5, content, options);
6220
- this.#record(existed ? "modify" : "write", path5);
6221
- }
6222
- async appendFile(path5, content, options) {
6223
- const existed = await this.#base.exists(path5);
6224
- await this.#base.appendFile(path5, content, options);
6225
- this.#record(existed ? "modify" : "write", path5);
6226
- }
6227
- async rm(path5, options) {
6228
- const toRecord = options?.recursive ? await this.#walk(path5, { includeRoot: true }) : await this.#base.exists(path5) ? [path5] : [];
6229
- await this.#base.rm(path5, options);
6230
- for (const p of toRecord) {
6231
- this.#record("delete", p);
6232
- }
6233
- }
6234
- async cp(src, dest, options) {
6235
- if (options?.recursive) {
6236
- const sources = await this.#walk(src, { includeRoot: false });
6237
- const destChecks = await Promise.all(
6238
- sources.map(async (srcFile) => {
6239
- const relative3 = srcFile.slice(src.length);
6240
- const destFile = this.#joinPath(dest, relative3);
6241
- return {
6242
- srcFile,
6243
- destFile,
6244
- existed: await this.#base.exists(destFile)
6245
- };
6246
- })
6247
- );
6248
- await this.#base.cp(src, dest, options);
6249
- for (const { srcFile, destFile, existed } of destChecks) {
6250
- this.#record("read", srcFile);
6251
- this.#record(existed ? "modify" : "write", destFile);
6252
- }
6253
- return;
6254
- }
6255
- const destExisted = await this.#base.exists(dest);
6256
- await this.#base.cp(src, dest, options);
6257
- this.#record("read", src);
6258
- this.#record(destExisted ? "modify" : "write", dest);
6259
- }
6260
- async #walk(root, { includeRoot }) {
6261
- if (!await this.#base.exists(root)) return [];
6262
- const stat = await this.#base.stat(root);
6263
- if (!stat.isDirectory) return [root];
6264
- const out = [];
6265
- const visit = async (dir) => {
6266
- const entries = await this.#base.readdir(dir);
6267
- for (const name of entries) {
6268
- const child = this.#joinPath(dir, name);
6269
- const childStat = await this.#base.stat(child);
6270
- if (childStat.isDirectory) {
6271
- await visit(child);
6272
- } else {
6273
- out.push(child);
6274
- }
6275
- }
6276
- };
6277
- await visit(root);
6278
- if (includeRoot) out.push(root);
6279
- return out;
6280
- }
6281
- #joinPath(a, b) {
6282
- if (!b) return a;
6283
- if (b.startsWith("/")) return `${a.replace(/\/$/, "")}${b}`;
6284
- return a.endsWith("/") ? `${a}${b}` : `${a}/${b}`;
6285
- }
6286
- async mv(src, dest) {
6287
- const destExisted = await this.#base.exists(dest);
6288
- await this.#base.mv(src, dest);
6289
- this.#record("delete", src);
6290
- this.#record(destExisted ? "modify" : "write", dest);
6291
- }
6292
- async symlink(target, linkPath) {
6293
- await this.#base.symlink(target, linkPath);
6294
- this.#record("write", linkPath);
6295
- }
6296
- async link(existingPath, newPath) {
6297
- await this.#base.link(existingPath, newPath);
6298
- this.#record("write", newPath);
6299
- }
6300
- mkdir(path5, options) {
6301
- return this.#base.mkdir(path5, options);
6302
- }
6303
- exists(path5) {
6304
- return this.#base.exists(path5);
6305
- }
6306
- stat(path5) {
6307
- return this.#base.stat(path5);
6308
- }
6309
- lstat(path5) {
6310
- return this.#base.lstat(path5);
6311
- }
6312
- readdir(path5) {
6313
- return this.#base.readdir(path5);
6314
- }
6315
- readlink(path5) {
6316
- return this.#base.readlink(path5);
6317
- }
6318
- realpath(path5) {
6319
- return this.#base.realpath(path5);
6320
- }
6321
- chmod(path5, mode) {
6322
- return this.#base.chmod(path5, mode);
6323
- }
6324
- utimes(path5, atime, mtime) {
6325
- return this.#base.utimes(path5, atime, mtime);
6326
- }
6327
- resolvePath(base, path5) {
6328
- return this.#base.resolvePath(base, path5);
6329
- }
6330
- getAllPaths() {
6331
- return this.#base.getAllPaths();
6332
- }
6333
- };
6334
-
6335
6313
  // packages/context/src/lib/sandbox/binary-bridges.ts
6336
6314
  import { existsSync as existsSync2 } from "fs";
6337
6315
  import { defineCommand } from "just-bash";
@@ -6431,8 +6409,10 @@ function resolveRealCwd(ctx) {
6431
6409
  // packages/context/src/lib/sandbox/docker-sandbox.ts
6432
6410
  import "bash-tool";
6433
6411
  import spawn3 from "nano-spawn";
6412
+ import { spawn as childSpawn } from "node:child_process";
6434
6413
  import { createHash } from "node:crypto";
6435
6414
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
6415
+ import { Readable } from "node:stream";
6436
6416
 
6437
6417
  // packages/context/src/lib/sandbox/docker-sandbox-errors.ts
6438
6418
  var DockerSandboxError = class extends Error {
@@ -6505,16 +6485,48 @@ var MissingRuntimeError = class extends DockerSandboxError {
6505
6485
  this.required = required;
6506
6486
  }
6507
6487
  };
6508
- var MountPathError = class extends DockerSandboxError {
6509
- hostPath;
6488
+ var VolumePathError = class extends DockerSandboxError {
6489
+ source;
6510
6490
  containerPath;
6511
- constructor(hostPath, containerPath) {
6491
+ reason;
6492
+ constructor(source, containerPath, reason) {
6512
6493
  super(
6513
- `Mount path does not exist on host: "${hostPath}" -> "${containerPath}"`
6494
+ `Invalid Docker volume path "${source}" -> "${containerPath}": ${reason}`
6514
6495
  );
6515
- this.name = "MountPathError";
6516
- this.hostPath = hostPath;
6496
+ this.name = "VolumePathError";
6497
+ this.source = source;
6517
6498
  this.containerPath = containerPath;
6499
+ this.reason = reason;
6500
+ }
6501
+ };
6502
+ var VolumeInspectError = class extends DockerSandboxError {
6503
+ volume;
6504
+ reason;
6505
+ constructor(volume, reason) {
6506
+ super(`Failed to inspect Docker volume "${volume}": ${reason}`);
6507
+ this.name = "VolumeInspectError";
6508
+ this.volume = volume;
6509
+ this.reason = reason;
6510
+ }
6511
+ };
6512
+ var VolumeCreateError = class extends DockerSandboxError {
6513
+ volume;
6514
+ reason;
6515
+ constructor(volume, reason) {
6516
+ super(`Failed to create Docker volume "${volume}": ${reason}`);
6517
+ this.name = "VolumeCreateError";
6518
+ this.volume = volume;
6519
+ this.reason = reason;
6520
+ }
6521
+ };
6522
+ var VolumeRemoveError = class extends DockerSandboxError {
6523
+ volume;
6524
+ reason;
6525
+ constructor(volume, reason) {
6526
+ super(`Failed to remove Docker volume "${volume}": ${reason}`);
6527
+ this.name = "VolumeRemoveError";
6528
+ this.volume = volume;
6529
+ this.reason = reason;
6518
6530
  }
6519
6531
  };
6520
6532
  var DockerfileBuildError = class extends DockerSandboxError {
@@ -6547,7 +6559,7 @@ function isDebianBased(image) {
6547
6559
  const debianPatterns = ["debian", "ubuntu", "node", "python"];
6548
6560
  return debianPatterns.some((pattern) => lower.includes(pattern));
6549
6561
  }
6550
- function shellQuote(s) {
6562
+ function shellQuote2(s) {
6551
6563
  return `'${s.replace(/'/g, `'\\''`)}'`;
6552
6564
  }
6553
6565
  function createInstallerContext(containerId, image) {
@@ -6595,7 +6607,7 @@ function createInstallerContext(containerId, image) {
6595
6607
  };
6596
6608
  const installPackages = async (packages) => {
6597
6609
  if (packages.length === 0) return;
6598
- const quoted = packages.map(shellQuote).join(" ");
6610
+ const quoted = packages.map(shellQuote2).join(" ");
6599
6611
  let cmd;
6600
6612
  if (packageManager === "apt-get") {
6601
6613
  cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
@@ -6617,7 +6629,7 @@ function createInstallerContext(containerId, image) {
6617
6629
  const ensureTool = async (checkName, installName) => {
6618
6630
  const cacheKey = installName ?? checkName;
6619
6631
  if (ensuredTools.has(cacheKey)) return;
6620
- const check = await exec(`which ${shellQuote(checkName)}`);
6632
+ const check = await exec(`which ${shellQuote2(checkName)}`);
6621
6633
  if (check.exitCode === 0) {
6622
6634
  ensuredTools.add(cacheKey);
6623
6635
  return;
@@ -6643,44 +6655,216 @@ function isDockerfileOptions(opts) {
6643
6655
  function isComposeOptions(opts) {
6644
6656
  return "compose" in opts;
6645
6657
  }
6658
+ var CONTAINER_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
6646
6659
  var DockerSandboxStrategy = class {
6647
6660
  context;
6648
- mounts;
6661
+ volumes;
6649
6662
  resources;
6650
6663
  env;
6664
+ name;
6665
+ createdVolumes = /* @__PURE__ */ new Set();
6651
6666
  constructor(args = {}) {
6652
- const { mounts = [], resources = {}, env = {} } = args;
6667
+ const { volumes = [], resources = {}, env = {}, name } = args;
6653
6668
  for (const key of Object.keys(env)) {
6654
- if (key.length === 0 || key.includes("=")) {
6655
- throw new DockerSandboxError(
6656
- `Invalid environment variable key: "${key}"`
6657
- );
6658
- }
6669
+ validateEnvKey(key);
6659
6670
  }
6660
- this.mounts = mounts;
6671
+ if (name !== void 0 && !CONTAINER_NAME_PATTERN.test(name)) {
6672
+ throw new DockerSandboxError(
6673
+ `Invalid container name: "${name}". Use only letters, numbers, underscore, period, or hyphen. The "sandbox-" prefix is added automatically.`
6674
+ );
6675
+ }
6676
+ this.volumes = volumes;
6661
6677
  this.resources = resources;
6662
6678
  this.env = env;
6679
+ this.name = name;
6663
6680
  }
6664
6681
  async create() {
6665
- this.validateMounts();
6666
6682
  const image = await this.getImage();
6667
- const containerId = await this.startContainer(image);
6668
- this.context = { containerId, image };
6683
+ let acquired;
6669
6684
  try {
6670
- await this.configure();
6685
+ acquired = await this.acquireContainer(image);
6686
+ this.context = { containerId: acquired.containerId, image };
6687
+ if (!acquired.attached) {
6688
+ await this.configure();
6689
+ }
6671
6690
  } catch (error) {
6672
- await this.stopContainer(containerId);
6691
+ if (acquired && !acquired.attached) {
6692
+ await this.stopContainer(acquired.containerId);
6693
+ }
6694
+ await this.cleanupCreatedVolumesAfterFailure(error);
6673
6695
  throw error;
6674
6696
  }
6675
6697
  return this.createSandboxMethods();
6676
6698
  }
6677
- validateMounts() {
6678
- for (const mount of this.mounts) {
6679
- if (!existsSync3(mount.hostPath)) {
6680
- throw new MountPathError(mount.hostPath, mount.containerPath);
6699
+ namedContainerId() {
6700
+ return `sandbox-${this.name}`;
6701
+ }
6702
+ defaultContainerId() {
6703
+ return `sandbox-${crypto.randomUUID().slice(0, 8)}`;
6704
+ }
6705
+ async acquireContainer(image) {
6706
+ if (!this.name) {
6707
+ const containerId2 = this.defaultContainerId();
6708
+ await this.prepareVolumes();
6709
+ await this.startContainer(image, containerId2);
6710
+ return { containerId: containerId2, attached: false };
6711
+ }
6712
+ const containerId = this.namedContainerId();
6713
+ const probe = await this.inspectContainer(containerId);
6714
+ if (probe === "running") {
6715
+ return { containerId, attached: true };
6716
+ }
6717
+ if (probe === "stopped") {
6718
+ await this.startStoppedContainer(containerId, image);
6719
+ return { containerId, attached: true };
6720
+ }
6721
+ await this.prepareVolumes();
6722
+ try {
6723
+ await this.startContainer(image, containerId);
6724
+ return { containerId, attached: false };
6725
+ } catch (error) {
6726
+ if (error instanceof ContainerCreationError && this.isNameConflictError(error.message)) {
6727
+ await this.cleanupCreatedVolumes();
6728
+ const raced = await this.inspectContainer(containerId);
6729
+ if (raced === "running") {
6730
+ return { containerId, attached: true };
6731
+ }
6732
+ if (raced === "stopped") {
6733
+ await this.startStoppedContainer(containerId, image);
6734
+ return { containerId, attached: true };
6735
+ }
6736
+ }
6737
+ throw error;
6738
+ }
6739
+ }
6740
+ async startStoppedContainer(containerId, image) {
6741
+ try {
6742
+ await spawn3("docker", ["start", containerId]);
6743
+ } catch (error) {
6744
+ const message2 = this.getDockerErrorMessage(error);
6745
+ if (this.isDockerUnavailableError(message2)) {
6746
+ throw new DockerNotAvailableError();
6747
+ }
6748
+ throw new ContainerCreationError(message2, image, error);
6749
+ }
6750
+ }
6751
+ async inspectContainer(containerId) {
6752
+ try {
6753
+ const result = await spawn3("docker", [
6754
+ "container",
6755
+ "inspect",
6756
+ "--format",
6757
+ "{{.State.Status}}",
6758
+ containerId
6759
+ ]);
6760
+ const status = result.stdout.trim();
6761
+ return status === "running" ? "running" : "stopped";
6762
+ } catch (error) {
6763
+ const message2 = this.getDockerErrorMessage(error);
6764
+ if (this.isDockerUnavailableError(message2)) {
6765
+ throw new DockerNotAvailableError();
6766
+ }
6767
+ if (this.isMissingContainerError(message2)) {
6768
+ return "absent";
6769
+ }
6770
+ throw new DockerSandboxError(
6771
+ `Failed to inspect container "${containerId}": ${message2}`
6772
+ );
6773
+ }
6774
+ }
6775
+ isMissingContainerError(message2) {
6776
+ return message2.toLowerCase().includes("no such container");
6777
+ }
6778
+ isNameConflictError(message2) {
6779
+ return message2.toLowerCase().includes("is already in use by container");
6780
+ }
6781
+ async prepareVolumes() {
6782
+ this.validateVolumes();
6783
+ for (const volume of this.volumes) {
6784
+ if (volume.type !== "volume") {
6785
+ continue;
6786
+ }
6787
+ const lifecycle = volume.lifecycle ?? "external";
6788
+ if (lifecycle === "external") {
6789
+ await this.inspectVolume(volume.name);
6790
+ continue;
6791
+ }
6792
+ const exists = await this.volumeExists(volume.name);
6793
+ if (exists) {
6794
+ throw new VolumeCreateError(
6795
+ volume.name,
6796
+ "managed volume already exists"
6797
+ );
6798
+ }
6799
+ await this.createVolume(volume);
6800
+ if (volume.removeOnDispose !== false) {
6801
+ this.createdVolumes.add(volume.name);
6802
+ }
6803
+ }
6804
+ }
6805
+ validateVolumes() {
6806
+ const containerPaths = /* @__PURE__ */ new Set();
6807
+ for (const volume of this.volumes) {
6808
+ const source = volume.type === "bind" ? volume.hostPath : volume.name;
6809
+ if (!volume.containerPath.startsWith("/")) {
6810
+ throw new VolumePathError(
6811
+ source,
6812
+ volume.containerPath,
6813
+ "containerPath must be absolute"
6814
+ );
6815
+ }
6816
+ this.validateMountValue("containerPath", volume.containerPath, volume);
6817
+ if (containerPaths.has(volume.containerPath)) {
6818
+ throw new VolumePathError(
6819
+ source,
6820
+ volume.containerPath,
6821
+ "containerPath must be unique"
6822
+ );
6823
+ }
6824
+ containerPaths.add(volume.containerPath);
6825
+ if (volume.type === "bind") {
6826
+ this.validateMountValue("hostPath", volume.hostPath, volume);
6827
+ if (!existsSync3(volume.hostPath)) {
6828
+ throw new VolumePathError(
6829
+ volume.hostPath,
6830
+ volume.containerPath,
6831
+ "hostPath does not exist on host"
6832
+ );
6833
+ }
6834
+ continue;
6835
+ }
6836
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(volume.name)) {
6837
+ throw new VolumePathError(
6838
+ volume.name,
6839
+ volume.containerPath,
6840
+ "volume name must start with an alphanumeric character and contain only letters, numbers, underscore, period, or hyphen"
6841
+ );
6842
+ }
6843
+ if (volume.subPath) {
6844
+ this.validateMountValue("subPath", volume.subPath, volume);
6845
+ }
6846
+ if ((volume.lifecycle ?? "external") === "external") {
6847
+ if (volume.driver || volume.driverOptions) {
6848
+ throw new VolumePathError(
6849
+ volume.name,
6850
+ volume.containerPath,
6851
+ 'driver and driverOptions require lifecycle "managed"'
6852
+ );
6853
+ }
6681
6854
  }
6682
6855
  }
6683
6856
  }
6857
+ validateMountValue(field, value, volume) {
6858
+ if (!value.includes(",")) {
6859
+ return;
6860
+ }
6861
+ const source = volume.type === "bind" ? volume.hostPath : volume.name;
6862
+ throw new VolumePathError(
6863
+ source,
6864
+ volume.containerPath,
6865
+ `${field} must not contain commas`
6866
+ );
6867
+ }
6684
6868
  buildDockerArgs(image, containerId) {
6685
6869
  const { memory = "1g", cpus = 2 } = this.resources;
6686
6870
  const args = [
@@ -6697,15 +6881,106 @@ var DockerSandboxStrategy = class {
6697
6881
  for (const [key, value] of Object.entries(this.env)) {
6698
6882
  args.push("-e", `${key}=${value}`);
6699
6883
  }
6700
- for (const mount of this.mounts) {
6701
- const mode = mount.readOnly !== false ? "ro" : "rw";
6702
- args.push("-v", `${mount.hostPath}:${mount.containerPath}:${mode}`);
6884
+ for (const volume of this.volumes) {
6885
+ args.push("--mount", this.buildVolumeMountArg(volume));
6703
6886
  }
6704
6887
  args.push(image, "tail", "-f", "/dev/null");
6705
6888
  return args;
6706
6889
  }
6707
- async startContainer(image) {
6708
- const containerId = `sandbox-${crypto.randomUUID().slice(0, 8)}`;
6890
+ buildVolumeMountArg(volume) {
6891
+ const readOnly = volume.readOnly !== false;
6892
+ const parts = volume.type === "bind" ? ["type=bind", `src=${volume.hostPath}`, `dst=${volume.containerPath}`] : [
6893
+ "type=volume",
6894
+ `src=${volume.name}`,
6895
+ `dst=${volume.containerPath}`,
6896
+ ...volume.subPath ? [`volume-subpath=${volume.subPath}`] : [],
6897
+ ...volume.noCopy ? ["volume-nocopy"] : []
6898
+ ];
6899
+ if (readOnly) {
6900
+ parts.push("readonly");
6901
+ }
6902
+ return parts.join(",");
6903
+ }
6904
+ async inspectVolume(name) {
6905
+ try {
6906
+ await spawn3("docker", ["volume", "inspect", name]);
6907
+ } catch (error) {
6908
+ const reason = this.getDockerErrorMessage(error);
6909
+ if (this.isDockerUnavailableError(reason)) {
6910
+ throw new DockerNotAvailableError();
6911
+ }
6912
+ throw new VolumeInspectError(name, reason);
6913
+ }
6914
+ }
6915
+ async volumeExists(name) {
6916
+ try {
6917
+ await this.inspectVolume(name);
6918
+ return true;
6919
+ } catch (error) {
6920
+ if (error instanceof VolumeInspectError) {
6921
+ if (this.isMissingVolumeInspectError(error.reason)) {
6922
+ return false;
6923
+ }
6924
+ throw error;
6925
+ }
6926
+ throw error;
6927
+ }
6928
+ }
6929
+ async createVolume(volume) {
6930
+ const args = ["volume", "create"];
6931
+ if (volume.driver) {
6932
+ args.push("--driver", volume.driver);
6933
+ }
6934
+ for (const [key, value] of Object.entries(volume.driverOptions ?? {})) {
6935
+ args.push("--opt", `${key}=${value}`);
6936
+ }
6937
+ args.push(volume.name);
6938
+ try {
6939
+ await spawn3("docker", args);
6940
+ } catch (error) {
6941
+ const reason = this.getDockerErrorMessage(error);
6942
+ if (this.isDockerUnavailableError(reason)) {
6943
+ throw new DockerNotAvailableError();
6944
+ }
6945
+ throw new VolumeCreateError(volume.name, reason);
6946
+ }
6947
+ }
6948
+ async cleanupCreatedVolumes() {
6949
+ const volumes = [...this.createdVolumes].reverse();
6950
+ for (const volume of volumes) {
6951
+ try {
6952
+ await spawn3("docker", ["volume", "rm", volume]);
6953
+ this.createdVolumes.delete(volume);
6954
+ } catch (error) {
6955
+ const reason = this.getDockerErrorMessage(error);
6956
+ if (this.isDockerUnavailableError(reason)) {
6957
+ throw new DockerNotAvailableError();
6958
+ }
6959
+ throw new VolumeRemoveError(volume, reason);
6960
+ }
6961
+ }
6962
+ }
6963
+ async cleanupCreatedVolumesAfterFailure(originalError) {
6964
+ try {
6965
+ await this.cleanupCreatedVolumes();
6966
+ } catch (cleanupError) {
6967
+ if (originalError instanceof Error) {
6968
+ const original = originalError;
6969
+ original.suppressed = [...original.suppressed ?? [], cleanupError];
6970
+ }
6971
+ }
6972
+ }
6973
+ getDockerErrorMessage(error) {
6974
+ const err = error;
6975
+ return err.stderr || err.stdout || err.message || String(error);
6976
+ }
6977
+ isDockerUnavailableError(message2) {
6978
+ return message2.includes("Cannot connect") || message2.includes("docker daemon");
6979
+ }
6980
+ isMissingVolumeInspectError(message2) {
6981
+ return message2.toLowerCase().includes("no such volume");
6982
+ }
6983
+ async startContainer(image, containerId) {
6709
6984
  const args = this.buildDockerArgs(image, containerId);
6710
6985
  try {
6711
6986
  await spawn3("docker", args);
@@ -6714,9 +6989,12 @@ var DockerSandboxStrategy = class {
6714
6989
  if (err.message?.includes("Cannot connect") || err.message?.includes("docker daemon") || err.stderr?.includes("Cannot connect")) {
6715
6990
  throw new DockerNotAvailableError();
6716
6991
  }
6717
- throw new ContainerCreationError(err.message || String(err), image, err);
6992
+ throw new ContainerCreationError(
6993
+ this.getDockerErrorMessage(err),
6994
+ image,
6995
+ err
6996
+ );
6718
6997
  }
6719
- return containerId;
6720
6998
  }
6721
6999
  async stopContainer(containerId) {
6722
7000
  try {
@@ -6724,15 +7002,13 @@ var DockerSandboxStrategy = class {
6724
7002
  } catch {
6725
7003
  }
6726
7004
  }
6727
- async exec(command) {
7005
+ async exec(command, options) {
6728
7006
  try {
6729
- const result = await spawn3("docker", [
6730
- "exec",
6731
- this.context.containerId,
6732
- "sh",
6733
- "-c",
6734
- command
6735
- ]);
7007
+ const result = await spawn3(
7008
+ "docker",
7009
+ ["exec", this.context.containerId, "sh", "-c", command],
7010
+ { signal: options?.signal }
7011
+ );
6736
7012
  return {
6737
7013
  stdout: result.stdout,
6738
7014
  stderr: result.stderr,
@@ -6747,12 +7023,22 @@ var DockerSandboxStrategy = class {
6747
7023
  };
6748
7024
  }
6749
7025
  }
7026
+ spawnProcess(command, options) {
7027
+ const child = childSpawn("docker", [
7028
+ "exec",
7029
+ ...buildDockerExecFlags(options),
7030
+ this.context.containerId,
7031
+ "sh",
7032
+ "-c",
7033
+ command
7034
+ ]);
7035
+ return toSandboxProcess(child, options?.signal);
7036
+ }
6750
7037
  createSandboxMethods() {
6751
7038
  const { containerId } = this.context;
6752
7039
  const sandbox = {
6753
- executeCommand: async (command) => {
6754
- return this.exec(command);
6755
- },
7040
+ executeCommand: async (command, options) => this.exec(command, options),
7041
+ spawn: (command, options) => this.spawnProcess(command, options),
6756
7042
  readFile: async (path5) => {
6757
7043
  const result = await sandbox.executeCommand(`base64 "${path5}"`);
6758
7044
  if (result.exitCode !== 0) {
@@ -6779,16 +7065,74 @@ var DockerSandboxStrategy = class {
6779
7065
  },
6780
7066
  dispose: async () => {
6781
7067
  await this.stopContainer(containerId);
7068
+ await this.cleanupCreatedVolumes();
6782
7069
  }
6783
7070
  };
6784
7071
  return sandbox;
6785
7072
  }
6786
7073
  };
7074
+ function validateEnvKey(key) {
7075
+ if (key.length === 0 || key.includes("=")) {
7076
+ throw new DockerSandboxError(`Invalid environment variable key: "${key}"`);
7077
+ }
7078
+ }
7079
+ function buildDockerExecFlags(options) {
7080
+ const flags = [];
7081
+ if (options?.cwd) {
7082
+ flags.push("-w", options.cwd);
7083
+ }
7084
+ if (options?.env) {
7085
+ for (const [key, value] of Object.entries(options.env)) {
7086
+ validateEnvKey(key);
7087
+ flags.push("-e", `${key}=${value}`);
7088
+ }
7089
+ }
7090
+ return flags;
7091
+ }
7092
+ function toSandboxProcess(child, abortSignal) {
7093
+ if (!child.stdout || !child.stderr) {
7094
+ child.kill("SIGKILL");
7095
+ throw new DockerSandboxError("docker exec child missing stdio streams");
7096
+ }
7097
+ const onAbort = abortSignal ? () => child.kill("SIGKILL") : void 0;
7098
+ if (abortSignal && onAbort) {
7099
+ if (abortSignal.aborted) onAbort();
7100
+ else abortSignal.addEventListener("abort", onAbort, { once: true });
7101
+ }
7102
+ return {
7103
+ stdout: Readable.toWeb(child.stdout),
7104
+ stderr: Readable.toWeb(child.stderr),
7105
+ exit: new Promise((resolve4, reject) => {
7106
+ const settle = () => {
7107
+ child.removeListener("exit", onExitEvent);
7108
+ child.removeListener("error", onError);
7109
+ if (abortSignal && onAbort) {
7110
+ abortSignal.removeEventListener("abort", onAbort);
7111
+ }
7112
+ };
7113
+ const onError = (err) => {
7114
+ settle();
7115
+ reject(err);
7116
+ };
7117
+ const onExitEvent = (code, exitSignal) => {
7118
+ settle();
7119
+ resolve4({ code, signal: exitSignal, success: code === 0 });
7120
+ };
7121
+ child.on("exit", onExitEvent);
7122
+ child.on("error", onError);
7123
+ })
7124
+ };
7125
+ }
6787
7126
  var RuntimeStrategy = class extends DockerSandboxStrategy {
6788
7127
  image;
6789
7128
  installers;
6790
7129
  constructor(args = {}) {
6791
- super({ mounts: args.mounts, resources: args.resources, env: args.env });
7130
+ super({
7131
+ volumes: args.volumes,
7132
+ resources: args.resources,
7133
+ env: args.env,
7134
+ name: args.name
7135
+ });
6792
7136
  this.image = args.image ?? "alpine:latest";
6793
7137
  this.installers = args.installers ?? [];
6794
7138
  }
@@ -6807,7 +7151,12 @@ var DockerfileStrategy = class extends DockerSandboxStrategy {
6807
7151
  dockerfile;
6808
7152
  dockerContext;
6809
7153
  constructor(args) {
6810
- super({ mounts: args.mounts, resources: args.resources, env: args.env });
7154
+ super({
7155
+ volumes: args.volumes,
7156
+ resources: args.resources,
7157
+ env: args.env,
7158
+ name: args.name
7159
+ });
6811
7160
  this.dockerfile = args.dockerfile;
6812
7161
  this.dockerContext = args.context ?? ".";
6813
7162
  this.imageTag = this.computeImageTag();
@@ -6876,7 +7225,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
6876
7225
  async getImage() {
6877
7226
  return "";
6878
7227
  }
6879
- async startContainer(_image) {
7228
+ async startContainer(_image, _containerId) {
6880
7229
  try {
6881
7230
  await spawn3("docker", [
6882
7231
  "compose",
@@ -6894,25 +7243,31 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
6894
7243
  }
6895
7244
  throw new ComposeStartError(this.composeFile, err.stderr || err.message);
6896
7245
  }
7246
+ }
7247
+ defaultContainerId() {
6897
7248
  return this.projectName;
6898
7249
  }
6899
7250
  async configure() {
6900
7251
  }
6901
- async exec(command) {
7252
+ async exec(command, options) {
6902
7253
  try {
6903
- const result = await spawn3("docker", [
6904
- "compose",
6905
- "-f",
6906
- this.composeFile,
6907
- "-p",
6908
- this.projectName,
6909
- "exec",
6910
- "-T",
6911
- this.service,
6912
- "sh",
6913
- "-c",
6914
- command
6915
- ]);
7254
+ const result = await spawn3(
7255
+ "docker",
7256
+ [
7257
+ "compose",
7258
+ "-f",
7259
+ this.composeFile,
7260
+ "-p",
7261
+ this.projectName,
7262
+ "exec",
7263
+ "-T",
7264
+ this.service,
7265
+ "sh",
7266
+ "-c",
7267
+ command
7268
+ ],
7269
+ { signal: options?.signal }
7270
+ );
6916
7271
  return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
6917
7272
  } catch (error) {
6918
7273
  const err = error;
@@ -6923,6 +7278,23 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
6923
7278
  };
6924
7279
  }
6925
7280
  }
7281
+ spawnProcess(command, options) {
7282
+ const child = childSpawn("docker", [
7283
+ "compose",
7284
+ "-f",
7285
+ this.composeFile,
7286
+ "-p",
7287
+ this.projectName,
7288
+ "exec",
7289
+ "-T",
7290
+ ...buildDockerExecFlags(options),
7291
+ this.service,
7292
+ "sh",
7293
+ "-c",
7294
+ command
7295
+ ]);
7296
+ return toSandboxProcess(child, options?.signal);
7297
+ }
6926
7298
  async stopContainer(_containerId) {
6927
7299
  try {
6928
7300
  await spawn3("docker", [
@@ -6949,17 +7321,19 @@ async function createDockerSandbox(options = {}) {
6949
7321
  strategy = new DockerfileStrategy({
6950
7322
  dockerfile: options.dockerfile,
6951
7323
  context: options.context,
6952
- mounts: options.mounts,
7324
+ volumes: options.volumes,
6953
7325
  resources: options.resources,
6954
- env: options.env
7326
+ env: options.env,
7327
+ name: options.name
6955
7328
  });
6956
7329
  } else {
6957
7330
  strategy = new RuntimeStrategy({
6958
7331
  image: options.image,
6959
7332
  installers: options.installers,
6960
- mounts: options.mounts,
7333
+ volumes: options.volumes,
6961
7334
  resources: options.resources,
6962
- env: options.env
7335
+ env: options.env,
7336
+ name: options.name
6963
7337
  });
6964
7338
  }
6965
7339
  return strategy.create();
@@ -6987,40 +7361,43 @@ async function createContainerTool(options = {}) {
6987
7361
  const {
6988
7362
  dockerfile,
6989
7363
  context,
6990
- mounts,
7364
+ volumes,
6991
7365
  resources,
6992
7366
  env,
7367
+ name,
6993
7368
  skills: skills2 = [],
6994
7369
  ...rest
6995
7370
  } = options;
6996
- sandboxOptions = { dockerfile, context, mounts, resources, env };
7371
+ sandboxOptions = { dockerfile, context, volumes, resources, env, name };
6997
7372
  bashOptions = rest;
6998
7373
  skillInputs = skills2;
6999
7374
  } else {
7000
7375
  const {
7001
7376
  image,
7002
7377
  installers,
7003
- mounts,
7378
+ volumes,
7004
7379
  resources,
7005
7380
  env,
7381
+ name,
7006
7382
  skills: skills2 = [],
7007
7383
  ...rest
7008
7384
  } = options;
7009
- sandboxOptions = { image, installers, mounts, resources, env };
7385
+ sandboxOptions = { image, installers, volumes, resources, env, name };
7010
7386
  bashOptions = rest;
7011
7387
  skillInputs = skills2;
7012
7388
  }
7013
- const sandbox = await createDockerSandbox(sandboxOptions);
7389
+ const backend = await createDockerSandbox(sandboxOptions);
7014
7390
  const toolkit = await createBashTool({
7015
7391
  ...bashOptions,
7016
- sandbox,
7392
+ sandbox: backend,
7017
7393
  skills: skillInputs
7018
7394
  });
7019
7395
  return {
7020
7396
  bash: toolkit.bash,
7021
7397
  tools: toolkit.tools,
7022
- sandbox,
7023
- skills: toolkit.skills
7398
+ sandbox: toolkit.sandbox,
7399
+ skills: toolkit.skills,
7400
+ drainFileEvents: toolkit.drainFileEvents
7024
7401
  };
7025
7402
  }
7026
7403
 
@@ -7096,9 +7473,9 @@ async function downloadAndInstall(ctx, name, url, binaryPath, source = "url") {
7096
7473
  function buildTarGzInstallCmd(name, url, binaryPathInArchive) {
7097
7474
  return `
7098
7475
  set -e
7099
- NAME=${shellQuote(name)}
7100
- URL=${shellQuote(url)}
7101
- BIN_IN_ARCHIVE=${shellQuote(binaryPathInArchive)}
7476
+ NAME=${shellQuote2(name)}
7477
+ URL=${shellQuote2(url)}
7478
+ BIN_IN_ARCHIVE=${shellQuote2(binaryPathInArchive)}
7102
7479
  TMPDIR=$(mktemp -d)
7103
7480
  cd "$TMPDIR"
7104
7481
  curl -fsSL "$URL" -o archive.tar.gz
@@ -7117,8 +7494,8 @@ function buildTarGzInstallCmd(name, url, binaryPathInArchive) {
7117
7494
  }
7118
7495
  function buildRawInstallCmd(name, url) {
7119
7496
  return `
7120
- NAME=${shellQuote(name)}
7121
- URL=${shellQuote(url)}
7497
+ NAME=${shellQuote2(name)}
7498
+ URL=${shellQuote2(url)}
7122
7499
  curl -fsSL "$URL" -o "/usr/local/bin/$NAME"
7123
7500
  chmod +x "/usr/local/bin/$NAME"
7124
7501
  `;
@@ -7330,8 +7707,11 @@ async function createVirtualSandbox(options) {
7330
7707
  customCommands: options.customCommands
7331
7708
  });
7332
7709
  return {
7333
- async executeCommand(command) {
7334
- const result = await bash.exec(command);
7710
+ async executeCommand(command, options2) {
7711
+ const result = await bash.exec(
7712
+ command,
7713
+ options2?.signal ? { signal: options2.signal } : void 0
7714
+ );
7335
7715
  return {
7336
7716
  stdout: result.stdout,
7337
7717
  stderr: result.stderr,
@@ -7348,6 +7728,8 @@ async function createVirtualSandbox(options) {
7348
7728
  typeof file.content === "string" ? file.content : Buffer.from(file.content).toString("utf-8")
7349
7729
  );
7350
7730
  }
7731
+ },
7732
+ async dispose() {
7351
7733
  }
7352
7734
  };
7353
7735
  }
@@ -10524,9 +10906,7 @@ export {
10524
10906
  MarkdownRenderer,
10525
10907
  MissingRuntimeError,
10526
10908
  ModelsRegistry,
10527
- MountPathError,
10528
10909
  NpmInstaller,
10529
- ObservedFs,
10530
10910
  PackageInstallError,
10531
10911
  PackageInstaller,
10532
10912
  PipInstaller,
@@ -10536,6 +10916,7 @@ export {
10536
10916
  PostgresStreamStore,
10537
10917
  PromiseResolver,
10538
10918
  RuntimeStrategy,
10919
+ SnapshotFailedError,
10539
10920
  SqlServerContextStore,
10540
10921
  SqliteContextStore,
10541
10922
  SqliteStreamStore,
@@ -10545,6 +10926,10 @@ export {
10545
10926
  TomlRenderer,
10546
10927
  ToonRenderer,
10547
10928
  UrlBinaryInstaller,
10929
+ VolumeCreateError,
10930
+ VolumeInspectError,
10931
+ VolumePathError,
10932
+ VolumeRemoveError,
10548
10933
  XmlRenderer,
10549
10934
  addUsage,
10550
10935
  advisorPreamble,
@@ -10639,6 +11024,7 @@ export {
10639
11024
  not,
10640
11025
  npm,
10641
11026
  nullUsage,
11027
+ observeSandboxFileEvents,
10642
11028
  once,
10643
11029
  or,
10644
11030
  parseFrontmatter,
@@ -10663,11 +11049,12 @@ export {
10663
11049
  resolveTz,
10664
11050
  role,
10665
11051
  runGuardrailChain,
11052
+ runWithAbortSignal,
10666
11053
  runWithBashMeta,
10667
11054
  seasonChanged,
10668
11055
  seasonReminder,
10669
11056
  selfCritique,
10670
- shellQuote,
11057
+ shellQuote2 as shellQuote,
10671
11058
  skills,
10672
11059
  skillsReminder,
10673
11060
  socraticPrompting,
@@ -10696,6 +11083,7 @@ export {
10696
11083
  user,
10697
11084
  visualizeGraph,
10698
11085
  weekChanged,
11086
+ withAbortSignal,
10699
11087
  withinLastN,
10700
11088
  workflow,
10701
11089
  yearChanged,