@deepagents/context 4.0.0 → 4.2.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 (48) hide show
  1. package/README.md +25 -16
  2. package/dist/browser.js +18 -8
  3. package/dist/browser.js.map +2 -2
  4. package/dist/index.js +2551 -1596
  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 +11 -4
  8. package/dist/lib/engine.d.ts.map +1 -1
  9. package/dist/lib/fragments/message/user.d.ts +28 -14
  10. package/dist/lib/fragments/message/user.d.ts.map +1 -1
  11. package/dist/lib/sandbox/agent-os-sandbox.d.ts +2 -2
  12. package/dist/lib/sandbox/agent-os-sandbox.d.ts.map +1 -1
  13. package/dist/lib/sandbox/apple-container-sandbox-errors.d.ts +44 -0
  14. package/dist/lib/sandbox/apple-container-sandbox-errors.d.ts.map +1 -0
  15. package/dist/lib/sandbox/apple-container-sandbox.d.ts +130 -0
  16. package/dist/lib/sandbox/apple-container-sandbox.d.ts.map +1 -0
  17. package/dist/lib/sandbox/bash-meta.d.ts +0 -3
  18. package/dist/lib/sandbox/bash-meta.d.ts.map +1 -1
  19. package/dist/lib/sandbox/bash-tool.d.ts +3 -3
  20. package/dist/lib/sandbox/bash-tool.d.ts.map +1 -1
  21. package/dist/lib/sandbox/cli-process.d.ts +24 -0
  22. package/dist/lib/sandbox/cli-process.d.ts.map +1 -0
  23. package/dist/lib/sandbox/container-engine.d.ts +150 -0
  24. package/dist/lib/sandbox/container-engine.d.ts.map +1 -0
  25. package/dist/lib/sandbox/container-sandbox-errors.d.ts +10 -0
  26. package/dist/lib/sandbox/container-sandbox-errors.d.ts.map +1 -0
  27. package/dist/lib/sandbox/container-sandbox.d.ts +90 -0
  28. package/dist/lib/sandbox/container-sandbox.d.ts.map +1 -0
  29. package/dist/lib/sandbox/daytona-sandbox.d.ts +2 -2
  30. package/dist/lib/sandbox/daytona-sandbox.d.ts.map +1 -1
  31. package/dist/lib/sandbox/docker-sandbox-errors.d.ts +2 -2
  32. package/dist/lib/sandbox/docker-sandbox-errors.d.ts.map +1 -1
  33. package/dist/lib/sandbox/docker-sandbox.d.ts +122 -218
  34. package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
  35. package/dist/lib/sandbox/index.d.ts +5 -0
  36. package/dist/lib/sandbox/index.d.ts.map +1 -1
  37. package/dist/lib/sandbox/microsandbox-sandbox.d.ts +93 -0
  38. package/dist/lib/sandbox/microsandbox-sandbox.d.ts.map +1 -0
  39. package/dist/lib/sandbox/shell-quote.d.ts +2 -2
  40. package/dist/lib/sandbox/strace/index.d.ts +6 -21
  41. package/dist/lib/sandbox/strace/index.d.ts.map +1 -1
  42. package/dist/lib/sandbox/strace/index.js +18 -7
  43. package/dist/lib/sandbox/strace/index.js.map +2 -2
  44. package/dist/lib/sandbox/types.d.ts +22 -3
  45. package/dist/lib/sandbox/types.d.ts.map +1 -1
  46. package/dist/lib/sandbox/virtual-sandbox.d.ts +2 -2
  47. package/dist/lib/sandbox/virtual-sandbox.d.ts.map +1 -1
  48. package/package.json +14 -8
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) {
@@ -1585,6 +1595,7 @@ var Agent = class _Agent {
1585
1595
  ),
1586
1596
  toolChoice: this.#options.toolChoice,
1587
1597
  onStepFinish: (step) => {
1598
+ if (!this.#options.logging) return;
1588
1599
  const toolCall2 = step.toolCalls.at(-1);
1589
1600
  if (toolCall2) {
1590
1601
  console.log(
@@ -1662,6 +1673,7 @@ var Agent = class _Agent {
1662
1673
  experimental_context: contextVariables,
1663
1674
  toolChoice: this.#options.toolChoice,
1664
1675
  onStepFinish: (step) => {
1676
+ if (!this.#options.logging) return;
1665
1677
  const toolCall2 = step.toolCalls.at(-1);
1666
1678
  if (toolCall2) {
1667
1679
  console.log(
@@ -1916,12 +1928,23 @@ function wrapToolsWithOutputReminders(tools, context) {
1916
1928
  wrapped[name] = toolDef;
1917
1929
  continue;
1918
1930
  }
1931
+ const originalToModelOutput = toolDef.toModelOutput;
1919
1932
  wrapped[name] = {
1920
1933
  ...toolDef,
1921
1934
  execute: async (input, options) => {
1922
1935
  const result = await execute.call(toolDef, input, options);
1923
1936
  if (isAsyncIterable(result)) return result;
1924
- return context.applyToolOutputReminders(result);
1937
+ return context.applyToolOutputReminders(result, {
1938
+ toolName: name,
1939
+ input
1940
+ });
1941
+ },
1942
+ toModelOutput: (args) => {
1943
+ const project = (output) => originalToModelOutput ? originalToModelOutput({
1944
+ ...args,
1945
+ output
1946
+ }) : defaultToolModelOutput(output);
1947
+ return toToolReminderModelOutput(args.output, project) ?? project(args.output);
1925
1948
  }
1926
1949
  };
1927
1950
  }
@@ -1930,6 +1953,9 @@ function wrapToolsWithOutputReminders(tools, context) {
1930
1953
  function isAsyncIterable(value) {
1931
1954
  return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
1932
1955
  }
1956
+ function defaultToolModelOutput(output) {
1957
+ return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output ?? null };
1958
+ }
1933
1959
  function agent(options) {
1934
1960
  return new Agent(options);
1935
1961
  }
@@ -3835,7 +3861,6 @@ var ContextEngine = class _ContextEngine {
3835
3861
  const reminders = matched.map((m) => ({
3836
3862
  text: m.resolved.text,
3837
3863
  asPart: m.config.asPart,
3838
- target: "user",
3839
3864
  metadata: m.resolved.metadata
3840
3865
  }));
3841
3866
  const carrier = {
@@ -3895,26 +3920,35 @@ var ContextEngine = class _ContextEngine {
3895
3920
  * Evaluate `target: 'tool-output'` reminders against a tool's raw result and
3896
3921
  * return the (possibly wrapped) output.
3897
3922
  *
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).
3923
+ * Called by the agent's tool wrapper right after each `execute()` resolves.
3924
+ * The wrapper passes the executing `call`, surfaced as
3925
+ * {@link WhenContext.executingTool}, so a predicate can gate on the live
3926
+ * tool's name/input/result — the only point at which the call being wrapped
3927
+ * is observable.
3928
+ * The store keeps the wrapped value with a host-only marker; the model-facing
3929
+ * projection strips that marker while preserving `result` + `systemReminder`.
3901
3930
  *
3902
3931
  * Returns the output unchanged when no tool-output reminder fires. Without a
3903
3932
  * persisted user message there is no turn context to evaluate against
3904
3933
  * (e.g. asTool forks that set a pending user without saving), so the output
3905
3934
  * passes through untouched.
3906
3935
  */
3907
- async applyToolOutputReminders(output) {
3936
+ async applyToolOutputReminders(output, call) {
3908
3937
  const configs = this.#remindersFor("tool-output");
3909
3938
  if (configs.length === 0) return output;
3910
3939
  await this.#ensureInitialized();
3911
3940
  const chain = await this.#getChainContext();
3912
3941
  const currentMessage = chain.lastMessage;
3913
3942
  if (!currentMessage) return output;
3914
- const matched = await evaluateFiredReminders(
3915
- configs,
3916
- this.#buildWhenCtx(chain, currentMessage)
3917
- );
3943
+ const whenCtx = this.#buildWhenCtx(chain, currentMessage);
3944
+ if (call) {
3945
+ whenCtx.executingTool = {
3946
+ name: call.toolName,
3947
+ input: call.input,
3948
+ output
3949
+ };
3950
+ }
3951
+ const matched = await evaluateFiredReminders(configs, whenCtx);
3918
3952
  if (matched.length === 0) return output;
3919
3953
  return applyRemindersToToolOutput(
3920
3954
  output,
@@ -6112,14 +6146,15 @@ function bindAbort(signal, onAbort) {
6112
6146
  }
6113
6147
  async function createAgentOsSandbox(options = {}) {
6114
6148
  const { AgentOs } = await importAgentOs();
6149
+ const { readiness, ...kernelOptions } = options;
6115
6150
  let os;
6116
6151
  try {
6117
- os = await AgentOs.create(options);
6152
+ os = await AgentOs.create(kernelOptions);
6118
6153
  } catch (error) {
6119
6154
  const err = error instanceof Error ? error : new Error(String(error));
6120
6155
  throw new AgentOsCreationError(err.message, err);
6121
6156
  }
6122
- return {
6157
+ const sandbox = {
6123
6158
  async executeCommand(command, { signal } = {}) {
6124
6159
  if (signal?.aborted) {
6125
6160
  return { stdout: "", stderr: "", exitCode: SIGKILL_EXIT_CODE };
@@ -6199,6 +6234,16 @@ async function createAgentOsSandbox(options = {}) {
6199
6234
  return this.dispose();
6200
6235
  }
6201
6236
  };
6237
+ if (readiness) {
6238
+ try {
6239
+ await readiness(sandbox);
6240
+ } catch (error) {
6241
+ await sandbox.dispose().catch(() => {
6242
+ });
6243
+ throw error;
6244
+ }
6245
+ }
6246
+ return sandbox;
6202
6247
  }
6203
6248
  async function useAgentOsSandbox(options, fn) {
6204
6249
  const sandbox = await createAgentOsSandbox(options);
@@ -6209,1740 +6254,2281 @@ async function useAgentOsSandbox(options, fn) {
6209
6254
  }
6210
6255
  }
6211
6256
 
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";
6257
+ // packages/context/src/lib/sandbox/apple-container-sandbox.ts
6258
+ import "bash-tool";
6259
+ import spawn3 from "nano-spawn";
6260
+ import { spawn as childSpawn2 } from "node:child_process";
6261
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
6262
+ import { tmpdir } from "node:os";
6263
+ import { join } from "node:path";
6247
6264
 
6248
- // packages/context/src/lib/sandbox/upload-skills.ts
6249
- import * as path3 from "node:path";
6265
+ // packages/context/src/lib/sandbox/container-sandbox-errors.ts
6266
+ var ContainerSandboxError = class extends Error {
6267
+ containerId;
6268
+ constructor(message2, containerId) {
6269
+ super(message2);
6270
+ this.name = "ContainerSandboxError";
6271
+ this.containerId = containerId;
6272
+ }
6273
+ };
6250
6274
 
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");
6275
+ // packages/context/src/lib/sandbox/apple-container-sandbox-errors.ts
6276
+ var AppleContainerSandboxError = class extends ContainerSandboxError {
6277
+ constructor(message2, containerId) {
6278
+ super(message2, containerId);
6279
+ this.name = "AppleContainerSandboxError";
6260
6280
  }
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');
6281
+ };
6282
+ var ContainerServiceNotRunningError = class extends AppleContainerSandboxError {
6283
+ constructor() {
6284
+ super(
6285
+ "Apple container service is not running. Start it with `container system start` (first run also needs `container system kernel set --recommended`)."
6286
+ );
6287
+ this.name = "ContainerServiceNotRunningError";
6265
6288
  }
6266
- if (!frontmatter.description || typeof frontmatter.description !== "string") {
6267
- throw new Error(
6268
- 'Invalid SKILL.md: frontmatter must have a "description" field'
6289
+ };
6290
+ var AppleContainerCreationError = class extends AppleContainerSandboxError {
6291
+ image;
6292
+ cause;
6293
+ constructor(message2, image, cause) {
6294
+ super(`Failed to create container from image "${image}": ${message2}`);
6295
+ this.name = "AppleContainerCreationError";
6296
+ this.image = image;
6297
+ this.cause = cause;
6298
+ }
6299
+ };
6300
+ var AppleContainerImageBuildError = class extends AppleContainerSandboxError {
6301
+ stderr;
6302
+ constructor(stderr) {
6303
+ super(`Container image build failed: ${stderr}`);
6304
+ this.name = "AppleContainerImageBuildError";
6305
+ this.stderr = stderr;
6306
+ }
6307
+ };
6308
+ var AppleContainerVolumePathError = class extends AppleContainerSandboxError {
6309
+ source;
6310
+ containerPath;
6311
+ reason;
6312
+ constructor(source, containerPath, reason) {
6313
+ super(
6314
+ `Invalid container volume path "${source}" -> "${containerPath}": ${reason}`
6269
6315
  );
6316
+ this.name = "AppleContainerVolumePathError";
6317
+ this.source = source;
6318
+ this.containerPath = containerPath;
6319
+ this.reason = reason;
6270
6320
  }
6271
- return {
6272
- frontmatter,
6273
- body: body.trim()
6274
- };
6321
+ };
6322
+ var AppleContainerVolumeInspectError = class extends AppleContainerSandboxError {
6323
+ volume;
6324
+ reason;
6325
+ constructor(volume, reason) {
6326
+ super(`Failed to inspect container volume "${volume}": ${reason}`);
6327
+ this.name = "AppleContainerVolumeInspectError";
6328
+ this.volume = volume;
6329
+ this.reason = reason;
6330
+ }
6331
+ };
6332
+ var AppleContainerVolumeCreateError = class extends AppleContainerSandboxError {
6333
+ volume;
6334
+ reason;
6335
+ constructor(volume, reason) {
6336
+ super(`Failed to create container volume "${volume}": ${reason}`);
6337
+ this.name = "AppleContainerVolumeCreateError";
6338
+ this.volume = volume;
6339
+ this.reason = reason;
6340
+ }
6341
+ };
6342
+ var AppleContainerVolumeRemoveError = class extends AppleContainerSandboxError {
6343
+ volume;
6344
+ reason;
6345
+ constructor(volume, reason) {
6346
+ super(`Failed to remove container volume "${volume}": ${reason}`);
6347
+ this.name = "AppleContainerVolumeRemoveError";
6348
+ this.volume = volume;
6349
+ this.reason = reason;
6350
+ }
6351
+ };
6352
+
6353
+ // packages/context/src/lib/sandbox/container-sandbox.ts
6354
+ import "bash-tool";
6355
+ import spawn2 from "nano-spawn";
6356
+ import { spawn as childSpawn } from "node:child_process";
6357
+ import { createHash } from "node:crypto";
6358
+ import { existsSync, readFileSync } from "node:fs";
6359
+
6360
+ // packages/context/src/lib/sandbox/cli-process.ts
6361
+ import { Readable as Readable2 } from "node:stream";
6362
+
6363
+ // packages/context/src/lib/sandbox/shell-quote.ts
6364
+ function shellQuote(s) {
6365
+ return `'${s.replace(/'/g, `'\\''`)}'`;
6275
6366
  }
6276
- function loadSkillMetadata(skillMdPath) {
6277
- const content = fs.readFileSync(skillMdPath, "utf-8");
6278
- const parsed = parseFrontmatter(content);
6279
- const skillDir = path.dirname(skillMdPath);
6367
+
6368
+ // packages/context/src/lib/sandbox/cli-process.ts
6369
+ function toSandboxProcess(child, abortSignal) {
6370
+ if (!child.stdout || !child.stderr) {
6371
+ child.kill("SIGKILL");
6372
+ throw new Error("exec child process is missing stdout/stderr streams");
6373
+ }
6374
+ const onAbort = () => child.kill("SIGKILL");
6375
+ if (abortSignal) {
6376
+ if (abortSignal.aborted) onAbort();
6377
+ else abortSignal.addEventListener("abort", onAbort, { once: true });
6378
+ }
6280
6379
  return {
6281
- name: parsed.frontmatter.name,
6282
- description: parsed.frontmatter.description,
6283
- path: skillDir,
6284
- skillMdPath
6380
+ stdout: Readable2.toWeb(child.stdout),
6381
+ stderr: Readable2.toWeb(child.stderr),
6382
+ exit: new Promise((resolve4, reject) => {
6383
+ const settle = () => {
6384
+ child.removeListener("exit", onExitEvent);
6385
+ child.removeListener("error", onError);
6386
+ abortSignal?.removeEventListener("abort", onAbort);
6387
+ };
6388
+ const onError = (err) => {
6389
+ settle();
6390
+ reject(err);
6391
+ };
6392
+ const onExitEvent = (code, exitSignal) => {
6393
+ settle();
6394
+ resolve4({ code, signal: exitSignal, success: code === 0 });
6395
+ };
6396
+ child.on("exit", onExitEvent);
6397
+ child.on("error", onError);
6398
+ })
6285
6399
  };
6286
6400
  }
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;
6401
+ var BASE64_WRITE_CHUNK = 32768;
6402
+ function base64WriteCommands(path5, content) {
6403
+ const base64 = Buffer.from(content).toString("base64");
6404
+ const quotedPath = shellQuote(path5);
6405
+ const commands = [];
6406
+ for (let offset = 0; offset < base64.length; offset += BASE64_WRITE_CHUNK) {
6407
+ const chunk = base64.slice(offset, offset + BASE64_WRITE_CHUNK);
6408
+ const redirect = offset === 0 ? ">" : ">>";
6409
+ commands.push(
6410
+ `printf '%s' ${shellQuote(chunk)} | base64 -d ${redirect} ${quotedPath}`
6411
+ );
6292
6412
  }
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
- }
6413
+ if (commands.length === 0) {
6414
+ commands.push(`printf '' > ${quotedPath}`);
6304
6415
  }
6305
- return skills2;
6416
+ return commands;
6306
6417
  }
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));
6315
- }
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;
6418
+ function base64ReadCommand(path5) {
6419
+ return `base64 ${shellQuote(path5)}`;
6341
6420
  }
6342
6421
 
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
- }
6369
- }
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
- }
6422
+ // packages/context/src/lib/sandbox/installers/installer.ts
6423
+ import "bash-tool";
6424
+ import spawn from "nano-spawn";
6425
+
6426
+ // packages/context/src/lib/sandbox/docker-sandbox-errors.ts
6427
+ var DockerSandboxError = class extends ContainerSandboxError {
6428
+ constructor(message2, containerId) {
6429
+ super(message2, containerId);
6430
+ this.name = "DockerSandboxError";
6384
6431
  }
6385
- if (filesToUpload.length > 0) {
6386
- await sandbox.writeFiles(filesToUpload);
6432
+ };
6433
+ var DockerNotAvailableError = class extends DockerSandboxError {
6434
+ constructor() {
6435
+ super("Docker is not available. Ensure Docker daemon is running.");
6436
+ this.name = "DockerNotAvailableError";
6387
6437
  }
6388
- return skills2;
6389
- }
6438
+ };
6439
+ var ContainerCreationError = class extends DockerSandboxError {
6440
+ image;
6441
+ cause;
6442
+ constructor(message2, image, cause) {
6443
+ super(`Failed to create container from image "${image}": ${message2}`);
6444
+ this.name = "ContainerCreationError";
6445
+ this.image = image;
6446
+ this.cause = cause;
6447
+ }
6448
+ };
6449
+ var PackageInstallError = class extends DockerSandboxError {
6450
+ packages;
6451
+ image;
6452
+ packageManager;
6453
+ stderr;
6454
+ constructor(packages, image, packageManager, stderr, containerId) {
6455
+ super(
6456
+ `Package installation failed for [${packages.join(", ")}] using ${packageManager} on ${image}: ${stderr}`,
6457
+ containerId
6458
+ );
6459
+ this.name = "PackageInstallError";
6460
+ this.packages = packages;
6461
+ this.image = image;
6462
+ this.packageManager = packageManager;
6463
+ this.stderr = stderr;
6464
+ }
6465
+ };
6466
+ var InstallError = class extends DockerSandboxError {
6467
+ target;
6468
+ source;
6469
+ reason;
6470
+ url;
6471
+ constructor(opts) {
6472
+ const where = opts.url ? `${opts.source} (${opts.url})` : opts.source;
6473
+ super(
6474
+ `Failed to install "${opts.target}" via ${where}: ${opts.reason}`,
6475
+ opts.containerId
6476
+ );
6477
+ this.name = "InstallError";
6478
+ this.target = opts.target;
6479
+ this.source = opts.source;
6480
+ this.reason = opts.reason;
6481
+ this.url = opts.url;
6482
+ }
6483
+ };
6484
+ var MissingRuntimeError = class extends DockerSandboxError {
6485
+ runtime;
6486
+ required;
6487
+ constructor(runtime, required, details, containerId) {
6488
+ const base = `Required runtime "${runtime}" is not installed (needs: ${required.join(", ")}).`;
6489
+ super(details ? `${base} ${details}` : base, containerId);
6490
+ this.name = "MissingRuntimeError";
6491
+ this.runtime = runtime;
6492
+ this.required = required;
6493
+ }
6494
+ };
6495
+ var VolumePathError = class extends DockerSandboxError {
6496
+ source;
6497
+ containerPath;
6498
+ reason;
6499
+ constructor(source, containerPath, reason) {
6500
+ super(
6501
+ `Invalid Docker volume path "${source}" -> "${containerPath}": ${reason}`
6502
+ );
6503
+ this.name = "VolumePathError";
6504
+ this.source = source;
6505
+ this.containerPath = containerPath;
6506
+ this.reason = reason;
6507
+ }
6508
+ };
6509
+ var VolumeInspectError = class extends DockerSandboxError {
6510
+ volume;
6511
+ reason;
6512
+ constructor(volume, reason) {
6513
+ super(`Failed to inspect Docker volume "${volume}": ${reason}`);
6514
+ this.name = "VolumeInspectError";
6515
+ this.volume = volume;
6516
+ this.reason = reason;
6517
+ }
6518
+ };
6519
+ var VolumeCreateError = class extends DockerSandboxError {
6520
+ volume;
6521
+ reason;
6522
+ constructor(volume, reason) {
6523
+ super(`Failed to create Docker volume "${volume}": ${reason}`);
6524
+ this.name = "VolumeCreateError";
6525
+ this.volume = volume;
6526
+ this.reason = reason;
6527
+ }
6528
+ };
6529
+ var VolumeRemoveError = class extends DockerSandboxError {
6530
+ volume;
6531
+ reason;
6532
+ constructor(volume, reason) {
6533
+ super(`Failed to remove Docker volume "${volume}": ${reason}`);
6534
+ this.name = "VolumeRemoveError";
6535
+ this.volume = volume;
6536
+ this.reason = reason;
6537
+ }
6538
+ };
6539
+ var DockerfileBuildError = class extends DockerSandboxError {
6540
+ stderr;
6541
+ constructor(stderr) {
6542
+ super(`Dockerfile build failed: ${stderr}`);
6543
+ this.name = "DockerfileBuildError";
6544
+ this.stderr = stderr;
6545
+ }
6546
+ };
6547
+ var ComposeStartError = class extends DockerSandboxError {
6548
+ composeFile;
6549
+ stderr;
6550
+ constructor(composeFile, stderr) {
6551
+ super(`Docker Compose failed to start: ${stderr}`);
6552
+ this.name = "ComposeStartError";
6553
+ this.composeFile = composeFile;
6554
+ this.stderr = stderr;
6555
+ }
6556
+ };
6390
6557
 
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) {
6558
+ // packages/context/src/lib/sandbox/installers/installer.ts
6559
+ var Installer = class {
6560
+ };
6561
+ function isDebianBased(image) {
6562
+ const lower = image.toLowerCase();
6563
+ if (lower.includes("alpine")) return false;
6564
+ const debianPatterns = ["debian", "ubuntu", "node", "python"];
6565
+ return debianPatterns.some((pattern) => lower.includes(pattern));
6566
+ }
6567
+ function createInstallerContext(containerId, image) {
6568
+ const packageManager = isDebianBased(image) ? "apt-get" : "apk";
6569
+ let archPromise = null;
6570
+ const ensuredTools = /* @__PURE__ */ new Set();
6571
+ let aptUpdated = false;
6572
+ const exec = async (command) => {
6573
+ try {
6574
+ const result = await spawn("docker", [
6575
+ "exec",
6576
+ containerId,
6577
+ "sh",
6578
+ "-c",
6579
+ command
6580
+ ]);
6581
+ return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
6582
+ } catch (error) {
6583
+ const err = error;
6584
+ return {
6585
+ stdout: err.stdout ?? "",
6586
+ stderr: err.stderr ?? err.message ?? "",
6587
+ exitCode: err.exitCode ?? 1
6588
+ };
6589
+ }
6590
+ };
6591
+ const arch = async () => {
6592
+ if (!archPromise) {
6593
+ const attempt = (async () => {
6594
+ const result = await exec("uname -m");
6595
+ if (result.exitCode !== 0) {
6596
+ throw new DockerSandboxError(
6597
+ `Failed to detect container architecture: ${result.stderr}`,
6598
+ containerId
6599
+ );
6600
+ }
6601
+ return result.stdout.trim();
6602
+ })();
6603
+ archPromise = attempt.catch((err) => {
6604
+ archPromise = null;
6605
+ throw err;
6606
+ });
6607
+ }
6608
+ return archPromise;
6609
+ };
6610
+ const installPackages = async (packages) => {
6611
+ if (packages.length === 0) return;
6612
+ const quoted = packages.map(shellQuote).join(" ");
6613
+ let cmd;
6614
+ if (packageManager === "apt-get") {
6615
+ cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
6616
+ } else {
6617
+ cmd = `apk add --no-cache ${quoted}`;
6618
+ }
6619
+ const result = await exec(cmd);
6620
+ if (result.exitCode !== 0) {
6621
+ throw new PackageInstallError(
6622
+ packages,
6623
+ image,
6624
+ packageManager,
6625
+ result.stderr,
6626
+ containerId
6627
+ );
6628
+ }
6629
+ if (packageManager === "apt-get") aptUpdated = true;
6630
+ };
6631
+ const ensureTool = async (checkName, installName) => {
6632
+ const cacheKey = installName ?? checkName;
6633
+ if (ensuredTools.has(cacheKey)) return;
6634
+ const check = await exec(`which ${shellQuote(checkName)}`);
6635
+ if (check.exitCode === 0) {
6636
+ ensuredTools.add(cacheKey);
6637
+ return;
6638
+ }
6639
+ await installPackages([installName ?? checkName]);
6640
+ ensuredTools.add(cacheKey);
6641
+ };
6394
6642
  return {
6395
- ...sandbox,
6396
- async executeCommand(command, options) {
6643
+ containerId,
6644
+ image,
6645
+ packageManager,
6646
+ arch,
6647
+ exec,
6648
+ installPackages,
6649
+ ensureTool
6650
+ };
6651
+ }
6652
+
6653
+ // packages/context/src/lib/sandbox/container-sandbox.ts
6654
+ var CONTAINER_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
6655
+ var ContainerSandboxStrategy = class {
6656
+ context;
6657
+ engine;
6658
+ opts;
6659
+ volumes;
6660
+ name;
6661
+ workdir;
6662
+ createdVolumes = /* @__PURE__ */ new Set();
6663
+ constructor(opts, engine) {
6664
+ this.opts = opts;
6665
+ this.engine = engine;
6666
+ const { volumes = [], env = {}, name, workdir = "/workspace" } = opts;
6667
+ for (const key of Object.keys(env)) {
6668
+ if (key.length === 0 || key.includes("=")) {
6669
+ throw this.engine.errors.generic(
6670
+ `Invalid environment variable key: "${key}"`
6671
+ );
6672
+ }
6673
+ }
6674
+ if (name !== void 0 && !CONTAINER_NAME_PATTERN.test(name)) {
6675
+ throw this.engine.errors.generic(
6676
+ `Invalid container name: "${name}". Use only letters, numbers, underscore, period, or hyphen. The "sandbox-" prefix is added automatically.`
6677
+ );
6678
+ }
6679
+ this.volumes = volumes;
6680
+ this.name = name;
6681
+ this.workdir = workdir;
6682
+ }
6683
+ async create() {
6684
+ const image = await this.getImage();
6685
+ let acquired;
6686
+ try {
6687
+ acquired = await this.acquireContainer(image);
6688
+ this.context = { containerId: acquired.containerId, image };
6689
+ if (!acquired.attached) {
6690
+ await this.engine.ensureWorkdir(this.context.containerId, this.workdir);
6691
+ await this.configure();
6692
+ }
6693
+ } catch (error) {
6694
+ if (acquired && !acquired.attached) {
6695
+ await this.stopContainer(acquired.containerId);
6696
+ }
6697
+ await this.cleanupCreatedVolumesAfterFailure(error);
6698
+ throw error;
6699
+ }
6700
+ const sandbox = this.createSandboxMethods();
6701
+ if (this.opts.readiness) {
6397
6702
  try {
6398
- return await sandbox.executeCommand(command, options);
6399
- } catch (err) {
6400
- if (err instanceof BashException) return err.format();
6401
- throw err;
6703
+ await this.opts.readiness(sandbox);
6704
+ } catch (error) {
6705
+ await sandbox.dispose().catch(() => {
6706
+ });
6707
+ throw error;
6708
+ }
6709
+ }
6710
+ return sandbox;
6711
+ }
6712
+ namedContainerId() {
6713
+ return `sandbox-${this.name}`;
6714
+ }
6715
+ defaultContainerId() {
6716
+ return `sandbox-${crypto.randomUUID().slice(0, 8)}`;
6717
+ }
6718
+ async acquireContainer(image) {
6719
+ if (!this.name) {
6720
+ const containerId2 = this.defaultContainerId();
6721
+ await this.prepareVolumes();
6722
+ await this.startContainer(image, containerId2);
6723
+ return { containerId: containerId2, attached: false };
6724
+ }
6725
+ const containerId = this.namedContainerId();
6726
+ const probe = await this.inspectContainer(containerId);
6727
+ if (probe === "running") {
6728
+ return { containerId, attached: true };
6729
+ }
6730
+ if (probe === "stopped") {
6731
+ await this.startStoppedContainer(containerId, image);
6732
+ return { containerId, attached: true };
6733
+ }
6734
+ await this.prepareVolumes();
6735
+ try {
6736
+ await this.startContainer(image, containerId);
6737
+ return { containerId, attached: false };
6738
+ } catch (error) {
6739
+ if (this.engine.isNameConflict(this.engine.errorMessage(error))) {
6740
+ await this.cleanupCreatedVolumes();
6741
+ const raced = await this.inspectContainer(containerId);
6742
+ if (raced === "running") {
6743
+ return { containerId, attached: true };
6744
+ }
6745
+ if (raced === "stopped") {
6746
+ await this.startStoppedContainer(containerId, image);
6747
+ return { containerId, attached: true };
6748
+ }
6402
6749
  }
6750
+ throw error;
6403
6751
  }
6404
- };
6405
- }
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");
6752
+ }
6753
+ async startStoppedContainer(containerId, image) {
6754
+ try {
6755
+ await spawn2(this.engine.cli, ["start", containerId]);
6756
+ } catch (error) {
6757
+ const message2 = this.engineErrorMessage(error);
6758
+ if (this.isServiceDown(message2)) {
6759
+ throw this.engine.errors.serviceNotAvailable();
6435
6760
  }
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
- })
6761
+ throw this.engine.errors.creation(message2, image, error);
6762
+ }
6763
+ }
6764
+ async inspectContainer(containerId) {
6765
+ try {
6766
+ const result = await spawn2(
6767
+ this.engine.cli,
6768
+ this.engine.inspectArgs(containerId)
6452
6769
  );
6453
- },
6454
- toModelOutput: ({ output }) => {
6455
- if (typeof output !== "object" || output === null) {
6456
- return { type: "json", value: output };
6770
+ return this.engine.parseStatus(result.stdout.trim());
6771
+ } catch (error) {
6772
+ const message2 = this.engineErrorMessage(error);
6773
+ if (this.isServiceDown(message2)) {
6774
+ throw this.engine.errors.serviceNotAvailable();
6457
6775
  }
6458
- const { meta: _meta, ...visible } = output;
6459
- return { type: "json", value: visible };
6776
+ if (this.engine.isMissingContainer(message2)) {
6777
+ return "absent";
6778
+ }
6779
+ throw this.engine.errors.generic(
6780
+ `Failed to inspect container "${containerId}": ${message2}`
6781
+ );
6460
6782
  }
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");
6783
+ }
6784
+ async prepareVolumes() {
6785
+ this.validateVolumes();
6786
+ for (const volume of this.volumes) {
6787
+ if (volume.type !== "volume") {
6788
+ continue;
6470
6789
  }
6471
- try {
6472
- return await originalWriteExecute(input, options2);
6473
- } catch (err) {
6474
- if (err instanceof BashException) return err.format();
6475
- throw err;
6790
+ const lifecycle = volume.lifecycle ?? "external";
6791
+ if (lifecycle === "external") {
6792
+ await this.inspectVolume(volume.name);
6793
+ continue;
6794
+ }
6795
+ const exists = await this.volumeExists(volume.name);
6796
+ if (exists) {
6797
+ throw this.engine.errors.volumeCreate(
6798
+ volume.name,
6799
+ "managed volume already exists"
6800
+ );
6801
+ }
6802
+ await this.createVolume(volume);
6803
+ if (volume.removeOnDispose !== false) {
6804
+ this.createdVolumes.add(volume.name);
6476
6805
  }
6477
6806
  }
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
- }
6807
+ }
6808
+ validateVolumes() {
6809
+ const containerPaths = /* @__PURE__ */ new Set();
6810
+ for (const volume of this.volumes) {
6811
+ const source = volume.type === "bind" ? volume.hostPath : volume.name;
6812
+ if (!volume.containerPath.startsWith("/")) {
6813
+ throw this.engine.errors.volumePath(
6814
+ source,
6815
+ volume.containerPath,
6816
+ "containerPath must be absolute"
6817
+ );
6508
6818
  }
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
- }
6819
+ this.validateMountValue("containerPath", volume.containerPath, volume);
6820
+ if (containerPaths.has(volume.containerPath)) {
6821
+ throw this.engine.errors.volumePath(
6822
+ source,
6823
+ volume.containerPath,
6824
+ "containerPath must be unique"
6825
+ );
6826
+ }
6827
+ containerPaths.add(volume.containerPath);
6828
+ if (volume.type === "bind") {
6829
+ this.validateMountValue("hostPath", volume.hostPath, volume);
6830
+ if (!existsSync(volume.hostPath)) {
6831
+ throw this.engine.errors.volumePath(
6832
+ volume.hostPath,
6833
+ volume.containerPath,
6834
+ "hostPath does not exist on host"
6835
+ );
6550
6836
  }
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
- };
6837
+ continue;
6838
+ }
6839
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(volume.name)) {
6840
+ throw this.engine.errors.volumePath(
6841
+ volume.name,
6842
+ volume.containerPath,
6843
+ "volume name must start with an alphanumeric character and contain only letters, numbers, underscore, period, or hyphen"
6844
+ );
6845
+ }
6846
+ if (volume.subPath) {
6847
+ this.validateMountValue("subPath", volume.subPath, volume);
6848
+ }
6849
+ if ((volume.lifecycle ?? "external") === "external") {
6850
+ if (volume.driver || volume.driverOptions) {
6851
+ throw this.engine.errors.volumePath(
6852
+ volume.name,
6853
+ volume.containerPath,
6854
+ 'driver and driverOptions require lifecycle "managed"'
6855
+ );
6558
6856
  }
6559
- return {
6560
- stdout: "",
6561
- stderr: `${name}: ${error instanceof Error ? error.message : String(error)}`,
6562
- exitCode: 127
6563
- };
6564
6857
  }
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();
6858
+ }
6859
+ }
6860
+ validateMountValue(field, value, volume) {
6861
+ if (!value.includes(",")) {
6862
+ return;
6863
+ }
6864
+ const source = volume.type === "bind" ? volume.hostPath : volume.name;
6865
+ throw this.engine.errors.volumePath(
6866
+ source,
6867
+ volume.containerPath,
6868
+ `${field} must not contain commas`
6869
+ );
6870
+ }
6871
+ buildRunArgs(image, containerId) {
6872
+ return this.engine.runArgs(image, containerId, this.opts, this.workdir);
6873
+ }
6874
+ async inspectVolume(name) {
6875
+ try {
6876
+ await spawn2(this.engine.cli, ["volume", "inspect", name]);
6877
+ } catch (error) {
6878
+ const reason = this.engineErrorMessage(error);
6879
+ if (this.isServiceDown(reason)) {
6880
+ throw this.engine.errors.serviceNotAvailable();
6881
+ }
6882
+ throw this.engine.errors.volumeInspect(name, reason);
6883
+ }
6884
+ }
6885
+ async volumeExists(name) {
6886
+ try {
6887
+ await this.inspectVolume(name);
6888
+ return true;
6889
+ } catch (error) {
6890
+ if (error instanceof ContainerSandboxError && this.engine.isMissingVolume(error.message)) {
6891
+ return false;
6892
+ }
6893
+ throw error;
6894
+ }
6895
+ }
6896
+ async createVolume(volume) {
6897
+ const args = this.engine.volumeCreateArgs(volume);
6898
+ try {
6899
+ await spawn2(this.engine.cli, args);
6900
+ } catch (error) {
6901
+ const reason = this.engineErrorMessage(error);
6902
+ if (this.isServiceDown(reason)) {
6903
+ throw this.engine.errors.serviceNotAvailable();
6904
+ }
6905
+ throw this.engine.errors.volumeCreate(volume.name, reason);
6906
+ }
6578
6907
  }
6579
- if (!existsSync2(realCwd)) {
6580
- realCwd = process.cwd();
6908
+ async cleanupCreatedVolumes() {
6909
+ const volumes = [...this.createdVolumes].reverse();
6910
+ for (const volume of volumes) {
6911
+ try {
6912
+ await spawn2(this.engine.cli, ["volume", "rm", volume]);
6913
+ this.createdVolumes.delete(volume);
6914
+ } catch (error) {
6915
+ const reason = this.engineErrorMessage(error);
6916
+ if (this.isServiceDown(reason)) {
6917
+ throw this.engine.errors.serviceNotAvailable();
6918
+ }
6919
+ throw this.engine.errors.volumeRemove(volume, reason);
6920
+ }
6921
+ }
6581
6922
  }
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;
6923
+ async cleanupCreatedVolumesAfterFailure(originalError) {
6924
+ try {
6925
+ await this.cleanupCreatedVolumes();
6926
+ } catch (cleanupError) {
6927
+ if (originalError instanceof Error) {
6928
+ const original = originalError;
6929
+ original.suppressed = [...original.suppressed ?? [], cleanupError];
6930
+ }
6931
+ }
6603
6932
  }
6604
- };
6605
- var DaytonaCreationError = class extends DaytonaSandboxError {
6606
- constructor(message2, cause) {
6607
- super(`Failed to create Daytona sandbox: ${message2}`, cause);
6608
- this.name = "DaytonaCreationError";
6933
+ engineErrorMessage(error) {
6934
+ return this.engine.errorMessage(error);
6609
6935
  }
6610
- };
6611
- var DaytonaCommandError = class extends DaytonaSandboxError {
6612
- constructor(message2, cause) {
6613
- super(message2, cause);
6614
- this.name = "DaytonaCommandError";
6936
+ isServiceDown(message2) {
6937
+ return this.engine.isServiceDown(message2);
6615
6938
  }
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);
6939
+ async startContainer(image, containerId) {
6940
+ const args = this.buildRunArgs(image, containerId);
6941
+ try {
6942
+ await spawn2(this.engine.cli, args);
6943
+ } catch (error) {
6944
+ const message2 = this.engineErrorMessage(error);
6945
+ if (this.isServiceDown(message2)) {
6946
+ throw this.engine.errors.serviceNotAvailable();
6947
+ }
6948
+ throw this.engine.errors.creation(message2, image, error);
6627
6949
  }
6628
- } catch (error) {
6629
- throw normalizeDaytonaError(error, sdk);
6630
6950
  }
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);
6951
+ async stopContainer(containerId) {
6952
+ try {
6953
+ await spawn2(this.engine.cli, ["stop", containerId]);
6954
+ } catch {
6648
6955
  }
6649
- throw error;
6650
- }
6651
- if (existing.state && UNRECOVERABLE_SANDBOX_STATES.has(existing.state)) {
6652
- await deleteSandboxQuietly(existing, options);
6653
- return createSandbox(client, options);
6654
6956
  }
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 {
6957
+ async exec(command, options) {
6958
+ try {
6959
+ const result = await spawn2(
6960
+ this.engine.cli,
6961
+ this.engine.execArgs(this.context.containerId, command),
6962
+ { signal: options?.signal }
6963
+ );
6964
+ return {
6965
+ stdout: result.stdout,
6966
+ stderr: result.stderr,
6967
+ exitCode: 0
6968
+ };
6969
+ } catch (error) {
6970
+ const err = error;
6971
+ return {
6972
+ stdout: err.stdout || "",
6973
+ stderr: err.stderr || err.message || "",
6974
+ exitCode: err.exitCode ?? 1
6975
+ };
6976
+ }
6662
6977
  }
6663
- }
6664
- async function startIfStopped(sandbox, options) {
6665
- if (sandbox.state && sandbox.state !== "started") {
6666
- await sandbox.start(options.startTimeout ?? options.createTimeout);
6978
+ spawnProcess(command, options) {
6979
+ const child = childSpawn(
6980
+ this.engine.cli,
6981
+ this.engine.execArgs(this.context.containerId, command, options)
6982
+ );
6983
+ return toSandboxProcess(child, options?.signal);
6667
6984
  }
6668
- }
6669
- function normalizeDaytonaError(error, sdk) {
6670
- const err = toError(error);
6671
- if (err instanceof sdk.DaytonaError) {
6672
- return err;
6985
+ createSandboxMethods() {
6986
+ const { containerId } = this.context;
6987
+ const sandbox = {
6988
+ executeCommand: async (command, options) => this.exec(command, options),
6989
+ spawn: (command, options) => this.spawnProcess(command, options),
6990
+ readFile: async (path5) => {
6991
+ const result = await sandbox.executeCommand(base64ReadCommand(path5));
6992
+ if (result.exitCode !== 0) {
6993
+ throw new Error(`Failed to read file "${path5}": ${result.stderr}`);
6994
+ }
6995
+ return Buffer.from(result.stdout, "base64").toString("utf-8");
6996
+ },
6997
+ writeFiles: async (files) => {
6998
+ for (const file of files) {
6999
+ const dir = file.path.substring(0, file.path.lastIndexOf("/"));
7000
+ if (dir) {
7001
+ await sandbox.executeCommand(`mkdir -p ${shellQuote(dir)}`);
7002
+ }
7003
+ for (const command of base64WriteCommands(file.path, file.content)) {
7004
+ const result = await sandbox.executeCommand(command);
7005
+ if (result.exitCode !== 0) {
7006
+ throw new Error(
7007
+ `Failed to write file "${file.path}": ${result.stderr}`
7008
+ );
7009
+ }
7010
+ }
7011
+ }
7012
+ },
7013
+ dispose: async () => {
7014
+ await this.stopContainer(containerId);
7015
+ await this.cleanupCreatedVolumes();
7016
+ },
7017
+ [Symbol.asyncDispose]() {
7018
+ return this.dispose();
7019
+ }
7020
+ };
7021
+ return sandbox;
6673
7022
  }
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
- });
7023
+ };
7024
+ var RuntimeStrategy = class extends ContainerSandboxStrategy {
7025
+ image;
7026
+ installers;
7027
+ constructor(opts, engine, mode) {
7028
+ super(opts, engine);
7029
+ this.image = mode.image;
7030
+ this.installers = mode.installers;
6702
7031
  }
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.'
6713
- );
7032
+ async getImage() {
7033
+ return this.image;
6714
7034
  }
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.'
7035
+ async configure() {
7036
+ const ctx = this.engine.createInstallerContext(
7037
+ this.context.containerId,
7038
+ this.image
6718
7039
  );
7040
+ for (const installer of this.installers) {
7041
+ await installer.install(ctx);
7042
+ }
6719
7043
  }
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
- );
7044
+ };
7045
+ var ContainerfileStrategy = class extends ContainerSandboxStrategy {
7046
+ dockerfile;
7047
+ dockerContext;
7048
+ showBuildLogs;
7049
+ identity;
7050
+ constructor(opts, engine, mode) {
7051
+ super(opts, engine);
7052
+ this.dockerfile = mode.dockerfile;
7053
+ this.dockerContext = mode.context;
7054
+ this.showBuildLogs = mode.showBuildLogs;
7055
+ this.identity = mode.identity;
6724
7056
  }
6725
- if (!options.sandboxId) {
6726
- return;
7057
+ async getImage() {
7058
+ const content = this.dockerfile.includes("\n") ? this.dockerfile : readFileSync(this.dockerfile, "utf-8");
7059
+ const tag = `sandbox-${createHash("sha256").update(content).update(this.identity ?? "").digest("hex").slice(0, 12)}`;
7060
+ if (!await this.engine.imageExists(tag)) {
7061
+ await this.engine.buildImage({
7062
+ tag,
7063
+ dockerfile: this.dockerfile,
7064
+ context: this.dockerContext,
7065
+ showBuildLogs: this.showBuildLogs,
7066
+ identity: this.identity
7067
+ });
7068
+ }
7069
+ return tag;
6727
7070
  }
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"
7071
+ async configure() {
7072
+ }
7073
+ };
7074
+
7075
+ // packages/context/src/lib/sandbox/apple-container-sandbox.ts
7076
+ var CLI = "container";
7077
+ var WORKDIR = "/workspace";
7078
+ function isAppleContainerfileOptions(opts) {
7079
+ return "dockerfile" in opts;
7080
+ }
7081
+ function buildMountArg(volume) {
7082
+ const readOnly = volume.readOnly !== false;
7083
+ if (volume.type === "volume") {
7084
+ return `${volume.name}:${volume.containerPath}${readOnly ? ":ro" : ""}`;
7085
+ }
7086
+ const parts = [
7087
+ "type=bind",
7088
+ `source=${volume.hostPath}`,
7089
+ `target=${volume.containerPath}`
6746
7090
  ];
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
- );
7091
+ if (readOnly) {
7092
+ parts.push("readonly");
6754
7093
  }
7094
+ return parts.join(",");
6755
7095
  }
6756
- function createDaytonaSandboxMethods(args) {
6757
- const { sandbox, commandTimeout } = args;
6758
- const executeCommand = async (command, options) => {
6759
- if (options?.signal?.aborted) {
6760
- return abortedCommandResult();
7096
+ function getCliErrorMessage(error) {
7097
+ const err = error;
7098
+ return err.stderr?.trim() || err.stdout?.trim() || err.message || String(error);
7099
+ }
7100
+ function safeParseArray(stdout) {
7101
+ try {
7102
+ const parsed = JSON.parse(stdout || "[]");
7103
+ return Array.isArray(parsed) ? parsed : [];
7104
+ } catch {
7105
+ return [];
7106
+ }
7107
+ }
7108
+ function readContainerStatus(entry) {
7109
+ const raw = entry?.status;
7110
+ const status = String(
7111
+ (typeof raw === "object" && raw !== null ? raw.state : raw) ?? ""
7112
+ ).toLowerCase();
7113
+ if (status === "running" || status === "booted") return "running";
7114
+ if (status === "stopped") return "stopped";
7115
+ throw new AppleContainerSandboxError(
7116
+ `Container is in an unexpected state "${status}"`
7117
+ );
7118
+ }
7119
+ var appleEngine = {
7120
+ cli: CLI,
7121
+ runArgs(image, containerId, opts) {
7122
+ const { memory = "1024M", cpus = 2 } = opts.resources ?? {};
7123
+ const args = [
7124
+ "run",
7125
+ "--detach",
7126
+ "--rm",
7127
+ "--name",
7128
+ containerId,
7129
+ "--memory",
7130
+ memory,
7131
+ "--cpus",
7132
+ String(cpus)
7133
+ ];
7134
+ if (opts.arch) {
7135
+ args.push("--arch", opts.arch);
6761
7136
  }
6762
- let aborted = false;
6763
- const abort = () => {
6764
- aborted = true;
6765
- };
6766
- options?.signal?.addEventListener("abort", abort, { once: true });
7137
+ for (const [key, value] of Object.entries(opts.env ?? {})) {
7138
+ args.push("--env", `${key}=${value}`);
7139
+ }
7140
+ for (const volume of opts.volumes ?? []) {
7141
+ args.push(
7142
+ volume.type === "volume" ? "--volume" : "--mount",
7143
+ buildMountArg(volume)
7144
+ );
7145
+ }
7146
+ args.push(image);
7147
+ if (opts.command === void 0) {
7148
+ args.push("tail", "-f", "/dev/null");
7149
+ } else if (opts.command !== null) {
7150
+ args.push(...opts.command);
7151
+ }
7152
+ return args;
7153
+ },
7154
+ execArgs(containerId, command, options) {
7155
+ const flags = ["--cwd", options?.cwd || WORKDIR];
7156
+ if (options?.env) {
7157
+ for (const [key, value] of Object.entries(options.env)) {
7158
+ if (key.length === 0 || key.includes("=")) {
7159
+ throw new AppleContainerSandboxError(
7160
+ `Invalid environment variable key: "${key}"`
7161
+ );
7162
+ }
7163
+ flags.push("--env", `${key}=${value}`);
7164
+ }
7165
+ }
7166
+ return ["exec", ...flags, containerId, "sh", "-c", command];
7167
+ },
7168
+ inspectArgs(containerId) {
7169
+ return ["inspect", containerId];
7170
+ },
7171
+ mountArg: buildMountArg,
7172
+ parseStatus(stdout) {
7173
+ const entries = safeParseArray(stdout);
7174
+ if (entries.length === 0) return "absent";
7175
+ return readContainerStatus(entries[0]);
7176
+ },
7177
+ volumeCreateArgs(volume) {
7178
+ return ["volume", "create", volume.name];
7179
+ },
7180
+ errorMessage: getCliErrorMessage,
7181
+ isServiceDown(message2) {
7182
+ return /apiserver|not running|connection refused|could not connect|xpc/i.test(
7183
+ message2
7184
+ );
7185
+ },
7186
+ isMissingContainer(message2) {
7187
+ return /not found|no such/i.test(message2);
7188
+ },
7189
+ isMissingVolume(message2) {
7190
+ return /not found|no such/i.test(message2);
7191
+ },
7192
+ isNameConflict(message2) {
7193
+ return /already exists/i.test(message2);
7194
+ },
7195
+ async ensureWorkdir(containerId, workdir) {
7196
+ await spawn3(CLI, ["exec", containerId, "sh", "-c", `mkdir -p ${workdir}`]);
7197
+ },
7198
+ defaultImage: "docker.io/library/alpine:latest",
7199
+ createInstallerContext: createAppleInstallerContext,
7200
+ async imageExists(tag) {
6767
7201
  try {
6768
- if (aborted || options?.signal?.aborted) {
6769
- return abortedCommandResult();
7202
+ await spawn3(CLI, ["image", "inspect", tag]);
7203
+ return true;
7204
+ } catch {
7205
+ return false;
7206
+ }
7207
+ },
7208
+ async buildImage(spec) {
7209
+ const archFlag = spec.identity ? ["--arch", spec.identity] : [];
7210
+ if (spec.dockerfile.includes("\n")) {
7211
+ const tempDir = await mkdtemp(join(tmpdir(), "sandbox-containerfile-"));
7212
+ const dockerfilePath = join(tempDir, "Dockerfile");
7213
+ await writeFile(dockerfilePath, spec.dockerfile);
7214
+ try {
7215
+ await runAppleBuild(
7216
+ ["build", ...archFlag, "-t", spec.tag, "-f", dockerfilePath, tempDir],
7217
+ spec.showBuildLogs
7218
+ );
7219
+ } finally {
7220
+ await rm(tempDir, { recursive: true, force: true });
6770
7221
  }
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
- };
7222
+ return;
7223
+ }
7224
+ await runAppleBuild(
7225
+ [
7226
+ "build",
7227
+ ...archFlag,
7228
+ "-t",
7229
+ spec.tag,
7230
+ "-f",
7231
+ spec.dockerfile,
7232
+ spec.context
7233
+ ],
7234
+ spec.showBuildLogs
7235
+ );
7236
+ },
7237
+ errors: {
7238
+ serviceNotAvailable: () => new ContainerServiceNotRunningError(),
7239
+ creation: (message2, image, cause) => new AppleContainerCreationError(message2, image, cause),
7240
+ generic: (message2, containerId) => new AppleContainerSandboxError(message2, containerId),
7241
+ volumePath: (source, containerPath, reason) => new AppleContainerVolumePathError(source, containerPath, reason),
7242
+ volumeInspect: (name, reason) => new AppleContainerVolumeInspectError(name, reason),
7243
+ volumeCreate: (name, reason) => new AppleContainerVolumeCreateError(name, reason),
7244
+ volumeRemove: (name, reason) => new AppleContainerVolumeRemoveError(name, reason)
7245
+ }
7246
+ };
7247
+ function createAppleInstallerContext(containerId, image) {
7248
+ const packageManager = isDebianBased(image) ? "apt-get" : "apk";
7249
+ let archPromise = null;
7250
+ const ensuredTools = /* @__PURE__ */ new Set();
7251
+ let aptUpdated = false;
7252
+ const exec = async (command) => {
7253
+ try {
7254
+ const result = await spawn3(CLI, [
7255
+ "exec",
7256
+ containerId,
7257
+ "sh",
7258
+ "-c",
7259
+ command
7260
+ ]);
7261
+ return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
6783
7262
  } catch (error) {
6784
- if (aborted) return abortedCommandResult();
6785
7263
  const err = error;
6786
7264
  return {
6787
7265
  stdout: err.stdout ?? "",
6788
- stderr: err.stderr ?? err.message ?? String(error),
7266
+ stderr: err.stderr ?? err.message ?? "",
6789
7267
  exitCode: err.exitCode ?? 1
6790
7268
  };
6791
- } finally {
6792
- options?.signal?.removeEventListener("abort", abort);
6793
7269
  }
6794
7270
  };
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
- }
7271
+ const arch = async () => {
7272
+ if (!archPromise) {
7273
+ const attempt = (async () => {
7274
+ const result = await exec("uname -m");
7275
+ if (result.exitCode !== 0) {
7276
+ throw new AppleContainerSandboxError(
7277
+ `Failed to detect container architecture: ${result.stderr}`,
7278
+ containerId
7279
+ );
6823
7280
  }
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
- }
6838
- },
6839
- async dispose() {
6840
- },
6841
- [Symbol.asyncDispose]() {
6842
- return this.dispose();
6843
- }
6844
- };
6845
- }
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
- });
6859
- return {
6860
- stdout: stdout.stream,
6861
- stderr: stderr.stream,
6862
- exit
6863
- };
6864
- }
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(() => {
7281
+ return result.stdout.trim();
7282
+ })();
7283
+ archPromise = attempt.catch((err) => {
7284
+ archPromise = null;
7285
+ throw err;
6886
7286
  });
6887
7287
  }
7288
+ return archPromise;
6888
7289
  };
6889
- if (signal) {
6890
- if (signal.aborted) abort();
6891
- else signal.addEventListener("abort", abort, { once: true });
6892
- }
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."
7290
+ const installPackages = async (packages) => {
7291
+ if (packages.length === 0) return;
7292
+ const quoted = packages.map(shellQuote).join(" ");
7293
+ let cmd;
7294
+ if (packageManager === "apt-get") {
7295
+ cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
7296
+ } else {
7297
+ cmd = `apk add --no-cache ${quoted}`;
7298
+ }
7299
+ const result = await exec(cmd);
7300
+ if (result.exitCode !== 0) {
7301
+ throw new PackageInstallError(
7302
+ packages,
7303
+ image,
7304
+ packageManager,
7305
+ result.stderr,
7306
+ containerId
6910
7307
  );
6911
7308
  }
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();
7309
+ if (packageManager === "apt-get") aptUpdated = true;
7310
+ };
7311
+ const ensureTool = async (checkName, installName) => {
7312
+ const cacheKey = installName ?? checkName;
7313
+ if (ensuredTools.has(cacheKey)) return;
7314
+ const check = await exec(`which ${shellQuote(checkName)}`);
7315
+ if (check.exitCode === 0) {
7316
+ ensuredTools.add(cacheKey);
7317
+ return;
7318
+ }
7319
+ await installPackages([installName ?? checkName]);
7320
+ ensuredTools.add(cacheKey);
7321
+ };
7322
+ return {
7323
+ containerId,
7324
+ image,
7325
+ packageManager,
7326
+ arch,
7327
+ exec,
7328
+ installPackages,
7329
+ ensureTool
7330
+ };
7331
+ }
7332
+ async function runAppleBuild(args, showBuildLogs) {
7333
+ try {
7334
+ if (showBuildLogs) {
7335
+ await runStreamed(CLI, args);
7336
+ } else {
7337
+ await spawn3(CLI, args);
6929
7338
  }
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
7339
  } catch (error) {
6940
- if (aborted) return abortedExitInfo();
6941
- const err = toError(error);
6942
- stdout.error(err);
6943
- stderr.error(err);
6944
- throw err;
7340
+ throw new AppleContainerImageBuildError(getCliErrorMessage(error));
7341
+ }
7342
+ }
7343
+ function runStreamed(command, args) {
7344
+ return new Promise((resolve4, reject) => {
7345
+ const child = childSpawn2(command, args, { stdio: "inherit" });
7346
+ child.once("error", reject);
7347
+ child.once(
7348
+ "exit",
7349
+ (code) => code === 0 ? resolve4() : reject(
7350
+ new Error(
7351
+ `${command} build exited with code ${code} (see build output above)`
7352
+ )
7353
+ )
7354
+ );
7355
+ });
7356
+ }
7357
+ async function createAppleContainerSandbox(options = {}) {
7358
+ if (isAppleContainerfileOptions(options)) {
7359
+ return new ContainerfileStrategy(options, appleEngine, {
7360
+ dockerfile: options.dockerfile,
7361
+ context: options.context ?? ".",
7362
+ showBuildLogs: options.showBuildLogs ?? false,
7363
+ identity: options.arch
7364
+ }).create();
7365
+ }
7366
+ return new RuntimeStrategy(options, appleEngine, {
7367
+ image: options.image ?? appleEngine.defaultImage,
7368
+ installers: options.installers ?? []
7369
+ }).create();
7370
+ }
7371
+ async function useAppleContainerSandbox(options, fn) {
7372
+ const sandbox = await createAppleContainerSandbox(options);
7373
+ try {
7374
+ return await fn(sandbox);
6945
7375
  } finally {
6946
- if (signal) {
6947
- signal.removeEventListener("abort", abort);
6948
- }
6949
- stdout.close();
6950
- stderr.close();
6951
- if (sessionCreated) {
6952
- await sandbox.process.deleteSession(sessionId).catch(() => {
6953
- });
6954
- }
7376
+ await sandbox.dispose();
6955
7377
  }
6956
7378
  }
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
- );
7379
+
7380
+ // packages/context/src/lib/sandbox/bash-exception.ts
7381
+ var BashException = class extends Error {
7382
+ };
7383
+
7384
+ // packages/context/src/lib/sandbox/bash-meta.ts
7385
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
7386
+ var store = new AsyncLocalStorage2();
7387
+ function runWithBashMeta(fn) {
7388
+ return store.run({ hidden: {} }, fn);
7389
+ }
7390
+ function useBashMeta() {
7391
+ const state = store.getStore();
7392
+ if (!state) return null;
7393
+ return {
7394
+ setHidden(patch) {
7395
+ state.hidden = { ...state.hidden, ...patch };
6970
7396
  }
6971
- await delay(DAYTONA_EXIT_POLL_INTERVAL_MS);
7397
+ };
7398
+ }
7399
+ function readBashMeta() {
7400
+ return store.getStore();
7401
+ }
7402
+
7403
+ // packages/context/src/lib/sandbox/bash-tool.ts
7404
+ import { tool as tool2 } from "ai";
7405
+ import {
7406
+ createBashTool as externalCreateBashTool
7407
+ } from "bash-tool";
7408
+ import z3 from "zod";
7409
+
7410
+ // packages/context/src/lib/sandbox/upload-skills.ts
7411
+ import * as path3 from "node:path";
7412
+
7413
+ // packages/context/src/lib/skills/loader.ts
7414
+ import * as fs from "node:fs";
7415
+ import * as path from "node:path";
7416
+ import YAML from "yaml";
7417
+ function parseFrontmatter(content) {
7418
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
7419
+ const match = content.match(frontmatterRegex);
7420
+ if (!match) {
7421
+ throw new Error("Invalid SKILL.md: missing or malformed frontmatter");
7422
+ }
7423
+ const [, yamlContent, body] = match;
7424
+ const frontmatter = YAML.parse(yamlContent);
7425
+ if (!frontmatter.name || typeof frontmatter.name !== "string") {
7426
+ throw new Error('Invalid SKILL.md: frontmatter must have a "name" field');
7427
+ }
7428
+ if (!frontmatter.description || typeof frontmatter.description !== "string") {
7429
+ throw new Error(
7430
+ 'Invalid SKILL.md: frontmatter must have a "description" field'
7431
+ );
6972
7432
  }
7433
+ return {
7434
+ frontmatter,
7435
+ body: body.trim()
7436
+ };
6973
7437
  }
6974
- function sessionExitPollTimeoutMs(commandTimeout) {
6975
- if (commandTimeout === void 0 || commandTimeout === 0) {
6976
- return DAYTONA_EXIT_POLL_TIMEOUT_MS;
7438
+ function loadSkillMetadata(skillMdPath) {
7439
+ const content = fs.readFileSync(skillMdPath, "utf-8");
7440
+ const parsed = parseFrontmatter(content);
7441
+ const skillDir = path.dirname(skillMdPath);
7442
+ return {
7443
+ name: parsed.frontmatter.name,
7444
+ description: parsed.frontmatter.description,
7445
+ path: skillDir,
7446
+ skillMdPath
7447
+ };
7448
+ }
7449
+ function discoverSkillsInDirectory(directory) {
7450
+ const skills2 = [];
7451
+ const expandedDir = directory.startsWith("~") ? path.join(process.env.HOME || "", directory.slice(1)) : directory;
7452
+ if (!fs.existsSync(expandedDir)) {
7453
+ return skills2;
6977
7454
  }
6978
- return Math.max(commandTimeout * 1e3, DAYTONA_EXIT_POLL_TIMEOUT_MS);
7455
+ const entries = fs.readdirSync(expandedDir, { withFileTypes: true });
7456
+ for (const entry of entries) {
7457
+ if (!entry.isDirectory()) continue;
7458
+ const skillMdPath = path.join(expandedDir, entry.name, "SKILL.md");
7459
+ if (!fs.existsSync(skillMdPath)) continue;
7460
+ try {
7461
+ const metadata = loadSkillMetadata(skillMdPath);
7462
+ skills2.push(metadata);
7463
+ } catch (error) {
7464
+ console.warn(`Warning: Failed to load skill at ${skillMdPath}:`, error);
7465
+ }
7466
+ }
7467
+ return skills2;
6979
7468
  }
6980
- function delay(ms) {
6981
- return new Promise((resolve4) => setTimeout(resolve4, ms));
7469
+
7470
+ // packages/context/src/lib/sandbox/walk.ts
7471
+ import { readFile, readdir } from "node:fs/promises";
7472
+ import * as path2 from "node:path";
7473
+ function expandHome(input) {
7474
+ if (!input.startsWith("~")) return input;
7475
+ const home = process.env.HOME ?? "";
7476
+ return path2.join(home, input.slice(1));
6982
7477
  }
6983
- function createTextReadable() {
6984
- const encoder = new TextEncoder();
6985
- let controller;
6986
- let closed = false;
6987
- return {
6988
- stream: new ReadableStream({
6989
- start(streamController) {
6990
- controller = streamController;
7478
+ async function walkDirectory(root) {
7479
+ const absoluteRoot = path2.resolve(expandHome(root));
7480
+ const files = [];
7481
+ async function walk(current) {
7482
+ let entries;
7483
+ try {
7484
+ entries = await readdir(current, { withFileTypes: true });
7485
+ } catch {
7486
+ return;
7487
+ }
7488
+ for (const entry of entries) {
7489
+ if (entry.name.startsWith(".")) continue;
7490
+ if (entry.isSymbolicLink()) continue;
7491
+ const entryPath = path2.join(current, entry.name);
7492
+ if (entry.isDirectory()) {
7493
+ await walk(entryPath);
7494
+ } else if (entry.isFile()) {
7495
+ const content = await readFile(entryPath);
7496
+ const relative3 = path2.relative(absoluteRoot, entryPath).split(path2.sep).join("/");
7497
+ files.push({ path: entryPath, relativePath: relative3, content });
6991
7498
  }
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();
7001
- },
7002
- error(error) {
7003
- if (closed) return;
7004
- closed = true;
7005
- controller?.error(error);
7006
7499
  }
7007
- };
7008
- }
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}`)}`;
7015
7500
  }
7016
- for (const [key] of entries) {
7017
- validateEnvKey(key);
7501
+ await walk(absoluteRoot);
7502
+ return files;
7503
+ }
7504
+
7505
+ // packages/context/src/lib/sandbox/upload-skills.ts
7506
+ function expandHome2(input) {
7507
+ if (!input.startsWith("~")) return input;
7508
+ const home = process.env.HOME ?? "";
7509
+ return path3.join(home, input.slice(1));
7510
+ }
7511
+ function joinSandbox(base, relative3) {
7512
+ if (!relative3) return base;
7513
+ const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
7514
+ return `${normalizedBase}/${relative3}`;
7515
+ }
7516
+ function discoverSkillMappings(inputs) {
7517
+ if (inputs.length === 0) return [];
7518
+ const discoveredByName = /* @__PURE__ */ new Map();
7519
+ for (const { host, sandbox: sandboxBase } of inputs) {
7520
+ const absoluteHost = path3.resolve(expandHome2(host));
7521
+ for (const skill of discoverSkillsInDirectory(host)) {
7522
+ const absoluteSkillMd = path3.resolve(skill.skillMdPath);
7523
+ const relative3 = path3.relative(absoluteHost, absoluteSkillMd).split(path3.sep).join("/");
7524
+ discoveredByName.set(skill.name, {
7525
+ name: skill.name,
7526
+ description: skill.description,
7527
+ host: skill.skillMdPath,
7528
+ sandbox: joinSandbox(sandboxBase, relative3)
7529
+ });
7530
+ }
7018
7531
  }
7019
- const exports = entries.map(([key, value]) => `export ${key}=${shellQuote(value)}`).join("; ");
7020
- return `sh -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
7532
+ return Array.from(discoveredByName.values());
7021
7533
  }
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
- );
7534
+ async function uploadSkills(sandbox, inputs) {
7535
+ if (inputs.length === 0) return [];
7536
+ const skills2 = discoverSkillMappings(inputs);
7537
+ const filesToUpload = [];
7538
+ for (const { host, sandbox: sandboxBase } of inputs) {
7539
+ const walked = await walkDirectory(host);
7540
+ for (const file of walked) {
7541
+ filesToUpload.push({
7542
+ path: joinSandbox(sandboxBase, file.relativePath),
7543
+ content: file.content
7544
+ });
7545
+ }
7027
7546
  }
7547
+ if (filesToUpload.length > 0) {
7548
+ await sandbox.writeFiles(filesToUpload);
7549
+ }
7550
+ return skills2;
7028
7551
  }
7029
- function createSessionId(kind) {
7030
- return `deepagents-${kind}-${randomUUID()}`;
7031
- }
7032
- function abortedCommandResult() {
7552
+
7553
+ // packages/context/src/lib/sandbox/bash-tool.ts
7554
+ var REASONING_INSTRUCTION = 'Every bash tool call must include a brief non-empty "reasoning" input explaining why the command is needed.';
7555
+ function withBashExceptionCatch(sandbox) {
7033
7556
  return {
7034
- stdout: "",
7035
- stderr: "Command aborted",
7036
- exitCode: 1
7557
+ ...sandbox,
7558
+ async executeCommand(command, options) {
7559
+ try {
7560
+ return await sandbox.executeCommand(command, options);
7561
+ } catch (err) {
7562
+ if (err instanceof BashException) return err.format();
7563
+ throw err;
7564
+ }
7565
+ }
7037
7566
  };
7038
7567
  }
7039
- function abortedExitInfo() {
7568
+ async function createBashTool(options) {
7569
+ const {
7570
+ skills: skillInputs = [],
7571
+ extraInstructions,
7572
+ sandbox: backend,
7573
+ destination,
7574
+ ...rest
7575
+ } = options;
7576
+ const sandbox = withAbortSignal(withBashExceptionCatch(backend));
7577
+ const combinedInstructions = [extraInstructions, REASONING_INSTRUCTION].filter(Boolean).join("\n\n");
7578
+ const toolkit = await externalCreateBashTool({
7579
+ ...rest,
7580
+ sandbox,
7581
+ destination,
7582
+ extraInstructions: combinedInstructions
7583
+ });
7584
+ const upstreamBash = toolkit.bash;
7585
+ const originalExecute = upstreamBash.execute;
7586
+ const toolBuilder = tool2;
7587
+ const bash = toolBuilder({
7588
+ ...upstreamBash,
7589
+ description: upstreamBash.description ?? "",
7590
+ inputSchema: z3.object({
7591
+ command: z3.string().describe("The bash command to execute"),
7592
+ reasoning: z3.string().trim().describe("Brief reason for executing this command")
7593
+ }),
7594
+ execute: async ({ command }, execOptions) => {
7595
+ if (!originalExecute) {
7596
+ throw new Error("bash tool execution is not available");
7597
+ }
7598
+ const { abortSignal } = execOptions;
7599
+ return runWithAbortSignal(
7600
+ abortSignal,
7601
+ () => runWithBashMeta(async () => {
7602
+ const result = await originalExecute({ command }, execOptions);
7603
+ const state = readBashMeta();
7604
+ if (!state) return result;
7605
+ if (Object.keys(state.hidden).length === 0) return result;
7606
+ return { ...result, meta: state.hidden };
7607
+ })
7608
+ );
7609
+ },
7610
+ toModelOutput: ({ output }) => {
7611
+ if (typeof output !== "object" || output === null) {
7612
+ return { type: "json", value: output };
7613
+ }
7614
+ const { meta: _meta, ...visible } = output;
7615
+ return { type: "json", value: visible };
7616
+ }
7617
+ });
7618
+ const upstreamWriteFile = toolkit.tools.writeFile;
7619
+ const originalWriteExecute = upstreamWriteFile.execute;
7620
+ const writeFileBuilder = tool2;
7621
+ const writeFile2 = writeFileBuilder({
7622
+ ...upstreamWriteFile,
7623
+ execute: async (input, options2) => {
7624
+ if (!originalWriteExecute) {
7625
+ throw new Error("writeFile tool execution is not available");
7626
+ }
7627
+ try {
7628
+ return await originalWriteExecute(input, options2);
7629
+ } catch (err) {
7630
+ if (err instanceof BashException) return err.format();
7631
+ throw err;
7632
+ }
7633
+ }
7634
+ });
7635
+ const skills2 = await uploadSkills(backend, skillInputs);
7040
7636
  return {
7041
- code: null,
7042
- signal: "SIGKILL",
7043
- success: false
7637
+ ...toolkit,
7638
+ sandbox,
7639
+ bash,
7640
+ tools: { ...toolkit.tools, bash, writeFile: writeFile2 },
7641
+ skills: skills2
7044
7642
  };
7045
7643
  }
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));
7644
+
7645
+ // packages/context/src/lib/sandbox/binary-bridges.ts
7646
+ import { existsSync as existsSync3 } from "fs";
7647
+ import { defineCommand } from "just-bash";
7648
+ import spawn4 from "nano-spawn";
7649
+ import * as path4 from "path";
7650
+ function createBinaryBridges(...binaries) {
7651
+ return binaries.map((input) => {
7652
+ const config = typeof input === "string" ? { name: input } : input;
7653
+ const { name, binaryPath = name, allowedArgs } = config;
7654
+ return defineCommand(name, async (args, ctx) => {
7655
+ if (allowedArgs) {
7656
+ const invalidArg = args.find((arg) => !allowedArgs.test(arg));
7657
+ if (invalidArg) {
7658
+ return {
7659
+ stdout: "",
7660
+ stderr: `${name}: argument '${invalidArg}' not allowed by security policy`,
7661
+ exitCode: 1
7662
+ };
7663
+ }
7664
+ }
7665
+ try {
7666
+ const realCwd = resolveRealCwd(ctx);
7667
+ const resolvedArgs = args.map((arg) => {
7668
+ if (arg.startsWith("-")) {
7669
+ return arg;
7670
+ }
7671
+ const hasExtension = path4.extname(arg) !== "";
7672
+ const hasPathSep = arg.includes(path4.sep) || arg.includes("/");
7673
+ const isRelative = arg.startsWith(".");
7674
+ if (hasExtension || hasPathSep || isRelative) {
7675
+ return path4.resolve(realCwd, arg);
7676
+ }
7677
+ return arg;
7678
+ });
7679
+ const mergedEnv = {
7680
+ ...process.env,
7681
+ ...Object.fromEntries(ctx.env),
7682
+ // ctx.env is a Map, convert to object
7683
+ PATH: process.env.PATH
7684
+ // Always use host PATH for binary bridges
7685
+ };
7686
+ const result = await spawn4(binaryPath, resolvedArgs, {
7687
+ cwd: realCwd,
7688
+ env: mergedEnv
7689
+ });
7690
+ return {
7691
+ stdout: result.stdout,
7692
+ stderr: result.stderr,
7693
+ exitCode: 0
7694
+ };
7695
+ } catch (error) {
7696
+ if (error && typeof error === "object") {
7697
+ const err = error;
7698
+ const cause = err.cause;
7699
+ if (cause?.code === "ENOENT") {
7700
+ return {
7701
+ stdout: "",
7702
+ stderr: `${name}: ${binaryPath} not found`,
7703
+ exitCode: 127
7704
+ };
7705
+ }
7706
+ }
7707
+ if (error && typeof error === "object" && "exitCode" in error) {
7708
+ const subprocessError = error;
7709
+ return {
7710
+ stdout: subprocessError.stdout ?? "",
7711
+ stderr: subprocessError.stderr ?? "",
7712
+ exitCode: subprocessError.exitCode ?? 1
7713
+ };
7714
+ }
7715
+ return {
7716
+ stdout: "",
7717
+ stderr: `${name}: ${error instanceof Error ? error.message : String(error)}`,
7718
+ exitCode: 127
7719
+ };
7720
+ }
7721
+ });
7722
+ });
7723
+ }
7724
+ function resolveRealCwd(ctx) {
7725
+ const fs2 = ctx.fs;
7726
+ let realCwd;
7727
+ if (fs2.root) {
7728
+ realCwd = path4.join(fs2.root, ctx.cwd);
7729
+ } else if (typeof fs2.getMountPoint === "function" && typeof fs2.toRealPath === "function") {
7730
+ const real = fs2.toRealPath(ctx.cwd);
7731
+ realCwd = real ?? process.cwd();
7732
+ } else {
7733
+ realCwd = process.cwd();
7734
+ }
7735
+ if (!existsSync3(realCwd)) {
7736
+ realCwd = process.cwd();
7737
+ }
7738
+ return realCwd;
7739
+ }
7740
+
7741
+ // packages/context/src/lib/sandbox/daytona-sandbox.ts
7742
+ import "bash-tool";
7743
+ import { randomUUID } from "node:crypto";
7744
+ var DAYTONA_DEFAULT_DESTINATION = "/home/daytona";
7745
+ var DAYTONA_EXIT_POLL_INTERVAL_MS = 250;
7746
+ var DAYTONA_EXIT_POLL_TIMEOUT_MS = 3e4;
7747
+ var DaytonaSandboxError = class extends Error {
7748
+ constructor(message2, cause) {
7749
+ super(message2);
7750
+ this.name = "DaytonaSandboxError";
7751
+ this.cause = cause;
7752
+ }
7753
+ };
7754
+ var DaytonaCreationError = class extends DaytonaSandboxError {
7755
+ constructor(message2, cause) {
7756
+ super(`Failed to create Daytona sandbox: ${message2}`, cause);
7757
+ this.name = "DaytonaCreationError";
7758
+ }
7759
+ };
7760
+ var DaytonaCommandError = class extends DaytonaSandboxError {
7761
+ constructor(message2, cause) {
7762
+ super(message2, cause);
7763
+ this.name = "DaytonaCommandError";
7764
+ }
7765
+ };
7766
+ async function createDaytonaSandbox(client, options = {}) {
7767
+ validateDaytonaOptions(options);
7768
+ const sdk = await import("@daytona/sdk");
7769
+ let sandbox;
7770
+ try {
7771
+ if (options.sandboxId !== void 0) {
7772
+ sandbox = await client.get(options.sandboxId);
7773
+ await startIfStopped(sandbox, options);
7774
+ } else {
7775
+ sandbox = await acquireReusedSandbox(client, options, sdk);
7776
+ }
7777
+ } catch (error) {
7778
+ throw normalizeDaytonaError(error, sdk);
7779
+ }
7780
+ const backend = createDaytonaSandboxMethods({
7781
+ sandbox,
7782
+ commandTimeout: options.commandTimeout
7783
+ });
7784
+ if (options.readiness) {
7785
+ try {
7786
+ await options.readiness(backend);
7787
+ } catch (error) {
7788
+ await backend.dispose().catch(() => {
7789
+ });
7790
+ throw error;
7052
7791
  }
7053
7792
  }
7054
- return [...dirs];
7793
+ return backend;
7055
7794
  }
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;
7795
+ var UNRECOVERABLE_SANDBOX_STATES = /* @__PURE__ */ new Set([
7796
+ "error",
7797
+ "build_failed",
7798
+ "destroyed"
7799
+ ]);
7800
+ async function acquireReusedSandbox(client, options, sdk) {
7801
+ let existing;
7802
+ try {
7803
+ existing = await client.get(options.name);
7804
+ } catch (error) {
7805
+ if (error instanceof sdk.DaytonaNotFoundError) {
7806
+ return createSandbox(client, options);
7061
7807
  }
7808
+ throw error;
7062
7809
  }
7063
- return output;
7810
+ if (existing.state && UNRECOVERABLE_SANDBOX_STATES.has(existing.state)) {
7811
+ await deleteSandboxQuietly(existing, options);
7812
+ return createSandbox(client, options);
7813
+ }
7814
+ await startIfStopped(existing, options);
7815
+ return existing;
7064
7816
  }
7065
- function toError(error) {
7066
- return error instanceof Error ? error : new Error(String(error));
7817
+ async function deleteSandboxQuietly(sandbox, options) {
7818
+ try {
7819
+ await sandbox.delete(options.deleteTimeout);
7820
+ } catch {
7821
+ }
7067
7822
  }
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;
7823
+ async function startIfStopped(sandbox, options) {
7824
+ if (sandbox.state && sandbox.state !== "started") {
7825
+ await sandbox.start(options.startTimeout ?? options.createTimeout);
7084
7826
  }
7085
- };
7086
- var DockerNotAvailableError = class extends DockerSandboxError {
7087
- constructor() {
7088
- super("Docker is not available. Ensure Docker daemon is running.");
7089
- this.name = "DockerNotAvailableError";
7827
+ }
7828
+ function normalizeDaytonaError(error, sdk) {
7829
+ const err = toError(error);
7830
+ if (err instanceof sdk.DaytonaError) {
7831
+ return err;
7090
7832
  }
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;
7833
+ return new DaytonaCreationError(err.message, err);
7834
+ }
7835
+ function createSandbox(client, options) {
7836
+ const base = compactObject({
7837
+ name: options.name,
7838
+ user: options.user,
7839
+ language: options.language,
7840
+ envVars: options.envVars,
7841
+ labels: options.labels,
7842
+ public: options.public,
7843
+ autoStopInterval: options.autoStopInterval,
7844
+ autoArchiveInterval: options.autoArchiveInterval,
7845
+ autoDeleteInterval: options.autoDeleteInterval,
7846
+ volumes: options.volumes,
7847
+ networkBlockAll: options.networkBlockAll,
7848
+ networkAllowList: options.networkAllowList,
7849
+ ephemeral: options.ephemeral
7850
+ });
7851
+ if (options.image !== void 0) {
7852
+ const params2 = compactObject({
7853
+ ...base,
7854
+ image: options.image,
7855
+ resources: options.resources
7856
+ });
7857
+ return client.create(params2, {
7858
+ timeout: options.createTimeout,
7859
+ onSnapshotCreateLogs: options.onSnapshotCreateLogs
7860
+ });
7100
7861
  }
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
7862
+ const params = compactObject({
7863
+ ...base,
7864
+ snapshot: options.snapshot
7865
+ });
7866
+ return client.create(params, { timeout: options.createTimeout });
7867
+ }
7868
+ function validateDaytonaOptions(options) {
7869
+ if (options.image && options.snapshot) {
7870
+ throw new DaytonaSandboxError(
7871
+ 'Daytona sandbox options cannot include both "image" and "snapshot". Choose one environment source.'
7111
7872
  );
7112
- this.name = "PackageInstallError";
7113
- this.packages = packages;
7114
- this.image = image;
7115
- this.packageManager = packageManager;
7116
- this.stderr = stderr;
7117
7873
  }
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
7874
+ if (options.resources && !options.image) {
7875
+ throw new DaytonaSandboxError(
7876
+ 'Daytona sandbox options can only include "resources" when creating from "image". The Daytona SDK does not apply resources during default or snapshot creation.'
7129
7877
  );
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
7878
  }
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}`
7879
+ if (options.sandboxId === void 0 && options.name === void 0) {
7880
+ throw new DaytonaSandboxError(
7881
+ 'Daytona sandbox options require "name" (get-or-create) or "sandboxId" (attach). An unnamed sandbox cannot be reclaimed, since dispose() does not delete it.'
7155
7882
  );
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
- }
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;
7198
7883
  }
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;
7884
+ if (!options.sandboxId) {
7885
+ return;
7886
+ }
7887
+ const creationOnlyFields = [
7888
+ "name",
7889
+ "user",
7890
+ "snapshot",
7891
+ "image",
7892
+ "language",
7893
+ "envVars",
7894
+ "labels",
7895
+ "public",
7896
+ "resources",
7897
+ "volumes",
7898
+ "networkAllowList",
7899
+ "networkBlockAll",
7900
+ "autoStopInterval",
7901
+ "autoArchiveInterval",
7902
+ "autoDeleteInterval",
7903
+ "ephemeral",
7904
+ "onSnapshotCreateLogs"
7905
+ ];
7906
+ const present = creationOnlyFields.filter((field) => {
7907
+ return options[field] !== void 0;
7908
+ });
7909
+ if (present.length > 0) {
7910
+ throw new DaytonaSandboxError(
7911
+ `Daytona sandbox options cannot combine "sandboxId" with creation options: ${present.join(", ")}`
7912
+ );
7208
7913
  }
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
7914
  }
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) => {
7915
+ function createDaytonaSandboxMethods(args) {
7916
+ const { sandbox, commandTimeout } = args;
7917
+ const executeCommand = async (command, options) => {
7918
+ if (options?.signal?.aborted) {
7919
+ return abortedCommandResult();
7920
+ }
7921
+ let aborted = false;
7922
+ const abort = () => {
7923
+ aborted = true;
7924
+ };
7925
+ options?.signal?.addEventListener("abort", abort, { once: true });
7228
7926
  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 };
7927
+ if (aborted || options?.signal?.aborted) {
7928
+ return abortedCommandResult();
7929
+ }
7930
+ const response = await sandbox.process.executeCommand(
7931
+ command,
7932
+ void 0,
7933
+ void 0,
7934
+ commandTimeout
7935
+ );
7936
+ if (aborted) return abortedCommandResult();
7937
+ return {
7938
+ stdout: response.result ?? response.artifacts?.stdout ?? "",
7939
+ stderr: "",
7940
+ exitCode: response.exitCode ?? 0
7941
+ };
7237
7942
  } catch (error) {
7943
+ if (aborted) return abortedCommandResult();
7238
7944
  const err = error;
7239
7945
  return {
7240
7946
  stdout: err.stdout ?? "",
7241
- stderr: err.stderr ?? err.message ?? "",
7947
+ stderr: err.stderr ?? err.message ?? String(error),
7242
7948
  exitCode: err.exitCode ?? 1
7243
7949
  };
7950
+ } finally {
7951
+ options?.signal?.removeEventListener("abort", abort);
7244
7952
  }
7245
7953
  };
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
- );
7255
- }
7256
- return result.stdout.trim();
7257
- })();
7258
- archPromise = attempt.catch((err) => {
7259
- archPromise = null;
7260
- throw err;
7954
+ return {
7955
+ executeCommand,
7956
+ spawn(command, options) {
7957
+ return spawnDaytonaProcess(sandbox, command, {
7958
+ ...options,
7959
+ commandTimeout
7261
7960
  });
7961
+ },
7962
+ async readFile(path5) {
7963
+ try {
7964
+ const bytes = await sandbox.fs.downloadFile(path5);
7965
+ return Buffer.from(bytes).toString("utf-8");
7966
+ } catch (error) {
7967
+ throw new DaytonaCommandError(
7968
+ `Failed to read file "${path5}": ${toError(error).message}`,
7969
+ toError(error)
7970
+ );
7971
+ }
7972
+ },
7973
+ async writeFiles(files) {
7974
+ try {
7975
+ for (const dir of uniqueParentDirectories(files.map((f) => f.path))) {
7976
+ const result = await executeCommand(`mkdir -p ${shellQuote(dir)}`);
7977
+ if (result.exitCode !== 0) {
7978
+ throw new DaytonaCommandError(
7979
+ `Failed to create directory "${dir}": ${result.stderr}`
7980
+ );
7981
+ }
7982
+ }
7983
+ await sandbox.fs.uploadFiles(
7984
+ files.map((file) => ({
7985
+ source: Buffer.from(file.content),
7986
+ destination: file.path
7987
+ }))
7988
+ );
7989
+ } catch (error) {
7990
+ if (error instanceof DaytonaSandboxError) throw error;
7991
+ const err = toError(error);
7992
+ throw new DaytonaCommandError(
7993
+ `Failed to write files: ${err.message}`,
7994
+ err
7995
+ );
7996
+ }
7997
+ },
7998
+ async dispose() {
7999
+ },
8000
+ [Symbol.asyncDispose]() {
8001
+ return this.dispose();
7262
8002
  }
7263
- return archPromise;
7264
- };
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}`;
7273
- }
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
7282
- );
7283
- }
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;
7293
- }
7294
- await installPackages([installName ?? checkName]);
7295
- ensuredTools.add(cacheKey);
7296
8003
  };
8004
+ }
8005
+ function spawnDaytonaProcess(sandbox, command, options = {}) {
8006
+ const sessionId = createSessionId("spawn");
8007
+ const stdout = createTextReadable();
8008
+ const stderr = createTextReadable();
8009
+ const exit = runSpawnedSession({
8010
+ sandbox,
8011
+ sessionId,
8012
+ command: buildSessionCommand(command, options),
8013
+ signal: options.signal,
8014
+ commandTimeout: options.commandTimeout,
8015
+ stdout,
8016
+ stderr
8017
+ });
7297
8018
  return {
7298
- containerId,
7299
- image,
7300
- packageManager,
7301
- arch,
7302
- exec,
7303
- installPackages,
7304
- ensureTool
8019
+ stdout: stdout.stream,
8020
+ stderr: stderr.stream,
8021
+ exit
7305
8022
  };
7306
8023
  }
7307
-
7308
- // packages/context/src/lib/sandbox/docker-sandbox.ts
7309
- function isDockerfileOptions(opts) {
7310
- return "dockerfile" in opts;
7311
- }
7312
- function isComposeOptions(opts) {
7313
- return "compose" in opts;
7314
- }
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 = {}) {
7327
- 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);
7338
- }
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
- );
7343
- }
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;
8024
+ async function runSpawnedSession(args) {
8025
+ const {
8026
+ sandbox,
8027
+ sessionId,
8028
+ command,
8029
+ signal,
8030
+ commandTimeout,
8031
+ stdout,
8032
+ stderr
8033
+ } = args;
8034
+ let sessionCreated = false;
8035
+ let aborted = signal?.aborted ?? false;
8036
+ let resolveAbort;
8037
+ const abortPromise = new Promise((resolve4) => {
8038
+ resolveAbort = () => resolve4("aborted");
8039
+ });
8040
+ const abort = () => {
8041
+ aborted = true;
8042
+ resolveAbort?.();
8043
+ if (sessionCreated) {
8044
+ sandbox.process.deleteSession(sessionId).catch(() => {
8045
+ });
7367
8046
  }
7368
- return this.createSandboxMethods();
7369
- }
7370
- namedContainerId() {
7371
- return `sandbox-${this.name}`;
7372
- }
7373
- defaultContainerId() {
7374
- return `sandbox-${crypto.randomUUID().slice(0, 8)}`;
8047
+ };
8048
+ if (signal) {
8049
+ if (signal.aborted) abort();
8050
+ else signal.addEventListener("abort", abort, { once: true });
7375
8051
  }
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 };
8052
+ try {
8053
+ await sandbox.process.createSession(sessionId);
8054
+ sessionCreated = true;
8055
+ if (aborted) return abortedExitInfo();
8056
+ const response = await sandbox.process.executeSessionCommand(
8057
+ sessionId,
8058
+ {
8059
+ command,
8060
+ runAsync: true,
8061
+ suppressInputEcho: true
8062
+ },
8063
+ commandTimeout
8064
+ );
8065
+ const commandId = response.cmdId;
8066
+ if (!commandId) {
8067
+ throw new DaytonaCommandError(
8068
+ "Daytona did not return a command id for the spawned session command."
8069
+ );
7382
8070
  }
7383
- const containerId = this.namedContainerId();
7384
- const probe = await this.inspectContainer(containerId);
7385
- if (probe === "running") {
7386
- return { containerId, attached: true };
8071
+ if (aborted) return abortedExitInfo();
8072
+ const logsTask = sandbox.process.getSessionCommandLogs(
8073
+ sessionId,
8074
+ commandId,
8075
+ (chunk) => stdout.enqueue(chunk),
8076
+ (chunk) => stderr.enqueue(chunk)
8077
+ );
8078
+ logsTask.catch(() => {
8079
+ });
8080
+ const winner = await Promise.race([
8081
+ logsTask.then(() => "logs"),
8082
+ abortPromise
8083
+ ]);
8084
+ if (winner === "aborted") {
8085
+ await sandbox.process.deleteSession(sessionId).catch(() => {
8086
+ });
8087
+ return abortedExitInfo();
7387
8088
  }
7388
- if (probe === "stopped") {
7389
- await this.startStoppedContainer(containerId, image);
7390
- return { containerId, attached: true };
8089
+ await logsTask;
8090
+ const code = await waitForSessionCommandExitCode({
8091
+ sandbox,
8092
+ sessionId,
8093
+ commandId,
8094
+ timeoutMs: sessionExitPollTimeoutMs(commandTimeout),
8095
+ isAborted: () => aborted
8096
+ });
8097
+ return { code, signal: null, success: code === 0 };
8098
+ } catch (error) {
8099
+ if (aborted) return abortedExitInfo();
8100
+ const err = toError(error);
8101
+ stdout.error(err);
8102
+ stderr.error(err);
8103
+ throw err;
8104
+ } finally {
8105
+ if (signal) {
8106
+ signal.removeEventListener("abort", abort);
7391
8107
  }
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;
8108
+ stdout.close();
8109
+ stderr.close();
8110
+ if (sessionCreated) {
8111
+ await sandbox.process.deleteSession(sessionId).catch(() => {
8112
+ });
7409
8113
  }
7410
8114
  }
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);
8115
+ }
8116
+ async function waitForSessionCommandExitCode(args) {
8117
+ const { sandbox, sessionId, commandId, timeoutMs, isAborted } = args;
8118
+ const deadline = Date.now() + timeoutMs;
8119
+ while (true) {
8120
+ if (isAborted()) throw new DaytonaCommandError("Daytona command aborted.");
8121
+ const info = await sandbox.process.getSessionCommand(sessionId, commandId);
8122
+ if (typeof info.exitCode === "number") {
8123
+ return info.exitCode;
8124
+ }
8125
+ if (Date.now() >= deadline) {
8126
+ throw new DaytonaCommandError(
8127
+ `Daytona session command "${commandId}" logs closed before an exit code became available.`
8128
+ );
7420
8129
  }
8130
+ await delay(DAYTONA_EXIT_POLL_INTERVAL_MS);
7421
8131
  }
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";
8132
+ }
8133
+ function sessionExitPollTimeoutMs(commandTimeout) {
8134
+ if (commandTimeout === void 0 || commandTimeout === 0) {
8135
+ return DAYTONA_EXIT_POLL_TIMEOUT_MS;
8136
+ }
8137
+ return Math.max(commandTimeout * 1e3, DAYTONA_EXIT_POLL_TIMEOUT_MS);
8138
+ }
8139
+ function delay(ms) {
8140
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
8141
+ }
8142
+ function createTextReadable() {
8143
+ const encoder = new TextEncoder();
8144
+ let controller;
8145
+ let closed = false;
8146
+ return {
8147
+ stream: new ReadableStream({
8148
+ start(streamController) {
8149
+ controller = streamController;
7440
8150
  }
7441
- throw new DockerSandboxError(
7442
- `Failed to inspect container "${containerId}": ${message2}`
7443
- );
8151
+ }),
8152
+ enqueue(chunk) {
8153
+ if (closed || !chunk) return;
8154
+ controller?.enqueue(encoder.encode(chunk));
8155
+ },
8156
+ close() {
8157
+ if (closed) return;
8158
+ closed = true;
8159
+ controller?.close();
8160
+ },
8161
+ error(error) {
8162
+ if (closed) return;
8163
+ closed = true;
8164
+ controller?.error(error);
7444
8165
  }
8166
+ };
8167
+ }
8168
+ function buildSessionCommand(command, options) {
8169
+ const cwdPrefix = options.cwd ? `cd ${shellQuote(options.cwd)} && ` : "";
8170
+ const env = options.env ?? {};
8171
+ const entries = Object.entries(env);
8172
+ if (entries.length === 0) {
8173
+ return `sh -lc ${shellQuote(`${cwdPrefix}${command}`)}`;
7445
8174
  }
7446
- isMissingContainerError(message2) {
7447
- return message2.toLowerCase().includes("no such container");
8175
+ for (const [key] of entries) {
8176
+ validateEnvKey(key);
7448
8177
  }
7449
- isNameConflictError(message2) {
7450
- return message2.toLowerCase().includes("is already in use by container");
8178
+ const exports = entries.map(([key, value]) => `export ${key}=${shellQuote(value)}`).join("; ");
8179
+ return `sh -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
8180
+ }
8181
+ function validateEnvKey(key) {
8182
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
8183
+ throw new DaytonaSandboxError(
8184
+ `Invalid environment variable key: "${key}". Use shell-compatible environment variable names.`
8185
+ );
7451
8186
  }
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
- }
8187
+ }
8188
+ function createSessionId(kind) {
8189
+ return `deepagents-${kind}-${randomUUID()}`;
8190
+ }
8191
+ function abortedCommandResult() {
8192
+ return {
8193
+ stdout: "",
8194
+ stderr: "Command aborted",
8195
+ exitCode: 1
8196
+ };
8197
+ }
8198
+ function abortedExitInfo() {
8199
+ return {
8200
+ code: null,
8201
+ signal: "SIGKILL",
8202
+ success: false
8203
+ };
8204
+ }
8205
+ function uniqueParentDirectories(paths) {
8206
+ const dirs = /* @__PURE__ */ new Set();
8207
+ for (const path5 of paths) {
8208
+ const index = path5.lastIndexOf("/");
8209
+ if (index > 0) {
8210
+ dirs.add(path5.slice(0, index));
7474
8211
  }
7475
8212
  }
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
- }
8213
+ return [...dirs];
8214
+ }
8215
+ function compactObject(input) {
8216
+ const output = {};
8217
+ for (const [key, value] of Object.entries(input)) {
8218
+ if (value !== void 0) {
8219
+ output[key] = value;
7526
8220
  }
7527
8221
  }
7528
- validateMountValue(field, value, volume) {
7529
- if (!value.includes(",")) {
7530
- return;
7531
- }
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`
8222
+ return output;
8223
+ }
8224
+ function toError(error) {
8225
+ return error instanceof Error ? error : new Error(String(error));
8226
+ }
8227
+
8228
+ // packages/context/src/lib/sandbox/docker-sandbox.ts
8229
+ import "bash-tool";
8230
+ import spawn5 from "nano-spawn";
8231
+ import { spawn as childSpawn3 } from "node:child_process";
8232
+ import { createHash as createHash2 } from "node:crypto";
8233
+ import { readFileSync as readFileSync3 } from "node:fs";
8234
+ function isDockerfileOptions(opts) {
8235
+ return "dockerfile" in opts;
8236
+ }
8237
+ function isComposeOptions(opts) {
8238
+ return "compose" in opts;
8239
+ }
8240
+ function dockerMountArg(volume) {
8241
+ const readOnly = volume.readOnly !== false;
8242
+ const parts = volume.type === "bind" ? ["type=bind", `src=${volume.hostPath}`, `dst=${volume.containerPath}`] : [
8243
+ "type=volume",
8244
+ `src=${volume.name}`,
8245
+ `dst=${volume.containerPath}`,
8246
+ ...volume.subPath ? [`volume-subpath=${volume.subPath}`] : [],
8247
+ ...volume.noCopy ? ["volume-nocopy"] : []
8248
+ ];
8249
+ if (readOnly) {
8250
+ parts.push("readonly");
8251
+ }
8252
+ return parts.join(",");
8253
+ }
8254
+ function runDockerBuild(args, stdin, showBuildLogs) {
8255
+ const stdio = [
8256
+ stdin === void 0 ? "inherit" : "pipe",
8257
+ showBuildLogs ? "inherit" : "ignore",
8258
+ showBuildLogs ? "inherit" : "pipe"
8259
+ ];
8260
+ return new Promise((resolve4, reject) => {
8261
+ const child = childSpawn3("docker", args, { stdio });
8262
+ let stderr = "";
8263
+ child.stderr?.on("data", (chunk) => {
8264
+ stderr += chunk;
8265
+ });
8266
+ child.once(
8267
+ "error",
8268
+ (error) => reject(new DockerfileBuildError(error.message))
7537
8269
  );
7538
- }
7539
- buildDockerArgs(image, containerId) {
7540
- const { memory = "1g", cpus = 2 } = this.resources;
8270
+ child.once("exit", (code) => {
8271
+ if (code === 0) {
8272
+ resolve4();
8273
+ return;
8274
+ }
8275
+ reject(
8276
+ new DockerfileBuildError(
8277
+ showBuildLogs ? `docker build exited with code ${code} (see build output above)` : stderr || `docker build exited with code ${code}`
8278
+ )
8279
+ );
8280
+ });
8281
+ if (stdin !== void 0 && child.stdin) {
8282
+ child.stdin.on("error", () => {
8283
+ });
8284
+ child.stdin.end(stdin);
8285
+ }
8286
+ });
8287
+ }
8288
+ var dockerEngine = {
8289
+ cli: "docker",
8290
+ runArgs(image, containerId, opts, workdir) {
8291
+ const {
8292
+ memory = "1g",
8293
+ cpus = 2,
8294
+ memorySwap,
8295
+ shmSize,
8296
+ pidsLimit,
8297
+ ulimits = [],
8298
+ cpusetCpus,
8299
+ cpuShares
8300
+ } = opts.resources ?? {};
8301
+ const security = opts.security ?? {};
8302
+ const network = opts.network ?? {};
7541
8303
  const args = [
7542
8304
  "run",
7543
8305
  "-d",
7544
8306
  "--rm",
7545
8307
  "--name",
7546
8308
  containerId,
7547
- `--memory=${memory}`,
7548
- `--cpus=${cpus}`,
8309
+ "--memory",
8310
+ memory,
8311
+ "--cpus",
8312
+ String(cpus),
7549
8313
  "-w",
7550
- "/workspace"
8314
+ workdir
7551
8315
  ];
7552
- if (this.platform) {
7553
- args.push("--platform", this.platform);
8316
+ if (memorySwap) {
8317
+ args.push("--memory-swap", memorySwap);
7554
8318
  }
7555
- for (const opt of this.securityOpt) {
8319
+ if (shmSize) {
8320
+ args.push("--shm-size", shmSize);
8321
+ }
8322
+ if (pidsLimit !== void 0) {
8323
+ args.push("--pids-limit", String(pidsLimit));
8324
+ }
8325
+ for (const ulimit of ulimits) {
8326
+ args.push("--ulimit", ulimit);
8327
+ }
8328
+ if (cpusetCpus) {
8329
+ args.push("--cpuset-cpus", cpusetCpus);
8330
+ }
8331
+ if (cpuShares !== void 0) {
8332
+ args.push("--cpu-shares", String(cpuShares));
8333
+ }
8334
+ if (opts.platform) {
8335
+ args.push("--platform", opts.platform);
8336
+ }
8337
+ if (opts.runtime) {
8338
+ args.push("--runtime", opts.runtime);
8339
+ }
8340
+ for (const cap of security.capDrop ?? []) {
8341
+ args.push("--cap-drop", cap);
8342
+ }
8343
+ for (const cap of security.capAdd ?? []) {
8344
+ args.push("--cap-add", cap);
8345
+ }
8346
+ if (security.readOnly) {
8347
+ args.push("--read-only");
8348
+ }
8349
+ if (security.user) {
8350
+ args.push("--user", security.user);
8351
+ }
8352
+ for (const mount of security.tmpfs ?? []) {
8353
+ args.push("--tmpfs", mount);
8354
+ }
8355
+ for (const opt of security.securityOpt ?? []) {
7556
8356
  args.push("--security-opt", opt);
7557
8357
  }
7558
- for (const [key, value] of Object.entries(this.env)) {
7559
- args.push("-e", `${key}=${value}`);
8358
+ if (network.mode) {
8359
+ args.push("--network", network.mode);
7560
8360
  }
7561
- for (const volume of this.volumes) {
7562
- args.push("--mount", this.buildVolumeMountArg(volume));
8361
+ for (const port of network.publish ?? []) {
8362
+ args.push("--publish", port);
7563
8363
  }
7564
- args.push(image);
7565
- if (this.command === void 0) {
7566
- args.push("tail", "-f", "/dev/null");
7567
- } else if (this.command !== null) {
7568
- args.push(...this.command);
8364
+ for (const server of network.dns ?? []) {
8365
+ args.push("--dns", server);
7569
8366
  }
7570
- 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");
8367
+ for (const host of network.addHost ?? []) {
8368
+ args.push("--add-host", host);
7583
8369
  }
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);
8370
+ if (network.hostname) {
8371
+ args.push("--hostname", network.hostname);
7595
8372
  }
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;
7605
- }
7606
- throw error;
7607
- }
7608
- throw error;
8373
+ if (opts.gpus) {
8374
+ args.push("--gpus", opts.gpus);
7609
8375
  }
7610
- }
7611
- async createVolume(volume) {
7612
- const args = ["volume", "create"];
7613
- if (volume.driver) {
7614
- args.push("--driver", volume.driver);
8376
+ for (const device of opts.devices ?? []) {
8377
+ args.push("--device", device);
7615
8378
  }
7616
- for (const [key, value] of Object.entries(volume.driverOptions ?? {})) {
7617
- args.push("--opt", `${key}=${value}`);
8379
+ if (opts.init) {
8380
+ args.push("--init");
7618
8381
  }
7619
- 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);
8382
+ for (const [key, value] of Object.entries(opts.labels ?? {})) {
8383
+ args.push("--label", `${key}=${value}`);
7628
8384
  }
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
- }
8385
+ for (const [key, value] of Object.entries(opts.sysctls ?? {})) {
8386
+ args.push("--sysctl", `${key}=${value}`);
7643
8387
  }
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
- }
8388
+ if (opts.entrypoint) {
8389
+ args.push("--entrypoint", opts.entrypoint);
7653
8390
  }
7654
- }
7655
- getDockerErrorMessage(error) {
7656
- const err = error;
7657
- return err.stderr || err.stdout || err.message || String(error);
7658
- }
7659
- isDockerUnavailableError(message2) {
7660
- return message2.includes("Cannot connect") || message2.includes("docker daemon");
7661
- }
7662
- isMissingVolumeInspectError(message2) {
7663
- 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
- );
8391
+ for (const [key, value] of Object.entries(opts.env ?? {})) {
8392
+ args.push("-e", `${key}=${value}`);
7679
8393
  }
7680
- }
7681
- async stopContainer(containerId) {
7682
- try {
7683
- await spawn3("docker", ["stop", containerId]);
7684
- } catch {
8394
+ for (const volume of opts.volumes ?? []) {
8395
+ args.push("--mount", dockerMountArg(volume));
7685
8396
  }
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
- };
8397
+ args.push(image);
8398
+ if (opts.command === void 0) {
8399
+ args.push("tail", "-f", "/dev/null");
8400
+ } else if (opts.command !== null) {
8401
+ args.push(...opts.command);
7706
8402
  }
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}"`
8403
+ return args;
8404
+ },
8405
+ execArgs(containerId, command, options) {
8406
+ const flags = [];
8407
+ if (options?.cwd) {
8408
+ flags.push("-w", options.cwd);
8409
+ }
8410
+ if (options?.env) {
8411
+ for (const [key, value] of Object.entries(options.env)) {
8412
+ if (key.length === 0 || key.includes("=")) {
8413
+ throw new DockerSandboxError(
8414
+ `Invalid environment variable key: "${key}"`
7740
8415
  );
7741
- if (result.exitCode !== 0) {
7742
- throw new Error(
7743
- `Failed to write file "${file.path}": ${result.stderr}`
7744
- );
7745
- }
7746
8416
  }
7747
- },
7748
- dispose: async () => {
7749
- await this.stopContainer(containerId);
7750
- await this.cleanupCreatedVolumes();
7751
- },
7752
- [Symbol.asyncDispose]() {
7753
- return this.dispose();
8417
+ flags.push("-e", `${key}=${value}`);
7754
8418
  }
7755
- };
7756
- return sandbox;
7757
- }
7758
- };
7759
- function validateEnvKey2(key) {
7760
- if (key.length === 0 || key.includes("=")) {
7761
- throw new DockerSandboxError(`Invalid environment variable key: "${key}"`);
7762
- }
7763
- }
7764
- function buildDockerExecFlags(options) {
7765
- const flags = [];
7766
- if (options?.cwd) {
7767
- flags.push("-w", options.cwd);
7768
- }
7769
- if (options?.env) {
7770
- for (const [key, value] of Object.entries(options.env)) {
7771
- validateEnvKey2(key);
7772
- flags.push("-e", `${key}=${value}`);
7773
- }
7774
- }
7775
- return flags;
7776
- }
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
8419
  }
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();
8420
+ return ["exec", ...flags, containerId, "sh", "-c", command];
8421
+ },
8422
+ inspectArgs(containerId) {
8423
+ return [
8424
+ "container",
8425
+ "inspect",
8426
+ "--format",
8427
+ "{{.State.Status}}",
8428
+ containerId
8429
+ ];
8430
+ },
8431
+ mountArg: dockerMountArg,
8432
+ parseStatus(status) {
8433
+ return status === "running" ? "running" : "stopped";
8434
+ },
8435
+ volumeCreateArgs(volume) {
8436
+ const args = ["volume", "create"];
8437
+ if (volume.driver) {
8438
+ args.push("--driver", volume.driver);
7869
8439
  }
7870
- return this.imageTag;
7871
- }
7872
- async configure() {
7873
- }
7874
- async imageExists() {
8440
+ for (const [key, value] of Object.entries(volume.driverOptions ?? {})) {
8441
+ args.push("--opt", `${key}=${value}`);
8442
+ }
8443
+ args.push(volume.name);
8444
+ return args;
8445
+ },
8446
+ errorMessage(error) {
8447
+ const err = error;
8448
+ return err.stderr || err.stdout || err.message || String(error);
8449
+ },
8450
+ isServiceDown(message2) {
8451
+ return message2.includes("Cannot connect") || message2.includes("docker daemon");
8452
+ },
8453
+ isMissingContainer(message2) {
8454
+ return message2.toLowerCase().includes("no such container");
8455
+ },
8456
+ isMissingVolume(message2) {
8457
+ return message2.toLowerCase().includes("no such volume");
8458
+ },
8459
+ isNameConflict(message2) {
8460
+ return message2.toLowerCase().includes("is already in use by container");
8461
+ },
8462
+ async ensureWorkdir() {
8463
+ },
8464
+ defaultImage: "alpine:latest",
8465
+ createInstallerContext,
8466
+ async imageExists(tag) {
7875
8467
  try {
7876
- await spawn3("docker", ["image", "inspect", this.imageTag]);
8468
+ await spawn5("docker", ["image", "inspect", tag]);
7877
8469
  return true;
7878
8470
  } catch {
7879
8471
  return false;
7880
8472
  }
8473
+ },
8474
+ async buildImage(spec) {
8475
+ const inline = spec.dockerfile.includes("\n");
8476
+ const args = [
8477
+ "build",
8478
+ ...spec.identity ? ["--platform", spec.identity] : [],
8479
+ "-t",
8480
+ spec.tag,
8481
+ "-f",
8482
+ inline ? "-" : spec.dockerfile,
8483
+ spec.context
8484
+ ];
8485
+ await runDockerBuild(
8486
+ args,
8487
+ inline ? spec.dockerfile : void 0,
8488
+ spec.showBuildLogs
8489
+ );
8490
+ },
8491
+ errors: {
8492
+ serviceNotAvailable: () => new DockerNotAvailableError(),
8493
+ creation: (message2, image, cause) => new ContainerCreationError(message2, image, cause),
8494
+ generic: (message2, containerId) => new DockerSandboxError(message2, containerId),
8495
+ volumePath: (source, containerPath, reason) => new VolumePathError(source, containerPath, reason),
8496
+ volumeInspect: (name, reason) => new VolumeInspectError(name, reason),
8497
+ volumeCreate: (name, reason) => new VolumeCreateError(name, reason),
8498
+ volumeRemove: (name, reason) => new VolumeRemoveError(name, reason)
7881
8499
  }
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
- }
8500
+ };
8501
+ function validateEnvKey2(key) {
8502
+ if (key.length === 0 || key.includes("=")) {
8503
+ throw new DockerSandboxError(`Invalid environment variable key: "${key}"`);
7912
8504
  }
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
- });
8505
+ }
8506
+ function buildDockerExecFlags(options) {
8507
+ const flags = [];
8508
+ if (options?.cwd) {
8509
+ flags.push("-w", options.cwd);
7931
8510
  }
7932
- };
7933
- var ComposeStrategy = class extends DockerSandboxStrategy {
8511
+ if (options?.env) {
8512
+ for (const [key, value] of Object.entries(options.env)) {
8513
+ validateEnvKey2(key);
8514
+ flags.push("-e", `${key}=${value}`);
8515
+ }
8516
+ }
8517
+ return flags;
8518
+ }
8519
+ var ComposeStrategy = class extends ContainerSandboxStrategy {
7934
8520
  projectName;
7935
8521
  composeFile;
7936
8522
  service;
7937
8523
  constructor(args) {
7938
- super({ resources: args.resources });
8524
+ super({ resources: args.resources }, dockerEngine);
7939
8525
  this.composeFile = args.compose;
7940
8526
  this.service = args.service;
7941
8527
  this.projectName = this.computeProjectName();
7942
8528
  }
7943
8529
  computeProjectName() {
7944
- const content = readFileSync2(this.composeFile, "utf-8");
7945
- const hash = createHash("sha256").update(content).digest("hex").slice(0, 8);
8530
+ const content = readFileSync3(this.composeFile, "utf-8");
8531
+ const hash = createHash2("sha256").update(content).digest("hex").slice(0, 8);
7946
8532
  return `sandbox-${hash}`;
7947
8533
  }
7948
8534
  async getImage() {
@@ -7950,7 +8536,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
7950
8536
  }
7951
8537
  async startContainer(_image, _containerId) {
7952
8538
  try {
7953
- await spawn3("docker", [
8539
+ await spawn5("docker", [
7954
8540
  "compose",
7955
8541
  "-f",
7956
8542
  this.composeFile,
@@ -7962,7 +8548,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
7962
8548
  } catch (error) {
7963
8549
  const err = error;
7964
8550
  if (err.stderr?.includes("Cannot connect")) {
7965
- throw new DockerNotAvailableError();
8551
+ throw this.engine.errors.serviceNotAvailable();
7966
8552
  }
7967
8553
  throw new ComposeStartError(this.composeFile, err.stderr || err.message);
7968
8554
  }
@@ -7974,7 +8560,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
7974
8560
  }
7975
8561
  async exec(command, options) {
7976
8562
  try {
7977
- const result = await spawn3(
8563
+ const result = await spawn5(
7978
8564
  "docker",
7979
8565
  [
7980
8566
  "compose",
@@ -8002,7 +8588,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
8002
8588
  }
8003
8589
  }
8004
8590
  spawnProcess(command, options) {
8005
- const child = childSpawn("docker", [
8591
+ const child = childSpawn3("docker", [
8006
8592
  "compose",
8007
8593
  "-f",
8008
8594
  this.composeFile,
@@ -8020,7 +8606,7 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
8020
8606
  }
8021
8607
  async stopContainer(_containerId) {
8022
8608
  try {
8023
- await spawn3("docker", [
8609
+ await spawn5("docker", [
8024
8610
  "compose",
8025
8611
  "-f",
8026
8612
  this.composeFile,
@@ -8033,40 +8619,25 @@ var ComposeStrategy = class extends DockerSandboxStrategy {
8033
8619
  }
8034
8620
  };
8035
8621
  async function createDockerSandbox(options = {}) {
8036
- let strategy;
8037
8622
  if (isComposeOptions(options)) {
8038
- strategy = new ComposeStrategy({
8623
+ return new ComposeStrategy({
8039
8624
  compose: options.compose,
8040
8625
  service: options.service,
8041
8626
  resources: options.resources
8042
- });
8043
- } else if (isDockerfileOptions(options)) {
8044
- strategy = new DockerfileStrategy({
8627
+ }).create();
8628
+ }
8629
+ if (isDockerfileOptions(options)) {
8630
+ return new ContainerfileStrategy(options, dockerEngine, {
8045
8631
  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
- });
8632
+ context: options.context ?? ".",
8633
+ showBuildLogs: options.showBuildLogs ?? false,
8634
+ identity: options.platform
8635
+ }).create();
8068
8636
  }
8069
- return strategy.create();
8637
+ return new RuntimeStrategy(options, dockerEngine, {
8638
+ image: options.image ?? dockerEngine.defaultImage,
8639
+ installers: options.installers ?? []
8640
+ }).create();
8070
8641
  }
8071
8642
  async function useSandbox(options, fn) {
8072
8643
  const sandbox = await createDockerSandbox(options);
@@ -8082,6 +8653,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
8082
8653
  import { posix as posix2 } from "node:path";
8083
8654
 
8084
8655
  // packages/context/src/lib/sandbox/strace/index.ts
8656
+ import spawn6 from "nano-spawn";
8085
8657
  import { posix } from "node:path";
8086
8658
  var STRACE_FLAGS = "-f -y -qq -e trace=%file,write,pwrite64,writev";
8087
8659
  function buildStraceCommand(command, traceFile, traceDir) {
@@ -8696,6 +9268,359 @@ function formatVersion(version) {
8696
9268
  return /^[<>=!~]/.test(version) ? version : `==${version}`;
8697
9269
  }
8698
9270
 
9271
+ // packages/context/src/lib/sandbox/microsandbox-sandbox.ts
9272
+ import "bash-tool";
9273
+ import { randomUUID as randomUUID3 } from "node:crypto";
9274
+ var MICROSANDBOX_DEFAULT_DESTINATION = "/workspace";
9275
+ var MICROSANDBOX_DEFAULT_IMAGE = "alpine";
9276
+ var MICROSANDBOX_MAX_NAME_BYTES = 128;
9277
+ var COMMAND_TIMEOUT_EXIT_CODE = 124;
9278
+ var MicrosandboxSandboxError = class extends Error {
9279
+ constructor(message2, cause) {
9280
+ super(message2);
9281
+ this.name = "MicrosandboxSandboxError";
9282
+ this.cause = cause;
9283
+ }
9284
+ };
9285
+ var MicrosandboxNotAvailableError = class extends MicrosandboxSandboxError {
9286
+ constructor(cause) {
9287
+ super(
9288
+ "microsandbox is not installed or its runtime is unavailable. Install it with: npm install microsandbox (requires Node >= 22 and hardware virtualization: Apple silicon, Linux with KVM, or Windows with WHP)",
9289
+ cause
9290
+ );
9291
+ this.name = "MicrosandboxNotAvailableError";
9292
+ }
9293
+ };
9294
+ var MicrosandboxCreationError = class extends MicrosandboxSandboxError {
9295
+ constructor(message2, cause) {
9296
+ super(`Failed to create microsandbox: ${message2}`, cause);
9297
+ this.name = "MicrosandboxCreationError";
9298
+ }
9299
+ };
9300
+ var MicrosandboxCommandError = class extends MicrosandboxSandboxError {
9301
+ constructor(message2, cause) {
9302
+ super(message2, cause);
9303
+ this.name = "MicrosandboxCommandError";
9304
+ }
9305
+ };
9306
+ async function createMicrosandboxSandbox(options = {}) {
9307
+ validateMicrosandboxOptions(options);
9308
+ const sdk = await importMicrosandbox();
9309
+ const ephemeral = options.name === void 0;
9310
+ const name = options.name ?? `deepagents-msb-${randomUUID3()}`;
9311
+ const workdir = options.workdir ?? MICROSANDBOX_DEFAULT_DESTINATION;
9312
+ let vm;
9313
+ try {
9314
+ vm = await acquireSandbox(sdk, { ...options, name, ephemeral, workdir });
9315
+ await vm.fs().mkdir(workdir);
9316
+ } catch (error) {
9317
+ throw normalizeMicrosandboxError(error, sdk);
9318
+ }
9319
+ const backend = createMicrosandboxMethods({
9320
+ sdk,
9321
+ vm,
9322
+ name,
9323
+ ephemeral,
9324
+ commandTimeout: options.commandTimeout
9325
+ });
9326
+ if (options.readiness) {
9327
+ try {
9328
+ await options.readiness(backend);
9329
+ } catch (error) {
9330
+ await backend.dispose().catch(() => {
9331
+ });
9332
+ throw error;
9333
+ }
9334
+ }
9335
+ return backend;
9336
+ }
9337
+ async function importMicrosandbox() {
9338
+ try {
9339
+ return await import("microsandbox");
9340
+ } catch (error) {
9341
+ throw new MicrosandboxNotAvailableError(toError2(error));
9342
+ }
9343
+ }
9344
+ async function acquireSandbox(sdk, options) {
9345
+ if (options.ephemeral || options.replace) {
9346
+ return buildSandbox(sdk, options);
9347
+ }
9348
+ let handle;
9349
+ try {
9350
+ handle = await sdk.Sandbox.get(options.name);
9351
+ } catch (error) {
9352
+ if (error instanceof sdk.SandboxNotFoundError) {
9353
+ return createFreshSandbox(sdk, options);
9354
+ }
9355
+ throw error;
9356
+ }
9357
+ if (handle.status === "running") {
9358
+ return handle.connect();
9359
+ }
9360
+ if (handle.status === "draining") {
9361
+ await handle.waitUntilStopped();
9362
+ }
9363
+ try {
9364
+ return await handle.start();
9365
+ } catch {
9366
+ await sdk.Sandbox.remove(options.name).catch(() => {
9367
+ });
9368
+ return buildSandbox(sdk, options);
9369
+ }
9370
+ }
9371
+ async function createFreshSandbox(sdk, options) {
9372
+ try {
9373
+ return await buildSandbox(sdk, options);
9374
+ } catch (error) {
9375
+ if (error instanceof sdk.SandboxAlreadyExistsError) {
9376
+ const handle = await sdk.Sandbox.get(options.name);
9377
+ return handle.status === "running" ? handle.connect() : handle.start();
9378
+ }
9379
+ throw error;
9380
+ }
9381
+ }
9382
+ function buildSandbox(sdk, options) {
9383
+ let builder = sdk.Sandbox.builder(options.name).image(options.image ?? MICROSANDBOX_DEFAULT_IMAGE).patch((patch) => patch.mkdir(options.workdir)).workdir(options.workdir);
9384
+ if (options.ephemeral) builder = builder.ephemeral(true);
9385
+ if (options.replace) builder = builder.replace();
9386
+ if (options.cpus !== void 0) builder = builder.cpus(options.cpus);
9387
+ if (options.memory !== void 0) builder = builder.memory(options.memory);
9388
+ if (options.env) builder = builder.envs(options.env);
9389
+ if (options.configure) builder = options.configure(builder);
9390
+ return builder.create();
9391
+ }
9392
+ function normalizeMicrosandboxError(error, sdk) {
9393
+ const err = toError2(error);
9394
+ if (err instanceof sdk.LibkrunfwNotFoundError) {
9395
+ return new MicrosandboxNotAvailableError(err);
9396
+ }
9397
+ if (err instanceof sdk.MicrosandboxError) {
9398
+ return err;
9399
+ }
9400
+ return new MicrosandboxCreationError(err.message, err);
9401
+ }
9402
+ function validateMicrosandboxOptions(options) {
9403
+ if (options.name !== void 0 && Buffer.byteLength(options.name, "utf-8") > MICROSANDBOX_MAX_NAME_BYTES) {
9404
+ throw new MicrosandboxSandboxError(
9405
+ `Microsandbox names are limited to ${MICROSANDBOX_MAX_NAME_BYTES} UTF-8 bytes.`
9406
+ );
9407
+ }
9408
+ if (options.replace && options.name === void 0) {
9409
+ throw new MicrosandboxSandboxError(
9410
+ 'Microsandbox options can only include "replace" together with "name" \u2014 an unnamed sandbox is always created fresh.'
9411
+ );
9412
+ }
9413
+ }
9414
+ function createMicrosandboxMethods(args) {
9415
+ const { sdk, vm, name, ephemeral, commandTimeout } = args;
9416
+ const spawn7 = (command, options = {}) => {
9417
+ return spawnMicrosandboxProcess(sdk, vm, command, {
9418
+ ...options,
9419
+ commandTimeout
9420
+ });
9421
+ };
9422
+ return {
9423
+ // `executeCommand` lowers onto the same streaming pump as `spawn` so a
9424
+ // single code path supports real cancellation. Cooperative abort (abandon
9425
+ // the promise, Daytona-style) is not an option here: the SDK is an
9426
+ // in-process native binding, so an abandoned exec keeps the guest process
9427
+ // and its libuv handle alive.
9428
+ async executeCommand(command, options) {
9429
+ const proc = spawn7(command, { signal: options?.signal });
9430
+ const [stdout, stderr, info] = await Promise.all([
9431
+ readAllText(proc.stdout),
9432
+ readAllText(proc.stderr),
9433
+ proc.exit
9434
+ ]);
9435
+ if (info.signal === "SIGKILL") {
9436
+ return abortedCommandResult2();
9437
+ }
9438
+ return { stdout, stderr, exitCode: info.code ?? 1 };
9439
+ },
9440
+ spawn: spawn7,
9441
+ async readFile(path5) {
9442
+ try {
9443
+ return await vm.fs().readToString(path5);
9444
+ } catch (error) {
9445
+ throw new MicrosandboxCommandError(
9446
+ `Failed to read file "${path5}": ${toError2(error).message}`,
9447
+ toError2(error)
9448
+ );
9449
+ }
9450
+ },
9451
+ async writeFiles(files) {
9452
+ try {
9453
+ const fs2 = vm.fs();
9454
+ for (const dir of uniqueParentDirectories2(files.map((f) => f.path))) {
9455
+ await fs2.mkdir(dir);
9456
+ }
9457
+ for (const file of files) {
9458
+ await fs2.write(file.path, file.content);
9459
+ }
9460
+ } catch (error) {
9461
+ const err = toError2(error);
9462
+ throw new MicrosandboxCommandError(
9463
+ `Failed to write files: ${err.message}`,
9464
+ err
9465
+ );
9466
+ }
9467
+ },
9468
+ async dispose() {
9469
+ try {
9470
+ await vm.stop();
9471
+ } catch {
9472
+ }
9473
+ if (ephemeral) {
9474
+ await sdk.Sandbox.remove(name).catch(() => {
9475
+ });
9476
+ }
9477
+ },
9478
+ [Symbol.asyncDispose]() {
9479
+ return this.dispose();
9480
+ }
9481
+ };
9482
+ }
9483
+ function spawnMicrosandboxProcess(sdk, vm, command, options) {
9484
+ const stdout = createByteReadable();
9485
+ const stderr = createByteReadable();
9486
+ const exit = pumpExecStream({ sdk, vm, command, options, stdout, stderr });
9487
+ return { stdout: stdout.stream, stderr: stderr.stream, exit };
9488
+ }
9489
+ async function pumpExecStream(args) {
9490
+ const { sdk, vm, command, options, stdout, stderr } = args;
9491
+ const { signal } = options;
9492
+ let handle;
9493
+ let aborted = signal?.aborted ?? false;
9494
+ let timedOut = false;
9495
+ let timeoutTimer;
9496
+ const abort = () => {
9497
+ aborted = true;
9498
+ handle?.kill().catch(() => {
9499
+ });
9500
+ };
9501
+ if (aborted) {
9502
+ stdout.close();
9503
+ stderr.close();
9504
+ return abortedExitInfo2();
9505
+ }
9506
+ signal?.addEventListener("abort", abort, { once: true });
9507
+ try {
9508
+ handle = await vm.execStreamWith("sh", (builder) => {
9509
+ builder.args(["-lc", command]).stdinNull();
9510
+ if (options.cwd) builder.cwd(options.cwd);
9511
+ if (options.env) builder.envs(options.env);
9512
+ return builder;
9513
+ });
9514
+ if (aborted) {
9515
+ return abortedExitInfo2();
9516
+ }
9517
+ if (options.commandTimeout) {
9518
+ const startedHandle = handle;
9519
+ timeoutTimer = setTimeout(() => {
9520
+ timedOut = true;
9521
+ startedHandle.kill().catch(() => {
9522
+ });
9523
+ }, options.commandTimeout);
9524
+ timeoutTimer.unref();
9525
+ }
9526
+ let code = null;
9527
+ for await (const event of handle) {
9528
+ if (event.kind === "stdout") stdout.enqueue(event.data);
9529
+ else if (event.kind === "stderr") stderr.enqueue(event.data);
9530
+ else if (event.kind === "exited") code = event.code;
9531
+ }
9532
+ if (aborted) {
9533
+ return abortedExitInfo2();
9534
+ }
9535
+ if (timedOut) {
9536
+ stderr.enqueue(new TextEncoder().encode("Command timed out"));
9537
+ return { code: COMMAND_TIMEOUT_EXIT_CODE, signal: null, success: false };
9538
+ }
9539
+ return { code, signal: null, success: code === 0 };
9540
+ } catch (error) {
9541
+ if (aborted) {
9542
+ return abortedExitInfo2();
9543
+ }
9544
+ if (timedOut || error instanceof sdk.ExecTimeoutError) {
9545
+ stderr.enqueue(new TextEncoder().encode("Command timed out"));
9546
+ return { code: COMMAND_TIMEOUT_EXIT_CODE, signal: null, success: false };
9547
+ }
9548
+ const err = toError2(error);
9549
+ stdout.error(err);
9550
+ stderr.error(err);
9551
+ throw err;
9552
+ } finally {
9553
+ clearTimeout(timeoutTimer);
9554
+ signal?.removeEventListener("abort", abort);
9555
+ stdout.close();
9556
+ stderr.close();
9557
+ await handle?.[Symbol.asyncDispose]();
9558
+ }
9559
+ }
9560
+ function createByteReadable() {
9561
+ let controller;
9562
+ let closed = false;
9563
+ return {
9564
+ stream: new ReadableStream({
9565
+ start(streamController) {
9566
+ controller = streamController;
9567
+ },
9568
+ cancel() {
9569
+ closed = true;
9570
+ }
9571
+ }),
9572
+ enqueue(chunk) {
9573
+ if (closed || chunk.length === 0) return;
9574
+ controller?.enqueue(chunk);
9575
+ },
9576
+ close() {
9577
+ if (closed) return;
9578
+ closed = true;
9579
+ controller?.close();
9580
+ },
9581
+ error(error) {
9582
+ if (closed) return;
9583
+ closed = true;
9584
+ controller?.error(error);
9585
+ }
9586
+ };
9587
+ }
9588
+ async function readAllText(stream) {
9589
+ const decoder2 = new TextDecoder();
9590
+ let text = "";
9591
+ for await (const chunk of stream) {
9592
+ text += decoder2.decode(chunk, { stream: true });
9593
+ }
9594
+ return text + decoder2.decode();
9595
+ }
9596
+ function abortedCommandResult2() {
9597
+ return {
9598
+ stdout: "",
9599
+ stderr: "Command aborted",
9600
+ exitCode: 1
9601
+ };
9602
+ }
9603
+ function abortedExitInfo2() {
9604
+ return {
9605
+ code: null,
9606
+ signal: "SIGKILL",
9607
+ success: false
9608
+ };
9609
+ }
9610
+ function uniqueParentDirectories2(paths) {
9611
+ const dirs = /* @__PURE__ */ new Set();
9612
+ for (const path5 of paths) {
9613
+ const index = path5.lastIndexOf("/");
9614
+ if (index > 0) {
9615
+ dirs.add(path5.slice(0, index));
9616
+ }
9617
+ }
9618
+ return [...dirs];
9619
+ }
9620
+ function toError2(error) {
9621
+ return error instanceof Error ? error : new Error(String(error));
9622
+ }
9623
+
8699
9624
  // packages/context/src/lib/sandbox/subcommand.ts
8700
9625
  import {
8701
9626
  defineCommand as defineCommand2,
@@ -8777,7 +9702,7 @@ async function createVirtualSandbox(options) {
8777
9702
  env: options.env,
8778
9703
  customCommands: options.customCommands
8779
9704
  });
8780
- return {
9705
+ const sandbox = {
8781
9706
  async executeCommand(command, options2) {
8782
9707
  const result = await bash.exec(
8783
9708
  command,
@@ -8806,6 +9731,16 @@ async function createVirtualSandbox(options) {
8806
9731
  return this.dispose();
8807
9732
  }
8808
9733
  };
9734
+ if (options.readiness) {
9735
+ try {
9736
+ await options.readiness(sandbox);
9737
+ } catch (error) {
9738
+ await sandbox.dispose().catch(() => {
9739
+ });
9740
+ throw error;
9741
+ }
9742
+ }
9743
+ return sandbox;
8809
9744
  }
8810
9745
 
8811
9746
  // packages/context/src/lib/skills/fragments.ts
@@ -12005,6 +12940,13 @@ export {
12005
12940
  AgentOsCreationError,
12006
12941
  AgentOsNotAvailableError,
12007
12942
  AgentOsSandboxError,
12943
+ AppleContainerCreationError,
12944
+ AppleContainerImageBuildError,
12945
+ AppleContainerSandboxError,
12946
+ AppleContainerVolumeCreateError,
12947
+ AppleContainerVolumeInspectError,
12948
+ AppleContainerVolumePathError,
12949
+ AppleContainerVolumeRemoveError,
12008
12950
  AsyncResolver,
12009
12951
  BM25Classifier,
12010
12952
  BashException,
@@ -12012,6 +12954,10 @@ export {
12012
12954
  ComposeStartError,
12013
12955
  ComposeStrategy,
12014
12956
  ContainerCreationError,
12957
+ ContainerSandboxError,
12958
+ ContainerSandboxStrategy,
12959
+ ContainerServiceNotRunningError,
12960
+ ContainerfileStrategy,
12015
12961
  ContextEngine,
12016
12962
  ContextRenderer,
12017
12963
  ContextStore,
@@ -12023,9 +12969,7 @@ export {
12023
12969
  DaytonaSandboxError,
12024
12970
  DockerNotAvailableError,
12025
12971
  DockerSandboxError,
12026
- DockerSandboxStrategy,
12027
12972
  DockerfileBuildError,
12028
- DockerfileStrategy,
12029
12973
  FragmentLoaderResolver,
12030
12974
  FunctionResolver,
12031
12975
  GeneratorResolver,
@@ -12035,7 +12979,12 @@ export {
12035
12979
  Installer,
12036
12980
  IterableResolver,
12037
12981
  LOCALE_METADATA_KEY,
12982
+ MICROSANDBOX_DEFAULT_DESTINATION,
12038
12983
  MarkdownRenderer,
12984
+ MicrosandboxCommandError,
12985
+ MicrosandboxCreationError,
12986
+ MicrosandboxNotAvailableError,
12987
+ MicrosandboxSandboxError,
12039
12988
  MissingRuntimeError,
12040
12989
  ModelsRegistry,
12041
12990
  NpmInstaller,
@@ -12072,6 +13021,7 @@ export {
12072
13021
  analogy,
12073
13022
  and,
12074
13023
  anyToolCalled,
13024
+ appleEngine,
12075
13025
  applyInlineReminder,
12076
13026
  applyPartReminder,
12077
13027
  applyReminderToMessage,
@@ -12095,11 +13045,13 @@ export {
12095
13045
  correction,
12096
13046
  createAdaptivePollingState,
12097
13047
  createAgentOsSandbox,
13048
+ createAppleContainerSandbox,
12098
13049
  createBashTool,
12099
13050
  createBinaryBridges,
12100
13051
  createDaytonaSandbox,
12101
13052
  createDockerSandbox,
12102
13053
  createInstallerContext,
13054
+ createMicrosandboxSandbox,
12103
13055
  createRepairToolCall,
12104
13056
  createVirtualSandbox,
12105
13057
  dateReminder,
@@ -12110,6 +13062,7 @@ export {
12110
13062
  defineSubcommandGroup,
12111
13063
  discoverSkillMappings,
12112
13064
  discoverSkillsInDirectory,
13065
+ dockerEngine,
12113
13066
  downloadAndInstall,
12114
13067
  elapsedExceeds,
12115
13068
  encodeSerializedValue,
@@ -12138,6 +13091,7 @@ export {
12138
13091
  hint,
12139
13092
  hourChanged,
12140
13093
  identity,
13094
+ isAppleContainerfileOptions,
12141
13095
  isComposeOptions,
12142
13096
  isConditionalReminder,
12143
13097
  isDebianBased,
@@ -12182,7 +13136,6 @@ export {
12182
13136
  resetAdaptivePolling,
12183
13137
  resolveReminder,
12184
13138
  resolveReminderAsync,
12185
- resolveReminderText,
12186
13139
  resolveTz,
12187
13140
  role,
12188
13141
  runGuardrailChain,
@@ -12208,6 +13161,7 @@ export {
12208
13161
  timeReminder,
12209
13162
  toFragment,
12210
13163
  toMessageFragment,
13164
+ toToolReminderModelOutput,
12211
13165
  toolCall,
12212
13166
  toolCallCount,
12213
13167
  toolCalled,
@@ -12216,6 +13170,7 @@ export {
12216
13170
  urlBinary,
12217
13171
  usageExceeds,
12218
13172
  useAgentOsSandbox,
13173
+ useAppleContainerSandbox,
12219
13174
  useBashMeta,
12220
13175
  useSandbox,
12221
13176
  user,