@deepagents/context 3.1.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -19
- package/dist/browser.js +48 -64
- package/dist/browser.js.map +3 -3
- package/dist/index.js +2004 -1479
- package/dist/index.js.map +4 -4
- package/dist/lib/agent.d.ts.map +1 -1
- package/dist/lib/chain-summary.d.ts.map +1 -1
- package/dist/lib/engine.d.ts +3 -3
- package/dist/lib/engine.d.ts.map +1 -1
- package/dist/lib/fragments/message/user.d.ts +51 -92
- package/dist/lib/fragments/message/user.d.ts.map +1 -1
- 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 +122 -0
- package/dist/lib/sandbox/apple-container-sandbox.d.ts.map +1 -0
- package/dist/lib/sandbox/bash-tool.d.ts +9 -28
- 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 +149 -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.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 -189
- package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/index.d.ts +6 -1
- package/dist/lib/sandbox/index.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/bin.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/index.d.ts +7 -7
- package/dist/lib/sandbox/installers/index.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/installer.d.ts +0 -5
- package/dist/lib/sandbox/installers/installer.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/url-binary.d.ts.map +1 -1
- package/dist/lib/sandbox/shell-quote.d.ts +10 -0
- package/dist/lib/sandbox/shell-quote.d.ts.map +1 -0
- package/dist/lib/sandbox/strace/file-change.d.ts +21 -0
- package/dist/lib/sandbox/strace/file-change.d.ts.map +1 -0
- package/dist/lib/sandbox/strace/file-changes.d.ts +66 -0
- package/dist/lib/sandbox/strace/file-changes.d.ts.map +1 -0
- package/dist/lib/sandbox/strace/index.d.ts +74 -0
- package/dist/lib/sandbox/strace/index.d.ts.map +1 -0
- package/dist/lib/sandbox/strace/index.js +296 -0
- package/dist/lib/sandbox/strace/index.js.map +7 -0
- package/dist/lib/sandbox/types.d.ts +9 -4
- package/dist/lib/sandbox/types.d.ts.map +1 -1
- package/dist/lib/sandbox/virtual-sandbox.d.ts.map +1 -1
- package/dist/lib/save/save-pipeline.d.ts +1 -13
- package/dist/lib/save/save-pipeline.d.ts.map +1 -1
- package/dist/lib/skills/skill-reminder.d.ts +2 -2
- package/dist/lib/skills/skill-reminder.d.ts.map +1 -1
- package/package.json +9 -3
- package/dist/lib/sandbox/file-changes.d.ts +0 -83
- package/dist/lib/sandbox/file-changes.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1161,6 +1161,11 @@ function getReminderRanges(metadata) {
|
|
|
1161
1161
|
end: record.end
|
|
1162
1162
|
}));
|
|
1163
1163
|
}
|
|
1164
|
+
function getReminderOnceIds(message2) {
|
|
1165
|
+
const meta = message2.metadata;
|
|
1166
|
+
if (!isRecord(meta) || !Array.isArray(meta.onceIds)) return [];
|
|
1167
|
+
return meta.onceIds.filter((id) => typeof id === "string");
|
|
1168
|
+
}
|
|
1164
1169
|
function getReminderMetadataRecords(metadata) {
|
|
1165
1170
|
const reminders = metadata?.reminders;
|
|
1166
1171
|
if (!Array.isArray(reminders)) return [];
|
|
@@ -1174,27 +1179,6 @@ function normalizeReminderTarget(target) {
|
|
|
1174
1179
|
if (target === "steer") return "steer";
|
|
1175
1180
|
throw new Error(`Unsupported reminder target: ${String(target)}`);
|
|
1176
1181
|
}
|
|
1177
|
-
function isConditionalReminderOptions(options) {
|
|
1178
|
-
return options !== void 0 && "when" in options;
|
|
1179
|
-
}
|
|
1180
|
-
function isPromiseLike(value) {
|
|
1181
|
-
return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
|
|
1182
|
-
}
|
|
1183
|
-
function normalizeImmediateReminderText(textOrFragment) {
|
|
1184
|
-
if (isFragment(textOrFragment)) {
|
|
1185
|
-
return new XmlRenderer().render([textOrFragment]);
|
|
1186
|
-
}
|
|
1187
|
-
if (typeof textOrFragment === "string") {
|
|
1188
|
-
return textOrFragment;
|
|
1189
|
-
}
|
|
1190
|
-
return (ctx) => {
|
|
1191
|
-
const resolved = textOrFragment(ctx);
|
|
1192
|
-
if (isPromiseLike(resolved)) {
|
|
1193
|
-
throw new Error("Async reminder text requires a when predicate");
|
|
1194
|
-
}
|
|
1195
|
-
return resolved;
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
1182
|
function normalizeConditionalReminderText(textOrFragment) {
|
|
1199
1183
|
return isFragment(textOrFragment) ? new XmlRenderer().render([textOrFragment]) : textOrFragment;
|
|
1200
1184
|
}
|
|
@@ -1202,7 +1186,7 @@ function isOutputAvailableToolPart(part) {
|
|
|
1202
1186
|
return isStaticToolUIPart(part) && part.state === "output-available";
|
|
1203
1187
|
}
|
|
1204
1188
|
function isToolOutputReminderEnvelope(value) {
|
|
1205
|
-
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";
|
|
1206
1190
|
}
|
|
1207
1191
|
function stripTextByRanges(text, ranges) {
|
|
1208
1192
|
if (ranges.length === 0) {
|
|
@@ -1325,7 +1309,8 @@ function applyInlineReminder(message2, value) {
|
|
|
1325
1309
|
};
|
|
1326
1310
|
}
|
|
1327
1311
|
function applyPartReminder(message2, value) {
|
|
1328
|
-
const
|
|
1312
|
+
const reminderText = formatTaggedReminder(value);
|
|
1313
|
+
const part = { type: "text", text: reminderText };
|
|
1329
1314
|
message2.parts.push(part);
|
|
1330
1315
|
const partIndex = message2.parts.length - 1;
|
|
1331
1316
|
return {
|
|
@@ -1334,13 +1319,10 @@ function applyPartReminder(message2, value) {
|
|
|
1334
1319
|
target: "user",
|
|
1335
1320
|
partIndex,
|
|
1336
1321
|
start: 0,
|
|
1337
|
-
end:
|
|
1322
|
+
end: reminderText.length,
|
|
1338
1323
|
mode: "part"
|
|
1339
1324
|
};
|
|
1340
1325
|
}
|
|
1341
|
-
function resolveReminderText(item, ctx) {
|
|
1342
|
-
return resolveReminder(item, ctx)?.text ?? "";
|
|
1343
|
-
}
|
|
1344
1326
|
function normalizeReminderResolution(value) {
|
|
1345
1327
|
if (typeof value === "string") {
|
|
1346
1328
|
return value.trim().length === 0 ? null : { text: value };
|
|
@@ -1390,7 +1372,19 @@ function applyRemindersToToolOutput(output, texts) {
|
|
|
1390
1372
|
if (texts.length === 0) return output;
|
|
1391
1373
|
return {
|
|
1392
1374
|
result: output === void 0 ? null : output,
|
|
1393
|
-
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
|
+
}
|
|
1394
1388
|
};
|
|
1395
1389
|
}
|
|
1396
1390
|
function mergeReminderMetadata(message2, addedReminders) {
|
|
@@ -1403,54 +1397,32 @@ function mergeReminderMetadata(message2, addedReminders) {
|
|
|
1403
1397
|
function reminder(textOrFragment, options) {
|
|
1404
1398
|
const target = normalizeReminderTarget(options?.target);
|
|
1405
1399
|
const asPart = target === "user" ? options?.asPart ?? false : false;
|
|
1406
|
-
if (
|
|
1407
|
-
const text2 = normalizeConditionalReminderText(textOrFragment);
|
|
1408
|
-
if (typeof text2 === "string") {
|
|
1409
|
-
assertReminderText(text2);
|
|
1410
|
-
}
|
|
1411
|
-
return {
|
|
1412
|
-
name: "reminder",
|
|
1413
|
-
data: null,
|
|
1414
|
-
metadata: {
|
|
1415
|
-
reminder: {
|
|
1416
|
-
text: text2,
|
|
1417
|
-
when: options.when,
|
|
1418
|
-
asPart,
|
|
1419
|
-
target
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1424
|
-
if (target !== "user") {
|
|
1400
|
+
if (options?.when === void 0 && target !== "user") {
|
|
1425
1401
|
throw new Error(`Reminder target "${target}" requires a when predicate`);
|
|
1426
1402
|
}
|
|
1427
|
-
const text =
|
|
1403
|
+
const text = normalizeConditionalReminderText(textOrFragment);
|
|
1428
1404
|
if (typeof text === "string") {
|
|
1429
1405
|
assertReminderText(text);
|
|
1430
1406
|
}
|
|
1431
1407
|
return {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1408
|
+
name: "reminder",
|
|
1409
|
+
data: null,
|
|
1410
|
+
metadata: {
|
|
1411
|
+
reminder: {
|
|
1412
|
+
text,
|
|
1413
|
+
when: options?.when ?? (() => true),
|
|
1414
|
+
asPart,
|
|
1415
|
+
target
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1435
1418
|
};
|
|
1436
1419
|
}
|
|
1437
|
-
function user(content
|
|
1420
|
+
function user(content) {
|
|
1438
1421
|
const message2 = typeof content === "string" ? {
|
|
1439
1422
|
id: generateId2(),
|
|
1440
1423
|
role: "user",
|
|
1441
1424
|
parts: [{ type: "text", text: content }]
|
|
1442
1425
|
} : { ...content, role: "user", parts: [...content.parts] };
|
|
1443
|
-
if (reminders.length > 0) {
|
|
1444
|
-
const plainText = extractPlainText(message2);
|
|
1445
|
-
const added = [];
|
|
1446
|
-
for (const item of reminders) {
|
|
1447
|
-
const meta = applyReminderToMessage(message2, item, {
|
|
1448
|
-
content: plainText
|
|
1449
|
-
});
|
|
1450
|
-
if (meta) added.push(meta);
|
|
1451
|
-
}
|
|
1452
|
-
mergeReminderMetadata(message2, added);
|
|
1453
|
-
}
|
|
1454
1426
|
return {
|
|
1455
1427
|
id: message2.id,
|
|
1456
1428
|
name: "user",
|
|
@@ -1466,6 +1438,16 @@ function user(content, ...reminders) {
|
|
|
1466
1438
|
}
|
|
1467
1439
|
};
|
|
1468
1440
|
}
|
|
1441
|
+
function applyUserRemindersToMessage(message2, reminders) {
|
|
1442
|
+
if (reminders.length === 0) return;
|
|
1443
|
+
const plainText = extractPlainText(message2);
|
|
1444
|
+
const added = [];
|
|
1445
|
+
for (const item of reminders) {
|
|
1446
|
+
const meta = applyReminderToMessage(message2, item, { content: plainText });
|
|
1447
|
+
if (meta) added.push(meta);
|
|
1448
|
+
}
|
|
1449
|
+
mergeReminderMetadata(message2, added);
|
|
1450
|
+
}
|
|
1469
1451
|
function synthesizeSteerUserMessage(text, firedAt, onceIds = []) {
|
|
1470
1452
|
const texts = Array.isArray(text) ? text : [text];
|
|
1471
1453
|
for (const value of texts) assertReminderText(value);
|
|
@@ -1944,12 +1926,20 @@ function wrapToolsWithOutputReminders(tools, context) {
|
|
|
1944
1926
|
wrapped[name] = toolDef;
|
|
1945
1927
|
continue;
|
|
1946
1928
|
}
|
|
1929
|
+
const originalToModelOutput = toolDef.toModelOutput;
|
|
1947
1930
|
wrapped[name] = {
|
|
1948
1931
|
...toolDef,
|
|
1949
1932
|
execute: async (input, options) => {
|
|
1950
1933
|
const result = await execute.call(toolDef, input, options);
|
|
1951
1934
|
if (isAsyncIterable(result)) return result;
|
|
1952
1935
|
return context.applyToolOutputReminders(result);
|
|
1936
|
+
},
|
|
1937
|
+
toModelOutput: (args) => {
|
|
1938
|
+
const project = (output) => originalToModelOutput ? originalToModelOutput({
|
|
1939
|
+
...args,
|
|
1940
|
+
output
|
|
1941
|
+
}) : defaultToolModelOutput(output);
|
|
1942
|
+
return toToolReminderModelOutput(args.output, project) ?? project(args.output);
|
|
1953
1943
|
}
|
|
1954
1944
|
};
|
|
1955
1945
|
}
|
|
@@ -1958,6 +1948,9 @@ function wrapToolsWithOutputReminders(tools, context) {
|
|
|
1958
1948
|
function isAsyncIterable(value) {
|
|
1959
1949
|
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
1960
1950
|
}
|
|
1951
|
+
function defaultToolModelOutput(output) {
|
|
1952
|
+
return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output ?? null };
|
|
1953
|
+
}
|
|
1961
1954
|
function agent(options) {
|
|
1962
1955
|
return new Agent(options);
|
|
1963
1956
|
}
|
|
@@ -2122,6 +2115,7 @@ var ChainSummaryBuilder = class {
|
|
|
2122
2115
|
}
|
|
2123
2116
|
return;
|
|
2124
2117
|
}
|
|
2118
|
+
for (const id of getReminderOnceIds(message2)) this.#firedOnceIds.add(id);
|
|
2125
2119
|
this.#turn++;
|
|
2126
2120
|
this.#lastMessageAt = msg.createdAt;
|
|
2127
2121
|
this.#lastMessage = message2;
|
|
@@ -2620,12 +2614,10 @@ async function evaluateFiredReminders(configs, whenCtx) {
|
|
|
2620
2614
|
var SavePipeline = class {
|
|
2621
2615
|
#engine;
|
|
2622
2616
|
#pending;
|
|
2623
|
-
#fragments;
|
|
2624
2617
|
#shouldBranch = true;
|
|
2625
|
-
constructor(engine, pending
|
|
2618
|
+
constructor(engine, pending) {
|
|
2626
2619
|
this.#engine = engine;
|
|
2627
2620
|
this.#pending = pending;
|
|
2628
|
-
this.#fragments = fragments;
|
|
2629
2621
|
}
|
|
2630
2622
|
async applyUpdateBranching(shouldBranch) {
|
|
2631
2623
|
this.#shouldBranch = shouldBranch;
|
|
@@ -2641,50 +2633,6 @@ var SavePipeline = class {
|
|
|
2641
2633
|
}
|
|
2642
2634
|
return this;
|
|
2643
2635
|
}
|
|
2644
|
-
/**
|
|
2645
|
-
* Fold any fired `target: 'user'` conditional reminders into the last pending
|
|
2646
|
-
* user message. `steer` reminders fire mid-loop via the engine's prepareStep
|
|
2647
|
-
* hook and `tool-output` reminders wrap at tool-execution time, so neither is
|
|
2648
|
-
* handled here — the save pipeline only carries user-message reminders.
|
|
2649
|
-
*/
|
|
2650
|
-
async evaluateUserReminders() {
|
|
2651
|
-
const configs = this.#fragments.filter(isConditionalReminder).map((fragment3) => fragment3.metadata.reminder).filter((config) => config.target === "user");
|
|
2652
|
-
if (configs.length === 0) return this;
|
|
2653
|
-
const fragmentIndex = this.#pending.findLastIndex(
|
|
2654
|
-
(fragment3) => fragment3.name === "user"
|
|
2655
|
-
);
|
|
2656
|
-
if (fragmentIndex < 0) return this;
|
|
2657
|
-
const fragment2 = this.#pending[fragmentIndex];
|
|
2658
|
-
if (!fragment2.codec) return this;
|
|
2659
|
-
const message2 = requireUserUIMessage(
|
|
2660
|
-
fragment2.codec.encode(),
|
|
2661
|
-
`Pending user fragment "${fragment2.name}"`
|
|
2662
|
-
);
|
|
2663
|
-
const chain = await this.#engine.getChainSummary();
|
|
2664
|
-
const whenCtx = {
|
|
2665
|
-
...this.#engine.buildBaseWhenCtx(chain),
|
|
2666
|
-
content: extractPlainText(message2),
|
|
2667
|
-
currentMessage: message2,
|
|
2668
|
-
lastAssistantMessage: chain.lastAssistantMessage,
|
|
2669
|
-
lastAssistantMessages: chain.lastAssistantMessages
|
|
2670
|
-
};
|
|
2671
|
-
const matched = await evaluateFiredReminders(configs, whenCtx);
|
|
2672
|
-
if (matched.length === 0) return this;
|
|
2673
|
-
const reminders = matched.map((m) => ({
|
|
2674
|
-
text: m.resolved.text,
|
|
2675
|
-
asPart: m.config.asPart,
|
|
2676
|
-
target: "user",
|
|
2677
|
-
metadata: m.resolved.metadata
|
|
2678
|
-
}));
|
|
2679
|
-
const originalId = fragment2.id;
|
|
2680
|
-
const recreated = user(
|
|
2681
|
-
originalId ? { ...message2, id: originalId } : message2,
|
|
2682
|
-
...reminders
|
|
2683
|
-
);
|
|
2684
|
-
if (originalId) recreated.id = originalId;
|
|
2685
|
-
this.#pending[fragmentIndex] = recreated;
|
|
2686
|
-
return this;
|
|
2687
|
-
}
|
|
2688
2636
|
async persist() {
|
|
2689
2637
|
let parentId = this.#engine.getActiveBranch().headMessageId;
|
|
2690
2638
|
const now = Date.now();
|
|
@@ -3718,13 +3666,10 @@ var ContextEngine = class _ContextEngine {
|
|
|
3718
3666
|
if (this.#pendingMessages.length === 0) {
|
|
3719
3667
|
return { headMessageId: this.#branch?.headMessageId ?? void 0 };
|
|
3720
3668
|
}
|
|
3721
|
-
const
|
|
3722
|
-
|
|
3723
|
-
this.#pendingMessages,
|
|
3724
|
-
this.#fragments
|
|
3725
|
-
);
|
|
3669
|
+
const pending = this.#pendingMessages;
|
|
3670
|
+
const pipeline = new SavePipeline(this.#asSavePipelineEngine(), pending);
|
|
3726
3671
|
await pipeline.applyUpdateBranching(options?.branch ?? true);
|
|
3727
|
-
await
|
|
3672
|
+
await this.#foldUserReminders(pending);
|
|
3728
3673
|
const result = await pipeline.persist();
|
|
3729
3674
|
this.#pendingMessages = [];
|
|
3730
3675
|
return result;
|
|
@@ -3765,7 +3710,7 @@ var ContextEngine = class _ContextEngine {
|
|
|
3765
3710
|
const priorStep = stepNumber >= 1 ? steps[stepNumber - 1] : void 0;
|
|
3766
3711
|
const canFire = (priorStep?.content?.length ?? 0) > 0;
|
|
3767
3712
|
if (canFire) {
|
|
3768
|
-
const configs = this.#
|
|
3713
|
+
const configs = this.#remindersFor("steer");
|
|
3769
3714
|
if (configs.length > 0) {
|
|
3770
3715
|
const whenCtx = await this.#steerWhenCtx(session);
|
|
3771
3716
|
const matched = await evaluateFiredReminders(configs, whenCtx);
|
|
@@ -3852,14 +3797,86 @@ var ContextEngine = class _ContextEngine {
|
|
|
3852
3797
|
);
|
|
3853
3798
|
await this.save({ branch: false });
|
|
3854
3799
|
}
|
|
3855
|
-
#
|
|
3856
|
-
return this.#fragments.filter(isConditionalReminder).map((fragment2) => fragment2.metadata.reminder).filter((config) => config.target ===
|
|
3800
|
+
#remindersFor(target) {
|
|
3801
|
+
return this.#fragments.filter(isConditionalReminder).map((fragment2) => fragment2.metadata.reminder).filter((config) => config.target === target);
|
|
3802
|
+
}
|
|
3803
|
+
#buildBaseWhenCtx(chain) {
|
|
3804
|
+
const rawUsage = this.#chatData?.metadata?.usage;
|
|
3805
|
+
const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
|
|
3806
|
+
const elapsed = chain.lastMessageAt !== void 0 ? Date.now() - chain.lastMessageAt : void 0;
|
|
3807
|
+
const chatData = this.#chatData;
|
|
3808
|
+
if (!chatData) {
|
|
3809
|
+
throw new Error("ContextEngine must be initialized before reminders run");
|
|
3810
|
+
}
|
|
3811
|
+
return {
|
|
3812
|
+
turn: chain.turn,
|
|
3813
|
+
messageCount: chain.messageCount,
|
|
3814
|
+
lastMessageAt: chain.lastMessageAt,
|
|
3815
|
+
lastMessage: chain.lastMessage,
|
|
3816
|
+
chat: chatData,
|
|
3817
|
+
usage,
|
|
3818
|
+
branch: this.#branchName,
|
|
3819
|
+
elapsed
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
#buildWhenCtx(chain, currentMessage) {
|
|
3823
|
+
return {
|
|
3824
|
+
...this.#buildBaseWhenCtx(chain),
|
|
3825
|
+
content: extractPlainText(currentMessage),
|
|
3826
|
+
currentMessage,
|
|
3827
|
+
lastAssistantMessage: chain.lastAssistantMessage,
|
|
3828
|
+
lastAssistantMessages: chain.lastAssistantMessages
|
|
3829
|
+
};
|
|
3830
|
+
}
|
|
3831
|
+
/**
|
|
3832
|
+
* `steer` reminders fire mid-loop via prepareStep and `tool-output` reminders
|
|
3833
|
+
* wrap at tool-execution time, so by the time save() runs only user-target
|
|
3834
|
+
* reminders remain to fold into the last pending user message.
|
|
3835
|
+
*/
|
|
3836
|
+
async #foldUserReminders(pending) {
|
|
3837
|
+
const configs = this.#remindersFor("user");
|
|
3838
|
+
if (configs.length === 0) return;
|
|
3839
|
+
const fragmentIndex = pending.findLastIndex(
|
|
3840
|
+
(fragment3) => fragment3.name === "user"
|
|
3841
|
+
);
|
|
3842
|
+
if (fragmentIndex < 0) return;
|
|
3843
|
+
const fragment2 = pending[fragmentIndex];
|
|
3844
|
+
if (!fragment2.codec) return;
|
|
3845
|
+
const message2 = requireUserUIMessage(
|
|
3846
|
+
fragment2.codec.encode(),
|
|
3847
|
+
`Pending user fragment "${fragment2.name}"`
|
|
3848
|
+
);
|
|
3849
|
+
const chain = await this.#getChainContext();
|
|
3850
|
+
const matched = await evaluateFiredReminders(configs, {
|
|
3851
|
+
...this.#buildWhenCtx(chain, message2),
|
|
3852
|
+
firedOnceIds: chain.firedOnceIds
|
|
3853
|
+
});
|
|
3854
|
+
if (matched.length === 0) return;
|
|
3855
|
+
const onceIds = [...new Set(matched.flatMap((m) => m.onceIds))];
|
|
3856
|
+
const reminders = matched.map((m) => ({
|
|
3857
|
+
text: m.resolved.text,
|
|
3858
|
+
asPart: m.config.asPart,
|
|
3859
|
+
metadata: m.resolved.metadata
|
|
3860
|
+
}));
|
|
3861
|
+
const carrier = {
|
|
3862
|
+
...message2,
|
|
3863
|
+
id: fragment2.id ?? message2.id,
|
|
3864
|
+
parts: [...message2.parts]
|
|
3865
|
+
};
|
|
3866
|
+
if (onceIds.length > 0) {
|
|
3867
|
+
carrier.metadata = {
|
|
3868
|
+
...carrier.metadata,
|
|
3869
|
+
onceIds
|
|
3870
|
+
};
|
|
3871
|
+
}
|
|
3872
|
+
applyUserRemindersToMessage(carrier, reminders);
|
|
3873
|
+
pending[fragmentIndex] = user(carrier);
|
|
3857
3874
|
}
|
|
3858
3875
|
async #steerWhenCtx(session) {
|
|
3859
3876
|
await this.#ensureInitialized();
|
|
3860
3877
|
if (!session.whenBase) {
|
|
3861
3878
|
const chain = await this.#getChainContext();
|
|
3862
|
-
const base2 = this.#
|
|
3879
|
+
const base2 = this.#buildBaseWhenCtx(chain);
|
|
3863
3880
|
const currentMessage2 = chain.lastMessage;
|
|
3864
3881
|
if (!currentMessage2) {
|
|
3865
3882
|
throw new Error(
|
|
@@ -3898,9 +3915,9 @@ var ContextEngine = class _ContextEngine {
|
|
|
3898
3915
|
* Evaluate `target: 'tool-output'` reminders against a tool's raw result and
|
|
3899
3916
|
* return the (possibly wrapped) output.
|
|
3900
3917
|
*
|
|
3901
|
-
* Called by the agent's tool wrapper right after each `execute()` resolves
|
|
3902
|
-
*
|
|
3903
|
-
*
|
|
3918
|
+
* Called by the agent's tool wrapper right after each `execute()` resolves.
|
|
3919
|
+
* The store keeps the wrapped value with a host-only marker; the model-facing
|
|
3920
|
+
* projection strips that marker while preserving `result` + `systemReminder`.
|
|
3904
3921
|
*
|
|
3905
3922
|
* Returns the output unchanged when no tool-output reminder fires. Without a
|
|
3906
3923
|
* persisted user message there is no turn context to evaluate against
|
|
@@ -3908,21 +3925,16 @@ var ContextEngine = class _ContextEngine {
|
|
|
3908
3925
|
* passes through untouched.
|
|
3909
3926
|
*/
|
|
3910
3927
|
async applyToolOutputReminders(output) {
|
|
3911
|
-
const configs = this.#
|
|
3928
|
+
const configs = this.#remindersFor("tool-output");
|
|
3912
3929
|
if (configs.length === 0) return output;
|
|
3913
3930
|
await this.#ensureInitialized();
|
|
3914
3931
|
const chain = await this.#getChainContext();
|
|
3915
3932
|
const currentMessage = chain.lastMessage;
|
|
3916
3933
|
if (!currentMessage) return output;
|
|
3917
|
-
const
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
currentMessage,
|
|
3922
|
-
lastAssistantMessage: chain.lastAssistantMessage,
|
|
3923
|
-
lastAssistantMessages: chain.lastAssistantMessages
|
|
3924
|
-
};
|
|
3925
|
-
const matched = await evaluateFiredReminders(configs, whenCtx);
|
|
3934
|
+
const matched = await evaluateFiredReminders(
|
|
3935
|
+
configs,
|
|
3936
|
+
this.#buildWhenCtx(chain, currentMessage)
|
|
3937
|
+
);
|
|
3926
3938
|
if (matched.length === 0) return output;
|
|
3927
3939
|
return applyRemindersToToolOutput(
|
|
3928
3940
|
output,
|
|
@@ -3933,7 +3945,6 @@ var ContextEngine = class _ContextEngine {
|
|
|
3933
3945
|
return {
|
|
3934
3946
|
store: this.#store,
|
|
3935
3947
|
chatId: this.#chatId,
|
|
3936
|
-
branchName: this.#branchName,
|
|
3937
3948
|
getActiveBranch: () => ({
|
|
3938
3949
|
id: this.#activeBranch.id,
|
|
3939
3950
|
headMessageId: this.#activeBranch.headMessageId
|
|
@@ -3945,29 +3956,7 @@ var ContextEngine = class _ContextEngine {
|
|
|
3945
3956
|
);
|
|
3946
3957
|
this.#activeBranch.headMessageId = headMessageId;
|
|
3947
3958
|
},
|
|
3948
|
-
rewindForUpdate: (parentId) => this.#rewindForUpdate(parentId)
|
|
3949
|
-
getChainSummary: () => this.#getChainContext(),
|
|
3950
|
-
buildBaseWhenCtx: (chain) => {
|
|
3951
|
-
const rawUsage = this.#chatData?.metadata?.usage;
|
|
3952
|
-
const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
|
|
3953
|
-
const elapsed = chain.lastMessageAt !== void 0 ? Date.now() - chain.lastMessageAt : void 0;
|
|
3954
|
-
const chatData = this.#chatData;
|
|
3955
|
-
if (!chatData) {
|
|
3956
|
-
throw new Error(
|
|
3957
|
-
"ContextEngine must be initialized before reminders run"
|
|
3958
|
-
);
|
|
3959
|
-
}
|
|
3960
|
-
return {
|
|
3961
|
-
turn: chain.turn,
|
|
3962
|
-
messageCount: chain.messageCount,
|
|
3963
|
-
lastMessageAt: chain.lastMessageAt,
|
|
3964
|
-
lastMessage: chain.lastMessage,
|
|
3965
|
-
chat: chatData,
|
|
3966
|
-
usage,
|
|
3967
|
-
branch: this.#branchName,
|
|
3968
|
-
elapsed
|
|
3969
|
-
};
|
|
3970
|
-
}
|
|
3959
|
+
rewindForUpdate: (parentId) => this.#rewindForUpdate(parentId)
|
|
3971
3960
|
};
|
|
3972
3961
|
}
|
|
3973
3962
|
/**
|
|
@@ -5495,7 +5484,7 @@ function once(id) {
|
|
|
5495
5484
|
return (ctx) => {
|
|
5496
5485
|
if (ctx.firedOnceIds === void 0) {
|
|
5497
5486
|
throw new Error(
|
|
5498
|
-
`once('${id}') is
|
|
5487
|
+
`once('${id}') is not supported on target:'tool-output' reminders`
|
|
5499
5488
|
);
|
|
5500
5489
|
}
|
|
5501
5490
|
if (ctx.firedOnceIds.has(id)) return false;
|
|
@@ -6225,6 +6214,9 @@ async function createAgentOsSandbox(options = {}) {
|
|
|
6225
6214
|
await os.dispose();
|
|
6226
6215
|
} catch {
|
|
6227
6216
|
}
|
|
6217
|
+
},
|
|
6218
|
+
[Symbol.asyncDispose]() {
|
|
6219
|
+
return this.dispose();
|
|
6228
6220
|
}
|
|
6229
6221
|
};
|
|
6230
6222
|
}
|
|
@@ -6237,57 +6229,180 @@ async function useAgentOsSandbox(options, fn) {
|
|
|
6237
6229
|
}
|
|
6238
6230
|
}
|
|
6239
6231
|
|
|
6240
|
-
// packages/context/src/lib/sandbox/
|
|
6241
|
-
|
|
6232
|
+
// packages/context/src/lib/sandbox/apple-container-sandbox.ts
|
|
6233
|
+
import "bash-tool";
|
|
6234
|
+
import spawn3 from "nano-spawn";
|
|
6235
|
+
import { spawn as childSpawn2 } from "node:child_process";
|
|
6236
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
6237
|
+
import { tmpdir } from "node:os";
|
|
6238
|
+
import { join } from "node:path";
|
|
6239
|
+
|
|
6240
|
+
// packages/context/src/lib/sandbox/container-sandbox-errors.ts
|
|
6241
|
+
var ContainerSandboxError = class extends Error {
|
|
6242
|
+
containerId;
|
|
6243
|
+
constructor(message2, containerId) {
|
|
6244
|
+
super(message2);
|
|
6245
|
+
this.name = "ContainerSandboxError";
|
|
6246
|
+
this.containerId = containerId;
|
|
6247
|
+
}
|
|
6242
6248
|
};
|
|
6243
6249
|
|
|
6244
|
-
// packages/context/src/lib/sandbox/
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6250
|
+
// packages/context/src/lib/sandbox/apple-container-sandbox-errors.ts
|
|
6251
|
+
var AppleContainerSandboxError = class extends ContainerSandboxError {
|
|
6252
|
+
constructor(message2, containerId) {
|
|
6253
|
+
super(message2, containerId);
|
|
6254
|
+
this.name = "AppleContainerSandboxError";
|
|
6255
|
+
}
|
|
6256
|
+
};
|
|
6257
|
+
var ContainerServiceNotRunningError = class extends AppleContainerSandboxError {
|
|
6258
|
+
constructor() {
|
|
6259
|
+
super(
|
|
6260
|
+
"Apple container service is not running. Start it with `container system start` (first run also needs `container system kernel set --recommended`)."
|
|
6261
|
+
);
|
|
6262
|
+
this.name = "ContainerServiceNotRunningError";
|
|
6263
|
+
}
|
|
6264
|
+
};
|
|
6265
|
+
var AppleContainerCreationError = class extends AppleContainerSandboxError {
|
|
6266
|
+
image;
|
|
6267
|
+
cause;
|
|
6268
|
+
constructor(message2, image, cause) {
|
|
6269
|
+
super(`Failed to create container from image "${image}": ${message2}`);
|
|
6270
|
+
this.name = "AppleContainerCreationError";
|
|
6271
|
+
this.image = image;
|
|
6272
|
+
this.cause = cause;
|
|
6273
|
+
}
|
|
6274
|
+
};
|
|
6275
|
+
var AppleContainerImageBuildError = class extends AppleContainerSandboxError {
|
|
6276
|
+
stderr;
|
|
6277
|
+
constructor(stderr) {
|
|
6278
|
+
super(`Container image build failed: ${stderr}`);
|
|
6279
|
+
this.name = "AppleContainerImageBuildError";
|
|
6280
|
+
this.stderr = stderr;
|
|
6281
|
+
}
|
|
6282
|
+
};
|
|
6283
|
+
var AppleContainerVolumePathError = class extends AppleContainerSandboxError {
|
|
6284
|
+
source;
|
|
6285
|
+
containerPath;
|
|
6286
|
+
reason;
|
|
6287
|
+
constructor(source, containerPath, reason) {
|
|
6288
|
+
super(
|
|
6289
|
+
`Invalid container volume path "${source}" -> "${containerPath}": ${reason}`
|
|
6290
|
+
);
|
|
6291
|
+
this.name = "AppleContainerVolumePathError";
|
|
6292
|
+
this.source = source;
|
|
6293
|
+
this.containerPath = containerPath;
|
|
6294
|
+
this.reason = reason;
|
|
6295
|
+
}
|
|
6296
|
+
};
|
|
6297
|
+
var AppleContainerVolumeInspectError = class extends AppleContainerSandboxError {
|
|
6298
|
+
volume;
|
|
6299
|
+
reason;
|
|
6300
|
+
constructor(volume, reason) {
|
|
6301
|
+
super(`Failed to inspect container volume "${volume}": ${reason}`);
|
|
6302
|
+
this.name = "AppleContainerVolumeInspectError";
|
|
6303
|
+
this.volume = volume;
|
|
6304
|
+
this.reason = reason;
|
|
6305
|
+
}
|
|
6306
|
+
};
|
|
6307
|
+
var AppleContainerVolumeCreateError = class extends AppleContainerSandboxError {
|
|
6308
|
+
volume;
|
|
6309
|
+
reason;
|
|
6310
|
+
constructor(volume, reason) {
|
|
6311
|
+
super(`Failed to create container volume "${volume}": ${reason}`);
|
|
6312
|
+
this.name = "AppleContainerVolumeCreateError";
|
|
6313
|
+
this.volume = volume;
|
|
6314
|
+
this.reason = reason;
|
|
6315
|
+
}
|
|
6316
|
+
};
|
|
6317
|
+
var AppleContainerVolumeRemoveError = class extends AppleContainerSandboxError {
|
|
6318
|
+
volume;
|
|
6319
|
+
reason;
|
|
6320
|
+
constructor(volume, reason) {
|
|
6321
|
+
super(`Failed to remove container volume "${volume}": ${reason}`);
|
|
6322
|
+
this.name = "AppleContainerVolumeRemoveError";
|
|
6323
|
+
this.volume = volume;
|
|
6324
|
+
this.reason = reason;
|
|
6325
|
+
}
|
|
6326
|
+
};
|
|
6327
|
+
|
|
6328
|
+
// packages/context/src/lib/sandbox/container-sandbox.ts
|
|
6329
|
+
import "bash-tool";
|
|
6330
|
+
import spawn2 from "nano-spawn";
|
|
6331
|
+
import { spawn as childSpawn } from "node:child_process";
|
|
6332
|
+
import { createHash } from "node:crypto";
|
|
6333
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6334
|
+
|
|
6335
|
+
// packages/context/src/lib/sandbox/cli-process.ts
|
|
6336
|
+
import { Readable as Readable2 } from "node:stream";
|
|
6337
|
+
|
|
6338
|
+
// packages/context/src/lib/sandbox/shell-quote.ts
|
|
6339
|
+
function shellQuote(s) {
|
|
6340
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
6249
6341
|
}
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6342
|
+
|
|
6343
|
+
// packages/context/src/lib/sandbox/cli-process.ts
|
|
6344
|
+
function toSandboxProcess(child, abortSignal) {
|
|
6345
|
+
if (!child.stdout || !child.stderr) {
|
|
6346
|
+
child.kill("SIGKILL");
|
|
6347
|
+
throw new Error("exec child process is missing stdout/stderr streams");
|
|
6348
|
+
}
|
|
6349
|
+
const onAbort = () => child.kill("SIGKILL");
|
|
6350
|
+
if (abortSignal) {
|
|
6351
|
+
if (abortSignal.aborted) onAbort();
|
|
6352
|
+
else abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
6353
|
+
}
|
|
6253
6354
|
return {
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6355
|
+
stdout: Readable2.toWeb(child.stdout),
|
|
6356
|
+
stderr: Readable2.toWeb(child.stderr),
|
|
6357
|
+
exit: new Promise((resolve4, reject) => {
|
|
6358
|
+
const settle = () => {
|
|
6359
|
+
child.removeListener("exit", onExitEvent);
|
|
6360
|
+
child.removeListener("error", onError);
|
|
6361
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
6362
|
+
};
|
|
6363
|
+
const onError = (err) => {
|
|
6364
|
+
settle();
|
|
6365
|
+
reject(err);
|
|
6366
|
+
};
|
|
6367
|
+
const onExitEvent = (code, exitSignal) => {
|
|
6368
|
+
settle();
|
|
6369
|
+
resolve4({ code, signal: exitSignal, success: code === 0 });
|
|
6370
|
+
};
|
|
6371
|
+
child.on("exit", onExitEvent);
|
|
6372
|
+
child.on("error", onError);
|
|
6373
|
+
})
|
|
6263
6374
|
};
|
|
6264
6375
|
}
|
|
6265
|
-
|
|
6266
|
-
|
|
6376
|
+
var BASE64_WRITE_CHUNK = 32768;
|
|
6377
|
+
function base64WriteCommands(path5, content) {
|
|
6378
|
+
const base64 = Buffer.from(content).toString("base64");
|
|
6379
|
+
const quotedPath = shellQuote(path5);
|
|
6380
|
+
const commands = [];
|
|
6381
|
+
for (let offset = 0; offset < base64.length; offset += BASE64_WRITE_CHUNK) {
|
|
6382
|
+
const chunk = base64.slice(offset, offset + BASE64_WRITE_CHUNK);
|
|
6383
|
+
const redirect = offset === 0 ? ">" : ">>";
|
|
6384
|
+
commands.push(
|
|
6385
|
+
`printf '%s' ${shellQuote(chunk)} | base64 -d ${redirect} ${quotedPath}`
|
|
6386
|
+
);
|
|
6387
|
+
}
|
|
6388
|
+
if (commands.length === 0) {
|
|
6389
|
+
commands.push(`printf '' > ${quotedPath}`);
|
|
6390
|
+
}
|
|
6391
|
+
return commands;
|
|
6392
|
+
}
|
|
6393
|
+
function base64ReadCommand(path5) {
|
|
6394
|
+
return `base64 ${shellQuote(path5)}`;
|
|
6267
6395
|
}
|
|
6268
|
-
|
|
6269
|
-
// packages/context/src/lib/sandbox/bash-tool.ts
|
|
6270
|
-
import { tool as tool2 } from "ai";
|
|
6271
|
-
import {
|
|
6272
|
-
createBashTool as externalCreateBashTool
|
|
6273
|
-
} from "bash-tool";
|
|
6274
|
-
import z3 from "zod";
|
|
6275
|
-
|
|
6276
|
-
// packages/context/src/lib/sandbox/file-changes.ts
|
|
6277
|
-
import { randomUUID } from "node:crypto";
|
|
6278
|
-
import { posix as posix2 } from "node:path";
|
|
6279
6396
|
|
|
6280
6397
|
// packages/context/src/lib/sandbox/installers/installer.ts
|
|
6281
6398
|
import "bash-tool";
|
|
6282
6399
|
import spawn from "nano-spawn";
|
|
6283
6400
|
|
|
6284
6401
|
// packages/context/src/lib/sandbox/docker-sandbox-errors.ts
|
|
6285
|
-
var DockerSandboxError = class extends
|
|
6286
|
-
containerId;
|
|
6402
|
+
var DockerSandboxError = class extends ContainerSandboxError {
|
|
6287
6403
|
constructor(message2, containerId) {
|
|
6288
|
-
super(message2);
|
|
6404
|
+
super(message2, containerId);
|
|
6289
6405
|
this.name = "DockerSandboxError";
|
|
6290
|
-
this.containerId = containerId;
|
|
6291
6406
|
}
|
|
6292
6407
|
};
|
|
6293
6408
|
var DockerNotAvailableError = class extends DockerSandboxError {
|
|
@@ -6424,9 +6539,6 @@ function isDebianBased(image) {
|
|
|
6424
6539
|
const debianPatterns = ["debian", "ubuntu", "node", "python"];
|
|
6425
6540
|
return debianPatterns.some((pattern) => lower.includes(pattern));
|
|
6426
6541
|
}
|
|
6427
|
-
function shellQuote(s) {
|
|
6428
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
6429
|
-
}
|
|
6430
6542
|
function createInstallerContext(containerId, image) {
|
|
6431
6543
|
const packageManager = isDebianBased(image) ? "apt-get" : "apk";
|
|
6432
6544
|
let archPromise = null;
|
|
@@ -6513,640 +6625,757 @@ function createInstallerContext(containerId, image) {
|
|
|
6513
6625
|
};
|
|
6514
6626
|
}
|
|
6515
6627
|
|
|
6516
|
-
// packages/context/src/lib/sandbox/
|
|
6517
|
-
var
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
};
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6628
|
+
// packages/context/src/lib/sandbox/container-sandbox.ts
|
|
6629
|
+
var CONTAINER_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
6630
|
+
var ContainerSandboxStrategy = class {
|
|
6631
|
+
context;
|
|
6632
|
+
engine;
|
|
6633
|
+
opts;
|
|
6634
|
+
volumes;
|
|
6635
|
+
name;
|
|
6636
|
+
workdir;
|
|
6637
|
+
createdVolumes = /* @__PURE__ */ new Set();
|
|
6638
|
+
constructor(opts, engine) {
|
|
6639
|
+
this.opts = opts;
|
|
6640
|
+
this.engine = engine;
|
|
6641
|
+
const { volumes = [], env = {}, name, workdir = "/workspace" } = opts;
|
|
6642
|
+
for (const key of Object.keys(env)) {
|
|
6643
|
+
if (key.length === 0 || key.includes("=")) {
|
|
6644
|
+
throw this.engine.errors.generic(
|
|
6645
|
+
`Invalid environment variable key: "${key}"`
|
|
6646
|
+
);
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
if (name !== void 0 && !CONTAINER_NAME_PATTERN.test(name)) {
|
|
6650
|
+
throw this.engine.errors.generic(
|
|
6651
|
+
`Invalid container name: "${name}". Use only letters, numbers, underscore, period, or hyphen. The "sandbox-" prefix is added automatically.`
|
|
6652
|
+
);
|
|
6653
|
+
}
|
|
6654
|
+
this.volumes = volumes;
|
|
6655
|
+
this.name = name;
|
|
6656
|
+
this.workdir = workdir;
|
|
6542
6657
|
}
|
|
6543
|
-
async
|
|
6544
|
-
const
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
this.
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6658
|
+
async create() {
|
|
6659
|
+
const image = await this.getImage();
|
|
6660
|
+
let acquired;
|
|
6661
|
+
try {
|
|
6662
|
+
acquired = await this.acquireContainer(image);
|
|
6663
|
+
this.context = { containerId: acquired.containerId, image };
|
|
6664
|
+
if (!acquired.attached) {
|
|
6665
|
+
await this.engine.ensureWorkdir(this.context.containerId, this.workdir);
|
|
6666
|
+
await this.configure();
|
|
6667
|
+
}
|
|
6668
|
+
} catch (error) {
|
|
6669
|
+
if (acquired && !acquired.attached) {
|
|
6670
|
+
await this.stopContainer(acquired.containerId);
|
|
6671
|
+
}
|
|
6672
|
+
await this.cleanupCreatedVolumesAfterFailure(error);
|
|
6673
|
+
throw error;
|
|
6674
|
+
}
|
|
6675
|
+
return this.createSandboxMethods();
|
|
6551
6676
|
}
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
return new UrlBinaryInstaller(options);
|
|
6555
|
-
}
|
|
6556
|
-
async function resolveUrl(ctx, options) {
|
|
6557
|
-
if (typeof options.url === "string") return options.url;
|
|
6558
|
-
const arch = await ctx.arch();
|
|
6559
|
-
const archUrl = options.url[arch];
|
|
6560
|
-
if (!archUrl) {
|
|
6561
|
-
throw new InstallError({
|
|
6562
|
-
target: options.name,
|
|
6563
|
-
source: "url",
|
|
6564
|
-
reason: `No URL provided for architecture "${arch}". Available: ${Object.keys(options.url).join(", ")}`,
|
|
6565
|
-
containerId: ctx.containerId
|
|
6566
|
-
});
|
|
6677
|
+
namedContainerId() {
|
|
6678
|
+
return `sandbox-${this.name}`;
|
|
6567
6679
|
}
|
|
6568
|
-
|
|
6569
|
-
}
|
|
6570
|
-
async function downloadAndInstall(ctx, name, url, binaryPath, source = "url") {
|
|
6571
|
-
await ctx.ensureTool("curl");
|
|
6572
|
-
const isTarGz = url.endsWith(".tar.gz") || url.endsWith(".tgz");
|
|
6573
|
-
const installCmd = isTarGz ? buildTarGzInstallCmd(name, url, binaryPath ?? name) : buildRawInstallCmd(name, url);
|
|
6574
|
-
const result = await ctx.exec(installCmd);
|
|
6575
|
-
if (result.exitCode !== 0) {
|
|
6576
|
-
throw new InstallError({
|
|
6577
|
-
target: name,
|
|
6578
|
-
source,
|
|
6579
|
-
url,
|
|
6580
|
-
reason: result.stderr,
|
|
6581
|
-
containerId: ctx.containerId
|
|
6582
|
-
});
|
|
6680
|
+
defaultContainerId() {
|
|
6681
|
+
return `sandbox-${crypto.randomUUID().slice(0, 8)}`;
|
|
6583
6682
|
}
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
if
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
}
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
var ENSURE_RUNTIME_HINT = "Pass `{ ensureRuntime: true }` to auto-install, or add it via `pkg([...])`.";
|
|
6619
|
-
var NpmInstaller = class extends Installer {
|
|
6620
|
-
kind;
|
|
6621
|
-
packageName;
|
|
6622
|
-
options;
|
|
6623
|
-
constructor(packageName, options = {}) {
|
|
6624
|
-
super();
|
|
6625
|
-
this.packageName = packageName;
|
|
6626
|
-
this.options = options;
|
|
6627
|
-
this.kind = `npm:${packageName}`;
|
|
6683
|
+
async acquireContainer(image) {
|
|
6684
|
+
if (!this.name) {
|
|
6685
|
+
const containerId2 = this.defaultContainerId();
|
|
6686
|
+
await this.prepareVolumes();
|
|
6687
|
+
await this.startContainer(image, containerId2);
|
|
6688
|
+
return { containerId: containerId2, attached: false };
|
|
6689
|
+
}
|
|
6690
|
+
const containerId = this.namedContainerId();
|
|
6691
|
+
const probe = await this.inspectContainer(containerId);
|
|
6692
|
+
if (probe === "running") {
|
|
6693
|
+
return { containerId, attached: true };
|
|
6694
|
+
}
|
|
6695
|
+
if (probe === "stopped") {
|
|
6696
|
+
await this.startStoppedContainer(containerId, image);
|
|
6697
|
+
return { containerId, attached: true };
|
|
6698
|
+
}
|
|
6699
|
+
await this.prepareVolumes();
|
|
6700
|
+
try {
|
|
6701
|
+
await this.startContainer(image, containerId);
|
|
6702
|
+
return { containerId, attached: false };
|
|
6703
|
+
} catch (error) {
|
|
6704
|
+
if (this.engine.isNameConflict(this.engine.errorMessage(error))) {
|
|
6705
|
+
await this.cleanupCreatedVolumes();
|
|
6706
|
+
const raced = await this.inspectContainer(containerId);
|
|
6707
|
+
if (raced === "running") {
|
|
6708
|
+
return { containerId, attached: true };
|
|
6709
|
+
}
|
|
6710
|
+
if (raced === "stopped") {
|
|
6711
|
+
await this.startStoppedContainer(containerId, image);
|
|
6712
|
+
return { containerId, attached: true };
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
throw error;
|
|
6716
|
+
}
|
|
6628
6717
|
}
|
|
6629
|
-
async
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
containerId: ctx.containerId
|
|
6639
|
-
});
|
|
6718
|
+
async startStoppedContainer(containerId, image) {
|
|
6719
|
+
try {
|
|
6720
|
+
await spawn2(this.engine.cli, ["start", containerId]);
|
|
6721
|
+
} catch (error) {
|
|
6722
|
+
const message2 = this.engineErrorMessage(error);
|
|
6723
|
+
if (this.isServiceDown(message2)) {
|
|
6724
|
+
throw this.engine.errors.serviceNotAvailable();
|
|
6725
|
+
}
|
|
6726
|
+
throw this.engine.errors.creation(message2, image, error);
|
|
6640
6727
|
}
|
|
6641
6728
|
}
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6729
|
+
async inspectContainer(containerId) {
|
|
6730
|
+
try {
|
|
6731
|
+
const result = await spawn2(
|
|
6732
|
+
this.engine.cli,
|
|
6733
|
+
this.engine.inspectArgs(containerId)
|
|
6734
|
+
);
|
|
6735
|
+
return this.engine.parseStatus(result.stdout.trim());
|
|
6736
|
+
} catch (error) {
|
|
6737
|
+
const message2 = this.engineErrorMessage(error);
|
|
6738
|
+
if (this.isServiceDown(message2)) {
|
|
6739
|
+
throw this.engine.errors.serviceNotAvailable();
|
|
6740
|
+
}
|
|
6741
|
+
if (this.engine.isMissingContainer(message2)) {
|
|
6742
|
+
return "absent";
|
|
6743
|
+
}
|
|
6744
|
+
throw this.engine.errors.generic(
|
|
6745
|
+
`Failed to inspect container "${containerId}": ${message2}`
|
|
6746
|
+
);
|
|
6747
|
+
}
|
|
6651
6748
|
}
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
"
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6749
|
+
async prepareVolumes() {
|
|
6750
|
+
this.validateVolumes();
|
|
6751
|
+
for (const volume of this.volumes) {
|
|
6752
|
+
if (volume.type !== "volume") {
|
|
6753
|
+
continue;
|
|
6754
|
+
}
|
|
6755
|
+
const lifecycle = volume.lifecycle ?? "external";
|
|
6756
|
+
if (lifecycle === "external") {
|
|
6757
|
+
await this.inspectVolume(volume.name);
|
|
6758
|
+
continue;
|
|
6759
|
+
}
|
|
6760
|
+
const exists = await this.volumeExists(volume.name);
|
|
6761
|
+
if (exists) {
|
|
6762
|
+
throw this.engine.errors.volumeCreate(
|
|
6763
|
+
volume.name,
|
|
6764
|
+
"managed volume already exists"
|
|
6765
|
+
);
|
|
6766
|
+
}
|
|
6767
|
+
await this.createVolume(volume);
|
|
6768
|
+
if (volume.removeOnDispose !== false) {
|
|
6769
|
+
this.createdVolumes.add(volume.name);
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6660
6772
|
}
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6773
|
+
validateVolumes() {
|
|
6774
|
+
const containerPaths = /* @__PURE__ */ new Set();
|
|
6775
|
+
for (const volume of this.volumes) {
|
|
6776
|
+
const source = volume.type === "bind" ? volume.hostPath : volume.name;
|
|
6777
|
+
if (!volume.containerPath.startsWith("/")) {
|
|
6778
|
+
throw this.engine.errors.volumePath(
|
|
6779
|
+
source,
|
|
6780
|
+
volume.containerPath,
|
|
6781
|
+
"containerPath must be absolute"
|
|
6782
|
+
);
|
|
6783
|
+
}
|
|
6784
|
+
this.validateMountValue("containerPath", volume.containerPath, volume);
|
|
6785
|
+
if (containerPaths.has(volume.containerPath)) {
|
|
6786
|
+
throw this.engine.errors.volumePath(
|
|
6787
|
+
source,
|
|
6788
|
+
volume.containerPath,
|
|
6789
|
+
"containerPath must be unique"
|
|
6790
|
+
);
|
|
6791
|
+
}
|
|
6792
|
+
containerPaths.add(volume.containerPath);
|
|
6793
|
+
if (volume.type === "bind") {
|
|
6794
|
+
this.validateMountValue("hostPath", volume.hostPath, volume);
|
|
6795
|
+
if (!existsSync(volume.hostPath)) {
|
|
6796
|
+
throw this.engine.errors.volumePath(
|
|
6797
|
+
volume.hostPath,
|
|
6798
|
+
volume.containerPath,
|
|
6799
|
+
"hostPath does not exist on host"
|
|
6800
|
+
);
|
|
6801
|
+
}
|
|
6802
|
+
continue;
|
|
6803
|
+
}
|
|
6804
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(volume.name)) {
|
|
6805
|
+
throw this.engine.errors.volumePath(
|
|
6806
|
+
volume.name,
|
|
6807
|
+
volume.containerPath,
|
|
6808
|
+
"volume name must start with an alphanumeric character and contain only letters, numbers, underscore, period, or hyphen"
|
|
6809
|
+
);
|
|
6810
|
+
}
|
|
6811
|
+
if (volume.subPath) {
|
|
6812
|
+
this.validateMountValue("subPath", volume.subPath, volume);
|
|
6813
|
+
}
|
|
6814
|
+
if ((volume.lifecycle ?? "external") === "external") {
|
|
6815
|
+
if (volume.driver || volume.driverOptions) {
|
|
6816
|
+
throw this.engine.errors.volumePath(
|
|
6817
|
+
volume.name,
|
|
6818
|
+
volume.containerPath,
|
|
6819
|
+
'driver and driverOptions require lifecycle "managed"'
|
|
6820
|
+
);
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
}
|
|
6680
6824
|
}
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
const
|
|
6686
|
-
|
|
6825
|
+
validateMountValue(field, value, volume) {
|
|
6826
|
+
if (!value.includes(",")) {
|
|
6827
|
+
return;
|
|
6828
|
+
}
|
|
6829
|
+
const source = volume.type === "bind" ? volume.hostPath : volume.name;
|
|
6830
|
+
throw this.engine.errors.volumePath(
|
|
6831
|
+
source,
|
|
6832
|
+
volume.containerPath,
|
|
6833
|
+
`${field} must not contain commas`
|
|
6687
6834
|
);
|
|
6688
|
-
if (result.exitCode === 0) return;
|
|
6689
|
-
throw new InstallError({
|
|
6690
|
-
target: this.#name,
|
|
6691
|
-
source: "bin",
|
|
6692
|
-
reason: explainFailure(result.exitCode, result.stderr, this.binary),
|
|
6693
|
-
containerId: ctx.containerId
|
|
6694
|
-
});
|
|
6695
6835
|
}
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6836
|
+
buildRunArgs(image, containerId) {
|
|
6837
|
+
return this.engine.runArgs(image, containerId, this.opts, this.workdir);
|
|
6838
|
+
}
|
|
6839
|
+
async inspectVolume(name) {
|
|
6840
|
+
try {
|
|
6841
|
+
await spawn2(this.engine.cli, ["volume", "inspect", name]);
|
|
6842
|
+
} catch (error) {
|
|
6843
|
+
const reason = this.engineErrorMessage(error);
|
|
6844
|
+
if (this.isServiceDown(reason)) {
|
|
6845
|
+
throw this.engine.errors.serviceNotAvailable();
|
|
6846
|
+
}
|
|
6847
|
+
throw this.engine.errors.volumeInspect(name, reason);
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
async volumeExists(name) {
|
|
6851
|
+
try {
|
|
6852
|
+
await this.inspectVolume(name);
|
|
6853
|
+
return true;
|
|
6854
|
+
} catch (error) {
|
|
6855
|
+
if (error instanceof ContainerSandboxError && this.engine.isMissingVolume(error.message)) {
|
|
6856
|
+
return false;
|
|
6857
|
+
}
|
|
6858
|
+
throw error;
|
|
6859
|
+
}
|
|
6860
|
+
}
|
|
6861
|
+
async createVolume(volume) {
|
|
6862
|
+
const args = this.engine.volumeCreateArgs(volume);
|
|
6863
|
+
try {
|
|
6864
|
+
await spawn2(this.engine.cli, args);
|
|
6865
|
+
} catch (error) {
|
|
6866
|
+
const reason = this.engineErrorMessage(error);
|
|
6867
|
+
if (this.isServiceDown(reason)) {
|
|
6868
|
+
throw this.engine.errors.serviceNotAvailable();
|
|
6869
|
+
}
|
|
6870
|
+
throw this.engine.errors.volumeCreate(volume.name, reason);
|
|
6871
|
+
}
|
|
6872
|
+
}
|
|
6873
|
+
async cleanupCreatedVolumes() {
|
|
6874
|
+
const volumes = [...this.createdVolumes].reverse();
|
|
6875
|
+
for (const volume of volumes) {
|
|
6876
|
+
try {
|
|
6877
|
+
await spawn2(this.engine.cli, ["volume", "rm", volume]);
|
|
6878
|
+
this.createdVolumes.delete(volume);
|
|
6879
|
+
} catch (error) {
|
|
6880
|
+
const reason = this.engineErrorMessage(error);
|
|
6881
|
+
if (this.isServiceDown(reason)) {
|
|
6882
|
+
throw this.engine.errors.serviceNotAvailable();
|
|
6883
|
+
}
|
|
6884
|
+
throw this.engine.errors.volumeRemove(volume, reason);
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
6887
|
+
}
|
|
6888
|
+
async cleanupCreatedVolumesAfterFailure(originalError) {
|
|
6889
|
+
try {
|
|
6890
|
+
await this.cleanupCreatedVolumes();
|
|
6891
|
+
} catch (cleanupError) {
|
|
6892
|
+
if (originalError instanceof Error) {
|
|
6893
|
+
const original = originalError;
|
|
6894
|
+
original.suppressed = [...original.suppressed ?? [], cleanupError];
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6703
6897
|
}
|
|
6704
|
-
|
|
6705
|
-
return
|
|
6898
|
+
engineErrorMessage(error) {
|
|
6899
|
+
return this.engine.errorMessage(error);
|
|
6706
6900
|
}
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
function resolveName(binary, options) {
|
|
6710
|
-
if (options.name) return options.name;
|
|
6711
|
-
const base = basename(binary);
|
|
6712
|
-
const dot = base.lastIndexOf(".");
|
|
6713
|
-
return dot > 0 ? base.slice(0, dot) : base;
|
|
6714
|
-
}
|
|
6715
|
-
|
|
6716
|
-
// packages/context/src/lib/sandbox/installers/pip.ts
|
|
6717
|
-
var PYTHON_BINARIES = ["python3", "pip3"];
|
|
6718
|
-
var PipInstaller = class extends Installer {
|
|
6719
|
-
kind;
|
|
6720
|
-
packageName;
|
|
6721
|
-
options;
|
|
6722
|
-
constructor(packageName, options = {}) {
|
|
6723
|
-
super();
|
|
6724
|
-
this.packageName = packageName;
|
|
6725
|
-
this.options = options;
|
|
6726
|
-
this.kind = `pip:${packageName}`;
|
|
6901
|
+
isServiceDown(message2) {
|
|
6902
|
+
return this.engine.isServiceDown(message2);
|
|
6727
6903
|
}
|
|
6728
|
-
async
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
containerId: ctx.containerId
|
|
6739
|
-
});
|
|
6904
|
+
async startContainer(image, containerId) {
|
|
6905
|
+
const args = this.buildRunArgs(image, containerId);
|
|
6906
|
+
try {
|
|
6907
|
+
await spawn2(this.engine.cli, args);
|
|
6908
|
+
} catch (error) {
|
|
6909
|
+
const message2 = this.engineErrorMessage(error);
|
|
6910
|
+
if (this.isServiceDown(message2)) {
|
|
6911
|
+
throw this.engine.errors.serviceNotAvailable();
|
|
6912
|
+
}
|
|
6913
|
+
throw this.engine.errors.creation(message2, image, error);
|
|
6740
6914
|
}
|
|
6741
6915
|
}
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
}
|
|
6746
|
-
|
|
6747
|
-
if (ensure) {
|
|
6748
|
-
const pipPackage = ctx.packageManager === "apk" ? "py3-pip" : "python3-pip";
|
|
6749
|
-
await ctx.ensureTool("python3");
|
|
6750
|
-
await ctx.ensureTool("pip3", pipPackage);
|
|
6751
|
-
return;
|
|
6752
|
-
}
|
|
6753
|
-
const check = await ctx.exec("which python3 && which pip3");
|
|
6754
|
-
if (check.exitCode !== 0) {
|
|
6755
|
-
throw new MissingRuntimeError(
|
|
6756
|
-
"pip",
|
|
6757
|
-
PYTHON_BINARIES,
|
|
6758
|
-
"Pass `{ ensureRuntime: true }` to auto-install, or add via `pkg([...])` (Alpine: `python3 py3-pip`; Debian: `python3 python3-pip`).",
|
|
6759
|
-
ctx.containerId
|
|
6760
|
-
);
|
|
6916
|
+
async stopContainer(containerId) {
|
|
6917
|
+
try {
|
|
6918
|
+
await spawn2(this.engine.cli, ["stop", containerId]);
|
|
6919
|
+
} catch {
|
|
6920
|
+
}
|
|
6761
6921
|
}
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6922
|
+
async exec(command, options) {
|
|
6923
|
+
try {
|
|
6924
|
+
const result = await spawn2(
|
|
6925
|
+
this.engine.cli,
|
|
6926
|
+
this.engine.execArgs(this.context.containerId, command),
|
|
6927
|
+
{ signal: options?.signal }
|
|
6928
|
+
);
|
|
6929
|
+
return {
|
|
6930
|
+
stdout: result.stdout,
|
|
6931
|
+
stderr: result.stderr,
|
|
6932
|
+
exitCode: 0
|
|
6933
|
+
};
|
|
6934
|
+
} catch (error) {
|
|
6935
|
+
const err = error;
|
|
6936
|
+
return {
|
|
6937
|
+
stdout: err.stdout || "",
|
|
6938
|
+
stderr: err.stderr || err.message || "",
|
|
6939
|
+
exitCode: err.exitCode ?? 1
|
|
6940
|
+
};
|
|
6941
|
+
}
|
|
6775
6942
|
}
|
|
6776
|
-
|
|
6777
|
-
const
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
await downloadAndInstall(
|
|
6781
|
-
ctx,
|
|
6782
|
-
this.options.name,
|
|
6783
|
-
url,
|
|
6784
|
-
this.options.binaryPath,
|
|
6785
|
-
"github-release"
|
|
6943
|
+
spawnProcess(command, options) {
|
|
6944
|
+
const child = childSpawn(
|
|
6945
|
+
this.engine.cli,
|
|
6946
|
+
this.engine.execArgs(this.context.containerId, command, options)
|
|
6786
6947
|
);
|
|
6948
|
+
return toSandboxProcess(child, options?.signal);
|
|
6787
6949
|
}
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6950
|
+
createSandboxMethods() {
|
|
6951
|
+
const { containerId } = this.context;
|
|
6952
|
+
const sandbox = {
|
|
6953
|
+
executeCommand: async (command, options) => this.exec(command, options),
|
|
6954
|
+
spawn: (command, options) => this.spawnProcess(command, options),
|
|
6955
|
+
readFile: async (path5) => {
|
|
6956
|
+
const result = await sandbox.executeCommand(base64ReadCommand(path5));
|
|
6957
|
+
if (result.exitCode !== 0) {
|
|
6958
|
+
throw new Error(`Failed to read file "${path5}": ${result.stderr}`);
|
|
6959
|
+
}
|
|
6960
|
+
return Buffer.from(result.stdout, "base64").toString("utf-8");
|
|
6961
|
+
},
|
|
6962
|
+
writeFiles: async (files) => {
|
|
6963
|
+
for (const file of files) {
|
|
6964
|
+
const dir = file.path.substring(0, file.path.lastIndexOf("/"));
|
|
6965
|
+
if (dir) {
|
|
6966
|
+
await sandbox.executeCommand(`mkdir -p ${shellQuote(dir)}`);
|
|
6967
|
+
}
|
|
6968
|
+
for (const command of base64WriteCommands(file.path, file.content)) {
|
|
6969
|
+
const result = await sandbox.executeCommand(command);
|
|
6970
|
+
if (result.exitCode !== 0) {
|
|
6971
|
+
throw new Error(
|
|
6972
|
+
`Failed to write file "${file.path}": ${result.stderr}`
|
|
6973
|
+
);
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
},
|
|
6978
|
+
dispose: async () => {
|
|
6979
|
+
await this.stopContainer(containerId);
|
|
6980
|
+
await this.cleanupCreatedVolumes();
|
|
6981
|
+
},
|
|
6982
|
+
[Symbol.asyncDispose]() {
|
|
6983
|
+
return this.dispose();
|
|
6984
|
+
}
|
|
6985
|
+
};
|
|
6986
|
+
return sandbox;
|
|
6809
6987
|
}
|
|
6810
6988
|
};
|
|
6811
|
-
var
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
return stderr.split("\n").filter((line) => !line.startsWith("strace: ")).join("\n");
|
|
6819
|
-
}
|
|
6820
|
-
var CREATE_OR_TRUNC_FLAGS = /O_(CREAT|TRUNC)/;
|
|
6821
|
-
var RENAME_SYSCALLS = /* @__PURE__ */ new Set(["rename", "renameat", "renameat2"]);
|
|
6822
|
-
var DELETE_SYSCALLS = /* @__PURE__ */ new Set(["unlink", "unlinkat", "rmdir"]);
|
|
6823
|
-
var MKDIR_SYSCALLS = /* @__PURE__ */ new Set(["mkdir", "mkdirat"]);
|
|
6824
|
-
var LINK_SYSCALLS = /* @__PURE__ */ new Set(["link", "linkat", "symlink", "symlinkat"]);
|
|
6825
|
-
var OPEN_SYSCALLS = /* @__PURE__ */ new Set(["open", "openat", "openat2", "creat"]);
|
|
6826
|
-
var WRITE_SYSCALLS = /* @__PURE__ */ new Set([
|
|
6827
|
-
"write",
|
|
6828
|
-
"pwrite64",
|
|
6829
|
-
"pwrite",
|
|
6830
|
-
"writev",
|
|
6831
|
-
"pwritev2"
|
|
6832
|
-
]);
|
|
6833
|
-
var TRUNCATE_SYSCALLS = /* @__PURE__ */ new Set(["truncate", "ftruncate"]);
|
|
6834
|
-
function stitchLines(raw) {
|
|
6835
|
-
const pending = /* @__PURE__ */ new Map();
|
|
6836
|
-
const out = [];
|
|
6837
|
-
for (const original of raw.split("\n")) {
|
|
6838
|
-
const line = original.trimEnd();
|
|
6839
|
-
if (!line) continue;
|
|
6840
|
-
const pid = line.match(/^\s*(\d+)\s+/)?.[1] ?? "0";
|
|
6841
|
-
if (/<unfinished \.\.\.>\s*$/.test(line)) {
|
|
6842
|
-
pending.set(pid, line.replace(/<unfinished \.\.\.>\s*$/, ""));
|
|
6843
|
-
continue;
|
|
6844
|
-
}
|
|
6845
|
-
const resumed = line.match(/<\.\.\.\s+\S+\s+resumed>(.*)$/);
|
|
6846
|
-
if (resumed) {
|
|
6847
|
-
const head = pending.get(pid);
|
|
6848
|
-
pending.delete(pid);
|
|
6849
|
-
if (head !== void 0) out.push(head + resumed[1]);
|
|
6850
|
-
continue;
|
|
6851
|
-
}
|
|
6852
|
-
out.push(line);
|
|
6989
|
+
var RuntimeStrategy = class extends ContainerSandboxStrategy {
|
|
6990
|
+
image;
|
|
6991
|
+
installers;
|
|
6992
|
+
constructor(opts, engine, mode) {
|
|
6993
|
+
super(opts, engine);
|
|
6994
|
+
this.image = mode.image;
|
|
6995
|
+
this.installers = mode.installers;
|
|
6853
6996
|
}
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6997
|
+
async getImage() {
|
|
6998
|
+
return this.image;
|
|
6999
|
+
}
|
|
7000
|
+
async configure() {
|
|
7001
|
+
const ctx = this.engine.createInstallerContext(
|
|
7002
|
+
this.context.containerId,
|
|
7003
|
+
this.image
|
|
7004
|
+
);
|
|
7005
|
+
for (const installer of this.installers) {
|
|
7006
|
+
await installer.install(ctx);
|
|
7007
|
+
}
|
|
6865
7008
|
}
|
|
6866
|
-
return { syscall, args, retval: rest.split(/\s+/)[0] };
|
|
6867
|
-
}
|
|
6868
|
-
var STRACE_ESCAPE = {
|
|
6869
|
-
n: 10,
|
|
6870
|
-
t: 9,
|
|
6871
|
-
r: 13,
|
|
6872
|
-
v: 11,
|
|
6873
|
-
f: 12,
|
|
6874
|
-
'"': 34,
|
|
6875
|
-
"\\": 92
|
|
6876
7009
|
};
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
7010
|
+
var ContainerfileStrategy = class extends ContainerSandboxStrategy {
|
|
7011
|
+
dockerfile;
|
|
7012
|
+
dockerContext;
|
|
7013
|
+
showBuildLogs;
|
|
7014
|
+
identity;
|
|
7015
|
+
constructor(opts, engine, mode) {
|
|
7016
|
+
super(opts, engine);
|
|
7017
|
+
this.dockerfile = mode.dockerfile;
|
|
7018
|
+
this.dockerContext = mode.context;
|
|
7019
|
+
this.showBuildLogs = mode.showBuildLogs;
|
|
7020
|
+
this.identity = mode.identity;
|
|
7021
|
+
}
|
|
7022
|
+
async getImage() {
|
|
7023
|
+
const content = this.dockerfile.includes("\n") ? this.dockerfile : readFileSync(this.dockerfile, "utf-8");
|
|
7024
|
+
const tag = `sandbox-${createHash("sha256").update(content).update(this.identity ?? "").digest("hex").slice(0, 12)}`;
|
|
7025
|
+
if (!await this.engine.imageExists(tag)) {
|
|
7026
|
+
await this.engine.buildImage({
|
|
7027
|
+
tag,
|
|
7028
|
+
dockerfile: this.dockerfile,
|
|
7029
|
+
context: this.dockerContext,
|
|
7030
|
+
showBuildLogs: this.showBuildLogs,
|
|
7031
|
+
identity: this.identity
|
|
7032
|
+
});
|
|
6891
7033
|
}
|
|
6892
|
-
|
|
6893
|
-
if (code < 128) bytes.push(code);
|
|
6894
|
-
else for (const b of Buffer.from(s[i], "utf8")) bytes.push(b);
|
|
6895
|
-
i += 1;
|
|
7034
|
+
return tag;
|
|
6896
7035
|
}
|
|
6897
|
-
|
|
7036
|
+
async configure() {
|
|
7037
|
+
}
|
|
7038
|
+
};
|
|
7039
|
+
|
|
7040
|
+
// packages/context/src/lib/sandbox/apple-container-sandbox.ts
|
|
7041
|
+
var CLI = "container";
|
|
7042
|
+
var WORKDIR = "/workspace";
|
|
7043
|
+
function isAppleContainerfileOptions(opts) {
|
|
7044
|
+
return "dockerfile" in opts;
|
|
6898
7045
|
}
|
|
6899
|
-
function
|
|
6900
|
-
const
|
|
6901
|
-
const
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
7046
|
+
function buildMountArg(volume) {
|
|
7047
|
+
const readOnly = volume.readOnly !== false;
|
|
7048
|
+
const parts = volume.type === "bind" ? [
|
|
7049
|
+
"type=bind",
|
|
7050
|
+
`source=${volume.hostPath}`,
|
|
7051
|
+
`target=${volume.containerPath}`
|
|
7052
|
+
] : [
|
|
7053
|
+
// Named volumes mount as virtiofs; `container` has no `type=volume`.
|
|
7054
|
+
"type=virtiofs",
|
|
7055
|
+
`source=${volume.name}`,
|
|
7056
|
+
`target=${volume.containerPath}`
|
|
7057
|
+
];
|
|
7058
|
+
if (readOnly) {
|
|
7059
|
+
parts.push("readonly");
|
|
6905
7060
|
}
|
|
6906
|
-
return
|
|
7061
|
+
return parts.join(",");
|
|
6907
7062
|
}
|
|
6908
|
-
function
|
|
6909
|
-
|
|
7063
|
+
function getCliErrorMessage(error) {
|
|
7064
|
+
const err = error;
|
|
7065
|
+
return err.stderr?.trim() || err.stdout?.trim() || err.message || String(error);
|
|
6910
7066
|
}
|
|
6911
|
-
function
|
|
6912
|
-
|
|
7067
|
+
function safeParseArray(stdout) {
|
|
7068
|
+
try {
|
|
7069
|
+
const parsed = JSON.parse(stdout || "[]");
|
|
7070
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
7071
|
+
} catch {
|
|
7072
|
+
return [];
|
|
7073
|
+
}
|
|
6913
7074
|
}
|
|
6914
|
-
function
|
|
6915
|
-
|
|
7075
|
+
function readContainerStatus(entry) {
|
|
7076
|
+
const status = String(
|
|
7077
|
+
entry?.status ?? ""
|
|
7078
|
+
).toLowerCase();
|
|
7079
|
+
if (status === "running" || status === "booted") return "running";
|
|
7080
|
+
if (status === "stopped") return "stopped";
|
|
7081
|
+
throw new AppleContainerSandboxError(
|
|
7082
|
+
`Container is in an unexpected state "${status}"`
|
|
7083
|
+
);
|
|
6916
7084
|
}
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
if (!call || call.errno || call.retval === "-1") continue;
|
|
6935
|
-
const { syscall, args } = call;
|
|
6936
|
-
const dir = dirfdPath(args);
|
|
6937
|
-
if (RENAME_SYSCALLS.has(syscall)) {
|
|
6938
|
-
const strings = quotedStrings(args);
|
|
6939
|
-
if (strings.length < 2) continue;
|
|
6940
|
-
const from = resolve4(dir, strings[0]);
|
|
6941
|
-
const to = resolve4(dir, strings[strings.length - 1]);
|
|
6942
|
-
state.delete(from);
|
|
6943
|
-
renames.push({ from, to });
|
|
6944
|
-
continue;
|
|
6945
|
-
}
|
|
6946
|
-
if (DELETE_SYSCALLS.has(syscall)) {
|
|
6947
|
-
const strings = quotedStrings(args);
|
|
6948
|
-
if (strings.length) remove(resolve4(dir, strings[0]));
|
|
6949
|
-
continue;
|
|
6950
|
-
}
|
|
6951
|
-
if (MKDIR_SYSCALLS.has(syscall)) {
|
|
6952
|
-
const strings = quotedStrings(args);
|
|
6953
|
-
if (strings.length) write(resolve4(dir, strings[0]));
|
|
6954
|
-
continue;
|
|
7085
|
+
var appleEngine = {
|
|
7086
|
+
cli: CLI,
|
|
7087
|
+
runArgs(image, containerId, opts) {
|
|
7088
|
+
const { memory = "1024M", cpus = 2 } = opts.resources ?? {};
|
|
7089
|
+
const args = [
|
|
7090
|
+
"run",
|
|
7091
|
+
"--detach",
|
|
7092
|
+
"--rm",
|
|
7093
|
+
"--name",
|
|
7094
|
+
containerId,
|
|
7095
|
+
"--memory",
|
|
7096
|
+
memory,
|
|
7097
|
+
"--cpus",
|
|
7098
|
+
String(cpus)
|
|
7099
|
+
];
|
|
7100
|
+
if (opts.arch) {
|
|
7101
|
+
args.push("--arch", opts.arch);
|
|
6955
7102
|
}
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
if (strings.length) write(resolve4(dir, strings[strings.length - 1]));
|
|
6959
|
-
continue;
|
|
7103
|
+
for (const [key, value] of Object.entries(opts.env ?? {})) {
|
|
7104
|
+
args.push("--env", `${key}=${value}`);
|
|
6960
7105
|
}
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
const strings = quotedStrings(args);
|
|
6964
|
-
if (strings.length) write(resolve4(dir, strings[0]));
|
|
6965
|
-
continue;
|
|
7106
|
+
for (const volume of opts.volumes ?? []) {
|
|
7107
|
+
args.push("--mount", buildMountArg(volume));
|
|
6966
7108
|
}
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
7109
|
+
args.push(image);
|
|
7110
|
+
if (opts.command === void 0) {
|
|
7111
|
+
args.push("tail", "-f", "/dev/null");
|
|
7112
|
+
} else if (opts.command !== null) {
|
|
7113
|
+
args.push(...opts.command);
|
|
6971
7114
|
}
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
7115
|
+
return args;
|
|
7116
|
+
},
|
|
7117
|
+
execArgs(containerId, command, options) {
|
|
7118
|
+
const flags = ["--cwd", options?.cwd || WORKDIR];
|
|
7119
|
+
if (options?.env) {
|
|
7120
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
7121
|
+
if (key.length === 0 || key.includes("=")) {
|
|
7122
|
+
throw new AppleContainerSandboxError(
|
|
7123
|
+
`Invalid environment variable key: "${key}"`
|
|
7124
|
+
);
|
|
7125
|
+
}
|
|
7126
|
+
flags.push("--env", `${key}=${value}`);
|
|
7127
|
+
}
|
|
6975
7128
|
}
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
7129
|
+
return ["exec", ...flags, containerId, "sh", "-c", command];
|
|
7130
|
+
},
|
|
7131
|
+
inspectArgs(containerId) {
|
|
7132
|
+
return ["inspect", containerId];
|
|
7133
|
+
},
|
|
7134
|
+
mountArg: buildMountArg,
|
|
7135
|
+
parseStatus(stdout) {
|
|
7136
|
+
const entries = safeParseArray(stdout);
|
|
7137
|
+
if (entries.length === 0) return "absent";
|
|
7138
|
+
return readContainerStatus(entries[0]);
|
|
7139
|
+
},
|
|
7140
|
+
volumeCreateArgs(volume) {
|
|
7141
|
+
return ["volume", "create", volume.name];
|
|
7142
|
+
},
|
|
7143
|
+
errorMessage: getCliErrorMessage,
|
|
7144
|
+
isServiceDown(message2) {
|
|
7145
|
+
return /apiserver|not running|connection refused|could not connect|xpc/i.test(
|
|
7146
|
+
message2
|
|
7147
|
+
);
|
|
7148
|
+
},
|
|
7149
|
+
isMissingContainer(message2) {
|
|
7150
|
+
return /not found|no such/i.test(message2);
|
|
7151
|
+
},
|
|
7152
|
+
isMissingVolume(message2) {
|
|
7153
|
+
return /not found|no such/i.test(message2);
|
|
7154
|
+
},
|
|
7155
|
+
isNameConflict(message2) {
|
|
7156
|
+
return /already exists/i.test(message2);
|
|
7157
|
+
},
|
|
7158
|
+
async ensureWorkdir(containerId, workdir) {
|
|
7159
|
+
await spawn3(CLI, ["exec", containerId, "sh", "-c", `mkdir -p ${workdir}`]);
|
|
7160
|
+
},
|
|
7161
|
+
defaultImage: "docker.io/library/alpine:latest",
|
|
7162
|
+
createInstallerContext: createAppleInstallerContext,
|
|
7163
|
+
async imageExists(tag) {
|
|
7164
|
+
try {
|
|
7165
|
+
await spawn3(CLI, ["image", "inspect", tag]);
|
|
7166
|
+
return true;
|
|
7167
|
+
} catch {
|
|
6980
7168
|
return false;
|
|
6981
7169
|
}
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
7170
|
+
},
|
|
7171
|
+
async buildImage(spec) {
|
|
7172
|
+
const archFlag = spec.identity ? ["--arch", spec.identity] : [];
|
|
7173
|
+
if (spec.dockerfile.includes("\n")) {
|
|
7174
|
+
const tempDir = await mkdtemp(join(tmpdir(), "sandbox-containerfile-"));
|
|
7175
|
+
const dockerfilePath = join(tempDir, "Dockerfile");
|
|
7176
|
+
await writeFile(dockerfilePath, spec.dockerfile);
|
|
7177
|
+
try {
|
|
7178
|
+
await runAppleBuild(
|
|
7179
|
+
["build", ...archFlag, "-t", spec.tag, "-f", dockerfilePath, tempDir],
|
|
7180
|
+
spec.showBuildLogs
|
|
7181
|
+
);
|
|
7182
|
+
} finally {
|
|
7183
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
7184
|
+
}
|
|
7185
|
+
return;
|
|
7186
|
+
}
|
|
7187
|
+
await runAppleBuild(
|
|
7188
|
+
[
|
|
7189
|
+
"build",
|
|
7190
|
+
...archFlag,
|
|
7191
|
+
"-t",
|
|
7192
|
+
spec.tag,
|
|
7193
|
+
"-f",
|
|
7194
|
+
spec.dockerfile,
|
|
7195
|
+
spec.context
|
|
7196
|
+
],
|
|
7197
|
+
spec.showBuildLogs
|
|
7198
|
+
);
|
|
7199
|
+
},
|
|
7200
|
+
errors: {
|
|
7201
|
+
serviceNotAvailable: () => new ContainerServiceNotRunningError(),
|
|
7202
|
+
creation: (message2, image, cause) => new AppleContainerCreationError(message2, image, cause),
|
|
7203
|
+
generic: (message2, containerId) => new AppleContainerSandboxError(message2, containerId),
|
|
7204
|
+
volumePath: (source, containerPath, reason) => new AppleContainerVolumePathError(source, containerPath, reason),
|
|
7205
|
+
volumeInspect: (name, reason) => new AppleContainerVolumeInspectError(name, reason),
|
|
7206
|
+
volumeCreate: (name, reason) => new AppleContainerVolumeCreateError(name, reason),
|
|
7207
|
+
volumeRemove: (name, reason) => new AppleContainerVolumeRemoveError(name, reason)
|
|
6997
7208
|
}
|
|
6998
|
-
tail.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
6999
|
-
changes.push(...tail);
|
|
7000
|
-
return changes;
|
|
7001
|
-
}
|
|
7002
|
-
var warnOnFileChangesError = (error) => {
|
|
7003
|
-
console.warn(
|
|
7004
|
-
"[traceFileChanges] onFileChanges threw on spawn; isolated",
|
|
7005
|
-
error
|
|
7006
|
-
);
|
|
7007
7209
|
};
|
|
7008
|
-
function
|
|
7009
|
-
const
|
|
7010
|
-
|
|
7011
|
-
const
|
|
7012
|
-
|
|
7013
|
-
const
|
|
7014
|
-
const innerWriteFiles = sandbox.writeFiles.bind(sandbox);
|
|
7015
|
-
const dest = posix2.normalize(destination);
|
|
7016
|
-
const underDestination = (path5) => dest === "/" || path5 === dest || path5.startsWith(`${dest}/`);
|
|
7017
|
-
const removeTrace = (traceFile) => void innerExecute(`rm -f ${shellQuote(traceFile)}`).catch(() => {
|
|
7018
|
-
});
|
|
7019
|
-
const readChanges = async (traceFile) => {
|
|
7210
|
+
function createAppleInstallerContext(containerId, image) {
|
|
7211
|
+
const packageManager = isDebianBased(image) ? "apt-get" : "apk";
|
|
7212
|
+
let archPromise = null;
|
|
7213
|
+
const ensuredTools = /* @__PURE__ */ new Set();
|
|
7214
|
+
let aptUpdated = false;
|
|
7215
|
+
const exec = async (command) => {
|
|
7020
7216
|
try {
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7217
|
+
const result = await spawn3(CLI, [
|
|
7218
|
+
"exec",
|
|
7219
|
+
containerId,
|
|
7220
|
+
"sh",
|
|
7221
|
+
"-c",
|
|
7222
|
+
command
|
|
7223
|
+
]);
|
|
7224
|
+
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
7225
|
+
} catch (error) {
|
|
7226
|
+
const err = error;
|
|
7227
|
+
return {
|
|
7228
|
+
stdout: err.stdout ?? "",
|
|
7229
|
+
stderr: err.stderr ?? err.message ?? "",
|
|
7230
|
+
exitCode: err.exitCode ?? 1
|
|
7231
|
+
};
|
|
7030
7232
|
}
|
|
7031
7233
|
};
|
|
7032
|
-
const
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7234
|
+
const arch = async () => {
|
|
7235
|
+
if (!archPromise) {
|
|
7236
|
+
const attempt = (async () => {
|
|
7237
|
+
const result = await exec("uname -m");
|
|
7238
|
+
if (result.exitCode !== 0) {
|
|
7239
|
+
throw new AppleContainerSandboxError(
|
|
7240
|
+
`Failed to detect container architecture: ${result.stderr}`,
|
|
7241
|
+
containerId
|
|
7242
|
+
);
|
|
7243
|
+
}
|
|
7244
|
+
return result.stdout.trim();
|
|
7245
|
+
})();
|
|
7246
|
+
archPromise = attempt.catch((err) => {
|
|
7247
|
+
archPromise = null;
|
|
7248
|
+
throw err;
|
|
7249
|
+
});
|
|
7250
|
+
}
|
|
7251
|
+
return archPromise;
|
|
7037
7252
|
};
|
|
7038
|
-
const
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7253
|
+
const installPackages = async (packages) => {
|
|
7254
|
+
if (packages.length === 0) return;
|
|
7255
|
+
const quoted = packages.map(shellQuote).join(" ");
|
|
7256
|
+
let cmd;
|
|
7257
|
+
if (packageManager === "apt-get") {
|
|
7258
|
+
cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
|
|
7259
|
+
} else {
|
|
7260
|
+
cmd = `apk add --no-cache ${quoted}`;
|
|
7045
7261
|
}
|
|
7262
|
+
const result = await exec(cmd);
|
|
7263
|
+
if (result.exitCode !== 0) {
|
|
7264
|
+
throw new PackageInstallError(
|
|
7265
|
+
packages,
|
|
7266
|
+
image,
|
|
7267
|
+
packageManager,
|
|
7268
|
+
result.stderr,
|
|
7269
|
+
containerId
|
|
7270
|
+
);
|
|
7271
|
+
}
|
|
7272
|
+
if (packageManager === "apt-get") aptUpdated = true;
|
|
7046
7273
|
};
|
|
7047
|
-
const
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
result = { ...raw, stderr: stripStraceDiagnostics(raw.stderr) };
|
|
7055
|
-
} catch (error) {
|
|
7056
|
-
removeTrace(traceFile);
|
|
7057
|
-
throw error;
|
|
7058
|
-
}
|
|
7059
|
-
if (execOptions?.signal?.aborted) {
|
|
7060
|
-
removeTrace(traceFile);
|
|
7061
|
-
return result;
|
|
7062
|
-
}
|
|
7063
|
-
await collectCommand(traceFile);
|
|
7064
|
-
return result;
|
|
7065
|
-
},
|
|
7066
|
-
readFile: innerReadFile,
|
|
7067
|
-
// The writeFile tool mutates the filesystem outside strace's view, so
|
|
7068
|
-
// observe it directly: synthesize a `write` change per file (under the
|
|
7069
|
-
// observation root) and run it through onFileChanges. A throw propagates to
|
|
7070
|
-
// the writeFile tool's execute (which rejects), the same gate as the bash
|
|
7071
|
-
// path — except there's no CommandResult here, so throw a plain Error to
|
|
7072
|
-
// reject; BashException.format() is meaningful only for bash commands.
|
|
7073
|
-
writeFiles: async (files) => {
|
|
7074
|
-
await innerWriteFiles(files);
|
|
7075
|
-
const now = Date.now();
|
|
7076
|
-
const changes = files.map(
|
|
7077
|
-
(f) => posix2.normalize(
|
|
7078
|
-
f.path.startsWith("/") ? f.path : posix2.join(dest, f.path)
|
|
7079
|
-
)
|
|
7080
|
-
).filter(underDestination).map((path5) => ({ op: "write", path: path5, timestamp: now }));
|
|
7081
|
-
if (changes.length) await onFileChanges?.(changes);
|
|
7082
|
-
},
|
|
7083
|
-
dispose: async () => {
|
|
7084
|
-
await innerExecute(`rm -rf ${shellQuote(traceDir)}`).catch(() => {
|
|
7085
|
-
});
|
|
7086
|
-
await sandbox.dispose();
|
|
7274
|
+
const ensureTool = async (checkName, installName) => {
|
|
7275
|
+
const cacheKey = installName ?? checkName;
|
|
7276
|
+
if (ensuredTools.has(cacheKey)) return;
|
|
7277
|
+
const check = await exec(`which ${shellQuote(checkName)}`);
|
|
7278
|
+
if (check.exitCode === 0) {
|
|
7279
|
+
ensuredTools.add(cacheKey);
|
|
7280
|
+
return;
|
|
7087
7281
|
}
|
|
7282
|
+
await installPackages([installName ?? checkName]);
|
|
7283
|
+
ensuredTools.add(cacheKey);
|
|
7284
|
+
};
|
|
7285
|
+
return {
|
|
7286
|
+
containerId,
|
|
7287
|
+
image,
|
|
7288
|
+
packageManager,
|
|
7289
|
+
arch,
|
|
7290
|
+
exec,
|
|
7291
|
+
installPackages,
|
|
7292
|
+
ensureTool
|
|
7088
7293
|
};
|
|
7089
|
-
if (sandbox.spawn) {
|
|
7090
|
-
const innerSpawn = sandbox.spawn.bind(sandbox);
|
|
7091
|
-
decorated.spawn = (command, spawnOptions) => {
|
|
7092
|
-
const traceFile = `${traceDir}/${randomUUID()}.strace`;
|
|
7093
|
-
const child = innerSpawn(
|
|
7094
|
-
buildStraceCommand(command, traceFile, traceDir),
|
|
7095
|
-
spawnOptions
|
|
7096
|
-
);
|
|
7097
|
-
const exit = (async () => {
|
|
7098
|
-
try {
|
|
7099
|
-
return await child.exit;
|
|
7100
|
-
} finally {
|
|
7101
|
-
await collectSpawn(traceFile).catch(() => {
|
|
7102
|
-
});
|
|
7103
|
-
}
|
|
7104
|
-
})();
|
|
7105
|
-
return { stdout: child.stdout, stderr: child.stderr, exit };
|
|
7106
|
-
};
|
|
7107
|
-
}
|
|
7108
|
-
return decorated;
|
|
7109
7294
|
}
|
|
7110
|
-
|
|
7111
|
-
var STRACE_MISSING = /strace:?\s+(?:command\s+)?not found/i;
|
|
7112
|
-
async function selfTestStrace(sandbox) {
|
|
7113
|
-
const probeDir = `/tmp/dat-strace-${randomUUID()}`;
|
|
7114
|
-
const traceFile = `/tmp/dat-strace-${randomUUID()}.trace`;
|
|
7115
|
-
const q = shellQuote;
|
|
7116
|
-
const sequence = `mkdir -p ${q(probeDir)} && echo hi > ${q(`${probeDir}/a.txt`)} && mv ${q(`${probeDir}/a.txt`)} ${q(`${probeDir}/b.txt`)} && echo more > ${q(`${probeDir}/c.txt`)}`;
|
|
7117
|
-
const wrapped = buildStraceCommand(sequence, traceFile, "/tmp");
|
|
7118
|
-
let result;
|
|
7119
|
-
let raw = "";
|
|
7295
|
+
async function runAppleBuild(args, showBuildLogs) {
|
|
7120
7296
|
try {
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
trace[0:600]=${raw.slice(0, 600)}`;
|
|
7126
|
-
if (PTRACE_DENIED.test(result.stderr)) {
|
|
7127
|
-
throw new StraceUnavailableError("ptrace-blocked", diagnostics);
|
|
7128
|
-
}
|
|
7129
|
-
if (!raw || result.exitCode === 127 || STRACE_MISSING.test(result.stderr)) {
|
|
7130
|
-
throw new StraceUnavailableError("strace-missing", diagnostics);
|
|
7131
|
-
}
|
|
7132
|
-
const changes = parseStraceTrace(raw, { destination: probeDir, traceFile });
|
|
7133
|
-
const hasRealFdPath = raw.includes(`<${probeDir}/`);
|
|
7134
|
-
const hasRename = changes.some(
|
|
7135
|
-
(c) => c.op === "rename" && c.from === `${probeDir}/a.txt` && c.path === `${probeDir}/b.txt`
|
|
7136
|
-
);
|
|
7137
|
-
const hasWrite = changes.some((c) => c.op === "write");
|
|
7138
|
-
const allUnderDir = changes.every(
|
|
7139
|
-
(c) => c.path === probeDir || c.path.startsWith(`${probeDir}/`)
|
|
7140
|
-
);
|
|
7141
|
-
if (!hasRealFdPath || !hasRename || !hasWrite || !allUnderDir) {
|
|
7142
|
-
throw new StraceUnavailableError("trace-unparseable", diagnostics);
|
|
7297
|
+
if (showBuildLogs) {
|
|
7298
|
+
await runStreamed(CLI, args);
|
|
7299
|
+
} else {
|
|
7300
|
+
await spawn3(CLI, args);
|
|
7143
7301
|
}
|
|
7302
|
+
} catch (error) {
|
|
7303
|
+
throw new AppleContainerImageBuildError(getCliErrorMessage(error));
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
function runStreamed(command, args) {
|
|
7307
|
+
return new Promise((resolve4, reject) => {
|
|
7308
|
+
const child = childSpawn2(command, args, { stdio: "inherit" });
|
|
7309
|
+
child.once("error", reject);
|
|
7310
|
+
child.once(
|
|
7311
|
+
"exit",
|
|
7312
|
+
(code) => code === 0 ? resolve4() : reject(
|
|
7313
|
+
new Error(
|
|
7314
|
+
`${command} build exited with code ${code} (see build output above)`
|
|
7315
|
+
)
|
|
7316
|
+
)
|
|
7317
|
+
);
|
|
7318
|
+
});
|
|
7319
|
+
}
|
|
7320
|
+
async function createAppleContainerSandbox(options = {}) {
|
|
7321
|
+
if (isAppleContainerfileOptions(options)) {
|
|
7322
|
+
return new ContainerfileStrategy(options, appleEngine, {
|
|
7323
|
+
dockerfile: options.dockerfile,
|
|
7324
|
+
context: options.context ?? ".",
|
|
7325
|
+
showBuildLogs: options.showBuildLogs ?? false,
|
|
7326
|
+
identity: options.arch
|
|
7327
|
+
}).create();
|
|
7328
|
+
}
|
|
7329
|
+
return new RuntimeStrategy(options, appleEngine, {
|
|
7330
|
+
image: options.image ?? appleEngine.defaultImage,
|
|
7331
|
+
installers: options.installers ?? []
|
|
7332
|
+
}).create();
|
|
7333
|
+
}
|
|
7334
|
+
async function useAppleContainerSandbox(options, fn) {
|
|
7335
|
+
const sandbox = await createAppleContainerSandbox(options);
|
|
7336
|
+
try {
|
|
7337
|
+
return await fn(sandbox);
|
|
7144
7338
|
} finally {
|
|
7145
|
-
|
|
7146
|
-
});
|
|
7339
|
+
await sandbox.dispose();
|
|
7147
7340
|
}
|
|
7148
7341
|
}
|
|
7149
7342
|
|
|
7343
|
+
// packages/context/src/lib/sandbox/bash-exception.ts
|
|
7344
|
+
var BashException = class extends Error {
|
|
7345
|
+
};
|
|
7346
|
+
|
|
7347
|
+
// packages/context/src/lib/sandbox/bash-meta.ts
|
|
7348
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
7349
|
+
var store = new AsyncLocalStorage2();
|
|
7350
|
+
function runWithBashMeta(fn) {
|
|
7351
|
+
return store.run({ hidden: {} }, fn);
|
|
7352
|
+
}
|
|
7353
|
+
function useBashMeta() {
|
|
7354
|
+
const state = store.getStore();
|
|
7355
|
+
if (!state) return null;
|
|
7356
|
+
return {
|
|
7357
|
+
setHidden(patch) {
|
|
7358
|
+
state.hidden = { ...state.hidden, ...patch };
|
|
7359
|
+
},
|
|
7360
|
+
setReminder(text) {
|
|
7361
|
+
state.reminder = text;
|
|
7362
|
+
},
|
|
7363
|
+
clearReminder() {
|
|
7364
|
+
state.reminder = void 0;
|
|
7365
|
+
}
|
|
7366
|
+
};
|
|
7367
|
+
}
|
|
7368
|
+
function readBashMeta() {
|
|
7369
|
+
return store.getStore();
|
|
7370
|
+
}
|
|
7371
|
+
|
|
7372
|
+
// packages/context/src/lib/sandbox/bash-tool.ts
|
|
7373
|
+
import { tool as tool2 } from "ai";
|
|
7374
|
+
import {
|
|
7375
|
+
createBashTool as externalCreateBashTool
|
|
7376
|
+
} from "bash-tool";
|
|
7377
|
+
import z3 from "zod";
|
|
7378
|
+
|
|
7150
7379
|
// packages/context/src/lib/sandbox/upload-skills.ts
|
|
7151
7380
|
import * as path3 from "node:path";
|
|
7152
7381
|
|
|
@@ -7311,18 +7540,9 @@ async function createBashTool(options) {
|
|
|
7311
7540
|
extraInstructions,
|
|
7312
7541
|
sandbox: backend,
|
|
7313
7542
|
destination,
|
|
7314
|
-
onFileChanges,
|
|
7315
|
-
onError,
|
|
7316
7543
|
...rest
|
|
7317
7544
|
} = options;
|
|
7318
|
-
const
|
|
7319
|
-
await selfTestStrace(backend);
|
|
7320
|
-
const tracked = traceFileChanges(backend, {
|
|
7321
|
-
destination: observationRoot,
|
|
7322
|
-
onFileChanges,
|
|
7323
|
-
onError
|
|
7324
|
-
});
|
|
7325
|
-
const sandbox = withAbortSignal(withBashExceptionCatch(tracked));
|
|
7545
|
+
const sandbox = withAbortSignal(withBashExceptionCatch(backend));
|
|
7326
7546
|
const combinedInstructions = [extraInstructions, REASONING_INSTRUCTION].filter(Boolean).join("\n\n");
|
|
7327
7547
|
const toolkit = await externalCreateBashTool({
|
|
7328
7548
|
...rest,
|
|
@@ -7373,7 +7593,7 @@ async function createBashTool(options) {
|
|
|
7373
7593
|
const upstreamWriteFile = toolkit.tools.writeFile;
|
|
7374
7594
|
const originalWriteExecute = upstreamWriteFile.execute;
|
|
7375
7595
|
const writeFileBuilder = tool2;
|
|
7376
|
-
const
|
|
7596
|
+
const writeFile2 = writeFileBuilder({
|
|
7377
7597
|
...upstreamWriteFile,
|
|
7378
7598
|
execute: async (input, options2) => {
|
|
7379
7599
|
if (!originalWriteExecute) {
|
|
@@ -7392,15 +7612,15 @@ async function createBashTool(options) {
|
|
|
7392
7612
|
...toolkit,
|
|
7393
7613
|
sandbox,
|
|
7394
7614
|
bash,
|
|
7395
|
-
tools: { ...toolkit.tools, bash, writeFile },
|
|
7615
|
+
tools: { ...toolkit.tools, bash, writeFile: writeFile2 },
|
|
7396
7616
|
skills: skills2
|
|
7397
7617
|
};
|
|
7398
7618
|
}
|
|
7399
7619
|
|
|
7400
7620
|
// packages/context/src/lib/sandbox/binary-bridges.ts
|
|
7401
|
-
import { existsSync as
|
|
7621
|
+
import { existsSync as existsSync3 } from "fs";
|
|
7402
7622
|
import { defineCommand } from "just-bash";
|
|
7403
|
-
import
|
|
7623
|
+
import spawn4 from "nano-spawn";
|
|
7404
7624
|
import * as path4 from "path";
|
|
7405
7625
|
function createBinaryBridges(...binaries) {
|
|
7406
7626
|
return binaries.map((input) => {
|
|
@@ -7438,7 +7658,7 @@ function createBinaryBridges(...binaries) {
|
|
|
7438
7658
|
PATH: process.env.PATH
|
|
7439
7659
|
// Always use host PATH for binary bridges
|
|
7440
7660
|
};
|
|
7441
|
-
const result = await
|
|
7661
|
+
const result = await spawn4(binaryPath, resolvedArgs, {
|
|
7442
7662
|
cwd: realCwd,
|
|
7443
7663
|
env: mergedEnv
|
|
7444
7664
|
});
|
|
@@ -7487,7 +7707,7 @@ function resolveRealCwd(ctx) {
|
|
|
7487
7707
|
} else {
|
|
7488
7708
|
realCwd = process.cwd();
|
|
7489
7709
|
}
|
|
7490
|
-
if (!
|
|
7710
|
+
if (!existsSync3(realCwd)) {
|
|
7491
7711
|
realCwd = process.cwd();
|
|
7492
7712
|
}
|
|
7493
7713
|
return realCwd;
|
|
@@ -7495,7 +7715,7 @@ function resolveRealCwd(ctx) {
|
|
|
7495
7715
|
|
|
7496
7716
|
// packages/context/src/lib/sandbox/daytona-sandbox.ts
|
|
7497
7717
|
import "bash-tool";
|
|
7498
|
-
import { randomUUID
|
|
7718
|
+
import { randomUUID } from "node:crypto";
|
|
7499
7719
|
var DAYTONA_DEFAULT_DESTINATION = "/home/daytona";
|
|
7500
7720
|
var DAYTONA_EXIT_POLL_INTERVAL_MS = 250;
|
|
7501
7721
|
var DAYTONA_EXIT_POLL_TIMEOUT_MS = 3e4;
|
|
@@ -7718,7 +7938,7 @@ function createDaytonaSandboxMethods(args) {
|
|
|
7718
7938
|
async writeFiles(files) {
|
|
7719
7939
|
try {
|
|
7720
7940
|
for (const dir of uniqueParentDirectories(files.map((f) => f.path))) {
|
|
7721
|
-
const result = await executeCommand(`mkdir -p ${
|
|
7941
|
+
const result = await executeCommand(`mkdir -p ${shellQuote(dir)}`);
|
|
7722
7942
|
if (result.exitCode !== 0) {
|
|
7723
7943
|
throw new DaytonaCommandError(
|
|
7724
7944
|
`Failed to create directory "${dir}": ${result.stderr}`
|
|
@@ -7741,6 +7961,9 @@ function createDaytonaSandboxMethods(args) {
|
|
|
7741
7961
|
}
|
|
7742
7962
|
},
|
|
7743
7963
|
async dispose() {
|
|
7964
|
+
},
|
|
7965
|
+
[Symbol.asyncDispose]() {
|
|
7966
|
+
return this.dispose();
|
|
7744
7967
|
}
|
|
7745
7968
|
};
|
|
7746
7969
|
}
|
|
@@ -7908,17 +8131,17 @@ function createTextReadable() {
|
|
|
7908
8131
|
};
|
|
7909
8132
|
}
|
|
7910
8133
|
function buildSessionCommand(command, options) {
|
|
7911
|
-
const cwdPrefix = options.cwd ? `cd ${
|
|
8134
|
+
const cwdPrefix = options.cwd ? `cd ${shellQuote(options.cwd)} && ` : "";
|
|
7912
8135
|
const env = options.env ?? {};
|
|
7913
8136
|
const entries = Object.entries(env);
|
|
7914
8137
|
if (entries.length === 0) {
|
|
7915
|
-
return `sh -lc ${
|
|
8138
|
+
return `sh -lc ${shellQuote(`${cwdPrefix}${command}`)}`;
|
|
7916
8139
|
}
|
|
7917
8140
|
for (const [key] of entries) {
|
|
7918
8141
|
validateEnvKey(key);
|
|
7919
8142
|
}
|
|
7920
|
-
const exports = entries.map(([key, value]) => `export ${key}=${
|
|
7921
|
-
return `sh -lc ${
|
|
8143
|
+
const exports = entries.map(([key, value]) => `export ${key}=${shellQuote(value)}`).join("; ");
|
|
8144
|
+
return `sh -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
|
|
7922
8145
|
}
|
|
7923
8146
|
function validateEnvKey(key) {
|
|
7924
8147
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
@@ -7928,7 +8151,7 @@ function validateEnvKey(key) {
|
|
|
7928
8151
|
}
|
|
7929
8152
|
}
|
|
7930
8153
|
function createSessionId(kind) {
|
|
7931
|
-
return `deepagents-${kind}-${
|
|
8154
|
+
return `deepagents-${kind}-${randomUUID()}`;
|
|
7932
8155
|
}
|
|
7933
8156
|
function abortedCommandResult() {
|
|
7934
8157
|
return {
|
|
@@ -7944,9 +8167,6 @@ function abortedExitInfo() {
|
|
|
7944
8167
|
success: false
|
|
7945
8168
|
};
|
|
7946
8169
|
}
|
|
7947
|
-
function shellQuote2(value) {
|
|
7948
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
7949
|
-
}
|
|
7950
8170
|
function uniqueParentDirectories(paths) {
|
|
7951
8171
|
const dirs = /* @__PURE__ */ new Set();
|
|
7952
8172
|
for (const path5 of paths) {
|
|
@@ -7972,759 +8192,1045 @@ function toError(error) {
|
|
|
7972
8192
|
|
|
7973
8193
|
// packages/context/src/lib/sandbox/docker-sandbox.ts
|
|
7974
8194
|
import "bash-tool";
|
|
7975
|
-
import
|
|
7976
|
-
import { spawn as
|
|
7977
|
-
import { createHash } from "node:crypto";
|
|
7978
|
-
import {
|
|
7979
|
-
import { Readable as Readable2 } from "node:stream";
|
|
8195
|
+
import spawn5 from "nano-spawn";
|
|
8196
|
+
import { spawn as childSpawn3 } from "node:child_process";
|
|
8197
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
8198
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
7980
8199
|
function isDockerfileOptions(opts) {
|
|
7981
8200
|
return "dockerfile" in opts;
|
|
7982
8201
|
}
|
|
7983
8202
|
function isComposeOptions(opts) {
|
|
7984
8203
|
return "compose" in opts;
|
|
7985
8204
|
}
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
for (const key of Object.keys(env)) {
|
|
7998
|
-
validateEnvKey2(key);
|
|
7999
|
-
}
|
|
8000
|
-
if (name !== void 0 && !CONTAINER_NAME_PATTERN.test(name)) {
|
|
8001
|
-
throw new DockerSandboxError(
|
|
8002
|
-
`Invalid container name: "${name}". Use only letters, numbers, underscore, period, or hyphen. The "sandbox-" prefix is added automatically.`
|
|
8003
|
-
);
|
|
8004
|
-
}
|
|
8005
|
-
this.volumes = volumes;
|
|
8006
|
-
this.resources = resources;
|
|
8007
|
-
this.env = env;
|
|
8008
|
-
this.name = name;
|
|
8009
|
-
this.command = command;
|
|
8205
|
+
function dockerMountArg(volume) {
|
|
8206
|
+
const readOnly = volume.readOnly !== false;
|
|
8207
|
+
const parts = volume.type === "bind" ? ["type=bind", `src=${volume.hostPath}`, `dst=${volume.containerPath}`] : [
|
|
8208
|
+
"type=volume",
|
|
8209
|
+
`src=${volume.name}`,
|
|
8210
|
+
`dst=${volume.containerPath}`,
|
|
8211
|
+
...volume.subPath ? [`volume-subpath=${volume.subPath}`] : [],
|
|
8212
|
+
...volume.noCopy ? ["volume-nocopy"] : []
|
|
8213
|
+
];
|
|
8214
|
+
if (readOnly) {
|
|
8215
|
+
parts.push("readonly");
|
|
8010
8216
|
}
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8217
|
+
return parts.join(",");
|
|
8218
|
+
}
|
|
8219
|
+
function runDockerBuild(args, stdin, showBuildLogs) {
|
|
8220
|
+
const stdio = [
|
|
8221
|
+
stdin === void 0 ? "inherit" : "pipe",
|
|
8222
|
+
showBuildLogs ? "inherit" : "ignore",
|
|
8223
|
+
showBuildLogs ? "inherit" : "pipe"
|
|
8224
|
+
];
|
|
8225
|
+
return new Promise((resolve4, reject) => {
|
|
8226
|
+
const child = childSpawn3("docker", args, { stdio });
|
|
8227
|
+
let stderr = "";
|
|
8228
|
+
child.stderr?.on("data", (chunk) => {
|
|
8229
|
+
stderr += chunk;
|
|
8230
|
+
});
|
|
8231
|
+
child.once(
|
|
8232
|
+
"error",
|
|
8233
|
+
(error) => reject(new DockerfileBuildError(error.message))
|
|
8234
|
+
);
|
|
8235
|
+
child.once("exit", (code) => {
|
|
8236
|
+
if (code === 0) {
|
|
8237
|
+
resolve4();
|
|
8238
|
+
return;
|
|
8019
8239
|
}
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8240
|
+
reject(
|
|
8241
|
+
new DockerfileBuildError(
|
|
8242
|
+
showBuildLogs ? `docker build exited with code ${code} (see build output above)` : stderr || `docker build exited with code ${code}`
|
|
8243
|
+
)
|
|
8244
|
+
);
|
|
8245
|
+
});
|
|
8246
|
+
if (stdin !== void 0 && child.stdin) {
|
|
8247
|
+
child.stdin.on("error", () => {
|
|
8248
|
+
});
|
|
8249
|
+
child.stdin.end(stdin);
|
|
8250
|
+
}
|
|
8251
|
+
});
|
|
8252
|
+
}
|
|
8253
|
+
var dockerEngine = {
|
|
8254
|
+
cli: "docker",
|
|
8255
|
+
runArgs(image, containerId, opts, workdir) {
|
|
8256
|
+
const {
|
|
8257
|
+
memory = "1g",
|
|
8258
|
+
cpus = 2,
|
|
8259
|
+
memorySwap,
|
|
8260
|
+
shmSize,
|
|
8261
|
+
pidsLimit,
|
|
8262
|
+
ulimits = [],
|
|
8263
|
+
cpusetCpus,
|
|
8264
|
+
cpuShares
|
|
8265
|
+
} = opts.resources ?? {};
|
|
8266
|
+
const security = opts.security ?? {};
|
|
8267
|
+
const network = opts.network ?? {};
|
|
8268
|
+
const args = [
|
|
8269
|
+
"run",
|
|
8270
|
+
"-d",
|
|
8271
|
+
"--rm",
|
|
8272
|
+
"--name",
|
|
8273
|
+
containerId,
|
|
8274
|
+
"--memory",
|
|
8275
|
+
memory,
|
|
8276
|
+
"--cpus",
|
|
8277
|
+
String(cpus),
|
|
8278
|
+
"-w",
|
|
8279
|
+
workdir
|
|
8280
|
+
];
|
|
8281
|
+
if (memorySwap) {
|
|
8282
|
+
args.push("--memory-swap", memorySwap);
|
|
8283
|
+
}
|
|
8284
|
+
if (shmSize) {
|
|
8285
|
+
args.push("--shm-size", shmSize);
|
|
8286
|
+
}
|
|
8287
|
+
if (pidsLimit !== void 0) {
|
|
8288
|
+
args.push("--pids-limit", String(pidsLimit));
|
|
8289
|
+
}
|
|
8290
|
+
for (const ulimit of ulimits) {
|
|
8291
|
+
args.push("--ulimit", ulimit);
|
|
8292
|
+
}
|
|
8293
|
+
if (cpusetCpus) {
|
|
8294
|
+
args.push("--cpuset-cpus", cpusetCpus);
|
|
8295
|
+
}
|
|
8296
|
+
if (cpuShares !== void 0) {
|
|
8297
|
+
args.push("--cpu-shares", String(cpuShares));
|
|
8298
|
+
}
|
|
8299
|
+
if (opts.platform) {
|
|
8300
|
+
args.push("--platform", opts.platform);
|
|
8301
|
+
}
|
|
8302
|
+
if (opts.runtime) {
|
|
8303
|
+
args.push("--runtime", opts.runtime);
|
|
8304
|
+
}
|
|
8305
|
+
for (const cap of security.capDrop ?? []) {
|
|
8306
|
+
args.push("--cap-drop", cap);
|
|
8307
|
+
}
|
|
8308
|
+
for (const cap of security.capAdd ?? []) {
|
|
8309
|
+
args.push("--cap-add", cap);
|
|
8310
|
+
}
|
|
8311
|
+
if (security.readOnly) {
|
|
8312
|
+
args.push("--read-only");
|
|
8313
|
+
}
|
|
8314
|
+
if (security.user) {
|
|
8315
|
+
args.push("--user", security.user);
|
|
8316
|
+
}
|
|
8317
|
+
for (const mount of security.tmpfs ?? []) {
|
|
8318
|
+
args.push("--tmpfs", mount);
|
|
8319
|
+
}
|
|
8320
|
+
for (const opt of security.securityOpt ?? []) {
|
|
8321
|
+
args.push("--security-opt", opt);
|
|
8322
|
+
}
|
|
8323
|
+
if (network.mode) {
|
|
8324
|
+
args.push("--network", network.mode);
|
|
8325
|
+
}
|
|
8326
|
+
for (const port of network.publish ?? []) {
|
|
8327
|
+
args.push("--publish", port);
|
|
8328
|
+
}
|
|
8329
|
+
for (const server of network.dns ?? []) {
|
|
8330
|
+
args.push("--dns", server);
|
|
8331
|
+
}
|
|
8332
|
+
for (const host of network.addHost ?? []) {
|
|
8333
|
+
args.push("--add-host", host);
|
|
8334
|
+
}
|
|
8335
|
+
if (network.hostname) {
|
|
8336
|
+
args.push("--hostname", network.hostname);
|
|
8337
|
+
}
|
|
8338
|
+
if (opts.gpus) {
|
|
8339
|
+
args.push("--gpus", opts.gpus);
|
|
8340
|
+
}
|
|
8341
|
+
for (const device of opts.devices ?? []) {
|
|
8342
|
+
args.push("--device", device);
|
|
8343
|
+
}
|
|
8344
|
+
if (opts.init) {
|
|
8345
|
+
args.push("--init");
|
|
8346
|
+
}
|
|
8347
|
+
for (const [key, value] of Object.entries(opts.labels ?? {})) {
|
|
8348
|
+
args.push("--label", `${key}=${value}`);
|
|
8349
|
+
}
|
|
8350
|
+
for (const [key, value] of Object.entries(opts.sysctls ?? {})) {
|
|
8351
|
+
args.push("--sysctl", `${key}=${value}`);
|
|
8352
|
+
}
|
|
8353
|
+
if (opts.entrypoint) {
|
|
8354
|
+
args.push("--entrypoint", opts.entrypoint);
|
|
8355
|
+
}
|
|
8356
|
+
for (const [key, value] of Object.entries(opts.env ?? {})) {
|
|
8357
|
+
args.push("-e", `${key}=${value}`);
|
|
8358
|
+
}
|
|
8359
|
+
for (const volume of opts.volumes ?? []) {
|
|
8360
|
+
args.push("--mount", dockerMountArg(volume));
|
|
8361
|
+
}
|
|
8362
|
+
args.push(image);
|
|
8363
|
+
if (opts.command === void 0) {
|
|
8364
|
+
args.push("tail", "-f", "/dev/null");
|
|
8365
|
+
} else if (opts.command !== null) {
|
|
8366
|
+
args.push(...opts.command);
|
|
8367
|
+
}
|
|
8368
|
+
return args;
|
|
8369
|
+
},
|
|
8370
|
+
execArgs(containerId, command, options) {
|
|
8371
|
+
const flags = [];
|
|
8372
|
+
if (options?.cwd) {
|
|
8373
|
+
flags.push("-w", options.cwd);
|
|
8374
|
+
}
|
|
8375
|
+
if (options?.env) {
|
|
8376
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
8377
|
+
if (key.length === 0 || key.includes("=")) {
|
|
8378
|
+
throw new DockerSandboxError(
|
|
8379
|
+
`Invalid environment variable key: "${key}"`
|
|
8380
|
+
);
|
|
8381
|
+
}
|
|
8382
|
+
flags.push("-e", `${key}=${value}`);
|
|
8023
8383
|
}
|
|
8024
|
-
await this.cleanupCreatedVolumesAfterFailure(error);
|
|
8025
|
-
throw error;
|
|
8026
8384
|
}
|
|
8027
|
-
return
|
|
8385
|
+
return ["exec", ...flags, containerId, "sh", "-c", command];
|
|
8386
|
+
},
|
|
8387
|
+
inspectArgs(containerId) {
|
|
8388
|
+
return [
|
|
8389
|
+
"container",
|
|
8390
|
+
"inspect",
|
|
8391
|
+
"--format",
|
|
8392
|
+
"{{.State.Status}}",
|
|
8393
|
+
containerId
|
|
8394
|
+
];
|
|
8395
|
+
},
|
|
8396
|
+
mountArg: dockerMountArg,
|
|
8397
|
+
parseStatus(status) {
|
|
8398
|
+
return status === "running" ? "running" : "stopped";
|
|
8399
|
+
},
|
|
8400
|
+
volumeCreateArgs(volume) {
|
|
8401
|
+
const args = ["volume", "create"];
|
|
8402
|
+
if (volume.driver) {
|
|
8403
|
+
args.push("--driver", volume.driver);
|
|
8404
|
+
}
|
|
8405
|
+
for (const [key, value] of Object.entries(volume.driverOptions ?? {})) {
|
|
8406
|
+
args.push("--opt", `${key}=${value}`);
|
|
8407
|
+
}
|
|
8408
|
+
args.push(volume.name);
|
|
8409
|
+
return args;
|
|
8410
|
+
},
|
|
8411
|
+
errorMessage(error) {
|
|
8412
|
+
const err = error;
|
|
8413
|
+
return err.stderr || err.stdout || err.message || String(error);
|
|
8414
|
+
},
|
|
8415
|
+
isServiceDown(message2) {
|
|
8416
|
+
return message2.includes("Cannot connect") || message2.includes("docker daemon");
|
|
8417
|
+
},
|
|
8418
|
+
isMissingContainer(message2) {
|
|
8419
|
+
return message2.toLowerCase().includes("no such container");
|
|
8420
|
+
},
|
|
8421
|
+
isMissingVolume(message2) {
|
|
8422
|
+
return message2.toLowerCase().includes("no such volume");
|
|
8423
|
+
},
|
|
8424
|
+
isNameConflict(message2) {
|
|
8425
|
+
return message2.toLowerCase().includes("is already in use by container");
|
|
8426
|
+
},
|
|
8427
|
+
async ensureWorkdir() {
|
|
8428
|
+
},
|
|
8429
|
+
defaultImage: "alpine:latest",
|
|
8430
|
+
createInstallerContext,
|
|
8431
|
+
async imageExists(tag) {
|
|
8432
|
+
try {
|
|
8433
|
+
await spawn5("docker", ["image", "inspect", tag]);
|
|
8434
|
+
return true;
|
|
8435
|
+
} catch {
|
|
8436
|
+
return false;
|
|
8437
|
+
}
|
|
8438
|
+
},
|
|
8439
|
+
async buildImage(spec) {
|
|
8440
|
+
const inline = spec.dockerfile.includes("\n");
|
|
8441
|
+
const args = [
|
|
8442
|
+
"build",
|
|
8443
|
+
...spec.identity ? ["--platform", spec.identity] : [],
|
|
8444
|
+
"-t",
|
|
8445
|
+
spec.tag,
|
|
8446
|
+
"-f",
|
|
8447
|
+
inline ? "-" : spec.dockerfile,
|
|
8448
|
+
spec.context
|
|
8449
|
+
];
|
|
8450
|
+
await runDockerBuild(
|
|
8451
|
+
args,
|
|
8452
|
+
inline ? spec.dockerfile : void 0,
|
|
8453
|
+
spec.showBuildLogs
|
|
8454
|
+
);
|
|
8455
|
+
},
|
|
8456
|
+
errors: {
|
|
8457
|
+
serviceNotAvailable: () => new DockerNotAvailableError(),
|
|
8458
|
+
creation: (message2, image, cause) => new ContainerCreationError(message2, image, cause),
|
|
8459
|
+
generic: (message2, containerId) => new DockerSandboxError(message2, containerId),
|
|
8460
|
+
volumePath: (source, containerPath, reason) => new VolumePathError(source, containerPath, reason),
|
|
8461
|
+
volumeInspect: (name, reason) => new VolumeInspectError(name, reason),
|
|
8462
|
+
volumeCreate: (name, reason) => new VolumeCreateError(name, reason),
|
|
8463
|
+
volumeRemove: (name, reason) => new VolumeRemoveError(name, reason)
|
|
8028
8464
|
}
|
|
8029
|
-
|
|
8030
|
-
|
|
8465
|
+
};
|
|
8466
|
+
function validateEnvKey2(key) {
|
|
8467
|
+
if (key.length === 0 || key.includes("=")) {
|
|
8468
|
+
throw new DockerSandboxError(`Invalid environment variable key: "${key}"`);
|
|
8031
8469
|
}
|
|
8032
|
-
|
|
8033
|
-
|
|
8470
|
+
}
|
|
8471
|
+
function buildDockerExecFlags(options) {
|
|
8472
|
+
const flags = [];
|
|
8473
|
+
if (options?.cwd) {
|
|
8474
|
+
flags.push("-w", options.cwd);
|
|
8034
8475
|
}
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
await this.startContainer(image, containerId2);
|
|
8040
|
-
return { containerId: containerId2, attached: false };
|
|
8041
|
-
}
|
|
8042
|
-
const containerId = this.namedContainerId();
|
|
8043
|
-
const probe = await this.inspectContainer(containerId);
|
|
8044
|
-
if (probe === "running") {
|
|
8045
|
-
return { containerId, attached: true };
|
|
8046
|
-
}
|
|
8047
|
-
if (probe === "stopped") {
|
|
8048
|
-
await this.startStoppedContainer(containerId, image);
|
|
8049
|
-
return { containerId, attached: true };
|
|
8476
|
+
if (options?.env) {
|
|
8477
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
8478
|
+
validateEnvKey2(key);
|
|
8479
|
+
flags.push("-e", `${key}=${value}`);
|
|
8050
8480
|
}
|
|
8051
|
-
|
|
8481
|
+
}
|
|
8482
|
+
return flags;
|
|
8483
|
+
}
|
|
8484
|
+
var ComposeStrategy = class extends ContainerSandboxStrategy {
|
|
8485
|
+
projectName;
|
|
8486
|
+
composeFile;
|
|
8487
|
+
service;
|
|
8488
|
+
constructor(args) {
|
|
8489
|
+
super({ resources: args.resources }, dockerEngine);
|
|
8490
|
+
this.composeFile = args.compose;
|
|
8491
|
+
this.service = args.service;
|
|
8492
|
+
this.projectName = this.computeProjectName();
|
|
8493
|
+
}
|
|
8494
|
+
computeProjectName() {
|
|
8495
|
+
const content = readFileSync3(this.composeFile, "utf-8");
|
|
8496
|
+
const hash = createHash2("sha256").update(content).digest("hex").slice(0, 8);
|
|
8497
|
+
return `sandbox-${hash}`;
|
|
8498
|
+
}
|
|
8499
|
+
async getImage() {
|
|
8500
|
+
return "";
|
|
8501
|
+
}
|
|
8502
|
+
async startContainer(_image, _containerId) {
|
|
8052
8503
|
try {
|
|
8053
|
-
await
|
|
8054
|
-
|
|
8504
|
+
await spawn5("docker", [
|
|
8505
|
+
"compose",
|
|
8506
|
+
"-f",
|
|
8507
|
+
this.composeFile,
|
|
8508
|
+
"-p",
|
|
8509
|
+
this.projectName,
|
|
8510
|
+
"up",
|
|
8511
|
+
"-d"
|
|
8512
|
+
]);
|
|
8055
8513
|
} catch (error) {
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
if (raced === "running") {
|
|
8060
|
-
return { containerId, attached: true };
|
|
8061
|
-
}
|
|
8062
|
-
if (raced === "stopped") {
|
|
8063
|
-
await this.startStoppedContainer(containerId, image);
|
|
8064
|
-
return { containerId, attached: true };
|
|
8065
|
-
}
|
|
8514
|
+
const err = error;
|
|
8515
|
+
if (err.stderr?.includes("Cannot connect")) {
|
|
8516
|
+
throw this.engine.errors.serviceNotAvailable();
|
|
8066
8517
|
}
|
|
8067
|
-
throw
|
|
8518
|
+
throw new ComposeStartError(this.composeFile, err.stderr || err.message);
|
|
8068
8519
|
}
|
|
8069
8520
|
}
|
|
8070
|
-
|
|
8521
|
+
defaultContainerId() {
|
|
8522
|
+
return this.projectName;
|
|
8523
|
+
}
|
|
8524
|
+
async configure() {
|
|
8525
|
+
}
|
|
8526
|
+
async exec(command, options) {
|
|
8071
8527
|
try {
|
|
8072
|
-
await
|
|
8528
|
+
const result = await spawn5(
|
|
8529
|
+
"docker",
|
|
8530
|
+
[
|
|
8531
|
+
"compose",
|
|
8532
|
+
"-f",
|
|
8533
|
+
this.composeFile,
|
|
8534
|
+
"-p",
|
|
8535
|
+
this.projectName,
|
|
8536
|
+
"exec",
|
|
8537
|
+
"-T",
|
|
8538
|
+
this.service,
|
|
8539
|
+
"sh",
|
|
8540
|
+
"-c",
|
|
8541
|
+
command
|
|
8542
|
+
],
|
|
8543
|
+
{ signal: options?.signal }
|
|
8544
|
+
);
|
|
8545
|
+
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
8073
8546
|
} catch (error) {
|
|
8074
|
-
const
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8547
|
+
const err = error;
|
|
8548
|
+
return {
|
|
8549
|
+
stdout: err.stdout || "",
|
|
8550
|
+
stderr: err.stderr || err.message || "",
|
|
8551
|
+
exitCode: err.exitCode ?? 1
|
|
8552
|
+
};
|
|
8079
8553
|
}
|
|
8080
8554
|
}
|
|
8081
|
-
|
|
8555
|
+
spawnProcess(command, options) {
|
|
8556
|
+
const child = childSpawn3("docker", [
|
|
8557
|
+
"compose",
|
|
8558
|
+
"-f",
|
|
8559
|
+
this.composeFile,
|
|
8560
|
+
"-p",
|
|
8561
|
+
this.projectName,
|
|
8562
|
+
"exec",
|
|
8563
|
+
"-T",
|
|
8564
|
+
...buildDockerExecFlags(options),
|
|
8565
|
+
this.service,
|
|
8566
|
+
"sh",
|
|
8567
|
+
"-c",
|
|
8568
|
+
command
|
|
8569
|
+
]);
|
|
8570
|
+
return toSandboxProcess(child, options?.signal);
|
|
8571
|
+
}
|
|
8572
|
+
async stopContainer(_containerId) {
|
|
8082
8573
|
try {
|
|
8083
|
-
|
|
8084
|
-
"
|
|
8085
|
-
"
|
|
8086
|
-
|
|
8087
|
-
"
|
|
8088
|
-
|
|
8574
|
+
await spawn5("docker", [
|
|
8575
|
+
"compose",
|
|
8576
|
+
"-f",
|
|
8577
|
+
this.composeFile,
|
|
8578
|
+
"-p",
|
|
8579
|
+
this.projectName,
|
|
8580
|
+
"down"
|
|
8089
8581
|
]);
|
|
8090
|
-
|
|
8091
|
-
return status === "running" ? "running" : "stopped";
|
|
8092
|
-
} catch (error) {
|
|
8093
|
-
const message2 = this.getDockerErrorMessage(error);
|
|
8094
|
-
if (this.isDockerUnavailableError(message2)) {
|
|
8095
|
-
throw new DockerNotAvailableError();
|
|
8096
|
-
}
|
|
8097
|
-
if (this.isMissingContainerError(message2)) {
|
|
8098
|
-
return "absent";
|
|
8099
|
-
}
|
|
8100
|
-
throw new DockerSandboxError(
|
|
8101
|
-
`Failed to inspect container "${containerId}": ${message2}`
|
|
8102
|
-
);
|
|
8582
|
+
} catch {
|
|
8103
8583
|
}
|
|
8104
8584
|
}
|
|
8105
|
-
|
|
8106
|
-
|
|
8585
|
+
};
|
|
8586
|
+
async function createDockerSandbox(options = {}) {
|
|
8587
|
+
if (isComposeOptions(options)) {
|
|
8588
|
+
return new ComposeStrategy({
|
|
8589
|
+
compose: options.compose,
|
|
8590
|
+
service: options.service,
|
|
8591
|
+
resources: options.resources
|
|
8592
|
+
}).create();
|
|
8107
8593
|
}
|
|
8108
|
-
|
|
8109
|
-
return
|
|
8594
|
+
if (isDockerfileOptions(options)) {
|
|
8595
|
+
return new ContainerfileStrategy(options, dockerEngine, {
|
|
8596
|
+
dockerfile: options.dockerfile,
|
|
8597
|
+
context: options.context ?? ".",
|
|
8598
|
+
showBuildLogs: options.showBuildLogs ?? false,
|
|
8599
|
+
identity: options.platform
|
|
8600
|
+
}).create();
|
|
8110
8601
|
}
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
const exists = await this.volumeExists(volume.name);
|
|
8123
|
-
if (exists) {
|
|
8124
|
-
throw new VolumeCreateError(
|
|
8125
|
-
volume.name,
|
|
8126
|
-
"managed volume already exists"
|
|
8127
|
-
);
|
|
8128
|
-
}
|
|
8129
|
-
await this.createVolume(volume);
|
|
8130
|
-
if (volume.removeOnDispose !== false) {
|
|
8131
|
-
this.createdVolumes.add(volume.name);
|
|
8132
|
-
}
|
|
8133
|
-
}
|
|
8602
|
+
return new RuntimeStrategy(options, dockerEngine, {
|
|
8603
|
+
image: options.image ?? dockerEngine.defaultImage,
|
|
8604
|
+
installers: options.installers ?? []
|
|
8605
|
+
}).create();
|
|
8606
|
+
}
|
|
8607
|
+
async function useSandbox(options, fn) {
|
|
8608
|
+
const sandbox = await createDockerSandbox(options);
|
|
8609
|
+
try {
|
|
8610
|
+
return await fn(sandbox);
|
|
8611
|
+
} finally {
|
|
8612
|
+
await sandbox.dispose();
|
|
8134
8613
|
}
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8614
|
+
}
|
|
8615
|
+
|
|
8616
|
+
// packages/context/src/lib/sandbox/strace/file-changes.ts
|
|
8617
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8618
|
+
import { posix as posix2 } from "node:path";
|
|
8619
|
+
|
|
8620
|
+
// packages/context/src/lib/sandbox/strace/index.ts
|
|
8621
|
+
import spawn6 from "nano-spawn";
|
|
8622
|
+
import { posix } from "node:path";
|
|
8623
|
+
var STRACE_FLAGS = "-f -y -qq -e trace=%file,write,pwrite64,writev";
|
|
8624
|
+
function buildStraceCommand(command, traceFile, traceDir) {
|
|
8625
|
+
return `mkdir -p ${shellQuote(traceDir)} 2>/dev/null; strace ${STRACE_FLAGS} -o ${shellQuote(traceFile)} -- sh -c ${shellQuote(command)}`;
|
|
8626
|
+
}
|
|
8627
|
+
function buildTracedCommand(command, traceFile, traceDir, sentinel) {
|
|
8628
|
+
return `mkdir -p ${shellQuote(traceDir)} 2>/dev/null; strace ${STRACE_FLAGS} -o ${shellQuote(traceFile)} -- sh -c ${shellQuote(command)}; __dat_rc=$?; printf '\\n%s\\n' ${shellQuote(sentinel)}; base64 ${shellQuote(traceFile)} 2>/dev/null; rm -f ${shellQuote(traceFile)} 2>/dev/null; exit $__dat_rc`;
|
|
8629
|
+
}
|
|
8630
|
+
function splitTracedOutput(stdout, sentinel) {
|
|
8631
|
+
const marker = `
|
|
8632
|
+
${sentinel}
|
|
8633
|
+
`;
|
|
8634
|
+
const at = stdout.indexOf(marker);
|
|
8635
|
+
if (at === -1) return { stdout, trace: "" };
|
|
8636
|
+
const base64 = stdout.slice(at + marker.length);
|
|
8637
|
+
return {
|
|
8638
|
+
stdout: stdout.slice(0, at),
|
|
8639
|
+
trace: base64 ? Buffer.from(base64, "base64").toString("utf8") : ""
|
|
8640
|
+
};
|
|
8641
|
+
}
|
|
8642
|
+
var CREATE_OR_TRUNC_FLAGS = /O_(CREAT|TRUNC)/;
|
|
8643
|
+
var RENAME_SYSCALLS = /* @__PURE__ */ new Set(["rename", "renameat", "renameat2"]);
|
|
8644
|
+
var DELETE_SYSCALLS = /* @__PURE__ */ new Set(["unlink", "unlinkat", "rmdir"]);
|
|
8645
|
+
var MKDIR_SYSCALLS = /* @__PURE__ */ new Set(["mkdir", "mkdirat"]);
|
|
8646
|
+
var LINK_SYSCALLS = /* @__PURE__ */ new Set(["link", "linkat", "symlink", "symlinkat"]);
|
|
8647
|
+
var OPEN_SYSCALLS = /* @__PURE__ */ new Set(["open", "openat", "openat2", "creat"]);
|
|
8648
|
+
var WRITE_SYSCALLS = /* @__PURE__ */ new Set([
|
|
8649
|
+
"write",
|
|
8650
|
+
"pwrite64",
|
|
8651
|
+
"pwrite",
|
|
8652
|
+
"writev",
|
|
8653
|
+
"pwritev2"
|
|
8654
|
+
]);
|
|
8655
|
+
var TRUNCATE_SYSCALLS = /* @__PURE__ */ new Set(["truncate", "ftruncate"]);
|
|
8656
|
+
function stitchLines(raw) {
|
|
8657
|
+
const pending = /* @__PURE__ */ new Map();
|
|
8658
|
+
const out = [];
|
|
8659
|
+
for (const original of raw.split("\n")) {
|
|
8660
|
+
const line = original.trimEnd();
|
|
8661
|
+
if (!line) continue;
|
|
8662
|
+
const pid = line.match(/^\s*(\d+)\s+/)?.[1] ?? "0";
|
|
8663
|
+
if (/<unfinished \.\.\.>\s*$/.test(line)) {
|
|
8664
|
+
pending.set(pid, line.replace(/<unfinished \.\.\.>\s*$/, ""));
|
|
8665
|
+
continue;
|
|
8666
|
+
}
|
|
8667
|
+
const resumed = line.match(/<\.\.\.\s+\S+\s+resumed>(.*)$/);
|
|
8668
|
+
if (resumed) {
|
|
8669
|
+
const head = pending.get(pid);
|
|
8670
|
+
pending.delete(pid);
|
|
8671
|
+
if (head !== void 0) out.push(head + resumed[1]);
|
|
8672
|
+
continue;
|
|
8185
8673
|
}
|
|
8674
|
+
out.push(line);
|
|
8186
8675
|
}
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8676
|
+
return out;
|
|
8677
|
+
}
|
|
8678
|
+
function parseCall(line) {
|
|
8679
|
+
const m = line.match(
|
|
8680
|
+
/^\s*(?:\d+\s+)?([a-z_][a-z0-9_]*)\((.*)\)\s*=\s*(.+?)\s*$/
|
|
8681
|
+
);
|
|
8682
|
+
if (!m) return null;
|
|
8683
|
+
const [, syscall, args, rest] = m;
|
|
8684
|
+
const errnoMatch = rest.match(/^(-?\d+)\s+([A-Z][A-Z0-9_]*)/);
|
|
8685
|
+
if (errnoMatch) {
|
|
8686
|
+
return { syscall, args, retval: errnoMatch[1], errno: errnoMatch[2] };
|
|
8687
|
+
}
|
|
8688
|
+
return { syscall, args, retval: rest.split(/\s+/)[0] };
|
|
8689
|
+
}
|
|
8690
|
+
var STRACE_ESCAPE = {
|
|
8691
|
+
n: 10,
|
|
8692
|
+
t: 9,
|
|
8693
|
+
r: 13,
|
|
8694
|
+
v: 11,
|
|
8695
|
+
f: 12,
|
|
8696
|
+
'"': 34,
|
|
8697
|
+
"\\": 92
|
|
8698
|
+
};
|
|
8699
|
+
function decodeStraceString(s) {
|
|
8700
|
+
const bytes = [];
|
|
8701
|
+
for (let i = 0; i < s.length; ) {
|
|
8702
|
+
if (s[i] === "\\" && i + 1 < s.length) {
|
|
8703
|
+
const octal = s.slice(i + 1, i + 4).match(/^[0-7]{1,3}/)?.[0];
|
|
8704
|
+
if (octal) {
|
|
8705
|
+
bytes.push(parseInt(octal, 8) & 255);
|
|
8706
|
+
i += 1 + octal.length;
|
|
8707
|
+
continue;
|
|
8708
|
+
}
|
|
8709
|
+
const mapped = STRACE_ESCAPE[s[i + 1]];
|
|
8710
|
+
bytes.push(mapped ?? s.charCodeAt(i + 1));
|
|
8711
|
+
i += 2;
|
|
8712
|
+
continue;
|
|
8190
8713
|
}
|
|
8191
|
-
const
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
`${field} must not contain commas`
|
|
8196
|
-
);
|
|
8714
|
+
const code = s.charCodeAt(i);
|
|
8715
|
+
if (code < 128) bytes.push(code);
|
|
8716
|
+
else for (const b of Buffer.from(s[i], "utf8")) bytes.push(b);
|
|
8717
|
+
i += 1;
|
|
8197
8718
|
}
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
`--memory=${memory}`,
|
|
8207
|
-
`--cpus=${cpus}`,
|
|
8208
|
-
"-w",
|
|
8209
|
-
"/workspace"
|
|
8210
|
-
];
|
|
8211
|
-
for (const [key, value] of Object.entries(this.env)) {
|
|
8212
|
-
args.push("-e", `${key}=${value}`);
|
|
8213
|
-
}
|
|
8214
|
-
for (const volume of this.volumes) {
|
|
8215
|
-
args.push("--mount", this.buildVolumeMountArg(volume));
|
|
8216
|
-
}
|
|
8217
|
-
args.push(image);
|
|
8218
|
-
if (this.command === void 0) {
|
|
8219
|
-
args.push("tail", "-f", "/dev/null");
|
|
8220
|
-
} else if (this.command !== null) {
|
|
8221
|
-
args.push(...this.command);
|
|
8222
|
-
}
|
|
8223
|
-
return args;
|
|
8719
|
+
return Buffer.from(bytes).toString("utf8");
|
|
8720
|
+
}
|
|
8721
|
+
function quotedStrings(args) {
|
|
8722
|
+
const out = [];
|
|
8723
|
+
const re = /"((?:[^"\\]|\\.)*)"/g;
|
|
8724
|
+
let m;
|
|
8725
|
+
while ((m = re.exec(args)) !== null) {
|
|
8726
|
+
out.push(decodeStraceString(m[1]));
|
|
8224
8727
|
}
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8728
|
+
return out;
|
|
8729
|
+
}
|
|
8730
|
+
function dirfdPath(args) {
|
|
8731
|
+
return args.match(/^\s*(?:AT_FDCWD|-?\d+)<([^>]*)>/)?.[1] ?? "/";
|
|
8732
|
+
}
|
|
8733
|
+
function fdPath(args) {
|
|
8734
|
+
return args.match(/^\s*-?\d+<([^>]*)>/)?.[1] ?? null;
|
|
8735
|
+
}
|
|
8736
|
+
function openFlags(args) {
|
|
8737
|
+
return args.match(/\bO_[A-Z_]+(?:\|O_[A-Z_]+)*/)?.[0] ?? "";
|
|
8738
|
+
}
|
|
8739
|
+
function matchesGlobs(path5, include, exclude) {
|
|
8740
|
+
return include.some((glob) => posix.matchesGlob(path5, glob)) && !exclude?.some((glob) => posix.matchesGlob(path5, glob));
|
|
8741
|
+
}
|
|
8742
|
+
function parseStraceTrace(raw, options) {
|
|
8743
|
+
const { include, exclude, traceDir, traceFile } = options;
|
|
8744
|
+
const now = Date.now();
|
|
8745
|
+
const state = /* @__PURE__ */ new Map();
|
|
8746
|
+
const renames = [];
|
|
8747
|
+
const resolve4 = (dir, p) => posix.normalize(p.startsWith("/") ? p : posix.join(dir, p));
|
|
8748
|
+
const write = (path5) => {
|
|
8749
|
+
state.set(path5, "write");
|
|
8750
|
+
};
|
|
8751
|
+
const remove = (path5) => {
|
|
8752
|
+
if (state.get(path5) === "write") state.delete(path5);
|
|
8753
|
+
else state.set(path5, "delete");
|
|
8754
|
+
};
|
|
8755
|
+
for (const line of stitchLines(raw)) {
|
|
8756
|
+
const call = parseCall(line);
|
|
8757
|
+
if (!call || call.errno || call.retval === "-1") continue;
|
|
8758
|
+
const { syscall, args } = call;
|
|
8759
|
+
const dir = dirfdPath(args);
|
|
8760
|
+
if (RENAME_SYSCALLS.has(syscall)) {
|
|
8761
|
+
const strings = quotedStrings(args);
|
|
8762
|
+
if (strings.length < 2) continue;
|
|
8763
|
+
const from = resolve4(dir, strings[0]);
|
|
8764
|
+
const to = resolve4(dir, strings[strings.length - 1]);
|
|
8765
|
+
state.delete(from);
|
|
8766
|
+
renames.push({ from, to });
|
|
8767
|
+
continue;
|
|
8236
8768
|
}
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
await spawn3("docker", ["volume", "inspect", name]);
|
|
8242
|
-
} catch (error) {
|
|
8243
|
-
const reason = this.getDockerErrorMessage(error);
|
|
8244
|
-
if (this.isDockerUnavailableError(reason)) {
|
|
8245
|
-
throw new DockerNotAvailableError();
|
|
8246
|
-
}
|
|
8247
|
-
throw new VolumeInspectError(name, reason);
|
|
8769
|
+
if (DELETE_SYSCALLS.has(syscall)) {
|
|
8770
|
+
const strings = quotedStrings(args);
|
|
8771
|
+
if (strings.length) remove(resolve4(dir, strings[0]));
|
|
8772
|
+
continue;
|
|
8248
8773
|
}
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
return true;
|
|
8254
|
-
} catch (error) {
|
|
8255
|
-
if (error instanceof VolumeInspectError) {
|
|
8256
|
-
if (this.isMissingVolumeInspectError(error.reason)) {
|
|
8257
|
-
return false;
|
|
8258
|
-
}
|
|
8259
|
-
throw error;
|
|
8260
|
-
}
|
|
8261
|
-
throw error;
|
|
8774
|
+
if (MKDIR_SYSCALLS.has(syscall)) {
|
|
8775
|
+
const strings = quotedStrings(args);
|
|
8776
|
+
if (strings.length) write(resolve4(dir, strings[0]));
|
|
8777
|
+
continue;
|
|
8262
8778
|
}
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
args.push("--driver", volume.driver);
|
|
8779
|
+
if (LINK_SYSCALLS.has(syscall)) {
|
|
8780
|
+
const strings = quotedStrings(args);
|
|
8781
|
+
if (strings.length) write(resolve4(dir, strings[strings.length - 1]));
|
|
8782
|
+
continue;
|
|
8268
8783
|
}
|
|
8269
|
-
|
|
8270
|
-
|
|
8784
|
+
if (OPEN_SYSCALLS.has(syscall)) {
|
|
8785
|
+
if (!CREATE_OR_TRUNC_FLAGS.test(openFlags(args))) continue;
|
|
8786
|
+
const strings = quotedStrings(args);
|
|
8787
|
+
if (strings.length) write(resolve4(dir, strings[0]));
|
|
8788
|
+
continue;
|
|
8271
8789
|
}
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
const reason = this.getDockerErrorMessage(error);
|
|
8277
|
-
if (this.isDockerUnavailableError(reason)) {
|
|
8278
|
-
throw new DockerNotAvailableError();
|
|
8279
|
-
}
|
|
8280
|
-
throw new VolumeCreateError(volume.name, reason);
|
|
8790
|
+
if (WRITE_SYSCALLS.has(syscall)) {
|
|
8791
|
+
const p = fdPath(args);
|
|
8792
|
+
if (p && Number(call.retval) > 0) write(posix.normalize(p));
|
|
8793
|
+
continue;
|
|
8281
8794
|
}
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
for (const volume of volumes) {
|
|
8286
|
-
try {
|
|
8287
|
-
await spawn3("docker", ["volume", "rm", volume]);
|
|
8288
|
-
this.createdVolumes.delete(volume);
|
|
8289
|
-
} catch (error) {
|
|
8290
|
-
const reason = this.getDockerErrorMessage(error);
|
|
8291
|
-
if (this.isDockerUnavailableError(reason)) {
|
|
8292
|
-
throw new DockerNotAvailableError();
|
|
8293
|
-
}
|
|
8294
|
-
throw new VolumeRemoveError(volume, reason);
|
|
8295
|
-
}
|
|
8795
|
+
if (TRUNCATE_SYSCALLS.has(syscall)) {
|
|
8796
|
+
const p = fdPath(args) ?? quotedStrings(args)[0];
|
|
8797
|
+
if (p) write(resolve4(dir, p));
|
|
8296
8798
|
}
|
|
8297
8799
|
}
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
if (originalError instanceof Error) {
|
|
8303
|
-
const original = originalError;
|
|
8304
|
-
original.suppressed = [...original.suppressed ?? [], cleanupError];
|
|
8305
|
-
}
|
|
8800
|
+
const keep = (path5) => {
|
|
8801
|
+
if (traceFile && path5 === traceFile) return false;
|
|
8802
|
+
if (traceDir && (path5 === traceDir || path5.startsWith(`${traceDir}/`))) {
|
|
8803
|
+
return false;
|
|
8306
8804
|
}
|
|
8805
|
+
if (/^\/(proc|sys|dev)(\/|$)/.test(path5)) return false;
|
|
8806
|
+
return matchesGlobs(path5, include, exclude);
|
|
8807
|
+
};
|
|
8808
|
+
const changes = [];
|
|
8809
|
+
const emittedRenameTargets = /* @__PURE__ */ new Set();
|
|
8810
|
+
for (const { from, to } of renames) {
|
|
8811
|
+
if (!keep(to)) continue;
|
|
8812
|
+
if (state.get(to) === "delete") continue;
|
|
8813
|
+
changes.push({ op: "rename", path: to, from, timestamp: now });
|
|
8814
|
+
emittedRenameTargets.add(to);
|
|
8307
8815
|
}
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
isDockerUnavailableError(message2) {
|
|
8313
|
-
return message2.includes("Cannot connect") || message2.includes("docker daemon");
|
|
8314
|
-
}
|
|
8315
|
-
isMissingVolumeInspectError(message2) {
|
|
8316
|
-
return message2.toLowerCase().includes("no such volume");
|
|
8317
|
-
}
|
|
8318
|
-
async startContainer(image, containerId) {
|
|
8319
|
-
const args = this.buildDockerArgs(image, containerId);
|
|
8320
|
-
try {
|
|
8321
|
-
await spawn3("docker", args);
|
|
8322
|
-
} catch (error) {
|
|
8323
|
-
const err = error;
|
|
8324
|
-
if (err.message?.includes("Cannot connect") || err.message?.includes("docker daemon") || err.stderr?.includes("Cannot connect")) {
|
|
8325
|
-
throw new DockerNotAvailableError();
|
|
8326
|
-
}
|
|
8327
|
-
throw new ContainerCreationError(
|
|
8328
|
-
this.getDockerErrorMessage(err),
|
|
8329
|
-
image,
|
|
8330
|
-
err
|
|
8331
|
-
);
|
|
8332
|
-
}
|
|
8816
|
+
const tail = [];
|
|
8817
|
+
for (const [path5, op] of state) {
|
|
8818
|
+
if (emittedRenameTargets.has(path5)) continue;
|
|
8819
|
+
if (keep(path5)) tail.push({ op, path: path5, timestamp: now });
|
|
8333
8820
|
}
|
|
8334
|
-
|
|
8821
|
+
tail.sort((a, b) => {
|
|
8822
|
+
if (a.path < b.path) return -1;
|
|
8823
|
+
if (a.path > b.path) return 1;
|
|
8824
|
+
return 0;
|
|
8825
|
+
});
|
|
8826
|
+
changes.push(...tail);
|
|
8827
|
+
return changes;
|
|
8828
|
+
}
|
|
8829
|
+
|
|
8830
|
+
// packages/context/src/lib/sandbox/strace/file-changes.ts
|
|
8831
|
+
var DEFAULT_TRACE_DIR = "/tmp/dat-trace";
|
|
8832
|
+
function stripStraceDiagnostics(stderr) {
|
|
8833
|
+
if (!stderr.includes("strace: ")) return stderr;
|
|
8834
|
+
return stderr.split("\n").filter((line) => !line.startsWith("strace: ")).join("\n");
|
|
8835
|
+
}
|
|
8836
|
+
function warnOnFileChangesError(error) {
|
|
8837
|
+
console.warn(
|
|
8838
|
+
"[withStraceFileChanges] onFileChanges threw on spawn; isolated",
|
|
8839
|
+
error
|
|
8840
|
+
);
|
|
8841
|
+
}
|
|
8842
|
+
async function withStraceFileChanges(sandbox, options) {
|
|
8843
|
+
const { include, exclude, onFileChanges } = options;
|
|
8844
|
+
const onError = options.onError ?? warnOnFileChangesError;
|
|
8845
|
+
const traceDir = options.traceDir ?? DEFAULT_TRACE_DIR;
|
|
8846
|
+
const innerExecute = sandbox.executeCommand.bind(sandbox);
|
|
8847
|
+
const innerReadFile = sandbox.readFile.bind(sandbox);
|
|
8848
|
+
const innerWriteFiles = sandbox.writeFiles.bind(sandbox);
|
|
8849
|
+
const removeTrace = (traceFile) => void innerExecute(`rm -f ${shellQuote(traceFile)}`).catch(() => {
|
|
8850
|
+
});
|
|
8851
|
+
const readChanges = async (traceFile) => {
|
|
8335
8852
|
try {
|
|
8336
|
-
await
|
|
8853
|
+
return parseStraceTrace(await innerReadFile(traceFile), {
|
|
8854
|
+
include,
|
|
8855
|
+
exclude,
|
|
8856
|
+
traceFile,
|
|
8857
|
+
traceDir
|
|
8858
|
+
});
|
|
8337
8859
|
} catch {
|
|
8860
|
+
return [];
|
|
8861
|
+
} finally {
|
|
8862
|
+
removeTrace(traceFile);
|
|
8338
8863
|
}
|
|
8339
|
-
}
|
|
8340
|
-
async
|
|
8864
|
+
};
|
|
8865
|
+
const collectSpawn = async (traceFile) => {
|
|
8866
|
+
const changes = await readChanges(traceFile);
|
|
8867
|
+
if (!changes.length || !onFileChanges) return;
|
|
8341
8868
|
try {
|
|
8342
|
-
|
|
8343
|
-
"docker",
|
|
8344
|
-
["exec", this.context.containerId, "sh", "-c", command],
|
|
8345
|
-
{ signal: options?.signal }
|
|
8346
|
-
);
|
|
8347
|
-
return {
|
|
8348
|
-
stdout: result.stdout,
|
|
8349
|
-
stderr: result.stderr,
|
|
8350
|
-
exitCode: 0
|
|
8351
|
-
};
|
|
8869
|
+
await onFileChanges(changes);
|
|
8352
8870
|
} catch (error) {
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8871
|
+
onError(error);
|
|
8872
|
+
}
|
|
8873
|
+
};
|
|
8874
|
+
const decorated = {
|
|
8875
|
+
async executeCommand(command, execOptions) {
|
|
8876
|
+
const traceFile = `${traceDir}/${randomUUID2()}.strace`;
|
|
8877
|
+
const sentinel = `__dat_trace_${randomUUID2()}__`;
|
|
8878
|
+
const wrapped = buildTracedCommand(
|
|
8879
|
+
command,
|
|
8880
|
+
traceFile,
|
|
8881
|
+
traceDir,
|
|
8882
|
+
sentinel
|
|
8883
|
+
);
|
|
8884
|
+
let raw;
|
|
8885
|
+
try {
|
|
8886
|
+
raw = await innerExecute(wrapped, execOptions);
|
|
8887
|
+
} catch (error) {
|
|
8888
|
+
removeTrace(traceFile);
|
|
8889
|
+
throw error;
|
|
8890
|
+
}
|
|
8891
|
+
const { stdout, trace } = splitTracedOutput(raw.stdout, sentinel);
|
|
8892
|
+
const result = {
|
|
8893
|
+
...raw,
|
|
8894
|
+
stdout,
|
|
8895
|
+
stderr: stripStraceDiagnostics(raw.stderr)
|
|
8358
8896
|
};
|
|
8897
|
+
if (execOptions?.signal?.aborted) return result;
|
|
8898
|
+
const changes = trace ? parseStraceTrace(trace, { include, exclude, traceFile, traceDir }) : [];
|
|
8899
|
+
if (changes.length) {
|
|
8900
|
+
useBashMeta()?.setHidden({ fileChanges: changes });
|
|
8901
|
+
await onFileChanges?.(changes);
|
|
8902
|
+
}
|
|
8903
|
+
return result;
|
|
8904
|
+
},
|
|
8905
|
+
readFile: innerReadFile,
|
|
8906
|
+
// The writeFile tool mutates the filesystem outside strace's view, so
|
|
8907
|
+
// observe it directly: synthesize a `write` change per file (under the
|
|
8908
|
+
// observation root) and run it through onFileChanges. A throw propagates to
|
|
8909
|
+
// the writeFile tool's execute (which rejects), the same gate as the bash
|
|
8910
|
+
// path — except there's no CommandResult here, so throw a plain Error to
|
|
8911
|
+
// reject; BashException.format() is meaningful only for bash commands.
|
|
8912
|
+
writeFiles: async (files) => {
|
|
8913
|
+
await innerWriteFiles(files);
|
|
8914
|
+
const now = Date.now();
|
|
8915
|
+
const changes = files.map((f) => posix2.normalize(f.path)).filter((path5) => matchesGlobs(path5, include, exclude)).map((path5) => ({ op: "write", path: path5, timestamp: now }));
|
|
8916
|
+
if (changes.length) await onFileChanges?.(changes);
|
|
8917
|
+
},
|
|
8918
|
+
dispose: async () => {
|
|
8919
|
+
await innerExecute(`rm -rf ${shellQuote(traceDir)}`).catch(() => {
|
|
8920
|
+
});
|
|
8921
|
+
await sandbox.dispose();
|
|
8922
|
+
},
|
|
8923
|
+
[Symbol.asyncDispose]() {
|
|
8924
|
+
return this.dispose();
|
|
8359
8925
|
}
|
|
8360
|
-
}
|
|
8361
|
-
|
|
8362
|
-
const
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
executeCommand: async (command, options) => this.exec(command, options),
|
|
8376
|
-
spawn: (command, options) => this.spawnProcess(command, options),
|
|
8377
|
-
readFile: async (path5) => {
|
|
8378
|
-
const result = await sandbox.executeCommand(`base64 "${path5}"`);
|
|
8379
|
-
if (result.exitCode !== 0) {
|
|
8380
|
-
throw new Error(`Failed to read file "${path5}": ${result.stderr}`);
|
|
8381
|
-
}
|
|
8382
|
-
return Buffer.from(result.stdout, "base64").toString("utf-8");
|
|
8383
|
-
},
|
|
8384
|
-
writeFiles: async (files) => {
|
|
8385
|
-
for (const file of files) {
|
|
8386
|
-
const dir = file.path.substring(0, file.path.lastIndexOf("/"));
|
|
8387
|
-
if (dir) {
|
|
8388
|
-
await sandbox.executeCommand(`mkdir -p "${dir}"`);
|
|
8389
|
-
}
|
|
8390
|
-
const base64Content = Buffer.from(file.content).toString("base64");
|
|
8391
|
-
const result = await sandbox.executeCommand(
|
|
8392
|
-
`echo "${base64Content}" | base64 -d > "${file.path}"`
|
|
8393
|
-
);
|
|
8394
|
-
if (result.exitCode !== 0) {
|
|
8395
|
-
throw new Error(
|
|
8396
|
-
`Failed to write file "${file.path}": ${result.stderr}`
|
|
8397
|
-
);
|
|
8398
|
-
}
|
|
8926
|
+
};
|
|
8927
|
+
if (sandbox.spawn) {
|
|
8928
|
+
const innerSpawn = sandbox.spawn.bind(sandbox);
|
|
8929
|
+
decorated.spawn = (command, spawnOptions) => {
|
|
8930
|
+
const traceFile = `${traceDir}/${randomUUID2()}.strace`;
|
|
8931
|
+
const child = innerSpawn(
|
|
8932
|
+
buildStraceCommand(command, traceFile, traceDir),
|
|
8933
|
+
spawnOptions
|
|
8934
|
+
);
|
|
8935
|
+
const exit = (async () => {
|
|
8936
|
+
try {
|
|
8937
|
+
return await child.exit;
|
|
8938
|
+
} finally {
|
|
8939
|
+
await collectSpawn(traceFile).catch(() => {
|
|
8940
|
+
});
|
|
8399
8941
|
}
|
|
8400
|
-
}
|
|
8401
|
-
|
|
8402
|
-
await this.stopContainer(containerId);
|
|
8403
|
-
await this.cleanupCreatedVolumes();
|
|
8404
|
-
}
|
|
8942
|
+
})();
|
|
8943
|
+
return { stdout: child.stdout, stderr: child.stderr, exit };
|
|
8405
8944
|
};
|
|
8406
|
-
return sandbox;
|
|
8407
|
-
}
|
|
8408
|
-
};
|
|
8409
|
-
function validateEnvKey2(key) {
|
|
8410
|
-
if (key.length === 0 || key.includes("=")) {
|
|
8411
|
-
throw new DockerSandboxError(`Invalid environment variable key: "${key}"`);
|
|
8412
8945
|
}
|
|
8946
|
+
return decorated;
|
|
8413
8947
|
}
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8948
|
+
|
|
8949
|
+
// packages/context/src/lib/sandbox/gcs.ts
|
|
8950
|
+
function gcs(options) {
|
|
8951
|
+
return {
|
|
8952
|
+
type: "bind",
|
|
8953
|
+
hostPath: options.hostPath,
|
|
8954
|
+
containerPath: options.mountPath,
|
|
8955
|
+
readOnly: options.readOnly ?? false
|
|
8956
|
+
};
|
|
8957
|
+
}
|
|
8958
|
+
|
|
8959
|
+
// packages/context/src/lib/sandbox/installers/bin.ts
|
|
8960
|
+
import { basename, posix as posix3 } from "node:path";
|
|
8961
|
+
var NOT_FOUND_EXIT = 11;
|
|
8962
|
+
var CHMOD_FAILED_EXIT = 12;
|
|
8963
|
+
var BinInstaller = class extends Installer {
|
|
8964
|
+
kind;
|
|
8965
|
+
binary;
|
|
8966
|
+
options;
|
|
8967
|
+
#name;
|
|
8968
|
+
#target;
|
|
8969
|
+
constructor(binary, options = {}) {
|
|
8970
|
+
super();
|
|
8971
|
+
this.binary = binary;
|
|
8972
|
+
this.options = options;
|
|
8973
|
+
this.#name = resolveName(binary, options);
|
|
8974
|
+
this.#target = options.target ?? posix3.join("/usr/local/bin", this.#name);
|
|
8975
|
+
this.kind = `bin:${this.#name}`;
|
|
8418
8976
|
}
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8977
|
+
async install(ctx) {
|
|
8978
|
+
const b = shellQuote(this.binary);
|
|
8979
|
+
const t = shellQuote(this.#target);
|
|
8980
|
+
const dir = shellQuote(posix3.dirname(this.#target));
|
|
8981
|
+
const result = await ctx.exec(
|
|
8982
|
+
`test -f ${b} || { echo "binary not found at ${this.binary}" >&2; exit ${NOT_FOUND_EXIT}; }; if ! test -x ${b}; then chmod +x ${b} || exit ${CHMOD_FAILED_EXIT}; fi; mkdir -p ${dir} && ln -sf ${b} ${t}`
|
|
8983
|
+
);
|
|
8984
|
+
if (result.exitCode === 0) return;
|
|
8985
|
+
throw new InstallError({
|
|
8986
|
+
target: this.#name,
|
|
8987
|
+
source: "bin",
|
|
8988
|
+
reason: explainFailure(result.exitCode, result.stderr, this.binary),
|
|
8989
|
+
containerId: ctx.containerId
|
|
8990
|
+
});
|
|
8424
8991
|
}
|
|
8425
|
-
|
|
8992
|
+
};
|
|
8993
|
+
function bin(binary, options) {
|
|
8994
|
+
return new BinInstaller(binary, options);
|
|
8426
8995
|
}
|
|
8427
|
-
function
|
|
8428
|
-
if (
|
|
8429
|
-
|
|
8430
|
-
throw new DockerSandboxError("docker exec child missing stdio streams");
|
|
8996
|
+
function explainFailure(exitCode, stderr, binary) {
|
|
8997
|
+
if (exitCode === NOT_FOUND_EXIT) {
|
|
8998
|
+
return `binary not found at ${binary}`;
|
|
8431
8999
|
}
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
if (abortSignal.aborted) onAbort();
|
|
8435
|
-
else abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
9000
|
+
if (exitCode === CHMOD_FAILED_EXIT && /read-only file system/i.test(stderr)) {
|
|
9001
|
+
return `${binary} is not executable and the bind mount is read-only \u2014 run \`chmod +x\` on the host, or mount with \`readOnly: false\`. (${stderr.trim()})`;
|
|
8436
9002
|
}
|
|
8437
|
-
return {
|
|
8438
|
-
stdout: Readable2.toWeb(child.stdout),
|
|
8439
|
-
stderr: Readable2.toWeb(child.stderr),
|
|
8440
|
-
exit: new Promise((resolve4, reject) => {
|
|
8441
|
-
const settle = () => {
|
|
8442
|
-
child.removeListener("exit", onExitEvent);
|
|
8443
|
-
child.removeListener("error", onError);
|
|
8444
|
-
if (abortSignal && onAbort) {
|
|
8445
|
-
abortSignal.removeEventListener("abort", onAbort);
|
|
8446
|
-
}
|
|
8447
|
-
};
|
|
8448
|
-
const onError = (err) => {
|
|
8449
|
-
settle();
|
|
8450
|
-
reject(err);
|
|
8451
|
-
};
|
|
8452
|
-
const onExitEvent = (code, exitSignal) => {
|
|
8453
|
-
settle();
|
|
8454
|
-
resolve4({ code, signal: exitSignal, success: code === 0 });
|
|
8455
|
-
};
|
|
8456
|
-
child.on("exit", onExitEvent);
|
|
8457
|
-
child.on("error", onError);
|
|
8458
|
-
})
|
|
8459
|
-
};
|
|
9003
|
+
return stderr || `bin installer failed with exit code ${exitCode}`;
|
|
8460
9004
|
}
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
return this.image;
|
|
9005
|
+
function resolveName(binary, options) {
|
|
9006
|
+
if (options.name) return options.name;
|
|
9007
|
+
const base = basename(binary);
|
|
9008
|
+
const dot = base.lastIndexOf(".");
|
|
9009
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
9010
|
+
}
|
|
9011
|
+
|
|
9012
|
+
// packages/context/src/lib/sandbox/installers/url-binary.ts
|
|
9013
|
+
var UrlBinaryInstaller = class extends Installer {
|
|
9014
|
+
kind;
|
|
9015
|
+
options;
|
|
9016
|
+
constructor(options) {
|
|
9017
|
+
super();
|
|
9018
|
+
this.options = options;
|
|
9019
|
+
this.kind = `url-binary:${options.name}`;
|
|
8477
9020
|
}
|
|
8478
|
-
async
|
|
8479
|
-
const
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
9021
|
+
async install(ctx) {
|
|
9022
|
+
const url = await resolveUrl(ctx, this.options);
|
|
9023
|
+
await downloadAndInstall(
|
|
9024
|
+
ctx,
|
|
9025
|
+
this.options.name,
|
|
9026
|
+
url,
|
|
9027
|
+
this.options.binaryPath
|
|
9028
|
+
);
|
|
8483
9029
|
}
|
|
8484
9030
|
};
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
9031
|
+
function urlBinary(options) {
|
|
9032
|
+
return new UrlBinaryInstaller(options);
|
|
9033
|
+
}
|
|
9034
|
+
async function resolveUrl(ctx, options) {
|
|
9035
|
+
if (typeof options.url === "string") return options.url;
|
|
9036
|
+
const arch = await ctx.arch();
|
|
9037
|
+
const archUrl = options.url[arch];
|
|
9038
|
+
if (!archUrl) {
|
|
9039
|
+
throw new InstallError({
|
|
9040
|
+
target: options.name,
|
|
9041
|
+
source: "url",
|
|
9042
|
+
reason: `No URL provided for architecture "${arch}". Available: ${Object.keys(options.url).join(", ")}`,
|
|
9043
|
+
containerId: ctx.containerId
|
|
8497
9044
|
});
|
|
8498
|
-
this.dockerfile = args.dockerfile;
|
|
8499
|
-
this.dockerContext = args.context ?? ".";
|
|
8500
|
-
this.showBuildLogs = args.showBuildLogs ?? false;
|
|
8501
|
-
this.imageTag = this.computeImageTag();
|
|
8502
|
-
}
|
|
8503
|
-
computeImageTag() {
|
|
8504
|
-
const content = this.isInlineDockerfile() ? this.dockerfile : readFileSync2(this.dockerfile, "utf-8");
|
|
8505
|
-
const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
8506
|
-
return `sandbox-${hash}`;
|
|
8507
|
-
}
|
|
8508
|
-
isInlineDockerfile() {
|
|
8509
|
-
return this.dockerfile.includes("\n");
|
|
8510
|
-
}
|
|
8511
|
-
async getImage() {
|
|
8512
|
-
const exists = await this.imageExists();
|
|
8513
|
-
if (!exists) {
|
|
8514
|
-
await this.buildImage();
|
|
8515
|
-
}
|
|
8516
|
-
return this.imageTag;
|
|
8517
9045
|
}
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
9046
|
+
return archUrl;
|
|
9047
|
+
}
|
|
9048
|
+
async function downloadAndInstall(ctx, name, url, binaryPath, source = "url") {
|
|
9049
|
+
await ctx.ensureTool("curl");
|
|
9050
|
+
const isTarGz = url.endsWith(".tar.gz") || url.endsWith(".tgz");
|
|
9051
|
+
const installCmd = isTarGz ? buildTarGzInstallCmd(name, url, binaryPath ?? name) : buildRawInstallCmd(name, url);
|
|
9052
|
+
const result = await ctx.exec(installCmd);
|
|
9053
|
+
if (result.exitCode !== 0) {
|
|
9054
|
+
throw new InstallError({
|
|
9055
|
+
target: name,
|
|
9056
|
+
source,
|
|
9057
|
+
url,
|
|
9058
|
+
reason: result.stderr,
|
|
9059
|
+
containerId: ctx.containerId
|
|
9060
|
+
});
|
|
8527
9061
|
}
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
9062
|
+
}
|
|
9063
|
+
function buildTarGzInstallCmd(name, url, binaryPathInArchive) {
|
|
9064
|
+
return `
|
|
9065
|
+
set -e
|
|
9066
|
+
NAME=${shellQuote(name)}
|
|
9067
|
+
URL=${shellQuote(url)}
|
|
9068
|
+
BIN_IN_ARCHIVE=${shellQuote(binaryPathInArchive)}
|
|
9069
|
+
TMPDIR=$(mktemp -d)
|
|
9070
|
+
cd "$TMPDIR"
|
|
9071
|
+
curl -fsSL "$URL" -o archive.tar.gz
|
|
9072
|
+
tar -xzf archive.tar.gz
|
|
9073
|
+
BINARY_FILE=$(find . -name "$BIN_IN_ARCHIVE" -o -name "$NAME" | head -1)
|
|
9074
|
+
if [ -z "$BINARY_FILE" ]; then
|
|
9075
|
+
echo "Binary not found in archive. Contents:" >&2
|
|
9076
|
+
find . -type f >&2
|
|
9077
|
+
exit 1
|
|
9078
|
+
fi
|
|
9079
|
+
chmod +x "$BINARY_FILE"
|
|
9080
|
+
mv "$BINARY_FILE" "/usr/local/bin/$NAME"
|
|
9081
|
+
cd /
|
|
9082
|
+
rm -rf "$TMPDIR"
|
|
9083
|
+
`;
|
|
9084
|
+
}
|
|
9085
|
+
function buildRawInstallCmd(name, url) {
|
|
9086
|
+
return `
|
|
9087
|
+
NAME=${shellQuote(name)}
|
|
9088
|
+
URL=${shellQuote(url)}
|
|
9089
|
+
curl -fsSL "$URL" -o "/usr/local/bin/$NAME"
|
|
9090
|
+
chmod +x "/usr/local/bin/$NAME"
|
|
9091
|
+
`;
|
|
9092
|
+
}
|
|
9093
|
+
|
|
9094
|
+
// packages/context/src/lib/sandbox/installers/github-release.ts
|
|
9095
|
+
var GithubReleaseInstaller = class extends Installer {
|
|
9096
|
+
kind;
|
|
9097
|
+
options;
|
|
9098
|
+
constructor(options) {
|
|
9099
|
+
super();
|
|
9100
|
+
this.options = options;
|
|
9101
|
+
this.kind = `github-release:${options.owner}/${options.repo}@${options.version}`;
|
|
8556
9102
|
}
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
(code) => code === 0 ? resolve4() : reject(
|
|
8569
|
-
new Error(
|
|
8570
|
-
`docker build exited with code ${code} (see build output above)`
|
|
8571
|
-
)
|
|
8572
|
-
)
|
|
8573
|
-
);
|
|
8574
|
-
});
|
|
9103
|
+
async install(ctx) {
|
|
9104
|
+
const arch = await ctx.arch();
|
|
9105
|
+
const assetName = this.options.asset(arch);
|
|
9106
|
+
const url = `https://github.com/${this.options.owner}/${this.options.repo}/releases/download/${this.options.version}/${assetName}`;
|
|
9107
|
+
await downloadAndInstall(
|
|
9108
|
+
ctx,
|
|
9109
|
+
this.options.name,
|
|
9110
|
+
url,
|
|
9111
|
+
this.options.binaryPath,
|
|
9112
|
+
"github-release"
|
|
9113
|
+
);
|
|
8575
9114
|
}
|
|
8576
9115
|
};
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
return "";
|
|
9116
|
+
function githubRelease(options) {
|
|
9117
|
+
return new GithubReleaseInstaller(options);
|
|
9118
|
+
}
|
|
9119
|
+
|
|
9120
|
+
// packages/context/src/lib/sandbox/installers/npm.ts
|
|
9121
|
+
var NODE_BINARIES = ["node", "npm"];
|
|
9122
|
+
var ENSURE_RUNTIME_HINT = "Pass `{ ensureRuntime: true }` to auto-install, or add it via `pkg([...])`.";
|
|
9123
|
+
var NpmInstaller = class extends Installer {
|
|
9124
|
+
kind;
|
|
9125
|
+
packageName;
|
|
9126
|
+
options;
|
|
9127
|
+
constructor(packageName, options = {}) {
|
|
9128
|
+
super();
|
|
9129
|
+
this.packageName = packageName;
|
|
9130
|
+
this.options = options;
|
|
9131
|
+
this.kind = `npm:${packageName}`;
|
|
8594
9132
|
}
|
|
8595
|
-
async
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
} catch (error) {
|
|
8607
|
-
const err = error;
|
|
8608
|
-
if (err.stderr?.includes("Cannot connect")) {
|
|
8609
|
-
throw new DockerNotAvailableError();
|
|
8610
|
-
}
|
|
8611
|
-
throw new ComposeStartError(this.composeFile, err.stderr || err.message);
|
|
9133
|
+
async install(ctx) {
|
|
9134
|
+
await ensureNodeRuntime(ctx, this.options.ensureRuntime ?? false);
|
|
9135
|
+
const spec = this.options.version ? `${this.packageName}@${this.options.version}` : this.packageName;
|
|
9136
|
+
const result = await ctx.exec(`npm install -g ${spec}`);
|
|
9137
|
+
if (result.exitCode !== 0) {
|
|
9138
|
+
throw new InstallError({
|
|
9139
|
+
target: this.packageName,
|
|
9140
|
+
source: "npm",
|
|
9141
|
+
reason: result.stderr,
|
|
9142
|
+
containerId: ctx.containerId
|
|
9143
|
+
});
|
|
8612
9144
|
}
|
|
8613
9145
|
}
|
|
8614
|
-
|
|
8615
|
-
|
|
9146
|
+
};
|
|
9147
|
+
function npm(packageName, options) {
|
|
9148
|
+
return new NpmInstaller(packageName, options);
|
|
9149
|
+
}
|
|
9150
|
+
async function ensureNodeRuntime(ctx, ensure) {
|
|
9151
|
+
if (ensure) {
|
|
9152
|
+
await ctx.ensureTool("node", "nodejs");
|
|
9153
|
+
await ctx.ensureTool("npm");
|
|
9154
|
+
return;
|
|
8616
9155
|
}
|
|
8617
|
-
|
|
9156
|
+
const check = await ctx.exec("which node && which npm");
|
|
9157
|
+
if (check.exitCode !== 0) {
|
|
9158
|
+
throw new MissingRuntimeError(
|
|
9159
|
+
"npm",
|
|
9160
|
+
NODE_BINARIES,
|
|
9161
|
+
ENSURE_RUNTIME_HINT,
|
|
9162
|
+
ctx.containerId
|
|
9163
|
+
);
|
|
8618
9164
|
}
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
"exec",
|
|
8630
|
-
"-T",
|
|
8631
|
-
this.service,
|
|
8632
|
-
"sh",
|
|
8633
|
-
"-c",
|
|
8634
|
-
command
|
|
8635
|
-
],
|
|
8636
|
-
{ signal: options?.signal }
|
|
8637
|
-
);
|
|
8638
|
-
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
8639
|
-
} catch (error) {
|
|
8640
|
-
const err = error;
|
|
8641
|
-
return {
|
|
8642
|
-
stdout: err.stdout || "",
|
|
8643
|
-
stderr: err.stderr || err.message || "",
|
|
8644
|
-
exitCode: err.exitCode ?? 1
|
|
8645
|
-
};
|
|
8646
|
-
}
|
|
9165
|
+
}
|
|
9166
|
+
|
|
9167
|
+
// packages/context/src/lib/sandbox/installers/package-manager.ts
|
|
9168
|
+
var PackageInstaller = class extends Installer {
|
|
9169
|
+
kind;
|
|
9170
|
+
packages;
|
|
9171
|
+
constructor(packages) {
|
|
9172
|
+
super();
|
|
9173
|
+
this.packages = packages;
|
|
9174
|
+
this.kind = packages.length === 0 ? "pkg:<empty>" : `pkg:${packages.join(",")}`;
|
|
8647
9175
|
}
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
"-f",
|
|
8652
|
-
this.composeFile,
|
|
8653
|
-
"-p",
|
|
8654
|
-
this.projectName,
|
|
8655
|
-
"exec",
|
|
8656
|
-
"-T",
|
|
8657
|
-
...buildDockerExecFlags(options),
|
|
8658
|
-
this.service,
|
|
8659
|
-
"sh",
|
|
8660
|
-
"-c",
|
|
8661
|
-
command
|
|
8662
|
-
]);
|
|
8663
|
-
return toSandboxProcess(child, options?.signal);
|
|
9176
|
+
async install(ctx) {
|
|
9177
|
+
if (this.packages.length === 0) return;
|
|
9178
|
+
await ctx.installPackages([...this.packages]);
|
|
8664
9179
|
}
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
9180
|
+
};
|
|
9181
|
+
function pkg(packages) {
|
|
9182
|
+
return new PackageInstaller(packages);
|
|
9183
|
+
}
|
|
9184
|
+
|
|
9185
|
+
// packages/context/src/lib/sandbox/installers/pip.ts
|
|
9186
|
+
var PYTHON_BINARIES = ["python3", "pip3"];
|
|
9187
|
+
var PipInstaller = class extends Installer {
|
|
9188
|
+
kind;
|
|
9189
|
+
packageName;
|
|
9190
|
+
options;
|
|
9191
|
+
constructor(packageName, options = {}) {
|
|
9192
|
+
super();
|
|
9193
|
+
this.packageName = packageName;
|
|
9194
|
+
this.options = options;
|
|
9195
|
+
this.kind = `pip:${packageName}`;
|
|
9196
|
+
}
|
|
9197
|
+
async install(ctx) {
|
|
9198
|
+
await ensurePythonRuntime(ctx, this.options.ensureRuntime ?? false);
|
|
9199
|
+
const spec = this.options.version ? `${this.packageName}${formatVersion(this.options.version)}` : this.packageName;
|
|
9200
|
+
const flags = this.options.breakSystemPackages ?? true ? "--break-system-packages" : "";
|
|
9201
|
+
const result = await ctx.exec(`pip3 install ${flags} ${spec}`.trim());
|
|
9202
|
+
if (result.exitCode !== 0) {
|
|
9203
|
+
throw new InstallError({
|
|
9204
|
+
target: this.packageName,
|
|
9205
|
+
source: "pypi",
|
|
9206
|
+
reason: result.stderr,
|
|
9207
|
+
containerId: ctx.containerId
|
|
9208
|
+
});
|
|
8676
9209
|
}
|
|
8677
9210
|
}
|
|
8678
9211
|
};
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
if (isComposeOptions(options)) {
|
|
8682
|
-
strategy = new ComposeStrategy({
|
|
8683
|
-
compose: options.compose,
|
|
8684
|
-
service: options.service,
|
|
8685
|
-
resources: options.resources
|
|
8686
|
-
});
|
|
8687
|
-
} else if (isDockerfileOptions(options)) {
|
|
8688
|
-
strategy = new DockerfileStrategy({
|
|
8689
|
-
dockerfile: options.dockerfile,
|
|
8690
|
-
context: options.context,
|
|
8691
|
-
showBuildLogs: options.showBuildLogs,
|
|
8692
|
-
volumes: options.volumes,
|
|
8693
|
-
resources: options.resources,
|
|
8694
|
-
env: options.env,
|
|
8695
|
-
name: options.name,
|
|
8696
|
-
command: options.command
|
|
8697
|
-
});
|
|
8698
|
-
} else {
|
|
8699
|
-
strategy = new RuntimeStrategy({
|
|
8700
|
-
image: options.image,
|
|
8701
|
-
installers: options.installers,
|
|
8702
|
-
volumes: options.volumes,
|
|
8703
|
-
resources: options.resources,
|
|
8704
|
-
env: options.env,
|
|
8705
|
-
name: options.name,
|
|
8706
|
-
command: options.command
|
|
8707
|
-
});
|
|
8708
|
-
}
|
|
8709
|
-
return strategy.create();
|
|
9212
|
+
function pip(packageName, options) {
|
|
9213
|
+
return new PipInstaller(packageName, options);
|
|
8710
9214
|
}
|
|
8711
|
-
async function
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
9215
|
+
async function ensurePythonRuntime(ctx, ensure) {
|
|
9216
|
+
if (ensure) {
|
|
9217
|
+
const pipPackage = ctx.packageManager === "apk" ? "py3-pip" : "python3-pip";
|
|
9218
|
+
await ctx.ensureTool("python3");
|
|
9219
|
+
await ctx.ensureTool("pip3", pipPackage);
|
|
9220
|
+
return;
|
|
9221
|
+
}
|
|
9222
|
+
const check = await ctx.exec("which python3 && which pip3");
|
|
9223
|
+
if (check.exitCode !== 0) {
|
|
9224
|
+
throw new MissingRuntimeError(
|
|
9225
|
+
"pip",
|
|
9226
|
+
PYTHON_BINARIES,
|
|
9227
|
+
"Pass `{ ensureRuntime: true }` to auto-install, or add via `pkg([...])` (Alpine: `python3 py3-pip`; Debian: `python3 python3-pip`).",
|
|
9228
|
+
ctx.containerId
|
|
9229
|
+
);
|
|
8717
9230
|
}
|
|
8718
9231
|
}
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
function gcs(options) {
|
|
8722
|
-
return {
|
|
8723
|
-
type: "bind",
|
|
8724
|
-
hostPath: options.hostPath,
|
|
8725
|
-
containerPath: options.mountPath,
|
|
8726
|
-
readOnly: options.readOnly ?? false
|
|
8727
|
-
};
|
|
9232
|
+
function formatVersion(version) {
|
|
9233
|
+
return /^[<>=!~]/.test(version) ? version : `==${version}`;
|
|
8728
9234
|
}
|
|
8729
9235
|
|
|
8730
9236
|
// packages/context/src/lib/sandbox/subcommand.ts
|
|
@@ -8832,6 +9338,9 @@ async function createVirtualSandbox(options) {
|
|
|
8832
9338
|
}
|
|
8833
9339
|
},
|
|
8834
9340
|
async dispose() {
|
|
9341
|
+
},
|
|
9342
|
+
[Symbol.asyncDispose]() {
|
|
9343
|
+
return this.dispose();
|
|
8835
9344
|
}
|
|
8836
9345
|
};
|
|
8837
9346
|
}
|
|
@@ -8896,11 +9405,14 @@ ${lines.join("\n")}`;
|
|
|
8896
9405
|
}
|
|
8897
9406
|
function skillsReminder(skillsOrClassifier, options) {
|
|
8898
9407
|
const classifier = Array.isArray(skillsOrClassifier) ? new BM25Classifier(skillsOrClassifier) : skillsOrClassifier;
|
|
8899
|
-
return reminder(
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
9408
|
+
return reminder(
|
|
9409
|
+
(ctx) => {
|
|
9410
|
+
const matches = classifier.match(ctx.content, options);
|
|
9411
|
+
if (matches.length === 0) return "";
|
|
9412
|
+
return formatSkillReminder(matches);
|
|
9413
|
+
},
|
|
9414
|
+
{ target: "user" }
|
|
9415
|
+
);
|
|
8904
9416
|
}
|
|
8905
9417
|
|
|
8906
9418
|
// packages/context/src/lib/soul/protocol.md
|
|
@@ -12030,6 +12542,13 @@ export {
|
|
|
12030
12542
|
AgentOsCreationError,
|
|
12031
12543
|
AgentOsNotAvailableError,
|
|
12032
12544
|
AgentOsSandboxError,
|
|
12545
|
+
AppleContainerCreationError,
|
|
12546
|
+
AppleContainerImageBuildError,
|
|
12547
|
+
AppleContainerSandboxError,
|
|
12548
|
+
AppleContainerVolumeCreateError,
|
|
12549
|
+
AppleContainerVolumeInspectError,
|
|
12550
|
+
AppleContainerVolumePathError,
|
|
12551
|
+
AppleContainerVolumeRemoveError,
|
|
12033
12552
|
AsyncResolver,
|
|
12034
12553
|
BM25Classifier,
|
|
12035
12554
|
BashException,
|
|
@@ -12037,6 +12556,10 @@ export {
|
|
|
12037
12556
|
ComposeStartError,
|
|
12038
12557
|
ComposeStrategy,
|
|
12039
12558
|
ContainerCreationError,
|
|
12559
|
+
ContainerSandboxError,
|
|
12560
|
+
ContainerSandboxStrategy,
|
|
12561
|
+
ContainerServiceNotRunningError,
|
|
12562
|
+
ContainerfileStrategy,
|
|
12040
12563
|
ContextEngine,
|
|
12041
12564
|
ContextRenderer,
|
|
12042
12565
|
ContextStore,
|
|
@@ -12048,9 +12571,7 @@ export {
|
|
|
12048
12571
|
DaytonaSandboxError,
|
|
12049
12572
|
DockerNotAvailableError,
|
|
12050
12573
|
DockerSandboxError,
|
|
12051
|
-
DockerSandboxStrategy,
|
|
12052
12574
|
DockerfileBuildError,
|
|
12053
|
-
DockerfileStrategy,
|
|
12054
12575
|
FragmentLoaderResolver,
|
|
12055
12576
|
FunctionResolver,
|
|
12056
12577
|
GeneratorResolver,
|
|
@@ -12076,7 +12597,6 @@ export {
|
|
|
12076
12597
|
SqlServerContextStore,
|
|
12077
12598
|
SqliteContextStore,
|
|
12078
12599
|
SqliteStreamStore,
|
|
12079
|
-
StraceUnavailableError,
|
|
12080
12600
|
StreamManager,
|
|
12081
12601
|
StreamStore,
|
|
12082
12602
|
TitleGenerator,
|
|
@@ -12102,6 +12622,7 @@ export {
|
|
|
12102
12622
|
applyPartReminder,
|
|
12103
12623
|
applyReminderToMessage,
|
|
12104
12624
|
applyRemindersToToolOutput,
|
|
12625
|
+
applyUserRemindersToMessage,
|
|
12105
12626
|
asStaticWordPartText,
|
|
12106
12627
|
asStaticWordText,
|
|
12107
12628
|
assertCountSpec,
|
|
@@ -12120,6 +12641,7 @@ export {
|
|
|
12120
12641
|
correction,
|
|
12121
12642
|
createAdaptivePollingState,
|
|
12122
12643
|
createAgentOsSandbox,
|
|
12644
|
+
createAppleContainerSandbox,
|
|
12123
12645
|
createBashTool,
|
|
12124
12646
|
createBinaryBridges,
|
|
12125
12647
|
createDaytonaSandbox,
|
|
@@ -12135,6 +12657,8 @@ export {
|
|
|
12135
12657
|
defineSubcommandGroup,
|
|
12136
12658
|
discoverSkillMappings,
|
|
12137
12659
|
discoverSkillsInDirectory,
|
|
12660
|
+
dockerEngine,
|
|
12661
|
+
downloadAndInstall,
|
|
12138
12662
|
elapsedExceeds,
|
|
12139
12663
|
encodeSerializedValue,
|
|
12140
12664
|
errorRecoveryGuardrail,
|
|
@@ -12153,6 +12677,7 @@ export {
|
|
|
12153
12677
|
getFragmentData,
|
|
12154
12678
|
getLocaleFromMessage,
|
|
12155
12679
|
getModelsRegistry,
|
|
12680
|
+
getReminderOnceIds,
|
|
12156
12681
|
getReminderRanges,
|
|
12157
12682
|
getSeason,
|
|
12158
12683
|
githubRelease,
|
|
@@ -12161,6 +12686,7 @@ export {
|
|
|
12161
12686
|
hint,
|
|
12162
12687
|
hourChanged,
|
|
12163
12688
|
identity,
|
|
12689
|
+
isAppleContainerfileOptions,
|
|
12164
12690
|
isComposeOptions,
|
|
12165
12691
|
isConditionalReminder,
|
|
12166
12692
|
isDebianBased,
|
|
@@ -12188,7 +12714,6 @@ export {
|
|
|
12188
12714
|
once,
|
|
12189
12715
|
or,
|
|
12190
12716
|
parseFrontmatter,
|
|
12191
|
-
parseStraceTrace,
|
|
12192
12717
|
pass,
|
|
12193
12718
|
persistedWriter,
|
|
12194
12719
|
persona,
|
|
@@ -12206,7 +12731,6 @@ export {
|
|
|
12206
12731
|
resetAdaptivePolling,
|
|
12207
12732
|
resolveReminder,
|
|
12208
12733
|
resolveReminderAsync,
|
|
12209
|
-
resolveReminderText,
|
|
12210
12734
|
resolveTz,
|
|
12211
12735
|
role,
|
|
12212
12736
|
runGuardrailChain,
|
|
@@ -12215,7 +12739,6 @@ export {
|
|
|
12215
12739
|
seasonChanged,
|
|
12216
12740
|
seasonReminder,
|
|
12217
12741
|
selfCritique,
|
|
12218
|
-
selfTestStrace,
|
|
12219
12742
|
shellQuote,
|
|
12220
12743
|
skills,
|
|
12221
12744
|
skillsReminder,
|
|
@@ -12233,21 +12756,23 @@ export {
|
|
|
12233
12756
|
timeReminder,
|
|
12234
12757
|
toFragment,
|
|
12235
12758
|
toMessageFragment,
|
|
12759
|
+
toToolReminderModelOutput,
|
|
12236
12760
|
toolCall,
|
|
12237
12761
|
toolCallCount,
|
|
12238
12762
|
toolCalled,
|
|
12239
12763
|
toolFailed,
|
|
12240
|
-
traceFileChanges,
|
|
12241
12764
|
uploadSkills,
|
|
12242
12765
|
urlBinary,
|
|
12243
12766
|
usageExceeds,
|
|
12244
12767
|
useAgentOsSandbox,
|
|
12768
|
+
useAppleContainerSandbox,
|
|
12245
12769
|
useBashMeta,
|
|
12246
12770
|
useSandbox,
|
|
12247
12771
|
user,
|
|
12248
12772
|
visualizeGraph,
|
|
12249
12773
|
weekChanged,
|
|
12250
12774
|
withAbortSignal,
|
|
12775
|
+
withStraceFileChanges,
|
|
12251
12776
|
withinLastN,
|
|
12252
12777
|
workflow,
|
|
12253
12778
|
yearChanged,
|