@deepagents/context 4.0.0 → 4.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.
Files changed (35) hide show
  1. package/README.md +19 -14
  2. package/dist/browser.js +18 -8
  3. package/dist/browser.js.map +2 -2
  4. package/dist/index.js +2115 -1565
  5. package/dist/index.js.map +4 -4
  6. package/dist/lib/agent.d.ts.map +1 -1
  7. package/dist/lib/engine.d.ts +3 -3
  8. package/dist/lib/engine.d.ts.map +1 -1
  9. package/dist/lib/fragments/message/user.d.ts +16 -14
  10. package/dist/lib/fragments/message/user.d.ts.map +1 -1
  11. package/dist/lib/sandbox/apple-container-sandbox-errors.d.ts +44 -0
  12. package/dist/lib/sandbox/apple-container-sandbox-errors.d.ts.map +1 -0
  13. package/dist/lib/sandbox/apple-container-sandbox.d.ts +122 -0
  14. package/dist/lib/sandbox/apple-container-sandbox.d.ts.map +1 -0
  15. package/dist/lib/sandbox/cli-process.d.ts +24 -0
  16. package/dist/lib/sandbox/cli-process.d.ts.map +1 -0
  17. package/dist/lib/sandbox/container-engine.d.ts +149 -0
  18. package/dist/lib/sandbox/container-engine.d.ts.map +1 -0
  19. package/dist/lib/sandbox/container-sandbox-errors.d.ts +10 -0
  20. package/dist/lib/sandbox/container-sandbox-errors.d.ts.map +1 -0
  21. package/dist/lib/sandbox/container-sandbox.d.ts +90 -0
  22. package/dist/lib/sandbox/container-sandbox.d.ts.map +1 -0
  23. package/dist/lib/sandbox/docker-sandbox-errors.d.ts +2 -2
  24. package/dist/lib/sandbox/docker-sandbox-errors.d.ts.map +1 -1
  25. package/dist/lib/sandbox/docker-sandbox.d.ts +122 -218
  26. package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
  27. package/dist/lib/sandbox/index.d.ts +4 -0
  28. package/dist/lib/sandbox/index.d.ts.map +1 -1
  29. package/dist/lib/sandbox/shell-quote.d.ts +2 -2
  30. package/dist/lib/sandbox/strace/index.d.ts +6 -21
  31. package/dist/lib/sandbox/strace/index.d.ts.map +1 -1
  32. package/dist/lib/sandbox/strace/index.js +18 -7
  33. package/dist/lib/sandbox/strace/index.js.map +2 -2
  34. package/dist/lib/sandbox/types.d.ts +3 -3
  35. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -1186,7 +1186,7 @@ function isOutputAvailableToolPart(part) {
1186
1186
  return isStaticToolUIPart(part) && part.state === "output-available";
1187
1187
  }
1188
1188
  function isToolOutputReminderEnvelope(value) {
1189
- return isRecord(value) && "result" in value && typeof value.systemReminder === "string" && value.systemReminder.startsWith(SYSTEM_REMINDER_OPEN_TAG);
1189
+ return isRecord(value) && isRecord(value.meta) && value.meta.reminder === true && "result" in value && typeof value.systemReminder === "string";
1190
1190
  }
1191
1191
  function stripTextByRanges(text, ranges) {
1192
1192
  if (ranges.length === 0) {
@@ -1309,7 +1309,8 @@ function applyInlineReminder(message2, value) {
1309
1309
  };
1310
1310
  }
1311
1311
  function applyPartReminder(message2, value) {
1312
- const part = { type: "text", text: value };
1312
+ const reminderText = formatTaggedReminder(value);
1313
+ const part = { type: "text", text: reminderText };
1313
1314
  message2.parts.push(part);
1314
1315
  const partIndex = message2.parts.length - 1;
1315
1316
  return {
@@ -1318,13 +1319,10 @@ function applyPartReminder(message2, value) {
1318
1319
  target: "user",
1319
1320
  partIndex,
1320
1321
  start: 0,
1321
- end: value.length,
1322
+ end: reminderText.length,
1322
1323
  mode: "part"
1323
1324
  };
1324
1325
  }
1325
- function resolveReminderText(item, ctx) {
1326
- return resolveReminder(item, ctx)?.text ?? "";
1327
- }
1328
1326
  function normalizeReminderResolution(value) {
1329
1327
  if (typeof value === "string") {
1330
1328
  return value.trim().length === 0 ? null : { text: value };
@@ -1374,7 +1372,19 @@ function applyRemindersToToolOutput(output, texts) {
1374
1372
  if (texts.length === 0) return output;
1375
1373
  return {
1376
1374
  result: output === void 0 ? null : output,
1377
- systemReminder: formatTaggedReminder(texts.join("\n"))
1375
+ systemReminder: formatTaggedReminder(texts.join("\n")),
1376
+ meta: { reminder: true }
1377
+ };
1378
+ }
1379
+ function toToolReminderModelOutput(output, projectResult) {
1380
+ if (!isToolOutputReminderEnvelope(output)) return null;
1381
+ const projected = projectResult(output.result);
1382
+ return {
1383
+ type: "json",
1384
+ value: {
1385
+ result: isRecord(projected) ? projected.value : projected,
1386
+ systemReminder: output.systemReminder
1387
+ }
1378
1388
  };
1379
1389
  }
1380
1390
  function mergeReminderMetadata(message2, addedReminders) {
@@ -1916,12 +1926,20 @@ function wrapToolsWithOutputReminders(tools, context) {
1916
1926
  wrapped[name] = toolDef;
1917
1927
  continue;
1918
1928
  }
1929
+ const originalToModelOutput = toolDef.toModelOutput;
1919
1930
  wrapped[name] = {
1920
1931
  ...toolDef,
1921
1932
  execute: async (input, options) => {
1922
1933
  const result = await execute.call(toolDef, input, options);
1923
1934
  if (isAsyncIterable(result)) return result;
1924
1935
  return context.applyToolOutputReminders(result);
1936
+ },
1937
+ toModelOutput: (args) => {
1938
+ const project = (output) => originalToModelOutput ? originalToModelOutput({
1939
+ ...args,
1940
+ output
1941
+ }) : defaultToolModelOutput(output);
1942
+ return toToolReminderModelOutput(args.output, project) ?? project(args.output);
1925
1943
  }
1926
1944
  };
1927
1945
  }
@@ -1930,6 +1948,9 @@ function wrapToolsWithOutputReminders(tools, context) {
1930
1948
  function isAsyncIterable(value) {
1931
1949
  return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
1932
1950
  }
1951
+ function defaultToolModelOutput(output) {
1952
+ return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output ?? null };
1953
+ }
1933
1954
  function agent(options) {
1934
1955
  return new Agent(options);
1935
1956
  }
@@ -3835,7 +3856,6 @@ var ContextEngine = class _ContextEngine {
3835
3856
  const reminders = matched.map((m) => ({
3836
3857
  text: m.resolved.text,
3837
3858
  asPart: m.config.asPart,
3838
- target: "user",
3839
3859
  metadata: m.resolved.metadata
3840
3860
  }));
3841
3861
  const carrier = {
@@ -3895,9 +3915,9 @@ var ContextEngine = class _ContextEngine {
3895
3915
  * Evaluate `target: 'tool-output'` reminders against a tool's raw result and
3896
3916
  * return the (possibly wrapped) output.
3897
3917
  *
3898
- * Called by the agent's tool wrapper right after each `execute()` resolves
3899
- * upstream of both the model and the store, so the next model step and the
3900
- * persisted chain see the exact same wrapped value (store/prompt parity).
3918
+ * Called by the agent's tool wrapper right after each `execute()` resolves.
3919
+ * The store keeps the wrapped value with a host-only marker; the model-facing
3920
+ * projection strips that marker while preserving `result` + `systemReminder`.
3901
3921
  *
3902
3922
  * Returns the output unchanged when no tool-output reminder fires. Without a
3903
3923
  * persisted user message there is no turn context to evaluate against
@@ -6209,1406 +6229,2175 @@ async function useAgentOsSandbox(options, fn) {
6209
6229
  }
6210
6230
  }
6211
6231
 
6212
- // packages/context/src/lib/sandbox/bash-exception.ts
6213
- var BashException = class extends Error {
6214
- };
6215
-
6216
- // packages/context/src/lib/sandbox/bash-meta.ts
6217
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
6218
- var store = new AsyncLocalStorage2();
6219
- function runWithBashMeta(fn) {
6220
- return store.run({ hidden: {} }, fn);
6221
- }
6222
- function useBashMeta() {
6223
- const state = store.getStore();
6224
- if (!state) return null;
6225
- return {
6226
- setHidden(patch) {
6227
- state.hidden = { ...state.hidden, ...patch };
6228
- },
6229
- setReminder(text) {
6230
- state.reminder = text;
6231
- },
6232
- clearReminder() {
6233
- state.reminder = void 0;
6234
- }
6235
- };
6236
- }
6237
- function readBashMeta() {
6238
- return store.getStore();
6239
- }
6240
-
6241
- // packages/context/src/lib/sandbox/bash-tool.ts
6242
- import { tool as tool2 } from "ai";
6243
- import {
6244
- createBashTool as externalCreateBashTool
6245
- } from "bash-tool";
6246
- import z3 from "zod";
6232
+ // packages/context/src/lib/sandbox/apple-container-sandbox.ts
6233
+ import "bash-tool";
6234
+ import spawn3 from "nano-spawn";
6235
+ import { spawn as childSpawn2 } from "node:child_process";
6236
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
6237
+ import { tmpdir } from "node:os";
6238
+ import { join } from "node:path";
6247
6239
 
6248
- // packages/context/src/lib/sandbox/upload-skills.ts
6249
- import * as path3 from "node:path";
6240
+ // packages/context/src/lib/sandbox/container-sandbox-errors.ts
6241
+ var ContainerSandboxError = class extends Error {
6242
+ containerId;
6243
+ constructor(message2, containerId) {
6244
+ super(message2);
6245
+ this.name = "ContainerSandboxError";
6246
+ this.containerId = containerId;
6247
+ }
6248
+ };
6250
6249
 
6251
- // packages/context/src/lib/skills/loader.ts
6252
- import * as fs from "node:fs";
6253
- import * as path from "node:path";
6254
- import YAML from "yaml";
6255
- function parseFrontmatter(content) {
6256
- const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
6257
- const match = content.match(frontmatterRegex);
6258
- if (!match) {
6259
- throw new Error("Invalid SKILL.md: missing or malformed frontmatter");
6250
+ // packages/context/src/lib/sandbox/apple-container-sandbox-errors.ts
6251
+ var AppleContainerSandboxError = class extends ContainerSandboxError {
6252
+ constructor(message2, containerId) {
6253
+ super(message2, containerId);
6254
+ this.name = "AppleContainerSandboxError";
6260
6255
  }
6261
- const [, yamlContent, body] = match;
6262
- const frontmatter = YAML.parse(yamlContent);
6263
- if (!frontmatter.name || typeof frontmatter.name !== "string") {
6264
- throw new Error('Invalid SKILL.md: frontmatter must have a "name" field');
6256
+ };
6257
+ var ContainerServiceNotRunningError = class extends AppleContainerSandboxError {
6258
+ constructor() {
6259
+ super(
6260
+ "Apple container service is not running. Start it with `container system start` (first run also needs `container system kernel set --recommended`)."
6261
+ );
6262
+ this.name = "ContainerServiceNotRunningError";
6265
6263
  }
6266
- if (!frontmatter.description || typeof frontmatter.description !== "string") {
6267
- throw new Error(
6268
- 'Invalid SKILL.md: frontmatter must have a "description" field'
6264
+ };
6265
+ var AppleContainerCreationError = class extends AppleContainerSandboxError {
6266
+ image;
6267
+ cause;
6268
+ constructor(message2, image, cause) {
6269
+ super(`Failed to create container from image "${image}": ${message2}`);
6270
+ this.name = "AppleContainerCreationError";
6271
+ this.image = image;
6272
+ this.cause = cause;
6273
+ }
6274
+ };
6275
+ var AppleContainerImageBuildError = class extends AppleContainerSandboxError {
6276
+ stderr;
6277
+ constructor(stderr) {
6278
+ super(`Container image build failed: ${stderr}`);
6279
+ this.name = "AppleContainerImageBuildError";
6280
+ this.stderr = stderr;
6281
+ }
6282
+ };
6283
+ var AppleContainerVolumePathError = class extends AppleContainerSandboxError {
6284
+ source;
6285
+ containerPath;
6286
+ reason;
6287
+ constructor(source, containerPath, reason) {
6288
+ super(
6289
+ `Invalid container volume path "${source}" -> "${containerPath}": ${reason}`
6269
6290
  );
6291
+ this.name = "AppleContainerVolumePathError";
6292
+ this.source = source;
6293
+ this.containerPath = containerPath;
6294
+ this.reason = reason;
6270
6295
  }
6271
- return {
6272
- frontmatter,
6273
- body: body.trim()
6274
- };
6296
+ };
6297
+ var AppleContainerVolumeInspectError = class extends AppleContainerSandboxError {
6298
+ volume;
6299
+ reason;
6300
+ constructor(volume, reason) {
6301
+ super(`Failed to inspect container volume "${volume}": ${reason}`);
6302
+ this.name = "AppleContainerVolumeInspectError";
6303
+ this.volume = volume;
6304
+ this.reason = reason;
6305
+ }
6306
+ };
6307
+ var AppleContainerVolumeCreateError = class extends AppleContainerSandboxError {
6308
+ volume;
6309
+ reason;
6310
+ constructor(volume, reason) {
6311
+ super(`Failed to create container volume "${volume}": ${reason}`);
6312
+ this.name = "AppleContainerVolumeCreateError";
6313
+ this.volume = volume;
6314
+ this.reason = reason;
6315
+ }
6316
+ };
6317
+ var AppleContainerVolumeRemoveError = class extends AppleContainerSandboxError {
6318
+ volume;
6319
+ reason;
6320
+ constructor(volume, reason) {
6321
+ super(`Failed to remove container volume "${volume}": ${reason}`);
6322
+ this.name = "AppleContainerVolumeRemoveError";
6323
+ this.volume = volume;
6324
+ this.reason = reason;
6325
+ }
6326
+ };
6327
+
6328
+ // packages/context/src/lib/sandbox/container-sandbox.ts
6329
+ import "bash-tool";
6330
+ import spawn2 from "nano-spawn";
6331
+ import { spawn as childSpawn } from "node:child_process";
6332
+ import { createHash } from "node:crypto";
6333
+ import { existsSync, readFileSync } from "node:fs";
6334
+
6335
+ // packages/context/src/lib/sandbox/cli-process.ts
6336
+ import { Readable as Readable2 } from "node:stream";
6337
+
6338
+ // packages/context/src/lib/sandbox/shell-quote.ts
6339
+ function shellQuote(s) {
6340
+ return `'${s.replace(/'/g, `'\\''`)}'`;
6275
6341
  }
6276
- function loadSkillMetadata(skillMdPath) {
6277
- const content = fs.readFileSync(skillMdPath, "utf-8");
6278
- const parsed = parseFrontmatter(content);
6279
- const skillDir = path.dirname(skillMdPath);
6342
+
6343
+ // packages/context/src/lib/sandbox/cli-process.ts
6344
+ function toSandboxProcess(child, abortSignal) {
6345
+ if (!child.stdout || !child.stderr) {
6346
+ child.kill("SIGKILL");
6347
+ throw new Error("exec child process is missing stdout/stderr streams");
6348
+ }
6349
+ const onAbort = () => child.kill("SIGKILL");
6350
+ if (abortSignal) {
6351
+ if (abortSignal.aborted) onAbort();
6352
+ else abortSignal.addEventListener("abort", onAbort, { once: true });
6353
+ }
6280
6354
  return {
6281
- name: parsed.frontmatter.name,
6282
- description: parsed.frontmatter.description,
6283
- path: skillDir,
6284
- skillMdPath
6355
+ stdout: Readable2.toWeb(child.stdout),
6356
+ stderr: Readable2.toWeb(child.stderr),
6357
+ exit: new Promise((resolve4, reject) => {
6358
+ const settle = () => {
6359
+ child.removeListener("exit", onExitEvent);
6360
+ child.removeListener("error", onError);
6361
+ abortSignal?.removeEventListener("abort", onAbort);
6362
+ };
6363
+ const onError = (err) => {
6364
+ settle();
6365
+ reject(err);
6366
+ };
6367
+ const onExitEvent = (code, exitSignal) => {
6368
+ settle();
6369
+ resolve4({ code, signal: exitSignal, success: code === 0 });
6370
+ };
6371
+ child.on("exit", onExitEvent);
6372
+ child.on("error", onError);
6373
+ })
6285
6374
  };
6286
6375
  }
6287
- function discoverSkillsInDirectory(directory) {
6288
- const skills2 = [];
6289
- const expandedDir = directory.startsWith("~") ? path.join(process.env.HOME || "", directory.slice(1)) : directory;
6290
- if (!fs.existsSync(expandedDir)) {
6291
- return skills2;
6376
+ var BASE64_WRITE_CHUNK = 32768;
6377
+ function base64WriteCommands(path5, content) {
6378
+ const base64 = Buffer.from(content).toString("base64");
6379
+ const quotedPath = shellQuote(path5);
6380
+ const commands = [];
6381
+ for (let offset = 0; offset < base64.length; offset += BASE64_WRITE_CHUNK) {
6382
+ const chunk = base64.slice(offset, offset + BASE64_WRITE_CHUNK);
6383
+ const redirect = offset === 0 ? ">" : ">>";
6384
+ commands.push(
6385
+ `printf '%s' ${shellQuote(chunk)} | base64 -d ${redirect} ${quotedPath}`
6386
+ );
6292
6387
  }
6293
- const entries = fs.readdirSync(expandedDir, { withFileTypes: true });
6294
- for (const entry of entries) {
6295
- if (!entry.isDirectory()) continue;
6296
- const skillMdPath = path.join(expandedDir, entry.name, "SKILL.md");
6297
- if (!fs.existsSync(skillMdPath)) continue;
6298
- try {
6299
- const metadata = loadSkillMetadata(skillMdPath);
6300
- skills2.push(metadata);
6301
- } catch (error) {
6302
- console.warn(`Warning: Failed to load skill at ${skillMdPath}:`, error);
6303
- }
6388
+ if (commands.length === 0) {
6389
+ commands.push(`printf '' > ${quotedPath}`);
6304
6390
  }
6305
- return skills2;
6306
- }
6307
-
6308
- // packages/context/src/lib/sandbox/walk.ts
6309
- import { readFile, readdir } from "node:fs/promises";
6310
- import * as path2 from "node:path";
6311
- function expandHome(input) {
6312
- if (!input.startsWith("~")) return input;
6313
- const home = process.env.HOME ?? "";
6314
- return path2.join(home, input.slice(1));
6391
+ return commands;
6315
6392
  }
6316
- async function walkDirectory(root) {
6317
- const absoluteRoot = path2.resolve(expandHome(root));
6318
- const files = [];
6319
- async function walk(current) {
6320
- let entries;
6321
- try {
6322
- entries = await readdir(current, { withFileTypes: true });
6323
- } catch {
6324
- return;
6325
- }
6326
- for (const entry of entries) {
6327
- if (entry.name.startsWith(".")) continue;
6328
- if (entry.isSymbolicLink()) continue;
6329
- const entryPath = path2.join(current, entry.name);
6330
- if (entry.isDirectory()) {
6331
- await walk(entryPath);
6332
- } else if (entry.isFile()) {
6333
- const content = await readFile(entryPath);
6334
- const relative3 = path2.relative(absoluteRoot, entryPath).split(path2.sep).join("/");
6335
- files.push({ path: entryPath, relativePath: relative3, content });
6336
- }
6337
- }
6338
- }
6339
- await walk(absoluteRoot);
6340
- return files;
6393
+ function base64ReadCommand(path5) {
6394
+ return `base64 ${shellQuote(path5)}`;
6341
6395
  }
6342
6396
 
6343
- // packages/context/src/lib/sandbox/upload-skills.ts
6344
- function expandHome2(input) {
6345
- if (!input.startsWith("~")) return input;
6346
- const home = process.env.HOME ?? "";
6347
- return path3.join(home, input.slice(1));
6348
- }
6349
- function joinSandbox(base, relative3) {
6350
- if (!relative3) return base;
6351
- const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
6352
- return `${normalizedBase}/${relative3}`;
6353
- }
6354
- function discoverSkillMappings(inputs) {
6355
- if (inputs.length === 0) return [];
6356
- const discoveredByName = /* @__PURE__ */ new Map();
6357
- for (const { host, sandbox: sandboxBase } of inputs) {
6358
- const absoluteHost = path3.resolve(expandHome2(host));
6359
- for (const skill of discoverSkillsInDirectory(host)) {
6360
- const absoluteSkillMd = path3.resolve(skill.skillMdPath);
6361
- const relative3 = path3.relative(absoluteHost, absoluteSkillMd).split(path3.sep).join("/");
6362
- discoveredByName.set(skill.name, {
6363
- name: skill.name,
6364
- description: skill.description,
6365
- host: skill.skillMdPath,
6366
- sandbox: joinSandbox(sandboxBase, relative3)
6367
- });
6368
- }
6397
+ // packages/context/src/lib/sandbox/installers/installer.ts
6398
+ import "bash-tool";
6399
+ import spawn from "nano-spawn";
6400
+
6401
+ // packages/context/src/lib/sandbox/docker-sandbox-errors.ts
6402
+ var DockerSandboxError = class extends ContainerSandboxError {
6403
+ constructor(message2, containerId) {
6404
+ super(message2, containerId);
6405
+ this.name = "DockerSandboxError";
6369
6406
  }
6370
- return Array.from(discoveredByName.values());
6371
- }
6372
- async function uploadSkills(sandbox, inputs) {
6373
- if (inputs.length === 0) return [];
6374
- const skills2 = discoverSkillMappings(inputs);
6375
- const filesToUpload = [];
6376
- for (const { host, sandbox: sandboxBase } of inputs) {
6377
- const walked = await walkDirectory(host);
6378
- for (const file of walked) {
6379
- filesToUpload.push({
6380
- path: joinSandbox(sandboxBase, file.relativePath),
6381
- content: file.content
6382
- });
6383
- }
6407
+ };
6408
+ var DockerNotAvailableError = class extends DockerSandboxError {
6409
+ constructor() {
6410
+ super("Docker is not available. Ensure Docker daemon is running.");
6411
+ this.name = "DockerNotAvailableError";
6384
6412
  }
6385
- if (filesToUpload.length > 0) {
6386
- await sandbox.writeFiles(filesToUpload);
6413
+ };
6414
+ var ContainerCreationError = class extends DockerSandboxError {
6415
+ image;
6416
+ cause;
6417
+ constructor(message2, image, cause) {
6418
+ super(`Failed to create container from image "${image}": ${message2}`);
6419
+ this.name = "ContainerCreationError";
6420
+ this.image = image;
6421
+ this.cause = cause;
6387
6422
  }
6388
- return skills2;
6389
- }
6423
+ };
6424
+ var PackageInstallError = class extends DockerSandboxError {
6425
+ packages;
6426
+ image;
6427
+ packageManager;
6428
+ stderr;
6429
+ constructor(packages, image, packageManager, stderr, containerId) {
6430
+ super(
6431
+ `Package installation failed for [${packages.join(", ")}] using ${packageManager} on ${image}: ${stderr}`,
6432
+ containerId
6433
+ );
6434
+ this.name = "PackageInstallError";
6435
+ this.packages = packages;
6436
+ this.image = image;
6437
+ this.packageManager = packageManager;
6438
+ this.stderr = stderr;
6439
+ }
6440
+ };
6441
+ var InstallError = class extends DockerSandboxError {
6442
+ target;
6443
+ source;
6444
+ reason;
6445
+ url;
6446
+ constructor(opts) {
6447
+ const where = opts.url ? `${opts.source} (${opts.url})` : opts.source;
6448
+ super(
6449
+ `Failed to install "${opts.target}" via ${where}: ${opts.reason}`,
6450
+ opts.containerId
6451
+ );
6452
+ this.name = "InstallError";
6453
+ this.target = opts.target;
6454
+ this.source = opts.source;
6455
+ this.reason = opts.reason;
6456
+ this.url = opts.url;
6457
+ }
6458
+ };
6459
+ var MissingRuntimeError = class extends DockerSandboxError {
6460
+ runtime;
6461
+ required;
6462
+ constructor(runtime, required, details, containerId) {
6463
+ const base = `Required runtime "${runtime}" is not installed (needs: ${required.join(", ")}).`;
6464
+ super(details ? `${base} ${details}` : base, containerId);
6465
+ this.name = "MissingRuntimeError";
6466
+ this.runtime = runtime;
6467
+ this.required = required;
6468
+ }
6469
+ };
6470
+ var VolumePathError = class extends DockerSandboxError {
6471
+ source;
6472
+ containerPath;
6473
+ reason;
6474
+ constructor(source, containerPath, reason) {
6475
+ super(
6476
+ `Invalid Docker volume path "${source}" -> "${containerPath}": ${reason}`
6477
+ );
6478
+ this.name = "VolumePathError";
6479
+ this.source = source;
6480
+ this.containerPath = containerPath;
6481
+ this.reason = reason;
6482
+ }
6483
+ };
6484
+ var VolumeInspectError = class extends DockerSandboxError {
6485
+ volume;
6486
+ reason;
6487
+ constructor(volume, reason) {
6488
+ super(`Failed to inspect Docker volume "${volume}": ${reason}`);
6489
+ this.name = "VolumeInspectError";
6490
+ this.volume = volume;
6491
+ this.reason = reason;
6492
+ }
6493
+ };
6494
+ var VolumeCreateError = class extends DockerSandboxError {
6495
+ volume;
6496
+ reason;
6497
+ constructor(volume, reason) {
6498
+ super(`Failed to create Docker volume "${volume}": ${reason}`);
6499
+ this.name = "VolumeCreateError";
6500
+ this.volume = volume;
6501
+ this.reason = reason;
6502
+ }
6503
+ };
6504
+ var VolumeRemoveError = class extends DockerSandboxError {
6505
+ volume;
6506
+ reason;
6507
+ constructor(volume, reason) {
6508
+ super(`Failed to remove Docker volume "${volume}": ${reason}`);
6509
+ this.name = "VolumeRemoveError";
6510
+ this.volume = volume;
6511
+ this.reason = reason;
6512
+ }
6513
+ };
6514
+ var DockerfileBuildError = class extends DockerSandboxError {
6515
+ stderr;
6516
+ constructor(stderr) {
6517
+ super(`Dockerfile build failed: ${stderr}`);
6518
+ this.name = "DockerfileBuildError";
6519
+ this.stderr = stderr;
6520
+ }
6521
+ };
6522
+ var ComposeStartError = class extends DockerSandboxError {
6523
+ composeFile;
6524
+ stderr;
6525
+ constructor(composeFile, stderr) {
6526
+ super(`Docker Compose failed to start: ${stderr}`);
6527
+ this.name = "ComposeStartError";
6528
+ this.composeFile = composeFile;
6529
+ this.stderr = stderr;
6530
+ }
6531
+ };
6390
6532
 
6391
- // packages/context/src/lib/sandbox/bash-tool.ts
6392
- var REASONING_INSTRUCTION = 'Every bash tool call must include a brief non-empty "reasoning" input explaining why the command is needed.';
6393
- function withBashExceptionCatch(sandbox) {
6394
- return {
6395
- ...sandbox,
6396
- async executeCommand(command, options) {
6397
- try {
6398
- return await sandbox.executeCommand(command, options);
6399
- } catch (err) {
6400
- if (err instanceof BashException) return err.format();
6533
+ // packages/context/src/lib/sandbox/installers/installer.ts
6534
+ var Installer = class {
6535
+ };
6536
+ function isDebianBased(image) {
6537
+ const lower = image.toLowerCase();
6538
+ if (lower.includes("alpine")) return false;
6539
+ const debianPatterns = ["debian", "ubuntu", "node", "python"];
6540
+ return debianPatterns.some((pattern) => lower.includes(pattern));
6541
+ }
6542
+ function createInstallerContext(containerId, image) {
6543
+ const packageManager = isDebianBased(image) ? "apt-get" : "apk";
6544
+ let archPromise = null;
6545
+ const ensuredTools = /* @__PURE__ */ new Set();
6546
+ let aptUpdated = false;
6547
+ const exec = async (command) => {
6548
+ try {
6549
+ const result = await spawn("docker", [
6550
+ "exec",
6551
+ containerId,
6552
+ "sh",
6553
+ "-c",
6554
+ command
6555
+ ]);
6556
+ return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
6557
+ } catch (error) {
6558
+ const err = error;
6559
+ return {
6560
+ stdout: err.stdout ?? "",
6561
+ stderr: err.stderr ?? err.message ?? "",
6562
+ exitCode: err.exitCode ?? 1
6563
+ };
6564
+ }
6565
+ };
6566
+ const arch = async () => {
6567
+ if (!archPromise) {
6568
+ const attempt = (async () => {
6569
+ const result = await exec("uname -m");
6570
+ if (result.exitCode !== 0) {
6571
+ throw new DockerSandboxError(
6572
+ `Failed to detect container architecture: ${result.stderr}`,
6573
+ containerId
6574
+ );
6575
+ }
6576
+ return result.stdout.trim();
6577
+ })();
6578
+ archPromise = attempt.catch((err) => {
6579
+ archPromise = null;
6401
6580
  throw err;
6402
- }
6581
+ });
6582
+ }
6583
+ return archPromise;
6584
+ };
6585
+ const installPackages = async (packages) => {
6586
+ if (packages.length === 0) return;
6587
+ const quoted = packages.map(shellQuote).join(" ");
6588
+ let cmd;
6589
+ if (packageManager === "apt-get") {
6590
+ cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
6591
+ } else {
6592
+ cmd = `apk add --no-cache ${quoted}`;
6593
+ }
6594
+ const result = await exec(cmd);
6595
+ if (result.exitCode !== 0) {
6596
+ throw new PackageInstallError(
6597
+ packages,
6598
+ image,
6599
+ packageManager,
6600
+ result.stderr,
6601
+ containerId
6602
+ );
6603
+ }
6604
+ if (packageManager === "apt-get") aptUpdated = true;
6605
+ };
6606
+ const ensureTool = async (checkName, installName) => {
6607
+ const cacheKey = installName ?? checkName;
6608
+ if (ensuredTools.has(cacheKey)) return;
6609
+ const check = await exec(`which ${shellQuote(checkName)}`);
6610
+ if (check.exitCode === 0) {
6611
+ ensuredTools.add(cacheKey);
6612
+ return;
6403
6613
  }
6614
+ await installPackages([installName ?? checkName]);
6615
+ ensuredTools.add(cacheKey);
6616
+ };
6617
+ return {
6618
+ containerId,
6619
+ image,
6620
+ packageManager,
6621
+ arch,
6622
+ exec,
6623
+ installPackages,
6624
+ ensureTool
6404
6625
  };
6405
6626
  }
6406
- async function createBashTool(options) {
6407
- const {
6408
- skills: skillInputs = [],
6409
- extraInstructions,
6410
- sandbox: backend,
6411
- destination,
6412
- ...rest
6413
- } = options;
6414
- const sandbox = withAbortSignal(withBashExceptionCatch(backend));
6415
- const combinedInstructions = [extraInstructions, REASONING_INSTRUCTION].filter(Boolean).join("\n\n");
6416
- const toolkit = await externalCreateBashTool({
6417
- ...rest,
6418
- sandbox,
6419
- destination,
6420
- extraInstructions: combinedInstructions
6421
- });
6422
- const upstreamBash = toolkit.bash;
6423
- const originalExecute = upstreamBash.execute;
6424
- const toolBuilder = tool2;
6425
- const bash = toolBuilder({
6426
- ...upstreamBash,
6427
- description: upstreamBash.description ?? "",
6428
- inputSchema: z3.object({
6429
- command: z3.string().describe("The bash command to execute"),
6430
- reasoning: z3.string().trim().describe("Brief reason for executing this command")
6431
- }),
6432
- execute: async ({ command }, execOptions) => {
6433
- if (!originalExecute) {
6434
- throw new Error("bash tool execution is not available");
6627
+
6628
+ // packages/context/src/lib/sandbox/container-sandbox.ts
6629
+ var CONTAINER_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
6630
+ var ContainerSandboxStrategy = class {
6631
+ context;
6632
+ engine;
6633
+ opts;
6634
+ volumes;
6635
+ name;
6636
+ workdir;
6637
+ createdVolumes = /* @__PURE__ */ new Set();
6638
+ constructor(opts, engine) {
6639
+ this.opts = opts;
6640
+ this.engine = engine;
6641
+ const { volumes = [], env = {}, name, workdir = "/workspace" } = opts;
6642
+ for (const key of Object.keys(env)) {
6643
+ if (key.length === 0 || key.includes("=")) {
6644
+ throw this.engine.errors.generic(
6645
+ `Invalid environment variable key: "${key}"`
6646
+ );
6647
+ }
6648
+ }
6649
+ if (name !== void 0 && !CONTAINER_NAME_PATTERN.test(name)) {
6650
+ throw this.engine.errors.generic(
6651
+ `Invalid container name: "${name}". Use only letters, numbers, underscore, period, or hyphen. The "sandbox-" prefix is added automatically.`
6652
+ );
6653
+ }
6654
+ this.volumes = volumes;
6655
+ this.name = name;
6656
+ this.workdir = workdir;
6657
+ }
6658
+ async create() {
6659
+ const image = await this.getImage();
6660
+ let acquired;
6661
+ try {
6662
+ acquired = await this.acquireContainer(image);
6663
+ this.context = { containerId: acquired.containerId, image };
6664
+ if (!acquired.attached) {
6665
+ await this.engine.ensureWorkdir(this.context.containerId, this.workdir);
6666
+ await this.configure();
6667
+ }
6668
+ } catch (error) {
6669
+ if (acquired && !acquired.attached) {
6670
+ await this.stopContainer(acquired.containerId);
6671
+ }
6672
+ await this.cleanupCreatedVolumesAfterFailure(error);
6673
+ throw error;
6674
+ }
6675
+ return this.createSandboxMethods();
6676
+ }
6677
+ namedContainerId() {
6678
+ return `sandbox-${this.name}`;
6679
+ }
6680
+ defaultContainerId() {
6681
+ return `sandbox-${crypto.randomUUID().slice(0, 8)}`;
6682
+ }
6683
+ async acquireContainer(image) {
6684
+ if (!this.name) {
6685
+ const containerId2 = this.defaultContainerId();
6686
+ await this.prepareVolumes();
6687
+ await this.startContainer(image, containerId2);
6688
+ return { containerId: containerId2, attached: false };
6689
+ }
6690
+ const containerId = this.namedContainerId();
6691
+ const probe = await this.inspectContainer(containerId);
6692
+ if (probe === "running") {
6693
+ return { containerId, attached: true };
6694
+ }
6695
+ if (probe === "stopped") {
6696
+ await this.startStoppedContainer(containerId, image);
6697
+ return { containerId, attached: true };
6698
+ }
6699
+ await this.prepareVolumes();
6700
+ try {
6701
+ await this.startContainer(image, containerId);
6702
+ return { containerId, attached: false };
6703
+ } catch (error) {
6704
+ if (this.engine.isNameConflict(this.engine.errorMessage(error))) {
6705
+ await this.cleanupCreatedVolumes();
6706
+ const raced = await this.inspectContainer(containerId);
6707
+ if (raced === "running") {
6708
+ return { containerId, attached: true };
6709
+ }
6710
+ if (raced === "stopped") {
6711
+ await this.startStoppedContainer(containerId, image);
6712
+ return { containerId, attached: true };
6713
+ }
6714
+ }
6715
+ throw error;
6716
+ }
6717
+ }
6718
+ async startStoppedContainer(containerId, image) {
6719
+ try {
6720
+ await spawn2(this.engine.cli, ["start", containerId]);
6721
+ } catch (error) {
6722
+ const message2 = this.engineErrorMessage(error);
6723
+ if (this.isServiceDown(message2)) {
6724
+ throw this.engine.errors.serviceNotAvailable();
6435
6725
  }
6436
- const { abortSignal } = execOptions;
6437
- return runWithAbortSignal(
6438
- abortSignal,
6439
- () => runWithBashMeta(async () => {
6440
- const result = await originalExecute({ command }, execOptions);
6441
- const state = readBashMeta();
6442
- if (!state) return result;
6443
- const hasHidden = Object.keys(state.hidden).length > 0;
6444
- const hasReminder = state.reminder !== void 0;
6445
- if (!hasHidden && !hasReminder) return result;
6446
- return {
6447
- ...result,
6448
- ...hasHidden ? { meta: state.hidden } : {},
6449
- ...hasReminder ? { reminder: state.reminder } : {}
6450
- };
6451
- })
6726
+ throw this.engine.errors.creation(message2, image, error);
6727
+ }
6728
+ }
6729
+ async inspectContainer(containerId) {
6730
+ try {
6731
+ const result = await spawn2(
6732
+ this.engine.cli,
6733
+ this.engine.inspectArgs(containerId)
6452
6734
  );
6453
- },
6454
- toModelOutput: ({ output }) => {
6455
- if (typeof output !== "object" || output === null) {
6456
- return { type: "json", value: output };
6735
+ return this.engine.parseStatus(result.stdout.trim());
6736
+ } catch (error) {
6737
+ const message2 = this.engineErrorMessage(error);
6738
+ if (this.isServiceDown(message2)) {
6739
+ throw this.engine.errors.serviceNotAvailable();
6457
6740
  }
6458
- const { meta: _meta, ...visible } = output;
6459
- return { type: "json", value: visible };
6741
+ if (this.engine.isMissingContainer(message2)) {
6742
+ return "absent";
6743
+ }
6744
+ throw this.engine.errors.generic(
6745
+ `Failed to inspect container "${containerId}": ${message2}`
6746
+ );
6460
6747
  }
6461
- });
6462
- const upstreamWriteFile = toolkit.tools.writeFile;
6463
- const originalWriteExecute = upstreamWriteFile.execute;
6464
- const writeFileBuilder = tool2;
6465
- const writeFile = writeFileBuilder({
6466
- ...upstreamWriteFile,
6467
- execute: async (input, options2) => {
6468
- if (!originalWriteExecute) {
6469
- throw new Error("writeFile tool execution is not available");
6748
+ }
6749
+ async prepareVolumes() {
6750
+ this.validateVolumes();
6751
+ for (const volume of this.volumes) {
6752
+ if (volume.type !== "volume") {
6753
+ continue;
6470
6754
  }
6471
- try {
6472
- return await originalWriteExecute(input, options2);
6473
- } catch (err) {
6474
- if (err instanceof BashException) return err.format();
6475
- throw err;
6755
+ const lifecycle = volume.lifecycle ?? "external";
6756
+ if (lifecycle === "external") {
6757
+ await this.inspectVolume(volume.name);
6758
+ continue;
6759
+ }
6760
+ const exists = await this.volumeExists(volume.name);
6761
+ if (exists) {
6762
+ throw this.engine.errors.volumeCreate(
6763
+ volume.name,
6764
+ "managed volume already exists"
6765
+ );
6766
+ }
6767
+ await this.createVolume(volume);
6768
+ if (volume.removeOnDispose !== false) {
6769
+ this.createdVolumes.add(volume.name);
6476
6770
  }
6477
6771
  }
6478
- });
6479
- const skills2 = await uploadSkills(backend, skillInputs);
6480
- return {
6481
- ...toolkit,
6482
- sandbox,
6483
- bash,
6484
- tools: { ...toolkit.tools, bash, writeFile },
6485
- skills: skills2
6486
- };
6487
- }
6488
-
6489
- // packages/context/src/lib/sandbox/binary-bridges.ts
6490
- import { existsSync as existsSync2 } from "fs";
6491
- import { defineCommand } from "just-bash";
6492
- import spawn from "nano-spawn";
6493
- import * as path4 from "path";
6494
- function createBinaryBridges(...binaries) {
6495
- return binaries.map((input) => {
6496
- const config = typeof input === "string" ? { name: input } : input;
6497
- const { name, binaryPath = name, allowedArgs } = config;
6498
- return defineCommand(name, async (args, ctx) => {
6499
- if (allowedArgs) {
6500
- const invalidArg = args.find((arg) => !allowedArgs.test(arg));
6501
- if (invalidArg) {
6502
- return {
6503
- stdout: "",
6504
- stderr: `${name}: argument '${invalidArg}' not allowed by security policy`,
6505
- exitCode: 1
6506
- };
6507
- }
6772
+ }
6773
+ validateVolumes() {
6774
+ const containerPaths = /* @__PURE__ */ new Set();
6775
+ for (const volume of this.volumes) {
6776
+ const source = volume.type === "bind" ? volume.hostPath : volume.name;
6777
+ if (!volume.containerPath.startsWith("/")) {
6778
+ throw this.engine.errors.volumePath(
6779
+ source,
6780
+ volume.containerPath,
6781
+ "containerPath must be absolute"
6782
+ );
6508
6783
  }
6509
- try {
6510
- const realCwd = resolveRealCwd(ctx);
6511
- const resolvedArgs = args.map((arg) => {
6512
- if (arg.startsWith("-")) {
6513
- return arg;
6514
- }
6515
- const hasExtension = path4.extname(arg) !== "";
6516
- const hasPathSep = arg.includes(path4.sep) || arg.includes("/");
6517
- const isRelative = arg.startsWith(".");
6518
- if (hasExtension || hasPathSep || isRelative) {
6519
- return path4.resolve(realCwd, arg);
6520
- }
6521
- return arg;
6522
- });
6523
- const mergedEnv = {
6524
- ...process.env,
6525
- ...Object.fromEntries(ctx.env),
6526
- // ctx.env is a Map, convert to object
6527
- PATH: process.env.PATH
6528
- // Always use host PATH for binary bridges
6529
- };
6530
- const result = await spawn(binaryPath, resolvedArgs, {
6531
- cwd: realCwd,
6532
- env: mergedEnv
6533
- });
6534
- return {
6535
- stdout: result.stdout,
6536
- stderr: result.stderr,
6537
- exitCode: 0
6538
- };
6539
- } catch (error) {
6540
- if (error && typeof error === "object") {
6541
- const err = error;
6542
- const cause = err.cause;
6543
- if (cause?.code === "ENOENT") {
6544
- return {
6545
- stdout: "",
6546
- stderr: `${name}: ${binaryPath} not found`,
6547
- exitCode: 127
6548
- };
6549
- }
6784
+ this.validateMountValue("containerPath", volume.containerPath, volume);
6785
+ if (containerPaths.has(volume.containerPath)) {
6786
+ throw this.engine.errors.volumePath(
6787
+ source,
6788
+ volume.containerPath,
6789
+ "containerPath must be unique"
6790
+ );
6791
+ }
6792
+ containerPaths.add(volume.containerPath);
6793
+ if (volume.type === "bind") {
6794
+ this.validateMountValue("hostPath", volume.hostPath, volume);
6795
+ if (!existsSync(volume.hostPath)) {
6796
+ throw this.engine.errors.volumePath(
6797
+ volume.hostPath,
6798
+ volume.containerPath,
6799
+ "hostPath does not exist on host"
6800
+ );
6550
6801
  }
6551
- if (error && typeof error === "object" && "exitCode" in error) {
6552
- const subprocessError = error;
6553
- return {
6554
- stdout: subprocessError.stdout ?? "",
6555
- stderr: subprocessError.stderr ?? "",
6556
- exitCode: subprocessError.exitCode ?? 1
6557
- };
6802
+ continue;
6803
+ }
6804
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(volume.name)) {
6805
+ throw this.engine.errors.volumePath(
6806
+ volume.name,
6807
+ volume.containerPath,
6808
+ "volume name must start with an alphanumeric character and contain only letters, numbers, underscore, period, or hyphen"
6809
+ );
6810
+ }
6811
+ if (volume.subPath) {
6812
+ this.validateMountValue("subPath", volume.subPath, volume);
6813
+ }
6814
+ if ((volume.lifecycle ?? "external") === "external") {
6815
+ if (volume.driver || volume.driverOptions) {
6816
+ throw this.engine.errors.volumePath(
6817
+ volume.name,
6818
+ volume.containerPath,
6819
+ 'driver and driverOptions require lifecycle "managed"'
6820
+ );
6558
6821
  }
6559
- return {
6560
- stdout: "",
6561
- stderr: `${name}: ${error instanceof Error ? error.message : String(error)}`,
6562
- exitCode: 127
6563
- };
6564
6822
  }
6565
- });
6566
- });
6567
- }
6568
- function resolveRealCwd(ctx) {
6569
- const fs2 = ctx.fs;
6570
- let realCwd;
6571
- if (fs2.root) {
6572
- realCwd = path4.join(fs2.root, ctx.cwd);
6573
- } else if (typeof fs2.getMountPoint === "function" && typeof fs2.toRealPath === "function") {
6574
- const real = fs2.toRealPath(ctx.cwd);
6575
- realCwd = real ?? process.cwd();
6576
- } else {
6577
- realCwd = process.cwd();
6823
+ }
6578
6824
  }
6579
- if (!existsSync2(realCwd)) {
6580
- realCwd = process.cwd();
6825
+ validateMountValue(field, value, volume) {
6826
+ if (!value.includes(",")) {
6827
+ return;
6828
+ }
6829
+ const source = volume.type === "bind" ? volume.hostPath : volume.name;
6830
+ throw this.engine.errors.volumePath(
6831
+ source,
6832
+ volume.containerPath,
6833
+ `${field} must not contain commas`
6834
+ );
6581
6835
  }
6582
- return realCwd;
6583
- }
6584
-
6585
- // packages/context/src/lib/sandbox/daytona-sandbox.ts
6586
- import "bash-tool";
6587
- import { randomUUID } from "node:crypto";
6588
-
6589
- // packages/context/src/lib/sandbox/shell-quote.ts
6590
- function shellQuote(s) {
6591
- return `'${s.replace(/'/g, `'\\''`)}'`;
6592
- }
6593
-
6594
- // packages/context/src/lib/sandbox/daytona-sandbox.ts
6595
- var DAYTONA_DEFAULT_DESTINATION = "/home/daytona";
6596
- var DAYTONA_EXIT_POLL_INTERVAL_MS = 250;
6597
- var DAYTONA_EXIT_POLL_TIMEOUT_MS = 3e4;
6598
- var DaytonaSandboxError = class extends Error {
6599
- constructor(message2, cause) {
6600
- super(message2);
6601
- this.name = "DaytonaSandboxError";
6602
- this.cause = cause;
6836
+ buildRunArgs(image, containerId) {
6837
+ return this.engine.runArgs(image, containerId, this.opts, this.workdir);
6838
+ }
6839
+ async inspectVolume(name) {
6840
+ try {
6841
+ await spawn2(this.engine.cli, ["volume", "inspect", name]);
6842
+ } catch (error) {
6843
+ const reason = this.engineErrorMessage(error);
6844
+ if (this.isServiceDown(reason)) {
6845
+ throw this.engine.errors.serviceNotAvailable();
6846
+ }
6847
+ throw this.engine.errors.volumeInspect(name, reason);
6848
+ }
6849
+ }
6850
+ async volumeExists(name) {
6851
+ try {
6852
+ await this.inspectVolume(name);
6853
+ return true;
6854
+ } catch (error) {
6855
+ if (error instanceof ContainerSandboxError && this.engine.isMissingVolume(error.message)) {
6856
+ return false;
6857
+ }
6858
+ throw error;
6859
+ }
6860
+ }
6861
+ async createVolume(volume) {
6862
+ const args = this.engine.volumeCreateArgs(volume);
6863
+ try {
6864
+ await spawn2(this.engine.cli, args);
6865
+ } catch (error) {
6866
+ const reason = this.engineErrorMessage(error);
6867
+ if (this.isServiceDown(reason)) {
6868
+ throw this.engine.errors.serviceNotAvailable();
6869
+ }
6870
+ throw this.engine.errors.volumeCreate(volume.name, reason);
6871
+ }
6872
+ }
6873
+ async cleanupCreatedVolumes() {
6874
+ const volumes = [...this.createdVolumes].reverse();
6875
+ for (const volume of volumes) {
6876
+ try {
6877
+ await spawn2(this.engine.cli, ["volume", "rm", volume]);
6878
+ this.createdVolumes.delete(volume);
6879
+ } catch (error) {
6880
+ const reason = this.engineErrorMessage(error);
6881
+ if (this.isServiceDown(reason)) {
6882
+ throw this.engine.errors.serviceNotAvailable();
6883
+ }
6884
+ throw this.engine.errors.volumeRemove(volume, reason);
6885
+ }
6886
+ }
6603
6887
  }
6604
- };
6605
- var DaytonaCreationError = class extends DaytonaSandboxError {
6606
- constructor(message2, cause) {
6607
- super(`Failed to create Daytona sandbox: ${message2}`, cause);
6608
- this.name = "DaytonaCreationError";
6888
+ async cleanupCreatedVolumesAfterFailure(originalError) {
6889
+ try {
6890
+ await this.cleanupCreatedVolumes();
6891
+ } catch (cleanupError) {
6892
+ if (originalError instanceof Error) {
6893
+ const original = originalError;
6894
+ original.suppressed = [...original.suppressed ?? [], cleanupError];
6895
+ }
6896
+ }
6609
6897
  }
6610
- };
6611
- var DaytonaCommandError = class extends DaytonaSandboxError {
6612
- constructor(message2, cause) {
6613
- super(message2, cause);
6614
- this.name = "DaytonaCommandError";
6898
+ engineErrorMessage(error) {
6899
+ return this.engine.errorMessage(error);
6615
6900
  }
6616
- };
6617
- async function createDaytonaSandbox(client, options = {}) {
6618
- validateDaytonaOptions(options);
6619
- const sdk = await import("@daytona/sdk");
6620
- let sandbox;
6621
- try {
6622
- if (options.sandboxId !== void 0) {
6623
- sandbox = await client.get(options.sandboxId);
6624
- await startIfStopped(sandbox, options);
6625
- } else {
6626
- sandbox = await acquireReusedSandbox(client, options, sdk);
6901
+ isServiceDown(message2) {
6902
+ return this.engine.isServiceDown(message2);
6903
+ }
6904
+ async startContainer(image, containerId) {
6905
+ const args = this.buildRunArgs(image, containerId);
6906
+ try {
6907
+ await spawn2(this.engine.cli, args);
6908
+ } catch (error) {
6909
+ const message2 = this.engineErrorMessage(error);
6910
+ if (this.isServiceDown(message2)) {
6911
+ throw this.engine.errors.serviceNotAvailable();
6912
+ }
6913
+ throw this.engine.errors.creation(message2, image, error);
6627
6914
  }
6628
- } catch (error) {
6629
- throw normalizeDaytonaError(error, sdk);
6630
6915
  }
6631
- return createDaytonaSandboxMethods({
6632
- sandbox,
6633
- commandTimeout: options.commandTimeout
6634
- });
6635
- }
6636
- var UNRECOVERABLE_SANDBOX_STATES = /* @__PURE__ */ new Set([
6637
- "error",
6638
- "build_failed",
6639
- "destroyed"
6640
- ]);
6641
- async function acquireReusedSandbox(client, options, sdk) {
6642
- let existing;
6643
- try {
6644
- existing = await client.get(options.name);
6645
- } catch (error) {
6646
- if (error instanceof sdk.DaytonaNotFoundError) {
6647
- return createSandbox(client, options);
6916
+ async stopContainer(containerId) {
6917
+ try {
6918
+ await spawn2(this.engine.cli, ["stop", containerId]);
6919
+ } catch {
6648
6920
  }
6649
- throw error;
6650
6921
  }
6651
- if (existing.state && UNRECOVERABLE_SANDBOX_STATES.has(existing.state)) {
6652
- await deleteSandboxQuietly(existing, options);
6653
- return createSandbox(client, options);
6922
+ async exec(command, options) {
6923
+ try {
6924
+ const result = await spawn2(
6925
+ this.engine.cli,
6926
+ this.engine.execArgs(this.context.containerId, command),
6927
+ { signal: options?.signal }
6928
+ );
6929
+ return {
6930
+ stdout: result.stdout,
6931
+ stderr: result.stderr,
6932
+ exitCode: 0
6933
+ };
6934
+ } catch (error) {
6935
+ const err = error;
6936
+ return {
6937
+ stdout: err.stdout || "",
6938
+ stderr: err.stderr || err.message || "",
6939
+ exitCode: err.exitCode ?? 1
6940
+ };
6941
+ }
6654
6942
  }
6655
- await startIfStopped(existing, options);
6656
- return existing;
6657
- }
6658
- async function deleteSandboxQuietly(sandbox, options) {
6659
- try {
6660
- await sandbox.delete(options.deleteTimeout);
6661
- } catch {
6943
+ spawnProcess(command, options) {
6944
+ const child = childSpawn(
6945
+ this.engine.cli,
6946
+ this.engine.execArgs(this.context.containerId, command, options)
6947
+ );
6948
+ return toSandboxProcess(child, options?.signal);
6662
6949
  }
6663
- }
6664
- async function startIfStopped(sandbox, options) {
6665
- if (sandbox.state && sandbox.state !== "started") {
6666
- await sandbox.start(options.startTimeout ?? options.createTimeout);
6950
+ createSandboxMethods() {
6951
+ const { containerId } = this.context;
6952
+ const sandbox = {
6953
+ executeCommand: async (command, options) => this.exec(command, options),
6954
+ spawn: (command, options) => this.spawnProcess(command, options),
6955
+ readFile: async (path5) => {
6956
+ const result = await sandbox.executeCommand(base64ReadCommand(path5));
6957
+ if (result.exitCode !== 0) {
6958
+ throw new Error(`Failed to read file "${path5}": ${result.stderr}`);
6959
+ }
6960
+ return Buffer.from(result.stdout, "base64").toString("utf-8");
6961
+ },
6962
+ writeFiles: async (files) => {
6963
+ for (const file of files) {
6964
+ const dir = file.path.substring(0, file.path.lastIndexOf("/"));
6965
+ if (dir) {
6966
+ await sandbox.executeCommand(`mkdir -p ${shellQuote(dir)}`);
6967
+ }
6968
+ for (const command of base64WriteCommands(file.path, file.content)) {
6969
+ const result = await sandbox.executeCommand(command);
6970
+ if (result.exitCode !== 0) {
6971
+ throw new Error(
6972
+ `Failed to write file "${file.path}": ${result.stderr}`
6973
+ );
6974
+ }
6975
+ }
6976
+ }
6977
+ },
6978
+ dispose: async () => {
6979
+ await this.stopContainer(containerId);
6980
+ await this.cleanupCreatedVolumes();
6981
+ },
6982
+ [Symbol.asyncDispose]() {
6983
+ return this.dispose();
6984
+ }
6985
+ };
6986
+ return sandbox;
6667
6987
  }
6668
- }
6669
- function normalizeDaytonaError(error, sdk) {
6670
- const err = toError(error);
6671
- if (err instanceof sdk.DaytonaError) {
6672
- return err;
6988
+ };
6989
+ var RuntimeStrategy = class extends ContainerSandboxStrategy {
6990
+ image;
6991
+ installers;
6992
+ constructor(opts, engine, mode) {
6993
+ super(opts, engine);
6994
+ this.image = mode.image;
6995
+ this.installers = mode.installers;
6673
6996
  }
6674
- return new DaytonaCreationError(err.message, err);
6675
- }
6676
- function createSandbox(client, options) {
6677
- const base = compactObject({
6678
- name: options.name,
6679
- user: options.user,
6680
- language: options.language,
6681
- envVars: options.envVars,
6682
- labels: options.labels,
6683
- public: options.public,
6684
- autoStopInterval: options.autoStopInterval,
6685
- autoArchiveInterval: options.autoArchiveInterval,
6686
- autoDeleteInterval: options.autoDeleteInterval,
6687
- volumes: options.volumes,
6688
- networkBlockAll: options.networkBlockAll,
6689
- networkAllowList: options.networkAllowList,
6690
- ephemeral: options.ephemeral
6691
- });
6692
- if (options.image !== void 0) {
6693
- const params2 = compactObject({
6694
- ...base,
6695
- image: options.image,
6696
- resources: options.resources
6697
- });
6698
- return client.create(params2, {
6699
- timeout: options.createTimeout,
6700
- onSnapshotCreateLogs: options.onSnapshotCreateLogs
6701
- });
6997
+ async getImage() {
6998
+ return this.image;
6702
6999
  }
6703
- const params = compactObject({
6704
- ...base,
6705
- snapshot: options.snapshot
6706
- });
6707
- return client.create(params, { timeout: options.createTimeout });
6708
- }
6709
- function validateDaytonaOptions(options) {
6710
- if (options.image && options.snapshot) {
6711
- throw new DaytonaSandboxError(
6712
- 'Daytona sandbox options cannot include both "image" and "snapshot". Choose one environment source.'
7000
+ async configure() {
7001
+ const ctx = this.engine.createInstallerContext(
7002
+ this.context.containerId,
7003
+ this.image
6713
7004
  );
7005
+ for (const installer of this.installers) {
7006
+ await installer.install(ctx);
7007
+ }
6714
7008
  }
6715
- if (options.resources && !options.image) {
6716
- throw new DaytonaSandboxError(
6717
- 'Daytona sandbox options can only include "resources" when creating from "image". The Daytona SDK does not apply resources during default or snapshot creation.'
6718
- );
7009
+ };
7010
+ var ContainerfileStrategy = class extends ContainerSandboxStrategy {
7011
+ dockerfile;
7012
+ dockerContext;
7013
+ showBuildLogs;
7014
+ identity;
7015
+ constructor(opts, engine, mode) {
7016
+ super(opts, engine);
7017
+ this.dockerfile = mode.dockerfile;
7018
+ this.dockerContext = mode.context;
7019
+ this.showBuildLogs = mode.showBuildLogs;
7020
+ this.identity = mode.identity;
6719
7021
  }
6720
- if (options.sandboxId === void 0 && options.name === void 0) {
6721
- throw new DaytonaSandboxError(
6722
- 'Daytona sandbox options require "name" (get-or-create) or "sandboxId" (attach). An unnamed sandbox cannot be reclaimed, since dispose() does not delete it.'
6723
- );
7022
+ async getImage() {
7023
+ const content = this.dockerfile.includes("\n") ? this.dockerfile : readFileSync(this.dockerfile, "utf-8");
7024
+ const tag = `sandbox-${createHash("sha256").update(content).update(this.identity ?? "").digest("hex").slice(0, 12)}`;
7025
+ if (!await this.engine.imageExists(tag)) {
7026
+ await this.engine.buildImage({
7027
+ tag,
7028
+ dockerfile: this.dockerfile,
7029
+ context: this.dockerContext,
7030
+ showBuildLogs: this.showBuildLogs,
7031
+ identity: this.identity
7032
+ });
7033
+ }
7034
+ return tag;
6724
7035
  }
6725
- if (!options.sandboxId) {
6726
- return;
7036
+ async configure() {
6727
7037
  }
6728
- const creationOnlyFields = [
6729
- "name",
6730
- "user",
6731
- "snapshot",
6732
- "image",
6733
- "language",
6734
- "envVars",
6735
- "labels",
6736
- "public",
6737
- "resources",
6738
- "volumes",
6739
- "networkAllowList",
6740
- "networkBlockAll",
6741
- "autoStopInterval",
6742
- "autoArchiveInterval",
6743
- "autoDeleteInterval",
6744
- "ephemeral",
6745
- "onSnapshotCreateLogs"
7038
+ };
7039
+
7040
+ // packages/context/src/lib/sandbox/apple-container-sandbox.ts
7041
+ var CLI = "container";
7042
+ var WORKDIR = "/workspace";
7043
+ function isAppleContainerfileOptions(opts) {
7044
+ return "dockerfile" in opts;
7045
+ }
7046
+ function buildMountArg(volume) {
7047
+ const readOnly = volume.readOnly !== false;
7048
+ const parts = volume.type === "bind" ? [
7049
+ "type=bind",
7050
+ `source=${volume.hostPath}`,
7051
+ `target=${volume.containerPath}`
7052
+ ] : [
7053
+ // Named volumes mount as virtiofs; `container` has no `type=volume`.
7054
+ "type=virtiofs",
7055
+ `source=${volume.name}`,
7056
+ `target=${volume.containerPath}`
6746
7057
  ];
6747
- const present = creationOnlyFields.filter((field) => {
6748
- return options[field] !== void 0;
6749
- });
6750
- if (present.length > 0) {
6751
- throw new DaytonaSandboxError(
6752
- `Daytona sandbox options cannot combine "sandboxId" with creation options: ${present.join(", ")}`
6753
- );
7058
+ if (readOnly) {
7059
+ parts.push("readonly");
6754
7060
  }
7061
+ return parts.join(",");
6755
7062
  }
6756
- function createDaytonaSandboxMethods(args) {
6757
- const { sandbox, commandTimeout } = args;
6758
- const executeCommand = async (command, options) => {
6759
- if (options?.signal?.aborted) {
6760
- return abortedCommandResult();
7063
+ function getCliErrorMessage(error) {
7064
+ const err = error;
7065
+ return err.stderr?.trim() || err.stdout?.trim() || err.message || String(error);
7066
+ }
7067
+ function safeParseArray(stdout) {
7068
+ try {
7069
+ const parsed = JSON.parse(stdout || "[]");
7070
+ return Array.isArray(parsed) ? parsed : [];
7071
+ } catch {
7072
+ return [];
7073
+ }
7074
+ }
7075
+ function readContainerStatus(entry) {
7076
+ const status = String(
7077
+ entry?.status ?? ""
7078
+ ).toLowerCase();
7079
+ if (status === "running" || status === "booted") return "running";
7080
+ if (status === "stopped") return "stopped";
7081
+ throw new AppleContainerSandboxError(
7082
+ `Container is in an unexpected state "${status}"`
7083
+ );
7084
+ }
7085
+ var appleEngine = {
7086
+ cli: CLI,
7087
+ runArgs(image, containerId, opts) {
7088
+ const { memory = "1024M", cpus = 2 } = opts.resources ?? {};
7089
+ const args = [
7090
+ "run",
7091
+ "--detach",
7092
+ "--rm",
7093
+ "--name",
7094
+ containerId,
7095
+ "--memory",
7096
+ memory,
7097
+ "--cpus",
7098
+ String(cpus)
7099
+ ];
7100
+ if (opts.arch) {
7101
+ args.push("--arch", opts.arch);
7102
+ }
7103
+ for (const [key, value] of Object.entries(opts.env ?? {})) {
7104
+ args.push("--env", `${key}=${value}`);
7105
+ }
7106
+ for (const volume of opts.volumes ?? []) {
7107
+ args.push("--mount", buildMountArg(volume));
7108
+ }
7109
+ args.push(image);
7110
+ if (opts.command === void 0) {
7111
+ args.push("tail", "-f", "/dev/null");
7112
+ } else if (opts.command !== null) {
7113
+ args.push(...opts.command);
7114
+ }
7115
+ return args;
7116
+ },
7117
+ execArgs(containerId, command, options) {
7118
+ const flags = ["--cwd", options?.cwd || WORKDIR];
7119
+ if (options?.env) {
7120
+ for (const [key, value] of Object.entries(options.env)) {
7121
+ if (key.length === 0 || key.includes("=")) {
7122
+ throw new AppleContainerSandboxError(
7123
+ `Invalid environment variable key: "${key}"`
7124
+ );
7125
+ }
7126
+ flags.push("--env", `${key}=${value}`);
7127
+ }
6761
7128
  }
6762
- let aborted = false;
6763
- const abort = () => {
6764
- aborted = true;
6765
- };
6766
- options?.signal?.addEventListener("abort", abort, { once: true });
7129
+ return ["exec", ...flags, containerId, "sh", "-c", command];
7130
+ },
7131
+ inspectArgs(containerId) {
7132
+ return ["inspect", containerId];
7133
+ },
7134
+ mountArg: buildMountArg,
7135
+ parseStatus(stdout) {
7136
+ const entries = safeParseArray(stdout);
7137
+ if (entries.length === 0) return "absent";
7138
+ return readContainerStatus(entries[0]);
7139
+ },
7140
+ volumeCreateArgs(volume) {
7141
+ return ["volume", "create", volume.name];
7142
+ },
7143
+ errorMessage: getCliErrorMessage,
7144
+ isServiceDown(message2) {
7145
+ return /apiserver|not running|connection refused|could not connect|xpc/i.test(
7146
+ message2
7147
+ );
7148
+ },
7149
+ isMissingContainer(message2) {
7150
+ return /not found|no such/i.test(message2);
7151
+ },
7152
+ isMissingVolume(message2) {
7153
+ return /not found|no such/i.test(message2);
7154
+ },
7155
+ isNameConflict(message2) {
7156
+ return /already exists/i.test(message2);
7157
+ },
7158
+ async ensureWorkdir(containerId, workdir) {
7159
+ await spawn3(CLI, ["exec", containerId, "sh", "-c", `mkdir -p ${workdir}`]);
7160
+ },
7161
+ defaultImage: "docker.io/library/alpine:latest",
7162
+ createInstallerContext: createAppleInstallerContext,
7163
+ async imageExists(tag) {
6767
7164
  try {
6768
- if (aborted || options?.signal?.aborted) {
6769
- return abortedCommandResult();
7165
+ await spawn3(CLI, ["image", "inspect", tag]);
7166
+ return true;
7167
+ } catch {
7168
+ return false;
7169
+ }
7170
+ },
7171
+ async buildImage(spec) {
7172
+ const archFlag = spec.identity ? ["--arch", spec.identity] : [];
7173
+ if (spec.dockerfile.includes("\n")) {
7174
+ const tempDir = await mkdtemp(join(tmpdir(), "sandbox-containerfile-"));
7175
+ const dockerfilePath = join(tempDir, "Dockerfile");
7176
+ await writeFile(dockerfilePath, spec.dockerfile);
7177
+ try {
7178
+ await runAppleBuild(
7179
+ ["build", ...archFlag, "-t", spec.tag, "-f", dockerfilePath, tempDir],
7180
+ spec.showBuildLogs
7181
+ );
7182
+ } finally {
7183
+ await rm(tempDir, { recursive: true, force: true });
6770
7184
  }
6771
- const response = await sandbox.process.executeCommand(
6772
- command,
6773
- void 0,
6774
- void 0,
6775
- commandTimeout
6776
- );
6777
- if (aborted) return abortedCommandResult();
6778
- return {
6779
- stdout: response.result ?? response.artifacts?.stdout ?? "",
6780
- stderr: "",
6781
- exitCode: response.exitCode ?? 0
6782
- };
7185
+ return;
7186
+ }
7187
+ await runAppleBuild(
7188
+ [
7189
+ "build",
7190
+ ...archFlag,
7191
+ "-t",
7192
+ spec.tag,
7193
+ "-f",
7194
+ spec.dockerfile,
7195
+ spec.context
7196
+ ],
7197
+ spec.showBuildLogs
7198
+ );
7199
+ },
7200
+ errors: {
7201
+ serviceNotAvailable: () => new ContainerServiceNotRunningError(),
7202
+ creation: (message2, image, cause) => new AppleContainerCreationError(message2, image, cause),
7203
+ generic: (message2, containerId) => new AppleContainerSandboxError(message2, containerId),
7204
+ volumePath: (source, containerPath, reason) => new AppleContainerVolumePathError(source, containerPath, reason),
7205
+ volumeInspect: (name, reason) => new AppleContainerVolumeInspectError(name, reason),
7206
+ volumeCreate: (name, reason) => new AppleContainerVolumeCreateError(name, reason),
7207
+ volumeRemove: (name, reason) => new AppleContainerVolumeRemoveError(name, reason)
7208
+ }
7209
+ };
7210
+ function createAppleInstallerContext(containerId, image) {
7211
+ const packageManager = isDebianBased(image) ? "apt-get" : "apk";
7212
+ let archPromise = null;
7213
+ const ensuredTools = /* @__PURE__ */ new Set();
7214
+ let aptUpdated = false;
7215
+ const exec = async (command) => {
7216
+ try {
7217
+ const result = await spawn3(CLI, [
7218
+ "exec",
7219
+ containerId,
7220
+ "sh",
7221
+ "-c",
7222
+ command
7223
+ ]);
7224
+ return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
6783
7225
  } catch (error) {
6784
- if (aborted) return abortedCommandResult();
6785
7226
  const err = error;
6786
7227
  return {
6787
7228
  stdout: err.stdout ?? "",
6788
- stderr: err.stderr ?? err.message ?? String(error),
7229
+ stderr: err.stderr ?? err.message ?? "",
6789
7230
  exitCode: err.exitCode ?? 1
6790
7231
  };
6791
- } finally {
6792
- options?.signal?.removeEventListener("abort", abort);
6793
7232
  }
6794
7233
  };
6795
- return {
6796
- executeCommand,
6797
- spawn(command, options) {
6798
- return spawnDaytonaProcess(sandbox, command, {
6799
- ...options,
6800
- commandTimeout
6801
- });
6802
- },
6803
- async readFile(path5) {
6804
- try {
6805
- const bytes = await sandbox.fs.downloadFile(path5);
6806
- return Buffer.from(bytes).toString("utf-8");
6807
- } catch (error) {
6808
- throw new DaytonaCommandError(
6809
- `Failed to read file "${path5}": ${toError(error).message}`,
6810
- toError(error)
6811
- );
6812
- }
6813
- },
6814
- async writeFiles(files) {
6815
- try {
6816
- for (const dir of uniqueParentDirectories(files.map((f) => f.path))) {
6817
- const result = await executeCommand(`mkdir -p ${shellQuote(dir)}`);
6818
- if (result.exitCode !== 0) {
6819
- throw new DaytonaCommandError(
6820
- `Failed to create directory "${dir}": ${result.stderr}`
6821
- );
6822
- }
7234
+ const arch = async () => {
7235
+ if (!archPromise) {
7236
+ const attempt = (async () => {
7237
+ const result = await exec("uname -m");
7238
+ if (result.exitCode !== 0) {
7239
+ throw new AppleContainerSandboxError(
7240
+ `Failed to detect container architecture: ${result.stderr}`,
7241
+ containerId
7242
+ );
6823
7243
  }
6824
- await sandbox.fs.uploadFiles(
6825
- files.map((file) => ({
6826
- source: Buffer.from(file.content),
6827
- destination: file.path
6828
- }))
6829
- );
6830
- } catch (error) {
6831
- if (error instanceof DaytonaSandboxError) throw error;
6832
- const err = toError(error);
6833
- throw new DaytonaCommandError(
6834
- `Failed to write files: ${err.message}`,
6835
- err
6836
- );
6837
- }
7244
+ return result.stdout.trim();
7245
+ })();
7246
+ archPromise = attempt.catch((err) => {
7247
+ archPromise = null;
7248
+ throw err;
7249
+ });
7250
+ }
7251
+ return archPromise;
7252
+ };
7253
+ const installPackages = async (packages) => {
7254
+ if (packages.length === 0) return;
7255
+ const quoted = packages.map(shellQuote).join(" ");
7256
+ let cmd;
7257
+ if (packageManager === "apt-get") {
7258
+ cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
7259
+ } else {
7260
+ cmd = `apk add --no-cache ${quoted}`;
7261
+ }
7262
+ const result = await exec(cmd);
7263
+ if (result.exitCode !== 0) {
7264
+ throw new PackageInstallError(
7265
+ packages,
7266
+ image,
7267
+ packageManager,
7268
+ result.stderr,
7269
+ containerId
7270
+ );
7271
+ }
7272
+ if (packageManager === "apt-get") aptUpdated = true;
7273
+ };
7274
+ const ensureTool = async (checkName, installName) => {
7275
+ const cacheKey = installName ?? checkName;
7276
+ if (ensuredTools.has(cacheKey)) return;
7277
+ const check = await exec(`which ${shellQuote(checkName)}`);
7278
+ if (check.exitCode === 0) {
7279
+ ensuredTools.add(cacheKey);
7280
+ return;
7281
+ }
7282
+ await installPackages([installName ?? checkName]);
7283
+ ensuredTools.add(cacheKey);
7284
+ };
7285
+ return {
7286
+ containerId,
7287
+ image,
7288
+ packageManager,
7289
+ arch,
7290
+ exec,
7291
+ installPackages,
7292
+ ensureTool
7293
+ };
7294
+ }
7295
+ async function runAppleBuild(args, showBuildLogs) {
7296
+ try {
7297
+ if (showBuildLogs) {
7298
+ await runStreamed(CLI, args);
7299
+ } else {
7300
+ await spawn3(CLI, args);
7301
+ }
7302
+ } catch (error) {
7303
+ throw new AppleContainerImageBuildError(getCliErrorMessage(error));
7304
+ }
7305
+ }
7306
+ function runStreamed(command, args) {
7307
+ return new Promise((resolve4, reject) => {
7308
+ const child = childSpawn2(command, args, { stdio: "inherit" });
7309
+ child.once("error", reject);
7310
+ child.once(
7311
+ "exit",
7312
+ (code) => code === 0 ? resolve4() : reject(
7313
+ new Error(
7314
+ `${command} build exited with code ${code} (see build output above)`
7315
+ )
7316
+ )
7317
+ );
7318
+ });
7319
+ }
7320
+ async function createAppleContainerSandbox(options = {}) {
7321
+ if (isAppleContainerfileOptions(options)) {
7322
+ return new ContainerfileStrategy(options, appleEngine, {
7323
+ dockerfile: options.dockerfile,
7324
+ context: options.context ?? ".",
7325
+ showBuildLogs: options.showBuildLogs ?? false,
7326
+ identity: options.arch
7327
+ }).create();
7328
+ }
7329
+ return new RuntimeStrategy(options, appleEngine, {
7330
+ image: options.image ?? appleEngine.defaultImage,
7331
+ installers: options.installers ?? []
7332
+ }).create();
7333
+ }
7334
+ async function useAppleContainerSandbox(options, fn) {
7335
+ const sandbox = await createAppleContainerSandbox(options);
7336
+ try {
7337
+ return await fn(sandbox);
7338
+ } finally {
7339
+ await sandbox.dispose();
7340
+ }
7341
+ }
7342
+
7343
+ // packages/context/src/lib/sandbox/bash-exception.ts
7344
+ var BashException = class extends Error {
7345
+ };
7346
+
7347
+ // packages/context/src/lib/sandbox/bash-meta.ts
7348
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
7349
+ var store = new AsyncLocalStorage2();
7350
+ function runWithBashMeta(fn) {
7351
+ return store.run({ hidden: {} }, fn);
7352
+ }
7353
+ function useBashMeta() {
7354
+ const state = store.getStore();
7355
+ if (!state) return null;
7356
+ return {
7357
+ setHidden(patch) {
7358
+ state.hidden = { ...state.hidden, ...patch };
6838
7359
  },
6839
- async dispose() {
7360
+ setReminder(text) {
7361
+ state.reminder = text;
6840
7362
  },
6841
- [Symbol.asyncDispose]() {
6842
- return this.dispose();
7363
+ clearReminder() {
7364
+ state.reminder = void 0;
6843
7365
  }
6844
7366
  };
6845
7367
  }
6846
- function spawnDaytonaProcess(sandbox, command, options = {}) {
6847
- const sessionId = createSessionId("spawn");
6848
- const stdout = createTextReadable();
6849
- const stderr = createTextReadable();
6850
- const exit = runSpawnedSession({
6851
- sandbox,
6852
- sessionId,
6853
- command: buildSessionCommand(command, options),
6854
- signal: options.signal,
6855
- commandTimeout: options.commandTimeout,
6856
- stdout,
6857
- stderr
6858
- });
7368
+ function readBashMeta() {
7369
+ return store.getStore();
7370
+ }
7371
+
7372
+ // packages/context/src/lib/sandbox/bash-tool.ts
7373
+ import { tool as tool2 } from "ai";
7374
+ import {
7375
+ createBashTool as externalCreateBashTool
7376
+ } from "bash-tool";
7377
+ import z3 from "zod";
7378
+
7379
+ // packages/context/src/lib/sandbox/upload-skills.ts
7380
+ import * as path3 from "node:path";
7381
+
7382
+ // packages/context/src/lib/skills/loader.ts
7383
+ import * as fs from "node:fs";
7384
+ import * as path from "node:path";
7385
+ import YAML from "yaml";
7386
+ function parseFrontmatter(content) {
7387
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
7388
+ const match = content.match(frontmatterRegex);
7389
+ if (!match) {
7390
+ throw new Error("Invalid SKILL.md: missing or malformed frontmatter");
7391
+ }
7392
+ const [, yamlContent, body] = match;
7393
+ const frontmatter = YAML.parse(yamlContent);
7394
+ if (!frontmatter.name || typeof frontmatter.name !== "string") {
7395
+ throw new Error('Invalid SKILL.md: frontmatter must have a "name" field');
7396
+ }
7397
+ if (!frontmatter.description || typeof frontmatter.description !== "string") {
7398
+ throw new Error(
7399
+ 'Invalid SKILL.md: frontmatter must have a "description" field'
7400
+ );
7401
+ }
6859
7402
  return {
6860
- stdout: stdout.stream,
6861
- stderr: stderr.stream,
6862
- exit
7403
+ frontmatter,
7404
+ body: body.trim()
6863
7405
  };
6864
7406
  }
6865
- async function runSpawnedSession(args) {
6866
- const {
6867
- sandbox,
6868
- sessionId,
6869
- command,
6870
- signal,
6871
- commandTimeout,
6872
- stdout,
6873
- stderr
6874
- } = args;
6875
- let sessionCreated = false;
6876
- let aborted = signal?.aborted ?? false;
6877
- let resolveAbort;
6878
- const abortPromise = new Promise((resolve4) => {
6879
- resolveAbort = () => resolve4("aborted");
6880
- });
6881
- const abort = () => {
6882
- aborted = true;
6883
- resolveAbort?.();
6884
- if (sessionCreated) {
6885
- sandbox.process.deleteSession(sessionId).catch(() => {
6886
- });
6887
- }
7407
+ function loadSkillMetadata(skillMdPath) {
7408
+ const content = fs.readFileSync(skillMdPath, "utf-8");
7409
+ const parsed = parseFrontmatter(content);
7410
+ const skillDir = path.dirname(skillMdPath);
7411
+ return {
7412
+ name: parsed.frontmatter.name,
7413
+ description: parsed.frontmatter.description,
7414
+ path: skillDir,
7415
+ skillMdPath
6888
7416
  };
6889
- if (signal) {
6890
- if (signal.aborted) abort();
6891
- else signal.addEventListener("abort", abort, { once: true });
7417
+ }
7418
+ function discoverSkillsInDirectory(directory) {
7419
+ const skills2 = [];
7420
+ const expandedDir = directory.startsWith("~") ? path.join(process.env.HOME || "", directory.slice(1)) : directory;
7421
+ if (!fs.existsSync(expandedDir)) {
7422
+ return skills2;
6892
7423
  }
6893
- try {
6894
- await sandbox.process.createSession(sessionId);
6895
- sessionCreated = true;
6896
- if (aborted) return abortedExitInfo();
6897
- const response = await sandbox.process.executeSessionCommand(
6898
- sessionId,
6899
- {
6900
- command,
6901
- runAsync: true,
6902
- suppressInputEcho: true
6903
- },
6904
- commandTimeout
6905
- );
6906
- const commandId = response.cmdId;
6907
- if (!commandId) {
6908
- throw new DaytonaCommandError(
6909
- "Daytona did not return a command id for the spawned session command."
6910
- );
7424
+ const entries = fs.readdirSync(expandedDir, { withFileTypes: true });
7425
+ for (const entry of entries) {
7426
+ if (!entry.isDirectory()) continue;
7427
+ const skillMdPath = path.join(expandedDir, entry.name, "SKILL.md");
7428
+ if (!fs.existsSync(skillMdPath)) continue;
7429
+ try {
7430
+ const metadata = loadSkillMetadata(skillMdPath);
7431
+ skills2.push(metadata);
7432
+ } catch (error) {
7433
+ console.warn(`Warning: Failed to load skill at ${skillMdPath}:`, error);
6911
7434
  }
6912
- if (aborted) return abortedExitInfo();
6913
- const logsTask = sandbox.process.getSessionCommandLogs(
6914
- sessionId,
6915
- commandId,
6916
- (chunk) => stdout.enqueue(chunk),
6917
- (chunk) => stderr.enqueue(chunk)
6918
- );
6919
- logsTask.catch(() => {
6920
- });
6921
- const winner = await Promise.race([
6922
- logsTask.then(() => "logs"),
6923
- abortPromise
6924
- ]);
6925
- if (winner === "aborted") {
6926
- await sandbox.process.deleteSession(sessionId).catch(() => {
6927
- });
6928
- return abortedExitInfo();
7435
+ }
7436
+ return skills2;
7437
+ }
7438
+
7439
+ // packages/context/src/lib/sandbox/walk.ts
7440
+ import { readFile, readdir } from "node:fs/promises";
7441
+ import * as path2 from "node:path";
7442
+ function expandHome(input) {
7443
+ if (!input.startsWith("~")) return input;
7444
+ const home = process.env.HOME ?? "";
7445
+ return path2.join(home, input.slice(1));
7446
+ }
7447
+ async function walkDirectory(root) {
7448
+ const absoluteRoot = path2.resolve(expandHome(root));
7449
+ const files = [];
7450
+ async function walk(current) {
7451
+ let entries;
7452
+ try {
7453
+ entries = await readdir(current, { withFileTypes: true });
7454
+ } catch {
7455
+ return;
6929
7456
  }
6930
- await logsTask;
6931
- const code = await waitForSessionCommandExitCode({
6932
- sandbox,
6933
- sessionId,
6934
- commandId,
6935
- timeoutMs: sessionExitPollTimeoutMs(commandTimeout),
6936
- isAborted: () => aborted
6937
- });
6938
- return { code, signal: null, success: code === 0 };
6939
- } catch (error) {
6940
- if (aborted) return abortedExitInfo();
6941
- const err = toError(error);
6942
- stdout.error(err);
6943
- stderr.error(err);
6944
- throw err;
6945
- } finally {
6946
- if (signal) {
6947
- signal.removeEventListener("abort", abort);
7457
+ for (const entry of entries) {
7458
+ if (entry.name.startsWith(".")) continue;
7459
+ if (entry.isSymbolicLink()) continue;
7460
+ const entryPath = path2.join(current, entry.name);
7461
+ if (entry.isDirectory()) {
7462
+ await walk(entryPath);
7463
+ } else if (entry.isFile()) {
7464
+ const content = await readFile(entryPath);
7465
+ const relative3 = path2.relative(absoluteRoot, entryPath).split(path2.sep).join("/");
7466
+ files.push({ path: entryPath, relativePath: relative3, content });
7467
+ }
6948
7468
  }
6949
- stdout.close();
6950
- stderr.close();
6951
- if (sessionCreated) {
6952
- await sandbox.process.deleteSession(sessionId).catch(() => {
7469
+ }
7470
+ await walk(absoluteRoot);
7471
+ return files;
7472
+ }
7473
+
7474
+ // packages/context/src/lib/sandbox/upload-skills.ts
7475
+ function expandHome2(input) {
7476
+ if (!input.startsWith("~")) return input;
7477
+ const home = process.env.HOME ?? "";
7478
+ return path3.join(home, input.slice(1));
7479
+ }
7480
+ function joinSandbox(base, relative3) {
7481
+ if (!relative3) return base;
7482
+ const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
7483
+ return `${normalizedBase}/${relative3}`;
7484
+ }
7485
+ function discoverSkillMappings(inputs) {
7486
+ if (inputs.length === 0) return [];
7487
+ const discoveredByName = /* @__PURE__ */ new Map();
7488
+ for (const { host, sandbox: sandboxBase } of inputs) {
7489
+ const absoluteHost = path3.resolve(expandHome2(host));
7490
+ for (const skill of discoverSkillsInDirectory(host)) {
7491
+ const absoluteSkillMd = path3.resolve(skill.skillMdPath);
7492
+ const relative3 = path3.relative(absoluteHost, absoluteSkillMd).split(path3.sep).join("/");
7493
+ discoveredByName.set(skill.name, {
7494
+ name: skill.name,
7495
+ description: skill.description,
7496
+ host: skill.skillMdPath,
7497
+ sandbox: joinSandbox(sandboxBase, relative3)
6953
7498
  });
6954
7499
  }
6955
7500
  }
7501
+ return Array.from(discoveredByName.values());
6956
7502
  }
6957
- async function waitForSessionCommandExitCode(args) {
6958
- const { sandbox, sessionId, commandId, timeoutMs, isAborted } = args;
6959
- const deadline = Date.now() + timeoutMs;
6960
- while (true) {
6961
- if (isAborted()) throw new DaytonaCommandError("Daytona command aborted.");
6962
- const info = await sandbox.process.getSessionCommand(sessionId, commandId);
6963
- if (typeof info.exitCode === "number") {
6964
- return info.exitCode;
6965
- }
6966
- if (Date.now() >= deadline) {
6967
- throw new DaytonaCommandError(
6968
- `Daytona session command "${commandId}" logs closed before an exit code became available.`
6969
- );
7503
+ async function uploadSkills(sandbox, inputs) {
7504
+ if (inputs.length === 0) return [];
7505
+ const skills2 = discoverSkillMappings(inputs);
7506
+ const filesToUpload = [];
7507
+ for (const { host, sandbox: sandboxBase } of inputs) {
7508
+ const walked = await walkDirectory(host);
7509
+ for (const file of walked) {
7510
+ filesToUpload.push({
7511
+ path: joinSandbox(sandboxBase, file.relativePath),
7512
+ content: file.content
7513
+ });
6970
7514
  }
6971
- await delay(DAYTONA_EXIT_POLL_INTERVAL_MS);
6972
7515
  }
6973
- }
6974
- function sessionExitPollTimeoutMs(commandTimeout) {
6975
- if (commandTimeout === void 0 || commandTimeout === 0) {
6976
- return DAYTONA_EXIT_POLL_TIMEOUT_MS;
7516
+ if (filesToUpload.length > 0) {
7517
+ await sandbox.writeFiles(filesToUpload);
6977
7518
  }
6978
- return Math.max(commandTimeout * 1e3, DAYTONA_EXIT_POLL_TIMEOUT_MS);
6979
- }
6980
- function delay(ms) {
6981
- return new Promise((resolve4) => setTimeout(resolve4, ms));
7519
+ return skills2;
6982
7520
  }
6983
- function createTextReadable() {
6984
- const encoder = new TextEncoder();
6985
- let controller;
6986
- let closed = false;
7521
+
7522
+ // packages/context/src/lib/sandbox/bash-tool.ts
7523
+ var REASONING_INSTRUCTION = 'Every bash tool call must include a brief non-empty "reasoning" input explaining why the command is needed.';
7524
+ function withBashExceptionCatch(sandbox) {
6987
7525
  return {
6988
- stream: new ReadableStream({
6989
- start(streamController) {
6990
- controller = streamController;
7526
+ ...sandbox,
7527
+ async executeCommand(command, options) {
7528
+ try {
7529
+ return await sandbox.executeCommand(command, options);
7530
+ } catch (err) {
7531
+ if (err instanceof BashException) return err.format();
7532
+ throw err;
7533
+ }
7534
+ }
7535
+ };
7536
+ }
7537
+ async function createBashTool(options) {
7538
+ const {
7539
+ skills: skillInputs = [],
7540
+ extraInstructions,
7541
+ sandbox: backend,
7542
+ destination,
7543
+ ...rest
7544
+ } = options;
7545
+ const sandbox = withAbortSignal(withBashExceptionCatch(backend));
7546
+ const combinedInstructions = [extraInstructions, REASONING_INSTRUCTION].filter(Boolean).join("\n\n");
7547
+ const toolkit = await externalCreateBashTool({
7548
+ ...rest,
7549
+ sandbox,
7550
+ destination,
7551
+ extraInstructions: combinedInstructions
7552
+ });
7553
+ const upstreamBash = toolkit.bash;
7554
+ const originalExecute = upstreamBash.execute;
7555
+ const toolBuilder = tool2;
7556
+ const bash = toolBuilder({
7557
+ ...upstreamBash,
7558
+ description: upstreamBash.description ?? "",
7559
+ inputSchema: z3.object({
7560
+ command: z3.string().describe("The bash command to execute"),
7561
+ reasoning: z3.string().trim().describe("Brief reason for executing this command")
7562
+ }),
7563
+ execute: async ({ command }, execOptions) => {
7564
+ if (!originalExecute) {
7565
+ throw new Error("bash tool execution is not available");
6991
7566
  }
6992
- }),
6993
- enqueue(chunk) {
6994
- if (closed || !chunk) return;
6995
- controller?.enqueue(encoder.encode(chunk));
6996
- },
6997
- close() {
6998
- if (closed) return;
6999
- closed = true;
7000
- controller?.close();
7567
+ const { abortSignal } = execOptions;
7568
+ return runWithAbortSignal(
7569
+ abortSignal,
7570
+ () => runWithBashMeta(async () => {
7571
+ const result = await originalExecute({ command }, execOptions);
7572
+ const state = readBashMeta();
7573
+ if (!state) return result;
7574
+ const hasHidden = Object.keys(state.hidden).length > 0;
7575
+ const hasReminder = state.reminder !== void 0;
7576
+ if (!hasHidden && !hasReminder) return result;
7577
+ return {
7578
+ ...result,
7579
+ ...hasHidden ? { meta: state.hidden } : {},
7580
+ ...hasReminder ? { reminder: state.reminder } : {}
7581
+ };
7582
+ })
7583
+ );
7001
7584
  },
7002
- error(error) {
7003
- if (closed) return;
7004
- closed = true;
7005
- controller?.error(error);
7585
+ toModelOutput: ({ output }) => {
7586
+ if (typeof output !== "object" || output === null) {
7587
+ return { type: "json", value: output };
7588
+ }
7589
+ const { meta: _meta, ...visible } = output;
7590
+ return { type: "json", value: visible };
7591
+ }
7592
+ });
7593
+ const upstreamWriteFile = toolkit.tools.writeFile;
7594
+ const originalWriteExecute = upstreamWriteFile.execute;
7595
+ const writeFileBuilder = tool2;
7596
+ const writeFile2 = writeFileBuilder({
7597
+ ...upstreamWriteFile,
7598
+ execute: async (input, options2) => {
7599
+ if (!originalWriteExecute) {
7600
+ throw new Error("writeFile tool execution is not available");
7601
+ }
7602
+ try {
7603
+ return await originalWriteExecute(input, options2);
7604
+ } catch (err) {
7605
+ if (err instanceof BashException) return err.format();
7606
+ throw err;
7607
+ }
7006
7608
  }
7609
+ });
7610
+ const skills2 = await uploadSkills(backend, skillInputs);
7611
+ return {
7612
+ ...toolkit,
7613
+ sandbox,
7614
+ bash,
7615
+ tools: { ...toolkit.tools, bash, writeFile: writeFile2 },
7616
+ skills: skills2
7007
7617
  };
7008
7618
  }
7009
- function buildSessionCommand(command, options) {
7010
- const cwdPrefix = options.cwd ? `cd ${shellQuote(options.cwd)} && ` : "";
7011
- const env = options.env ?? {};
7012
- const entries = Object.entries(env);
7013
- if (entries.length === 0) {
7014
- return `sh -lc ${shellQuote(`${cwdPrefix}${command}`)}`;
7619
+
7620
+ // packages/context/src/lib/sandbox/binary-bridges.ts
7621
+ import { existsSync as existsSync3 } from "fs";
7622
+ import { defineCommand } from "just-bash";
7623
+ import spawn4 from "nano-spawn";
7624
+ import * as path4 from "path";
7625
+ function createBinaryBridges(...binaries) {
7626
+ return binaries.map((input) => {
7627
+ const config = typeof input === "string" ? { name: input } : input;
7628
+ const { name, binaryPath = name, allowedArgs } = config;
7629
+ return defineCommand(name, async (args, ctx) => {
7630
+ if (allowedArgs) {
7631
+ const invalidArg = args.find((arg) => !allowedArgs.test(arg));
7632
+ if (invalidArg) {
7633
+ return {
7634
+ stdout: "",
7635
+ stderr: `${name}: argument '${invalidArg}' not allowed by security policy`,
7636
+ exitCode: 1
7637
+ };
7638
+ }
7639
+ }
7640
+ try {
7641
+ const realCwd = resolveRealCwd(ctx);
7642
+ const resolvedArgs = args.map((arg) => {
7643
+ if (arg.startsWith("-")) {
7644
+ return arg;
7645
+ }
7646
+ const hasExtension = path4.extname(arg) !== "";
7647
+ const hasPathSep = arg.includes(path4.sep) || arg.includes("/");
7648
+ const isRelative = arg.startsWith(".");
7649
+ if (hasExtension || hasPathSep || isRelative) {
7650
+ return path4.resolve(realCwd, arg);
7651
+ }
7652
+ return arg;
7653
+ });
7654
+ const mergedEnv = {
7655
+ ...process.env,
7656
+ ...Object.fromEntries(ctx.env),
7657
+ // ctx.env is a Map, convert to object
7658
+ PATH: process.env.PATH
7659
+ // Always use host PATH for binary bridges
7660
+ };
7661
+ const result = await spawn4(binaryPath, resolvedArgs, {
7662
+ cwd: realCwd,
7663
+ env: mergedEnv
7664
+ });
7665
+ return {
7666
+ stdout: result.stdout,
7667
+ stderr: result.stderr,
7668
+ exitCode: 0
7669
+ };
7670
+ } catch (error) {
7671
+ if (error && typeof error === "object") {
7672
+ const err = error;
7673
+ const cause = err.cause;
7674
+ if (cause?.code === "ENOENT") {
7675
+ return {
7676
+ stdout: "",
7677
+ stderr: `${name}: ${binaryPath} not found`,
7678
+ exitCode: 127
7679
+ };
7680
+ }
7681
+ }
7682
+ if (error && typeof error === "object" && "exitCode" in error) {
7683
+ const subprocessError = error;
7684
+ return {
7685
+ stdout: subprocessError.stdout ?? "",
7686
+ stderr: subprocessError.stderr ?? "",
7687
+ exitCode: subprocessError.exitCode ?? 1
7688
+ };
7689
+ }
7690
+ return {
7691
+ stdout: "",
7692
+ stderr: `${name}: ${error instanceof Error ? error.message : String(error)}`,
7693
+ exitCode: 127
7694
+ };
7695
+ }
7696
+ });
7697
+ });
7698
+ }
7699
+ function resolveRealCwd(ctx) {
7700
+ const fs2 = ctx.fs;
7701
+ let realCwd;
7702
+ if (fs2.root) {
7703
+ realCwd = path4.join(fs2.root, ctx.cwd);
7704
+ } else if (typeof fs2.getMountPoint === "function" && typeof fs2.toRealPath === "function") {
7705
+ const real = fs2.toRealPath(ctx.cwd);
7706
+ realCwd = real ?? process.cwd();
7707
+ } else {
7708
+ realCwd = process.cwd();
7015
7709
  }
7016
- for (const [key] of entries) {
7017
- validateEnvKey(key);
7710
+ if (!existsSync3(realCwd)) {
7711
+ realCwd = process.cwd();
7018
7712
  }
7019
- const exports = entries.map(([key, value]) => `export ${key}=${shellQuote(value)}`).join("; ");
7020
- return `sh -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
7713
+ return realCwd;
7021
7714
  }
7022
- function validateEnvKey(key) {
7023
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
7024
- throw new DaytonaSandboxError(
7025
- `Invalid environment variable key: "${key}". Use shell-compatible environment variable names.`
7026
- );
7715
+
7716
+ // packages/context/src/lib/sandbox/daytona-sandbox.ts
7717
+ import "bash-tool";
7718
+ import { randomUUID } from "node:crypto";
7719
+ var DAYTONA_DEFAULT_DESTINATION = "/home/daytona";
7720
+ var DAYTONA_EXIT_POLL_INTERVAL_MS = 250;
7721
+ var DAYTONA_EXIT_POLL_TIMEOUT_MS = 3e4;
7722
+ var DaytonaSandboxError = class extends Error {
7723
+ constructor(message2, cause) {
7724
+ super(message2);
7725
+ this.name = "DaytonaSandboxError";
7726
+ this.cause = cause;
7027
7727
  }
7028
- }
7029
- function createSessionId(kind) {
7030
- return `deepagents-${kind}-${randomUUID()}`;
7031
- }
7032
- function abortedCommandResult() {
7033
- return {
7034
- stdout: "",
7035
- stderr: "Command aborted",
7036
- exitCode: 1
7037
- };
7038
- }
7039
- function abortedExitInfo() {
7040
- return {
7041
- code: null,
7042
- signal: "SIGKILL",
7043
- success: false
7044
- };
7045
- }
7046
- function uniqueParentDirectories(paths) {
7047
- const dirs = /* @__PURE__ */ new Set();
7048
- for (const path5 of paths) {
7049
- const index = path5.lastIndexOf("/");
7050
- if (index > 0) {
7051
- dirs.add(path5.slice(0, index));
7728
+ };
7729
+ var DaytonaCreationError = class extends DaytonaSandboxError {
7730
+ constructor(message2, cause) {
7731
+ super(`Failed to create Daytona sandbox: ${message2}`, cause);
7732
+ this.name = "DaytonaCreationError";
7733
+ }
7734
+ };
7735
+ var DaytonaCommandError = class extends DaytonaSandboxError {
7736
+ constructor(message2, cause) {
7737
+ super(message2, cause);
7738
+ this.name = "DaytonaCommandError";
7739
+ }
7740
+ };
7741
+ async function createDaytonaSandbox(client, options = {}) {
7742
+ validateDaytonaOptions(options);
7743
+ const sdk = await import("@daytona/sdk");
7744
+ let sandbox;
7745
+ try {
7746
+ if (options.sandboxId !== void 0) {
7747
+ sandbox = await client.get(options.sandboxId);
7748
+ await startIfStopped(sandbox, options);
7749
+ } else {
7750
+ sandbox = await acquireReusedSandbox(client, options, sdk);
7052
7751
  }
7752
+ } catch (error) {
7753
+ throw normalizeDaytonaError(error, sdk);
7053
7754
  }
7054
- return [...dirs];
7755
+ return createDaytonaSandboxMethods({
7756
+ sandbox,
7757
+ commandTimeout: options.commandTimeout
7758
+ });
7055
7759
  }
7056
- function compactObject(input) {
7057
- const output = {};
7058
- for (const [key, value] of Object.entries(input)) {
7059
- if (value !== void 0) {
7060
- output[key] = value;
7760
+ var UNRECOVERABLE_SANDBOX_STATES = /* @__PURE__ */ new Set([
7761
+ "error",
7762
+ "build_failed",
7763
+ "destroyed"
7764
+ ]);
7765
+ async function acquireReusedSandbox(client, options, sdk) {
7766
+ let existing;
7767
+ try {
7768
+ existing = await client.get(options.name);
7769
+ } catch (error) {
7770
+ if (error instanceof sdk.DaytonaNotFoundError) {
7771
+ return createSandbox(client, options);
7061
7772
  }
7773
+ throw error;
7062
7774
  }
7063
- return output;
7775
+ if (existing.state && UNRECOVERABLE_SANDBOX_STATES.has(existing.state)) {
7776
+ await deleteSandboxQuietly(existing, options);
7777
+ return createSandbox(client, options);
7778
+ }
7779
+ await startIfStopped(existing, options);
7780
+ return existing;
7064
7781
  }
7065
- function toError(error) {
7066
- return error instanceof Error ? error : new Error(String(error));
7782
+ async function deleteSandboxQuietly(sandbox, options) {
7783
+ try {
7784
+ await sandbox.delete(options.deleteTimeout);
7785
+ } catch {
7786
+ }
7067
7787
  }
7068
-
7069
- // packages/context/src/lib/sandbox/docker-sandbox.ts
7070
- import "bash-tool";
7071
- import spawn3 from "nano-spawn";
7072
- import { spawn as childSpawn } from "node:child_process";
7073
- import { createHash } from "node:crypto";
7074
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
7075
- import { Readable as Readable2 } from "node:stream";
7076
-
7077
- // packages/context/src/lib/sandbox/docker-sandbox-errors.ts
7078
- var DockerSandboxError = class extends Error {
7079
- containerId;
7080
- constructor(message2, containerId) {
7081
- super(message2);
7082
- this.name = "DockerSandboxError";
7083
- this.containerId = containerId;
7788
+ async function startIfStopped(sandbox, options) {
7789
+ if (sandbox.state && sandbox.state !== "started") {
7790
+ await sandbox.start(options.startTimeout ?? options.createTimeout);
7084
7791
  }
7085
- };
7086
- var DockerNotAvailableError = class extends DockerSandboxError {
7087
- constructor() {
7088
- super("Docker is not available. Ensure Docker daemon is running.");
7089
- this.name = "DockerNotAvailableError";
7792
+ }
7793
+ function normalizeDaytonaError(error, sdk) {
7794
+ const err = toError(error);
7795
+ if (err instanceof sdk.DaytonaError) {
7796
+ return err;
7090
7797
  }
7091
- };
7092
- var ContainerCreationError = class extends DockerSandboxError {
7093
- image;
7094
- cause;
7095
- constructor(message2, image, cause) {
7096
- super(`Failed to create container from image "${image}": ${message2}`);
7097
- this.name = "ContainerCreationError";
7098
- this.image = image;
7099
- this.cause = cause;
7798
+ return new DaytonaCreationError(err.message, err);
7799
+ }
7800
+ function createSandbox(client, options) {
7801
+ const base = compactObject({
7802
+ name: options.name,
7803
+ user: options.user,
7804
+ language: options.language,
7805
+ envVars: options.envVars,
7806
+ labels: options.labels,
7807
+ public: options.public,
7808
+ autoStopInterval: options.autoStopInterval,
7809
+ autoArchiveInterval: options.autoArchiveInterval,
7810
+ autoDeleteInterval: options.autoDeleteInterval,
7811
+ volumes: options.volumes,
7812
+ networkBlockAll: options.networkBlockAll,
7813
+ networkAllowList: options.networkAllowList,
7814
+ ephemeral: options.ephemeral
7815
+ });
7816
+ if (options.image !== void 0) {
7817
+ const params2 = compactObject({
7818
+ ...base,
7819
+ image: options.image,
7820
+ resources: options.resources
7821
+ });
7822
+ return client.create(params2, {
7823
+ timeout: options.createTimeout,
7824
+ onSnapshotCreateLogs: options.onSnapshotCreateLogs
7825
+ });
7100
7826
  }
7101
- };
7102
- var PackageInstallError = class extends DockerSandboxError {
7103
- packages;
7104
- image;
7105
- packageManager;
7106
- stderr;
7107
- constructor(packages, image, packageManager, stderr, containerId) {
7108
- super(
7109
- `Package installation failed for [${packages.join(", ")}] using ${packageManager} on ${image}: ${stderr}`,
7110
- containerId
7827
+ const params = compactObject({
7828
+ ...base,
7829
+ snapshot: options.snapshot
7830
+ });
7831
+ return client.create(params, { timeout: options.createTimeout });
7832
+ }
7833
+ function validateDaytonaOptions(options) {
7834
+ if (options.image && options.snapshot) {
7835
+ throw new DaytonaSandboxError(
7836
+ 'Daytona sandbox options cannot include both "image" and "snapshot". Choose one environment source.'
7111
7837
  );
7112
- this.name = "PackageInstallError";
7113
- this.packages = packages;
7114
- this.image = image;
7115
- this.packageManager = packageManager;
7116
- this.stderr = stderr;
7117
7838
  }
7118
- };
7119
- var InstallError = class extends DockerSandboxError {
7120
- target;
7121
- source;
7122
- reason;
7123
- url;
7124
- constructor(opts) {
7125
- const where = opts.url ? `${opts.source} (${opts.url})` : opts.source;
7126
- super(
7127
- `Failed to install "${opts.target}" via ${where}: ${opts.reason}`,
7128
- opts.containerId
7839
+ if (options.resources && !options.image) {
7840
+ throw new DaytonaSandboxError(
7841
+ 'Daytona sandbox options can only include "resources" when creating from "image". The Daytona SDK does not apply resources during default or snapshot creation.'
7129
7842
  );
7130
- this.name = "InstallError";
7131
- this.target = opts.target;
7132
- this.source = opts.source;
7133
- this.reason = opts.reason;
7134
- this.url = opts.url;
7135
- }
7136
- };
7137
- var MissingRuntimeError = class extends DockerSandboxError {
7138
- runtime;
7139
- required;
7140
- constructor(runtime, required, details, containerId) {
7141
- const base = `Required runtime "${runtime}" is not installed (needs: ${required.join(", ")}).`;
7142
- super(details ? `${base} ${details}` : base, containerId);
7143
- this.name = "MissingRuntimeError";
7144
- this.runtime = runtime;
7145
- this.required = required;
7146
7843
  }
7147
- };
7148
- var VolumePathError = class extends DockerSandboxError {
7149
- source;
7150
- containerPath;
7151
- reason;
7152
- constructor(source, containerPath, reason) {
7153
- super(
7154
- `Invalid Docker volume path "${source}" -> "${containerPath}": ${reason}`
7844
+ if (options.sandboxId === void 0 && options.name === void 0) {
7845
+ throw new DaytonaSandboxError(
7846
+ 'Daytona sandbox options require "name" (get-or-create) or "sandboxId" (attach). An unnamed sandbox cannot be reclaimed, since dispose() does not delete it.'
7155
7847
  );
7156
- this.name = "VolumePathError";
7157
- this.source = source;
7158
- this.containerPath = containerPath;
7159
- this.reason = reason;
7160
- }
7161
- };
7162
- var VolumeInspectError = class extends DockerSandboxError {
7163
- volume;
7164
- reason;
7165
- constructor(volume, reason) {
7166
- super(`Failed to inspect Docker volume "${volume}": ${reason}`);
7167
- this.name = "VolumeInspectError";
7168
- this.volume = volume;
7169
- this.reason = reason;
7170
- }
7171
- };
7172
- var VolumeCreateError = class extends DockerSandboxError {
7173
- volume;
7174
- reason;
7175
- constructor(volume, reason) {
7176
- super(`Failed to create Docker volume "${volume}": ${reason}`);
7177
- this.name = "VolumeCreateError";
7178
- this.volume = volume;
7179
- this.reason = reason;
7180
- }
7181
- };
7182
- var VolumeRemoveError = class extends DockerSandboxError {
7183
- volume;
7184
- reason;
7185
- constructor(volume, reason) {
7186
- super(`Failed to remove Docker volume "${volume}": ${reason}`);
7187
- this.name = "VolumeRemoveError";
7188
- this.volume = volume;
7189
- this.reason = reason;
7190
7848
  }
7191
- };
7192
- var DockerfileBuildError = class extends DockerSandboxError {
7193
- stderr;
7194
- constructor(stderr) {
7195
- super(`Dockerfile build failed: ${stderr}`);
7196
- this.name = "DockerfileBuildError";
7197
- this.stderr = stderr;
7849
+ if (!options.sandboxId) {
7850
+ return;
7198
7851
  }
7199
- };
7200
- var ComposeStartError = class extends DockerSandboxError {
7201
- composeFile;
7202
- stderr;
7203
- constructor(composeFile, stderr) {
7204
- super(`Docker Compose failed to start: ${stderr}`);
7205
- this.name = "ComposeStartError";
7206
- this.composeFile = composeFile;
7207
- this.stderr = stderr;
7852
+ const creationOnlyFields = [
7853
+ "name",
7854
+ "user",
7855
+ "snapshot",
7856
+ "image",
7857
+ "language",
7858
+ "envVars",
7859
+ "labels",
7860
+ "public",
7861
+ "resources",
7862
+ "volumes",
7863
+ "networkAllowList",
7864
+ "networkBlockAll",
7865
+ "autoStopInterval",
7866
+ "autoArchiveInterval",
7867
+ "autoDeleteInterval",
7868
+ "ephemeral",
7869
+ "onSnapshotCreateLogs"
7870
+ ];
7871
+ const present = creationOnlyFields.filter((field) => {
7872
+ return options[field] !== void 0;
7873
+ });
7874
+ if (present.length > 0) {
7875
+ throw new DaytonaSandboxError(
7876
+ `Daytona sandbox options cannot combine "sandboxId" with creation options: ${present.join(", ")}`
7877
+ );
7208
7878
  }
7209
- };
7210
-
7211
- // packages/context/src/lib/sandbox/installers/installer.ts
7212
- import "bash-tool";
7213
- import spawn2 from "nano-spawn";
7214
- var Installer = class {
7215
- };
7216
- function isDebianBased(image) {
7217
- const lower = image.toLowerCase();
7218
- if (lower.includes("alpine")) return false;
7219
- const debianPatterns = ["debian", "ubuntu", "node", "python"];
7220
- return debianPatterns.some((pattern) => lower.includes(pattern));
7221
7879
  }
7222
- function createInstallerContext(containerId, image) {
7223
- const packageManager = isDebianBased(image) ? "apt-get" : "apk";
7224
- let archPromise = null;
7225
- const ensuredTools = /* @__PURE__ */ new Set();
7226
- let aptUpdated = false;
7227
- const exec = async (command) => {
7880
+ function createDaytonaSandboxMethods(args) {
7881
+ const { sandbox, commandTimeout } = args;
7882
+ const executeCommand = async (command, options) => {
7883
+ if (options?.signal?.aborted) {
7884
+ return abortedCommandResult();
7885
+ }
7886
+ let aborted = false;
7887
+ const abort = () => {
7888
+ aborted = true;
7889
+ };
7890
+ options?.signal?.addEventListener("abort", abort, { once: true });
7228
7891
  try {
7229
- const result = await spawn2("docker", [
7230
- "exec",
7231
- containerId,
7232
- "sh",
7233
- "-c",
7234
- command
7235
- ]);
7236
- return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
7892
+ if (aborted || options?.signal?.aborted) {
7893
+ return abortedCommandResult();
7894
+ }
7895
+ const response = await sandbox.process.executeCommand(
7896
+ command,
7897
+ void 0,
7898
+ void 0,
7899
+ commandTimeout
7900
+ );
7901
+ if (aborted) return abortedCommandResult();
7902
+ return {
7903
+ stdout: response.result ?? response.artifacts?.stdout ?? "",
7904
+ stderr: "",
7905
+ exitCode: response.exitCode ?? 0
7906
+ };
7237
7907
  } catch (error) {
7908
+ if (aborted) return abortedCommandResult();
7238
7909
  const err = error;
7239
7910
  return {
7240
7911
  stdout: err.stdout ?? "",
7241
- stderr: err.stderr ?? err.message ?? "",
7912
+ stderr: err.stderr ?? err.message ?? String(error),
7242
7913
  exitCode: err.exitCode ?? 1
7243
7914
  };
7915
+ } finally {
7916
+ options?.signal?.removeEventListener("abort", abort);
7244
7917
  }
7245
7918
  };
7246
- const arch = async () => {
7247
- if (!archPromise) {
7248
- const attempt = (async () => {
7249
- const result = await exec("uname -m");
7250
- if (result.exitCode !== 0) {
7251
- throw new DockerSandboxError(
7252
- `Failed to detect container architecture: ${result.stderr}`,
7253
- containerId
7254
- );
7919
+ return {
7920
+ executeCommand,
7921
+ spawn(command, options) {
7922
+ return spawnDaytonaProcess(sandbox, command, {
7923
+ ...options,
7924
+ commandTimeout
7925
+ });
7926
+ },
7927
+ async readFile(path5) {
7928
+ try {
7929
+ const bytes = await sandbox.fs.downloadFile(path5);
7930
+ return Buffer.from(bytes).toString("utf-8");
7931
+ } catch (error) {
7932
+ throw new DaytonaCommandError(
7933
+ `Failed to read file "${path5}": ${toError(error).message}`,
7934
+ toError(error)
7935
+ );
7936
+ }
7937
+ },
7938
+ async writeFiles(files) {
7939
+ try {
7940
+ for (const dir of uniqueParentDirectories(files.map((f) => f.path))) {
7941
+ const result = await executeCommand(`mkdir -p ${shellQuote(dir)}`);
7942
+ if (result.exitCode !== 0) {
7943
+ throw new DaytonaCommandError(
7944
+ `Failed to create directory "${dir}": ${result.stderr}`
7945
+ );
7946
+ }
7255
7947
  }
7256
- return result.stdout.trim();
7257
- })();
7258
- archPromise = attempt.catch((err) => {
7259
- archPromise = null;
7260
- throw err;
7948
+ await sandbox.fs.uploadFiles(
7949
+ files.map((file) => ({
7950
+ source: Buffer.from(file.content),
7951
+ destination: file.path
7952
+ }))
7953
+ );
7954
+ } catch (error) {
7955
+ if (error instanceof DaytonaSandboxError) throw error;
7956
+ const err = toError(error);
7957
+ throw new DaytonaCommandError(
7958
+ `Failed to write files: ${err.message}`,
7959
+ err
7960
+ );
7961
+ }
7962
+ },
7963
+ async dispose() {
7964
+ },
7965
+ [Symbol.asyncDispose]() {
7966
+ return this.dispose();
7967
+ }
7968
+ };
7969
+ }
7970
+ function spawnDaytonaProcess(sandbox, command, options = {}) {
7971
+ const sessionId = createSessionId("spawn");
7972
+ const stdout = createTextReadable();
7973
+ const stderr = createTextReadable();
7974
+ const exit = runSpawnedSession({
7975
+ sandbox,
7976
+ sessionId,
7977
+ command: buildSessionCommand(command, options),
7978
+ signal: options.signal,
7979
+ commandTimeout: options.commandTimeout,
7980
+ stdout,
7981
+ stderr
7982
+ });
7983
+ return {
7984
+ stdout: stdout.stream,
7985
+ stderr: stderr.stream,
7986
+ exit
7987
+ };
7988
+ }
7989
+ async function runSpawnedSession(args) {
7990
+ const {
7991
+ sandbox,
7992
+ sessionId,
7993
+ command,
7994
+ signal,
7995
+ commandTimeout,
7996
+ stdout,
7997
+ stderr
7998
+ } = args;
7999
+ let sessionCreated = false;
8000
+ let aborted = signal?.aborted ?? false;
8001
+ let resolveAbort;
8002
+ const abortPromise = new Promise((resolve4) => {
8003
+ resolveAbort = () => resolve4("aborted");
8004
+ });
8005
+ const abort = () => {
8006
+ aborted = true;
8007
+ resolveAbort?.();
8008
+ if (sessionCreated) {
8009
+ sandbox.process.deleteSession(sessionId).catch(() => {
7261
8010
  });
7262
8011
  }
7263
- return archPromise;
7264
8012
  };
7265
- const installPackages = async (packages) => {
7266
- if (packages.length === 0) return;
7267
- const quoted = packages.map(shellQuote).join(" ");
7268
- let cmd;
7269
- if (packageManager === "apt-get") {
7270
- cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
7271
- } else {
7272
- cmd = `apk add --no-cache ${quoted}`;
8013
+ if (signal) {
8014
+ if (signal.aborted) abort();
8015
+ else signal.addEventListener("abort", abort, { once: true });
8016
+ }
8017
+ try {
8018
+ await sandbox.process.createSession(sessionId);
8019
+ sessionCreated = true;
8020
+ if (aborted) return abortedExitInfo();
8021
+ const response = await sandbox.process.executeSessionCommand(
8022
+ sessionId,
8023
+ {
8024
+ command,
8025
+ runAsync: true,
8026
+ suppressInputEcho: true
8027
+ },
8028
+ commandTimeout
8029
+ );
8030
+ const commandId = response.cmdId;
8031
+ if (!commandId) {
8032
+ throw new DaytonaCommandError(
8033
+ "Daytona did not return a command id for the spawned session command."
8034
+ );
7273
8035
  }
7274
- const result = await exec(cmd);
7275
- if (result.exitCode !== 0) {
7276
- throw new PackageInstallError(
7277
- packages,
7278
- image,
7279
- packageManager,
7280
- result.stderr,
7281
- containerId
8036
+ if (aborted) return abortedExitInfo();
8037
+ const logsTask = sandbox.process.getSessionCommandLogs(
8038
+ sessionId,
8039
+ commandId,
8040
+ (chunk) => stdout.enqueue(chunk),
8041
+ (chunk) => stderr.enqueue(chunk)
8042
+ );
8043
+ logsTask.catch(() => {
8044
+ });
8045
+ const winner = await Promise.race([
8046
+ logsTask.then(() => "logs"),
8047
+ abortPromise
8048
+ ]);
8049
+ if (winner === "aborted") {
8050
+ await sandbox.process.deleteSession(sessionId).catch(() => {
8051
+ });
8052
+ return abortedExitInfo();
8053
+ }
8054
+ await logsTask;
8055
+ const code = await waitForSessionCommandExitCode({
8056
+ sandbox,
8057
+ sessionId,
8058
+ commandId,
8059
+ timeoutMs: sessionExitPollTimeoutMs(commandTimeout),
8060
+ isAborted: () => aborted
8061
+ });
8062
+ return { code, signal: null, success: code === 0 };
8063
+ } catch (error) {
8064
+ if (aborted) return abortedExitInfo();
8065
+ const err = toError(error);
8066
+ stdout.error(err);
8067
+ stderr.error(err);
8068
+ throw err;
8069
+ } finally {
8070
+ if (signal) {
8071
+ signal.removeEventListener("abort", abort);
8072
+ }
8073
+ stdout.close();
8074
+ stderr.close();
8075
+ if (sessionCreated) {
8076
+ await sandbox.process.deleteSession(sessionId).catch(() => {
8077
+ });
8078
+ }
8079
+ }
8080
+ }
8081
+ async function waitForSessionCommandExitCode(args) {
8082
+ const { sandbox, sessionId, commandId, timeoutMs, isAborted } = args;
8083
+ const deadline = Date.now() + timeoutMs;
8084
+ while (true) {
8085
+ if (isAborted()) throw new DaytonaCommandError("Daytona command aborted.");
8086
+ const info = await sandbox.process.getSessionCommand(sessionId, commandId);
8087
+ if (typeof info.exitCode === "number") {
8088
+ return info.exitCode;
8089
+ }
8090
+ if (Date.now() >= deadline) {
8091
+ throw new DaytonaCommandError(
8092
+ `Daytona session command "${commandId}" logs closed before an exit code became available.`
7282
8093
  );
7283
8094
  }
7284
- if (packageManager === "apt-get") aptUpdated = true;
7285
- };
7286
- const ensureTool = async (checkName, installName) => {
7287
- const cacheKey = installName ?? checkName;
7288
- if (ensuredTools.has(cacheKey)) return;
7289
- const check = await exec(`which ${shellQuote(checkName)}`);
7290
- if (check.exitCode === 0) {
7291
- ensuredTools.add(cacheKey);
7292
- return;
8095
+ await delay(DAYTONA_EXIT_POLL_INTERVAL_MS);
8096
+ }
8097
+ }
8098
+ function sessionExitPollTimeoutMs(commandTimeout) {
8099
+ if (commandTimeout === void 0 || commandTimeout === 0) {
8100
+ return DAYTONA_EXIT_POLL_TIMEOUT_MS;
8101
+ }
8102
+ return Math.max(commandTimeout * 1e3, DAYTONA_EXIT_POLL_TIMEOUT_MS);
8103
+ }
8104
+ function delay(ms) {
8105
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
8106
+ }
8107
+ function createTextReadable() {
8108
+ const encoder = new TextEncoder();
8109
+ let controller;
8110
+ let closed = false;
8111
+ return {
8112
+ stream: new ReadableStream({
8113
+ start(streamController) {
8114
+ controller = streamController;
8115
+ }
8116
+ }),
8117
+ enqueue(chunk) {
8118
+ if (closed || !chunk) return;
8119
+ controller?.enqueue(encoder.encode(chunk));
8120
+ },
8121
+ close() {
8122
+ if (closed) return;
8123
+ closed = true;
8124
+ controller?.close();
8125
+ },
8126
+ error(error) {
8127
+ if (closed) return;
8128
+ closed = true;
8129
+ controller?.error(error);
7293
8130
  }
7294
- await installPackages([installName ?? checkName]);
7295
- ensuredTools.add(cacheKey);
7296
8131
  };
8132
+ }
8133
+ function buildSessionCommand(command, options) {
8134
+ const cwdPrefix = options.cwd ? `cd ${shellQuote(options.cwd)} && ` : "";
8135
+ const env = options.env ?? {};
8136
+ const entries = Object.entries(env);
8137
+ if (entries.length === 0) {
8138
+ return `sh -lc ${shellQuote(`${cwdPrefix}${command}`)}`;
8139
+ }
8140
+ for (const [key] of entries) {
8141
+ validateEnvKey(key);
8142
+ }
8143
+ const exports = entries.map(([key, value]) => `export ${key}=${shellQuote(value)}`).join("; ");
8144
+ return `sh -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
8145
+ }
8146
+ function validateEnvKey(key) {
8147
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
8148
+ throw new DaytonaSandboxError(
8149
+ `Invalid environment variable key: "${key}". Use shell-compatible environment variable names.`
8150
+ );
8151
+ }
8152
+ }
8153
+ function createSessionId(kind) {
8154
+ return `deepagents-${kind}-${randomUUID()}`;
8155
+ }
8156
+ function abortedCommandResult() {
7297
8157
  return {
7298
- containerId,
7299
- image,
7300
- packageManager,
7301
- arch,
7302
- exec,
7303
- installPackages,
7304
- ensureTool
8158
+ stdout: "",
8159
+ stderr: "Command aborted",
8160
+ exitCode: 1
7305
8161
  };
7306
8162
  }
8163
+ function abortedExitInfo() {
8164
+ return {
8165
+ code: null,
8166
+ signal: "SIGKILL",
8167
+ success: false
8168
+ };
8169
+ }
8170
+ function uniqueParentDirectories(paths) {
8171
+ const dirs = /* @__PURE__ */ new Set();
8172
+ for (const path5 of paths) {
8173
+ const index = path5.lastIndexOf("/");
8174
+ if (index > 0) {
8175
+ dirs.add(path5.slice(0, index));
8176
+ }
8177
+ }
8178
+ return [...dirs];
8179
+ }
8180
+ function compactObject(input) {
8181
+ const output = {};
8182
+ for (const [key, value] of Object.entries(input)) {
8183
+ if (value !== void 0) {
8184
+ output[key] = value;
8185
+ }
8186
+ }
8187
+ return output;
8188
+ }
8189
+ function toError(error) {
8190
+ return error instanceof Error ? error : new Error(String(error));
8191
+ }
7307
8192
 
7308
8193
  // packages/context/src/lib/sandbox/docker-sandbox.ts
8194
+ import "bash-tool";
8195
+ import spawn5 from "nano-spawn";
8196
+ import { spawn as childSpawn3 } from "node:child_process";
8197
+ import { createHash as createHash2 } from "node:crypto";
8198
+ import { readFileSync as readFileSync3 } from "node:fs";
7309
8199
  function isDockerfileOptions(opts) {
7310
8200
  return "dockerfile" in opts;
7311
8201
  }
7312
8202
  function isComposeOptions(opts) {
7313
8203
  return "compose" in opts;
7314
8204
  }
7315
- var CONTAINER_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
7316
- var DockerSandboxStrategy = class {
7317
- context;
7318
- volumes;
7319
- resources;
7320
- env;
7321
- name;
7322
- command;
7323
- securityOpt;
7324
- platform;
7325
- createdVolumes = /* @__PURE__ */ new Set();
7326
- constructor(args = {}) {
8205
+ function dockerMountArg(volume) {
8206
+ const readOnly = volume.readOnly !== false;
8207
+ const parts = volume.type === "bind" ? ["type=bind", `src=${volume.hostPath}`, `dst=${volume.containerPath}`] : [
8208
+ "type=volume",
8209
+ `src=${volume.name}`,
8210
+ `dst=${volume.containerPath}`,
8211
+ ...volume.subPath ? [`volume-subpath=${volume.subPath}`] : [],
8212
+ ...volume.noCopy ? ["volume-nocopy"] : []
8213
+ ];
8214
+ if (readOnly) {
8215
+ parts.push("readonly");
8216
+ }
8217
+ return parts.join(",");
8218
+ }
8219
+ function runDockerBuild(args, stdin, showBuildLogs) {
8220
+ const stdio = [
8221
+ stdin === void 0 ? "inherit" : "pipe",
8222
+ showBuildLogs ? "inherit" : "ignore",
8223
+ showBuildLogs ? "inherit" : "pipe"
8224
+ ];
8225
+ return new Promise((resolve4, reject) => {
8226
+ const child = childSpawn3("docker", args, { stdio });
8227
+ let stderr = "";
8228
+ child.stderr?.on("data", (chunk) => {
8229
+ stderr += chunk;
8230
+ });
8231
+ child.once(
8232
+ "error",
8233
+ (error) => reject(new DockerfileBuildError(error.message))
8234
+ );
8235
+ child.once("exit", (code) => {
8236
+ if (code === 0) {
8237
+ resolve4();
8238
+ return;
8239
+ }
8240
+ reject(
8241
+ new DockerfileBuildError(
8242
+ showBuildLogs ? `docker build exited with code ${code} (see build output above)` : stderr || `docker build exited with code ${code}`
8243
+ )
8244
+ );
8245
+ });
8246
+ if (stdin !== void 0 && child.stdin) {
8247
+ child.stdin.on("error", () => {
8248
+ });
8249
+ child.stdin.end(stdin);
8250
+ }
8251
+ });
8252
+ }
8253
+ var dockerEngine = {
8254
+ cli: "docker",
8255
+ runArgs(image, containerId, opts, workdir) {
7327
8256
  const {
7328
- volumes = [],
7329
- resources = {},
7330
- env = {},
7331
- name,
7332
- command,
7333
- securityOpt = [],
7334
- platform
7335
- } = args;
7336
- for (const key of Object.keys(env)) {
7337
- validateEnvKey2(key);
8257
+ memory = "1g",
8258
+ cpus = 2,
8259
+ memorySwap,
8260
+ shmSize,
8261
+ pidsLimit,
8262
+ ulimits = [],
8263
+ cpusetCpus,
8264
+ cpuShares
8265
+ } = opts.resources ?? {};
8266
+ const security = opts.security ?? {};
8267
+ const network = opts.network ?? {};
8268
+ const args = [
8269
+ "run",
8270
+ "-d",
8271
+ "--rm",
8272
+ "--name",
8273
+ containerId,
8274
+ "--memory",
8275
+ memory,
8276
+ "--cpus",
8277
+ String(cpus),
8278
+ "-w",
8279
+ workdir
8280
+ ];
8281
+ if (memorySwap) {
8282
+ args.push("--memory-swap", memorySwap);
7338
8283
  }
7339
- if (name !== void 0 && !CONTAINER_NAME_PATTERN.test(name)) {
7340
- throw new DockerSandboxError(
7341
- `Invalid container name: "${name}". Use only letters, numbers, underscore, period, or hyphen. The "sandbox-" prefix is added automatically.`
7342
- );
8284
+ if (shmSize) {
8285
+ args.push("--shm-size", shmSize);
7343
8286
  }
7344
- this.volumes = volumes;
7345
- this.resources = resources;
7346
- this.env = env;
7347
- this.name = name;
7348
- this.command = command;
7349
- this.securityOpt = securityOpt;
7350
- this.platform = platform;
7351
- }
7352
- async create() {
7353
- const image = await this.getImage();
7354
- let acquired;
7355
- try {
7356
- acquired = await this.acquireContainer(image);
7357
- this.context = { containerId: acquired.containerId, image };
7358
- if (!acquired.attached) {
7359
- await this.configure();
7360
- }
7361
- } catch (error) {
7362
- if (acquired && !acquired.attached) {
7363
- await this.stopContainer(acquired.containerId);
7364
- }
7365
- await this.cleanupCreatedVolumesAfterFailure(error);
7366
- throw error;
8287
+ if (pidsLimit !== void 0) {
8288
+ args.push("--pids-limit", String(pidsLimit));
7367
8289
  }
7368
- return this.createSandboxMethods();
7369
- }
7370
- namedContainerId() {
7371
- return `sandbox-${this.name}`;
7372
- }
7373
- defaultContainerId() {
7374
- return `sandbox-${crypto.randomUUID().slice(0, 8)}`;
7375
- }
7376
- async acquireContainer(image) {
7377
- if (!this.name) {
7378
- const containerId2 = this.defaultContainerId();
7379
- await this.prepareVolumes();
7380
- await this.startContainer(image, containerId2);
7381
- return { containerId: containerId2, attached: false };
8290
+ for (const ulimit of ulimits) {
8291
+ args.push("--ulimit", ulimit);
7382
8292
  }
7383
- const containerId = this.namedContainerId();
7384
- const probe = await this.inspectContainer(containerId);
7385
- if (probe === "running") {
7386
- return { containerId, attached: true };
8293
+ if (cpusetCpus) {
8294
+ args.push("--cpuset-cpus", cpusetCpus);
7387
8295
  }
7388
- if (probe === "stopped") {
7389
- await this.startStoppedContainer(containerId, image);
7390
- return { containerId, attached: true };
8296
+ if (cpuShares !== void 0) {
8297
+ args.push("--cpu-shares", String(cpuShares));
7391
8298
  }
7392
- await this.prepareVolumes();
7393
- try {
7394
- await this.startContainer(image, containerId);
7395
- return { containerId, attached: false };
7396
- } catch (error) {
7397
- if (error instanceof ContainerCreationError && this.isNameConflictError(error.message)) {
7398
- await this.cleanupCreatedVolumes();
7399
- const raced = await this.inspectContainer(containerId);
7400
- if (raced === "running") {
7401
- return { containerId, attached: true };
7402
- }
7403
- if (raced === "stopped") {
7404
- await this.startStoppedContainer(containerId, image);
7405
- return { containerId, attached: true };
7406
- }
7407
- }
7408
- throw error;
8299
+ if (opts.platform) {
8300
+ args.push("--platform", opts.platform);
7409
8301
  }
7410
- }
7411
- async startStoppedContainer(containerId, image) {
7412
- try {
7413
- await spawn3("docker", ["start", containerId]);
7414
- } catch (error) {
7415
- const message2 = this.getDockerErrorMessage(error);
7416
- if (this.isDockerUnavailableError(message2)) {
7417
- throw new DockerNotAvailableError();
7418
- }
7419
- throw new ContainerCreationError(message2, image, error);
8302
+ if (opts.runtime) {
8303
+ args.push("--runtime", opts.runtime);
7420
8304
  }
7421
- }
7422
- async inspectContainer(containerId) {
7423
- try {
7424
- const result = await spawn3("docker", [
7425
- "container",
7426
- "inspect",
7427
- "--format",
7428
- "{{.State.Status}}",
7429
- containerId
7430
- ]);
7431
- const status = result.stdout.trim();
7432
- return status === "running" ? "running" : "stopped";
7433
- } catch (error) {
7434
- const message2 = this.getDockerErrorMessage(error);
7435
- if (this.isDockerUnavailableError(message2)) {
7436
- throw new DockerNotAvailableError();
7437
- }
7438
- if (this.isMissingContainerError(message2)) {
7439
- return "absent";
7440
- }
7441
- throw new DockerSandboxError(
7442
- `Failed to inspect container "${containerId}": ${message2}`
7443
- );
8305
+ for (const cap of security.capDrop ?? []) {
8306
+ args.push("--cap-drop", cap);
7444
8307
  }
7445
- }
7446
- isMissingContainerError(message2) {
7447
- return message2.toLowerCase().includes("no such container");
7448
- }
7449
- isNameConflictError(message2) {
7450
- return message2.toLowerCase().includes("is already in use by container");
7451
- }
7452
- async prepareVolumes() {
7453
- this.validateVolumes();
7454
- for (const volume of this.volumes) {
7455
- if (volume.type !== "volume") {
7456
- continue;
7457
- }
7458
- const lifecycle = volume.lifecycle ?? "external";
7459
- if (lifecycle === "external") {
7460
- await this.inspectVolume(volume.name);
7461
- continue;
7462
- }
7463
- const exists = await this.volumeExists(volume.name);
7464
- if (exists) {
7465
- throw new VolumeCreateError(
7466
- volume.name,
7467
- "managed volume already exists"
7468
- );
7469
- }
7470
- await this.createVolume(volume);
7471
- if (volume.removeOnDispose !== false) {
7472
- this.createdVolumes.add(volume.name);
7473
- }
8308
+ for (const cap of security.capAdd ?? []) {
8309
+ args.push("--cap-add", cap);
7474
8310
  }
7475
- }
7476
- validateVolumes() {
7477
- const containerPaths = /* @__PURE__ */ new Set();
7478
- for (const volume of this.volumes) {
7479
- const source = volume.type === "bind" ? volume.hostPath : volume.name;
7480
- if (!volume.containerPath.startsWith("/")) {
7481
- throw new VolumePathError(
7482
- source,
7483
- volume.containerPath,
7484
- "containerPath must be absolute"
7485
- );
7486
- }
7487
- this.validateMountValue("containerPath", volume.containerPath, volume);
7488
- if (containerPaths.has(volume.containerPath)) {
7489
- throw new VolumePathError(
7490
- source,
7491
- volume.containerPath,
7492
- "containerPath must be unique"
7493
- );
7494
- }
7495
- containerPaths.add(volume.containerPath);
7496
- if (volume.type === "bind") {
7497
- this.validateMountValue("hostPath", volume.hostPath, volume);
7498
- if (!existsSync3(volume.hostPath)) {
7499
- throw new VolumePathError(
7500
- volume.hostPath,
7501
- volume.containerPath,
7502
- "hostPath does not exist on host"
7503
- );
7504
- }
7505
- continue;
7506
- }
7507
- if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(volume.name)) {
7508
- throw new VolumePathError(
7509
- volume.name,
7510
- volume.containerPath,
7511
- "volume name must start with an alphanumeric character and contain only letters, numbers, underscore, period, or hyphen"
7512
- );
7513
- }
7514
- if (volume.subPath) {
7515
- this.validateMountValue("subPath", volume.subPath, volume);
7516
- }
7517
- if ((volume.lifecycle ?? "external") === "external") {
7518
- if (volume.driver || volume.driverOptions) {
7519
- throw new VolumePathError(
7520
- volume.name,
7521
- volume.containerPath,
7522
- 'driver and driverOptions require lifecycle "managed"'
7523
- );
7524
- }
7525
- }
8311
+ if (security.readOnly) {
8312
+ args.push("--read-only");
8313
+ }
8314
+ if (security.user) {
8315
+ args.push("--user", security.user);
8316
+ }
8317
+ for (const mount of security.tmpfs ?? []) {
8318
+ args.push("--tmpfs", mount);
8319
+ }
8320
+ for (const opt of security.securityOpt ?? []) {
8321
+ args.push("--security-opt", opt);
8322
+ }
8323
+ if (network.mode) {
8324
+ args.push("--network", network.mode);
8325
+ }
8326
+ for (const port of network.publish ?? []) {
8327
+ args.push("--publish", port);
8328
+ }
8329
+ for (const server of network.dns ?? []) {
8330
+ args.push("--dns", server);
8331
+ }
8332
+ for (const host of network.addHost ?? []) {
8333
+ args.push("--add-host", host);
8334
+ }
8335
+ if (network.hostname) {
8336
+ args.push("--hostname", network.hostname);
8337
+ }
8338
+ if (opts.gpus) {
8339
+ args.push("--gpus", opts.gpus);
7526
8340
  }
7527
- }
7528
- validateMountValue(field, value, volume) {
7529
- if (!value.includes(",")) {
7530
- return;
8341
+ for (const device of opts.devices ?? []) {
8342
+ args.push("--device", device);
7531
8343
  }
7532
- const source = volume.type === "bind" ? volume.hostPath : volume.name;
7533
- throw new VolumePathError(
7534
- source,
7535
- volume.containerPath,
7536
- `${field} must not contain commas`
7537
- );
7538
- }
7539
- buildDockerArgs(image, containerId) {
7540
- const { memory = "1g", cpus = 2 } = this.resources;
7541
- const args = [
7542
- "run",
7543
- "-d",
7544
- "--rm",
7545
- "--name",
7546
- containerId,
7547
- `--memory=${memory}`,
7548
- `--cpus=${cpus}`,
7549
- "-w",
7550
- "/workspace"
7551
- ];
7552
- if (this.platform) {
7553
- args.push("--platform", this.platform);
8344
+ if (opts.init) {
8345
+ args.push("--init");
7554
8346
  }
7555
- for (const opt of this.securityOpt) {
7556
- args.push("--security-opt", opt);
8347
+ for (const [key, value] of Object.entries(opts.labels ?? {})) {
8348
+ args.push("--label", `${key}=${value}`);
8349
+ }
8350
+ for (const [key, value] of Object.entries(opts.sysctls ?? {})) {
8351
+ args.push("--sysctl", `${key}=${value}`);
7557
8352
  }
7558
- for (const [key, value] of Object.entries(this.env)) {
8353
+ if (opts.entrypoint) {
8354
+ args.push("--entrypoint", opts.entrypoint);
8355
+ }
8356
+ for (const [key, value] of Object.entries(opts.env ?? {})) {
7559
8357
  args.push("-e", `${key}=${value}`);
7560
8358
  }
7561
- for (const volume of this.volumes) {
7562
- args.push("--mount", this.buildVolumeMountArg(volume));
8359
+ for (const volume of opts.volumes ?? []) {
8360
+ args.push("--mount", dockerMountArg(volume));
7563
8361
  }
7564
8362
  args.push(image);
7565
- if (this.command === void 0) {
8363
+ if (opts.command === void 0) {
7566
8364
  args.push("tail", "-f", "/dev/null");
7567
- } else if (this.command !== null) {
7568
- args.push(...this.command);
8365
+ } else if (opts.command !== null) {
8366
+ args.push(...opts.command);
7569
8367
  }
7570
8368
  return args;
7571
- }
7572
- buildVolumeMountArg(volume) {
7573
- const readOnly = volume.readOnly !== false;
7574
- const parts = volume.type === "bind" ? ["type=bind", `src=${volume.hostPath}`, `dst=${volume.containerPath}`] : [
7575
- "type=volume",
7576
- `src=${volume.name}`,
7577
- `dst=${volume.containerPath}`,
7578
- ...volume.subPath ? [`volume-subpath=${volume.subPath}`] : [],
7579
- ...volume.noCopy ? ["volume-nocopy"] : []
7580
- ];
7581
- if (readOnly) {
7582
- parts.push("readonly");
7583
- }
7584
- return parts.join(",");
7585
- }
7586
- async inspectVolume(name) {
7587
- try {
7588
- await spawn3("docker", ["volume", "inspect", name]);
7589
- } catch (error) {
7590
- const reason = this.getDockerErrorMessage(error);
7591
- if (this.isDockerUnavailableError(reason)) {
7592
- throw new DockerNotAvailableError();
7593
- }
7594
- throw new VolumeInspectError(name, reason);
7595
- }
7596
- }
7597
- async volumeExists(name) {
7598
- try {
7599
- await this.inspectVolume(name);
7600
- return true;
7601
- } catch (error) {
7602
- if (error instanceof VolumeInspectError) {
7603
- if (this.isMissingVolumeInspectError(error.reason)) {
7604
- return false;
8369
+ },
8370
+ execArgs(containerId, command, options) {
8371
+ const flags = [];
8372
+ if (options?.cwd) {
8373
+ flags.push("-w", options.cwd);
8374
+ }
8375
+ if (options?.env) {
8376
+ for (const [key, value] of Object.entries(options.env)) {
8377
+ if (key.length === 0 || key.includes("=")) {
8378
+ throw new DockerSandboxError(
8379
+ `Invalid environment variable key: "${key}"`
8380
+ );
7605
8381
  }
7606
- throw error;
8382
+ flags.push("-e", `${key}=${value}`);
7607
8383
  }
7608
- throw error;
7609
8384
  }
7610
- }
7611
- async createVolume(volume) {
8385
+ return ["exec", ...flags, containerId, "sh", "-c", command];
8386
+ },
8387
+ inspectArgs(containerId) {
8388
+ return [
8389
+ "container",
8390
+ "inspect",
8391
+ "--format",
8392
+ "{{.State.Status}}",
8393
+ containerId
8394
+ ];
8395
+ },
8396
+ mountArg: dockerMountArg,
8397
+ parseStatus(status) {
8398
+ return status === "running" ? "running" : "stopped";
8399
+ },
8400
+ volumeCreateArgs(volume) {
7612
8401
  const args = ["volume", "create"];
7613
8402
  if (volume.driver) {
7614
8403
  args.push("--driver", volume.driver);
@@ -7617,143 +8406,61 @@ var DockerSandboxStrategy = class {
7617
8406
  args.push("--opt", `${key}=${value}`);
7618
8407
  }
7619
8408
  args.push(volume.name);
7620
- try {
7621
- await spawn3("docker", args);
7622
- } catch (error) {
7623
- const reason = this.getDockerErrorMessage(error);
7624
- if (this.isDockerUnavailableError(reason)) {
7625
- throw new DockerNotAvailableError();
7626
- }
7627
- throw new VolumeCreateError(volume.name, reason);
7628
- }
7629
- }
7630
- async cleanupCreatedVolumes() {
7631
- const volumes = [...this.createdVolumes].reverse();
7632
- for (const volume of volumes) {
7633
- try {
7634
- await spawn3("docker", ["volume", "rm", volume]);
7635
- this.createdVolumes.delete(volume);
7636
- } catch (error) {
7637
- const reason = this.getDockerErrorMessage(error);
7638
- if (this.isDockerUnavailableError(reason)) {
7639
- throw new DockerNotAvailableError();
7640
- }
7641
- throw new VolumeRemoveError(volume, reason);
7642
- }
7643
- }
7644
- }
7645
- async cleanupCreatedVolumesAfterFailure(originalError) {
7646
- try {
7647
- await this.cleanupCreatedVolumes();
7648
- } catch (cleanupError) {
7649
- if (originalError instanceof Error) {
7650
- const original = originalError;
7651
- original.suppressed = [...original.suppressed ?? [], cleanupError];
7652
- }
7653
- }
7654
- }
7655
- getDockerErrorMessage(error) {
8409
+ return args;
8410
+ },
8411
+ errorMessage(error) {
7656
8412
  const err = error;
7657
8413
  return err.stderr || err.stdout || err.message || String(error);
7658
- }
7659
- isDockerUnavailableError(message2) {
8414
+ },
8415
+ isServiceDown(message2) {
7660
8416
  return message2.includes("Cannot connect") || message2.includes("docker daemon");
7661
- }
7662
- isMissingVolumeInspectError(message2) {
8417
+ },
8418
+ isMissingContainer(message2) {
8419
+ return message2.toLowerCase().includes("no such container");
8420
+ },
8421
+ isMissingVolume(message2) {
7663
8422
  return message2.toLowerCase().includes("no such volume");
7664
- }
7665
- async startContainer(image, containerId) {
7666
- const args = this.buildDockerArgs(image, containerId);
7667
- try {
7668
- await spawn3("docker", args);
7669
- } catch (error) {
7670
- const err = error;
7671
- if (err.message?.includes("Cannot connect") || err.message?.includes("docker daemon") || err.stderr?.includes("Cannot connect")) {
7672
- throw new DockerNotAvailableError();
7673
- }
7674
- throw new ContainerCreationError(
7675
- this.getDockerErrorMessage(err),
7676
- image,
7677
- err
7678
- );
7679
- }
7680
- }
7681
- async stopContainer(containerId) {
8423
+ },
8424
+ isNameConflict(message2) {
8425
+ return message2.toLowerCase().includes("is already in use by container");
8426
+ },
8427
+ async ensureWorkdir() {
8428
+ },
8429
+ defaultImage: "alpine:latest",
8430
+ createInstallerContext,
8431
+ async imageExists(tag) {
7682
8432
  try {
7683
- await spawn3("docker", ["stop", containerId]);
8433
+ await spawn5("docker", ["image", "inspect", tag]);
8434
+ return true;
7684
8435
  } catch {
8436
+ return false;
7685
8437
  }
7686
- }
7687
- async exec(command, options) {
7688
- try {
7689
- const result = await spawn3(
7690
- "docker",
7691
- ["exec", this.context.containerId, "sh", "-c", command],
7692
- { signal: options?.signal }
7693
- );
7694
- return {
7695
- stdout: result.stdout,
7696
- stderr: result.stderr,
7697
- exitCode: 0
7698
- };
7699
- } catch (error) {
7700
- const err = error;
7701
- return {
7702
- stdout: err.stdout || "",
7703
- stderr: err.stderr || err.message || "",
7704
- exitCode: err.exitCode ?? 1
7705
- };
7706
- }
7707
- }
7708
- spawnProcess(command, options) {
7709
- const child = childSpawn("docker", [
7710
- "exec",
7711
- ...buildDockerExecFlags(options),
7712
- this.context.containerId,
7713
- "sh",
7714
- "-c",
7715
- command
7716
- ]);
7717
- return toSandboxProcess(child, options?.signal);
7718
- }
7719
- createSandboxMethods() {
7720
- const { containerId } = this.context;
7721
- const sandbox = {
7722
- executeCommand: async (command, options) => this.exec(command, options),
7723
- spawn: (command, options) => this.spawnProcess(command, options),
7724
- readFile: async (path5) => {
7725
- const result = await sandbox.executeCommand(`base64 "${path5}"`);
7726
- if (result.exitCode !== 0) {
7727
- throw new Error(`Failed to read file "${path5}": ${result.stderr}`);
7728
- }
7729
- return Buffer.from(result.stdout, "base64").toString("utf-8");
7730
- },
7731
- writeFiles: async (files) => {
7732
- for (const file of files) {
7733
- const dir = file.path.substring(0, file.path.lastIndexOf("/"));
7734
- if (dir) {
7735
- await sandbox.executeCommand(`mkdir -p "${dir}"`);
7736
- }
7737
- const base64Content = Buffer.from(file.content).toString("base64");
7738
- const result = await sandbox.executeCommand(
7739
- `echo "${base64Content}" | base64 -d > "${file.path}"`
7740
- );
7741
- if (result.exitCode !== 0) {
7742
- throw new Error(
7743
- `Failed to write file "${file.path}": ${result.stderr}`
7744
- );
7745
- }
7746
- }
7747
- },
7748
- dispose: async () => {
7749
- await this.stopContainer(containerId);
7750
- await this.cleanupCreatedVolumes();
7751
- },
7752
- [Symbol.asyncDispose]() {
7753
- return this.dispose();
7754
- }
7755
- };
7756
- return sandbox;
8438
+ },
8439
+ async buildImage(spec) {
8440
+ const inline = spec.dockerfile.includes("\n");
8441
+ const args = [
8442
+ "build",
8443
+ ...spec.identity ? ["--platform", spec.identity] : [],
8444
+ "-t",
8445
+ spec.tag,
8446
+ "-f",
8447
+ inline ? "-" : spec.dockerfile,
8448
+ spec.context
8449
+ ];
8450
+ await runDockerBuild(
8451
+ args,
8452
+ inline ? spec.dockerfile : void 0,
8453
+ spec.showBuildLogs
8454
+ );
8455
+ },
8456
+ errors: {
8457
+ serviceNotAvailable: () => new DockerNotAvailableError(),
8458
+ creation: (message2, image, cause) => new ContainerCreationError(message2, image, cause),
8459
+ generic: (message2, containerId) => new DockerSandboxError(message2, containerId),
8460
+ volumePath: (source, containerPath, reason) => new VolumePathError(source, containerPath, reason),
8461
+ volumeInspect: (name, reason) => new VolumeInspectError(name, reason),
8462
+ volumeCreate: (name, reason) => new VolumeCreateError(name, reason),
8463
+ volumeRemove: (name, reason) => new VolumeRemoveError(name, reason)
7757
8464
  }
7758
8465
  };
7759
8466
  function validateEnvKey2(key) {
@@ -7774,175 +8481,19 @@ function buildDockerExecFlags(options) {
7774
8481
  }
7775
8482
  return flags;
7776
8483
  }
7777
- function toSandboxProcess(child, abortSignal) {
7778
- if (!child.stdout || !child.stderr) {
7779
- child.kill("SIGKILL");
7780
- throw new DockerSandboxError("docker exec child missing stdio streams");
7781
- }
7782
- const onAbort = abortSignal ? () => child.kill("SIGKILL") : void 0;
7783
- if (abortSignal && onAbort) {
7784
- if (abortSignal.aborted) onAbort();
7785
- else abortSignal.addEventListener("abort", onAbort, { once: true });
7786
- }
7787
- return {
7788
- stdout: Readable2.toWeb(child.stdout),
7789
- stderr: Readable2.toWeb(child.stderr),
7790
- exit: new Promise((resolve4, reject) => {
7791
- const settle = () => {
7792
- child.removeListener("exit", onExitEvent);
7793
- child.removeListener("error", onError);
7794
- if (abortSignal && onAbort) {
7795
- abortSignal.removeEventListener("abort", onAbort);
7796
- }
7797
- };
7798
- const onError = (err) => {
7799
- settle();
7800
- reject(err);
7801
- };
7802
- const onExitEvent = (code, exitSignal) => {
7803
- settle();
7804
- resolve4({ code, signal: exitSignal, success: code === 0 });
7805
- };
7806
- child.on("exit", onExitEvent);
7807
- child.on("error", onError);
7808
- })
7809
- };
7810
- }
7811
- var RuntimeStrategy = class extends DockerSandboxStrategy {
7812
- image;
7813
- installers;
7814
- constructor(args = {}) {
7815
- super({
7816
- volumes: args.volumes,
7817
- resources: args.resources,
7818
- env: args.env,
7819
- name: args.name,
7820
- command: args.command,
7821
- securityOpt: args.securityOpt,
7822
- platform: args.platform
7823
- });
7824
- this.image = args.image ?? "alpine:latest";
7825
- this.installers = args.installers ?? [];
7826
- }
7827
- async getImage() {
7828
- return this.image;
7829
- }
7830
- async configure() {
7831
- const ctx = createInstallerContext(this.context.containerId, this.image);
7832
- for (const installer of this.installers) {
7833
- await installer.install(ctx);
7834
- }
7835
- }
7836
- };
7837
- var DockerfileStrategy = class extends DockerSandboxStrategy {
7838
- imageTag;
7839
- dockerfile;
7840
- dockerContext;
7841
- showBuildLogs;
7842
- constructor(args) {
7843
- super({
7844
- volumes: args.volumes,
7845
- resources: args.resources,
7846
- env: args.env,
7847
- name: args.name,
7848
- command: args.command,
7849
- securityOpt: args.securityOpt,
7850
- platform: args.platform
7851
- });
7852
- this.dockerfile = args.dockerfile;
7853
- this.dockerContext = args.context ?? ".";
7854
- this.showBuildLogs = args.showBuildLogs ?? false;
7855
- this.imageTag = this.computeImageTag();
7856
- }
7857
- computeImageTag() {
7858
- const content = this.isInlineDockerfile() ? this.dockerfile : readFileSync2(this.dockerfile, "utf-8");
7859
- const hash = createHash("sha256").update(content).update(this.platform ?? "").digest("hex").slice(0, 12);
7860
- return `sandbox-${hash}`;
7861
- }
7862
- isInlineDockerfile() {
7863
- return this.dockerfile.includes("\n");
7864
- }
7865
- async getImage() {
7866
- const exists = await this.imageExists();
7867
- if (!exists) {
7868
- await this.buildImage();
7869
- }
7870
- return this.imageTag;
7871
- }
7872
- async configure() {
7873
- }
7874
- async imageExists() {
7875
- try {
7876
- await spawn3("docker", ["image", "inspect", this.imageTag]);
7877
- return true;
7878
- } catch {
7879
- return false;
7880
- }
7881
- }
7882
- async buildImage() {
7883
- try {
7884
- const platformFlag = this.platform ? `--platform ${this.platform} ` : "";
7885
- if (this.isInlineDockerfile()) {
7886
- const buildCmd = `echo '${this.dockerfile.replace(/'/g, "'\\''")}' | docker build ${platformFlag}-t ${this.imageTag} -f - ${this.dockerContext}`;
7887
- if (this.showBuildLogs) {
7888
- await this.runStreamed("sh", ["-c", buildCmd]);
7889
- } else {
7890
- await spawn3("sh", ["-c", buildCmd]);
7891
- }
7892
- } else {
7893
- const args = [
7894
- "build",
7895
- ...this.platform ? ["--platform", this.platform] : [],
7896
- "-t",
7897
- this.imageTag,
7898
- "-f",
7899
- this.dockerfile,
7900
- this.dockerContext
7901
- ];
7902
- if (this.showBuildLogs) {
7903
- await this.runStreamed("docker", args);
7904
- } else {
7905
- await spawn3("docker", args);
7906
- }
7907
- }
7908
- } catch (error) {
7909
- const err = error;
7910
- throw new DockerfileBuildError(err.stderr || err.message);
7911
- }
7912
- }
7913
- /**
7914
- * Run a build command with stdio inherited so its output streams live to the
7915
- * parent terminal. On failure the build error is already on screen; the
7916
- * rejection just carries the exit code.
7917
- */
7918
- runStreamed(command, args) {
7919
- return new Promise((resolve4, reject) => {
7920
- const child = childSpawn(command, args, { stdio: "inherit" });
7921
- child.once("error", reject);
7922
- child.once(
7923
- "exit",
7924
- (code) => code === 0 ? resolve4() : reject(
7925
- new Error(
7926
- `docker build exited with code ${code} (see build output above)`
7927
- )
7928
- )
7929
- );
7930
- });
7931
- }
7932
- };
7933
- var ComposeStrategy = class extends DockerSandboxStrategy {
8484
+ var ComposeStrategy = class extends ContainerSandboxStrategy {
7934
8485
  projectName;
7935
8486
  composeFile;
7936
8487
  service;
7937
8488
  constructor(args) {
7938
- super({ resources: args.resources });
8489
+ super({ resources: args.resources }, dockerEngine);
7939
8490
  this.composeFile = args.compose;
7940
8491
  this.service = args.service;
7941
8492
  this.projectName = this.computeProjectName();
7942
8493
  }
7943
8494
  computeProjectName() {
7944
- const content = readFileSync2(this.composeFile, "utf-8");
7945
- const hash = createHash("sha256").update(content).digest("hex").slice(0, 8);
8495
+ const content = readFileSync3(this.composeFile, "utf-8");
8496
+ const hash = createHash2("sha256").update(content).digest("hex").slice(0, 8);
7946
8497
  return `sandbox-${hash}`;
7947
8498
  }
7948
8499
  async getImage() {
@@ -7950,7 +8501,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
7950
8501
  }
7951
8502
  async startContainer(_image, _containerId) {
7952
8503
  try {
7953
- await spawn3("docker", [
8504
+ await spawn5("docker", [
7954
8505
  "compose",
7955
8506
  "-f",
7956
8507
  this.composeFile,
@@ -7962,7 +8513,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
7962
8513
  } catch (error) {
7963
8514
  const err = error;
7964
8515
  if (err.stderr?.includes("Cannot connect")) {
7965
- throw new DockerNotAvailableError();
8516
+ throw this.engine.errors.serviceNotAvailable();
7966
8517
  }
7967
8518
  throw new ComposeStartError(this.composeFile, err.stderr || err.message);
7968
8519
  }
@@ -7974,7 +8525,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
7974
8525
  }
7975
8526
  async exec(command, options) {
7976
8527
  try {
7977
- const result = await spawn3(
8528
+ const result = await spawn5(
7978
8529
  "docker",
7979
8530
  [
7980
8531
  "compose",
@@ -8002,7 +8553,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
8002
8553
  }
8003
8554
  }
8004
8555
  spawnProcess(command, options) {
8005
- const child = childSpawn("docker", [
8556
+ const child = childSpawn3("docker", [
8006
8557
  "compose",
8007
8558
  "-f",
8008
8559
  this.composeFile,
@@ -8020,7 +8571,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
8020
8571
  }
8021
8572
  async stopContainer(_containerId) {
8022
8573
  try {
8023
- await spawn3("docker", [
8574
+ await spawn5("docker", [
8024
8575
  "compose",
8025
8576
  "-f",
8026
8577
  this.composeFile,
@@ -8033,40 +8584,25 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
8033
8584
  }
8034
8585
  };
8035
8586
  async function createDockerSandbox(options = {}) {
8036
- let strategy;
8037
8587
  if (isComposeOptions(options)) {
8038
- strategy = new ComposeStrategy({
8588
+ return new ComposeStrategy({
8039
8589
  compose: options.compose,
8040
8590
  service: options.service,
8041
8591
  resources: options.resources
8042
- });
8043
- } else if (isDockerfileOptions(options)) {
8044
- strategy = new DockerfileStrategy({
8592
+ }).create();
8593
+ }
8594
+ if (isDockerfileOptions(options)) {
8595
+ return new ContainerfileStrategy(options, dockerEngine, {
8045
8596
  dockerfile: options.dockerfile,
8046
- context: options.context,
8047
- showBuildLogs: options.showBuildLogs,
8048
- volumes: options.volumes,
8049
- resources: options.resources,
8050
- env: options.env,
8051
- name: options.name,
8052
- command: options.command,
8053
- securityOpt: options.securityOpt,
8054
- platform: options.platform
8055
- });
8056
- } else {
8057
- strategy = new RuntimeStrategy({
8058
- image: options.image,
8059
- installers: options.installers,
8060
- volumes: options.volumes,
8061
- resources: options.resources,
8062
- env: options.env,
8063
- name: options.name,
8064
- command: options.command,
8065
- securityOpt: options.securityOpt,
8066
- platform: options.platform
8067
- });
8597
+ context: options.context ?? ".",
8598
+ showBuildLogs: options.showBuildLogs ?? false,
8599
+ identity: options.platform
8600
+ }).create();
8068
8601
  }
8069
- return strategy.create();
8602
+ return new RuntimeStrategy(options, dockerEngine, {
8603
+ image: options.image ?? dockerEngine.defaultImage,
8604
+ installers: options.installers ?? []
8605
+ }).create();
8070
8606
  }
8071
8607
  async function useSandbox(options, fn) {
8072
8608
  const sandbox = await createDockerSandbox(options);
@@ -8082,6 +8618,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
8082
8618
  import { posix as posix2 } from "node:path";
8083
8619
 
8084
8620
  // packages/context/src/lib/sandbox/strace/index.ts
8621
+ import spawn6 from "nano-spawn";
8085
8622
  import { posix } from "node:path";
8086
8623
  var STRACE_FLAGS = "-f -y -qq -e trace=%file,write,pwrite64,writev";
8087
8624
  function buildStraceCommand(command, traceFile, traceDir) {
@@ -12005,6 +12542,13 @@ export {
12005
12542
  AgentOsCreationError,
12006
12543
  AgentOsNotAvailableError,
12007
12544
  AgentOsSandboxError,
12545
+ AppleContainerCreationError,
12546
+ AppleContainerImageBuildError,
12547
+ AppleContainerSandboxError,
12548
+ AppleContainerVolumeCreateError,
12549
+ AppleContainerVolumeInspectError,
12550
+ AppleContainerVolumePathError,
12551
+ AppleContainerVolumeRemoveError,
12008
12552
  AsyncResolver,
12009
12553
  BM25Classifier,
12010
12554
  BashException,
@@ -12012,6 +12556,10 @@ export {
12012
12556
  ComposeStartError,
12013
12557
  ComposeStrategy,
12014
12558
  ContainerCreationError,
12559
+ ContainerSandboxError,
12560
+ ContainerSandboxStrategy,
12561
+ ContainerServiceNotRunningError,
12562
+ ContainerfileStrategy,
12015
12563
  ContextEngine,
12016
12564
  ContextRenderer,
12017
12565
  ContextStore,
@@ -12023,9 +12571,7 @@ export {
12023
12571
  DaytonaSandboxError,
12024
12572
  DockerNotAvailableError,
12025
12573
  DockerSandboxError,
12026
- DockerSandboxStrategy,
12027
12574
  DockerfileBuildError,
12028
- DockerfileStrategy,
12029
12575
  FragmentLoaderResolver,
12030
12576
  FunctionResolver,
12031
12577
  GeneratorResolver,
@@ -12095,6 +12641,7 @@ export {
12095
12641
  correction,
12096
12642
  createAdaptivePollingState,
12097
12643
  createAgentOsSandbox,
12644
+ createAppleContainerSandbox,
12098
12645
  createBashTool,
12099
12646
  createBinaryBridges,
12100
12647
  createDaytonaSandbox,
@@ -12110,6 +12657,7 @@ export {
12110
12657
  defineSubcommandGroup,
12111
12658
  discoverSkillMappings,
12112
12659
  discoverSkillsInDirectory,
12660
+ dockerEngine,
12113
12661
  downloadAndInstall,
12114
12662
  elapsedExceeds,
12115
12663
  encodeSerializedValue,
@@ -12138,6 +12686,7 @@ export {
12138
12686
  hint,
12139
12687
  hourChanged,
12140
12688
  identity,
12689
+ isAppleContainerfileOptions,
12141
12690
  isComposeOptions,
12142
12691
  isConditionalReminder,
12143
12692
  isDebianBased,
@@ -12182,7 +12731,6 @@ export {
12182
12731
  resetAdaptivePolling,
12183
12732
  resolveReminder,
12184
12733
  resolveReminderAsync,
12185
- resolveReminderText,
12186
12734
  resolveTz,
12187
12735
  role,
12188
12736
  runGuardrailChain,
@@ -12208,6 +12756,7 @@ export {
12208
12756
  timeReminder,
12209
12757
  toFragment,
12210
12758
  toMessageFragment,
12759
+ toToolReminderModelOutput,
12211
12760
  toolCall,
12212
12761
  toolCallCount,
12213
12762
  toolCalled,
@@ -12216,6 +12765,7 @@ export {
12216
12765
  urlBinary,
12217
12766
  usageExceeds,
12218
12767
  useAgentOsSandbox,
12768
+ useAppleContainerSandbox,
12219
12769
  useBashMeta,
12220
12770
  useSandbox,
12221
12771
  user,