@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.
- package/README.md +25 -16
- package/dist/browser.js +18 -8
- package/dist/browser.js.map +2 -2
- package/dist/index.js +2551 -1596
- package/dist/index.js.map +4 -4
- package/dist/lib/agent.d.ts.map +1 -1
- package/dist/lib/engine.d.ts +11 -4
- package/dist/lib/engine.d.ts.map +1 -1
- package/dist/lib/fragments/message/user.d.ts +28 -14
- package/dist/lib/fragments/message/user.d.ts.map +1 -1
- package/dist/lib/sandbox/agent-os-sandbox.d.ts +2 -2
- package/dist/lib/sandbox/agent-os-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/apple-container-sandbox-errors.d.ts +44 -0
- package/dist/lib/sandbox/apple-container-sandbox-errors.d.ts.map +1 -0
- package/dist/lib/sandbox/apple-container-sandbox.d.ts +130 -0
- package/dist/lib/sandbox/apple-container-sandbox.d.ts.map +1 -0
- package/dist/lib/sandbox/bash-meta.d.ts +0 -3
- package/dist/lib/sandbox/bash-meta.d.ts.map +1 -1
- package/dist/lib/sandbox/bash-tool.d.ts +3 -3
- package/dist/lib/sandbox/bash-tool.d.ts.map +1 -1
- package/dist/lib/sandbox/cli-process.d.ts +24 -0
- package/dist/lib/sandbox/cli-process.d.ts.map +1 -0
- package/dist/lib/sandbox/container-engine.d.ts +150 -0
- package/dist/lib/sandbox/container-engine.d.ts.map +1 -0
- package/dist/lib/sandbox/container-sandbox-errors.d.ts +10 -0
- package/dist/lib/sandbox/container-sandbox-errors.d.ts.map +1 -0
- package/dist/lib/sandbox/container-sandbox.d.ts +90 -0
- package/dist/lib/sandbox/container-sandbox.d.ts.map +1 -0
- package/dist/lib/sandbox/daytona-sandbox.d.ts +2 -2
- package/dist/lib/sandbox/daytona-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/docker-sandbox-errors.d.ts +2 -2
- package/dist/lib/sandbox/docker-sandbox-errors.d.ts.map +1 -1
- package/dist/lib/sandbox/docker-sandbox.d.ts +122 -218
- package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/index.d.ts +5 -0
- package/dist/lib/sandbox/index.d.ts.map +1 -1
- package/dist/lib/sandbox/microsandbox-sandbox.d.ts +93 -0
- package/dist/lib/sandbox/microsandbox-sandbox.d.ts.map +1 -0
- package/dist/lib/sandbox/shell-quote.d.ts +2 -2
- package/dist/lib/sandbox/strace/index.d.ts +6 -21
- package/dist/lib/sandbox/strace/index.d.ts.map +1 -1
- package/dist/lib/sandbox/strace/index.js +18 -7
- package/dist/lib/sandbox/strace/index.js.map +2 -2
- package/dist/lib/sandbox/types.d.ts +22 -3
- package/dist/lib/sandbox/types.d.ts.map +1 -1
- package/dist/lib/sandbox/virtual-sandbox.d.ts +2 -2
- package/dist/lib/sandbox/virtual-sandbox.d.ts.map +1 -1
- 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"
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
3900
|
-
*
|
|
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
|
|
3915
|
-
|
|
3916
|
-
|
|
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(
|
|
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
|
-
|
|
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/
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
import {
|
|
6218
|
-
|
|
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/
|
|
6249
|
-
|
|
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/
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
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
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
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
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
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
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
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
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
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
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
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
|
-
|
|
6288
|
-
|
|
6289
|
-
const
|
|
6290
|
-
|
|
6291
|
-
|
|
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
|
-
|
|
6294
|
-
|
|
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
|
|
6416
|
+
return commands;
|
|
6306
6417
|
}
|
|
6307
|
-
|
|
6308
|
-
|
|
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/
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
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
|
-
|
|
6386
|
-
|
|
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
|
-
|
|
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/
|
|
6392
|
-
var
|
|
6393
|
-
|
|
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
|
-
|
|
6396
|
-
|
|
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
|
-
|
|
6399
|
-
} catch (
|
|
6400
|
-
|
|
6401
|
-
|
|
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
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
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
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
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
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
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
|
-
|
|
6459
|
-
|
|
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
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
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
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
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
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
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
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
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
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
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
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
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
|
-
|
|
6580
|
-
|
|
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
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
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
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
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
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
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
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
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
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
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
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
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
|
-
|
|
6704
|
-
|
|
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
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
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
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
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
|
-
|
|
6726
|
-
|
|
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
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
"
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
"
|
|
6745
|
-
|
|
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
|
-
|
|
6748
|
-
|
|
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
|
|
6757
|
-
const
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
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
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
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
|
-
|
|
6769
|
-
|
|
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
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
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 ??
|
|
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
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
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
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
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
|
-
|
|
6890
|
-
if (
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
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 (
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
)
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
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
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
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
|
-
|
|
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
|
|
6975
|
-
|
|
6976
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6981
|
-
|
|
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
|
|
6984
|
-
const
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
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
|
-
|
|
7017
|
-
|
|
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
|
-
|
|
7020
|
-
return `sh -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
|
|
7532
|
+
return Array.from(discoveredByName.values());
|
|
7021
7533
|
}
|
|
7022
|
-
function
|
|
7023
|
-
if (
|
|
7024
|
-
|
|
7025
|
-
|
|
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
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
function
|
|
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
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
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
|
|
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
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7637
|
+
...toolkit,
|
|
7638
|
+
sandbox,
|
|
7639
|
+
bash,
|
|
7640
|
+
tools: { ...toolkit.tools, bash, writeFile: writeFile2 },
|
|
7641
|
+
skills: skills2
|
|
7044
7642
|
};
|
|
7045
7643
|
}
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
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
|
|
7793
|
+
return backend;
|
|
7055
7794
|
}
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
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
|
-
|
|
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
|
|
7066
|
-
|
|
7817
|
+
async function deleteSandboxQuietly(sandbox, options) {
|
|
7818
|
+
try {
|
|
7819
|
+
await sandbox.delete(options.deleteTimeout);
|
|
7820
|
+
} catch {
|
|
7821
|
+
}
|
|
7067
7822
|
}
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
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
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7827
|
+
}
|
|
7828
|
+
function normalizeDaytonaError(error, sdk) {
|
|
7829
|
+
const err = toError(error);
|
|
7830
|
+
if (err instanceof sdk.DaytonaError) {
|
|
7831
|
+
return err;
|
|
7090
7832
|
}
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
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
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
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
|
-
|
|
7120
|
-
|
|
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
|
-
|
|
7149
|
-
|
|
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
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
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
|
|
7223
|
-
const
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
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
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
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
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
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
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
arch,
|
|
7302
|
-
exec,
|
|
7303
|
-
installPackages,
|
|
7304
|
-
ensureTool
|
|
8019
|
+
stdout: stdout.stream,
|
|
8020
|
+
stderr: stderr.stream,
|
|
8021
|
+
exit
|
|
7305
8022
|
};
|
|
7306
8023
|
}
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
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
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
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
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
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
|
-
|
|
7384
|
-
const
|
|
7385
|
-
|
|
7386
|
-
|
|
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
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
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
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
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
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
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
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
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
|
-
|
|
7442
|
-
|
|
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
|
-
|
|
7447
|
-
|
|
8175
|
+
for (const [key] of entries) {
|
|
8176
|
+
validateEnvKey(key);
|
|
7448
8177
|
}
|
|
7449
|
-
|
|
7450
|
-
|
|
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
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
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
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
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
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
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
|
-
|
|
7540
|
-
|
|
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
|
-
|
|
7548
|
-
|
|
8309
|
+
"--memory",
|
|
8310
|
+
memory,
|
|
8311
|
+
"--cpus",
|
|
8312
|
+
String(cpus),
|
|
7549
8313
|
"-w",
|
|
7550
|
-
|
|
8314
|
+
workdir
|
|
7551
8315
|
];
|
|
7552
|
-
if (
|
|
7553
|
-
args.push("--
|
|
8316
|
+
if (memorySwap) {
|
|
8317
|
+
args.push("--memory-swap", memorySwap);
|
|
7554
8318
|
}
|
|
7555
|
-
|
|
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
|
-
|
|
7559
|
-
args.push("
|
|
8358
|
+
if (network.mode) {
|
|
8359
|
+
args.push("--network", network.mode);
|
|
7560
8360
|
}
|
|
7561
|
-
for (const
|
|
7562
|
-
args.push("--
|
|
8361
|
+
for (const port of network.publish ?? []) {
|
|
8362
|
+
args.push("--publish", port);
|
|
7563
8363
|
}
|
|
7564
|
-
|
|
7565
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7617
|
-
args.push("--
|
|
8379
|
+
if (opts.init) {
|
|
8380
|
+
args.push("--init");
|
|
7618
8381
|
}
|
|
7619
|
-
|
|
7620
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
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
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
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
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
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
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
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
|
|
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
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
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
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
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
|
-
|
|
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 =
|
|
7945
|
-
const hash =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
8623
|
+
return new ComposeStrategy({
|
|
8039
8624
|
compose: options.compose,
|
|
8040
8625
|
service: options.service,
|
|
8041
8626
|
resources: options.resources
|
|
8042
|
-
});
|
|
8043
|
-
}
|
|
8044
|
-
|
|
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
|
-
|
|
8049
|
-
|
|
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
|
|
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
|
-
|
|
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,
|