@deepagents/context 3.0.1 → 3.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 +61 -4
- package/dist/browser.js +58 -90
- package/dist/browser.js.map +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1637 -1214
- package/dist/index.js.map +4 -4
- package/dist/lib/agent.d.ts.map +1 -1
- package/dist/lib/chain-summary.d.ts +1 -0
- package/dist/lib/chain-summary.d.ts.map +1 -1
- package/dist/lib/chat.d.ts.map +1 -1
- package/dist/lib/engine.d.ts +51 -1
- package/dist/lib/engine.d.ts.map +1 -1
- package/dist/lib/fragments/message/user.d.ts +53 -16
- package/dist/lib/fragments/message/user.d.ts.map +1 -1
- package/dist/lib/fragments/reminders/once.d.ts +22 -0
- package/dist/lib/fragments/reminders/once.d.ts.map +1 -0
- package/dist/lib/fragments/reminders/turn-predicates.d.ts +1 -1
- package/dist/lib/fragments/reminders/turn-predicates.d.ts.map +1 -1
- package/dist/lib/sandbox/bash-tool.d.ts +28 -4
- package/dist/lib/sandbox/bash-tool.d.ts.map +1 -1
- package/dist/lib/sandbox/docker-sandbox.d.ts +14 -0
- package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/file-changes.d.ts +83 -0
- package/dist/lib/sandbox/file-changes.d.ts.map +1 -0
- package/dist/lib/sandbox/index.d.ts +1 -1
- package/dist/lib/sandbox/index.d.ts.map +1 -1
- package/dist/lib/sandbox/types.d.ts +0 -8
- package/dist/lib/sandbox/types.d.ts.map +1 -1
- package/dist/lib/save/reminder-eval.d.ts +19 -0
- package/dist/lib/save/reminder-eval.d.ts.map +1 -0
- package/dist/lib/save/save-pipeline.d.ts +8 -2
- package/dist/lib/save/save-pipeline.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/lib/sandbox/file-events.d.ts +0 -42
- package/dist/lib/sandbox/file-events.d.ts.map +0 -1
- package/dist/lib/save/reminder-target-handler.d.ts +0 -36
- package/dist/lib/save/reminder-target-handler.d.ts.map +0 -1
- package/dist/lib/save/tool-output-target-handler.d.ts +0 -7
- package/dist/lib/save/tool-output-target-handler.d.ts.map +0 -1
- package/dist/lib/save/user-target-handler.d.ts +0 -7
- package/dist/lib/save/user-target-handler.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1038,10 +1038,10 @@ ${rendered.split("\n").slice(1).join("\n")}`;
|
|
|
1038
1038
|
return `${this.#pad(depth)}-`;
|
|
1039
1039
|
}
|
|
1040
1040
|
const lines = entries.split("\n");
|
|
1041
|
-
const
|
|
1041
|
+
const first2 = lines[0].trimStart();
|
|
1042
1042
|
const rest = lines.slice(1).join("\n");
|
|
1043
|
-
return rest ? `${this.#pad(depth)}- ${
|
|
1044
|
-
${rest}` : `${this.#pad(depth)}- ${
|
|
1043
|
+
return rest ? `${this.#pad(depth)}- ${first2}
|
|
1044
|
+
${rest}` : `${this.#pad(depth)}- ${first2}`;
|
|
1045
1045
|
}
|
|
1046
1046
|
return `${this.#pad(depth)}- ${this.#formatValue(item)}`;
|
|
1047
1047
|
}
|
|
@@ -1168,12 +1168,10 @@ function getReminderMetadataRecords(metadata) {
|
|
|
1168
1168
|
(item) => isRecord(item) && typeof item.partIndex === "number" && typeof item.start === "number" && typeof item.end === "number"
|
|
1169
1169
|
);
|
|
1170
1170
|
}
|
|
1171
|
-
function reminderTargetOf(record) {
|
|
1172
|
-
return record.target === "tool-output" ? "tool-output" : "user";
|
|
1173
|
-
}
|
|
1174
1171
|
function normalizeReminderTarget(target) {
|
|
1175
1172
|
if (target === void 0 || target === "user") return "user";
|
|
1176
1173
|
if (target === "tool-output") return "tool-output";
|
|
1174
|
+
if (target === "steer") return "steer";
|
|
1177
1175
|
throw new Error(`Unsupported reminder target: ${String(target)}`);
|
|
1178
1176
|
}
|
|
1179
1177
|
function isConditionalReminderOptions(options) {
|
|
@@ -1204,7 +1202,7 @@ function isOutputAvailableToolPart(part) {
|
|
|
1204
1202
|
return isStaticToolUIPart(part) && part.state === "output-available";
|
|
1205
1203
|
}
|
|
1206
1204
|
function isToolOutputReminderEnvelope(value) {
|
|
1207
|
-
return isRecord(value) && typeof value.systemReminder === "string";
|
|
1205
|
+
return isRecord(value) && "result" in value && typeof value.systemReminder === "string" && value.systemReminder.startsWith(SYSTEM_REMINDER_OPEN_TAG);
|
|
1208
1206
|
}
|
|
1209
1207
|
function stripTextByRanges(text, ranges) {
|
|
1210
1208
|
if (ranges.length === 0) {
|
|
@@ -1233,43 +1231,22 @@ function stripTextByRanges(text, ranges) {
|
|
|
1233
1231
|
return output.trimEnd();
|
|
1234
1232
|
}
|
|
1235
1233
|
function stripReminders(message2) {
|
|
1234
|
+
if (isSyntheticSteerMessage(message2)) {
|
|
1235
|
+
return stripSyntheticSteerMessage(message2);
|
|
1236
|
+
}
|
|
1236
1237
|
const reminderRecords = getReminderMetadataRecords(
|
|
1237
1238
|
isRecord(message2.metadata) ? message2.metadata : void 0
|
|
1238
1239
|
);
|
|
1239
1240
|
const rangesByPartIndex = /* @__PURE__ */ new Map();
|
|
1240
|
-
const toolRemindersByPartIndex = /* @__PURE__ */ new Map();
|
|
1241
1241
|
for (const range of reminderRecords) {
|
|
1242
|
-
if (reminderTargetOf(range) === "tool-output") {
|
|
1243
|
-
const records = toolRemindersByPartIndex.get(range.partIndex) ?? [];
|
|
1244
|
-
records.push(range);
|
|
1245
|
-
toolRemindersByPartIndex.set(range.partIndex, records);
|
|
1246
|
-
continue;
|
|
1247
|
-
}
|
|
1248
1242
|
const partRanges = rangesByPartIndex.get(range.partIndex) ?? [];
|
|
1249
1243
|
partRanges.push({ start: range.start, end: range.end });
|
|
1250
1244
|
rangesByPartIndex.set(range.partIndex, partRanges);
|
|
1251
1245
|
}
|
|
1252
1246
|
const strippedParts = message2.parts.flatMap((part, partIndex) => {
|
|
1253
1247
|
const clonedPart = { ...part };
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
if (typeof clonedPart.output === "string") {
|
|
1257
|
-
return [
|
|
1258
|
-
{
|
|
1259
|
-
...clonedPart,
|
|
1260
|
-
output: stripTextByRanges(
|
|
1261
|
-
clonedPart.output,
|
|
1262
|
-
toolReminderRecords.map((record) => ({
|
|
1263
|
-
start: record.start,
|
|
1264
|
-
end: record.end
|
|
1265
|
-
}))
|
|
1266
|
-
)
|
|
1267
|
-
}
|
|
1268
|
-
];
|
|
1269
|
-
}
|
|
1270
|
-
if (isToolOutputReminderEnvelope(clonedPart.output)) {
|
|
1271
|
-
return [{ ...clonedPart, output: clonedPart.output.result }];
|
|
1272
|
-
}
|
|
1248
|
+
if (isOutputAvailableToolPart(clonedPart) && isToolOutputReminderEnvelope(clonedPart.output)) {
|
|
1249
|
+
return [{ ...clonedPart, output: clonedPart.output.result }];
|
|
1273
1250
|
}
|
|
1274
1251
|
const ranges = rangesByPartIndex.get(partIndex);
|
|
1275
1252
|
if (clonedPart.type !== "text" || ranges === void 0) {
|
|
@@ -1409,64 +1386,12 @@ function applyReminderToMessage(message2, item, ctx) {
|
|
|
1409
1386
|
}
|
|
1410
1387
|
return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
|
|
1411
1388
|
}
|
|
1412
|
-
function
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
if (match) return null;
|
|
1418
|
-
match = { partIndex, part };
|
|
1419
|
-
}
|
|
1420
|
-
return match;
|
|
1421
|
-
}
|
|
1422
|
-
function applyToolOutputRemindersToMessage(message2, reminders) {
|
|
1423
|
-
if (reminders.length === 0) return [];
|
|
1424
|
-
const target = findSingleOutputAvailableToolPart(message2);
|
|
1425
|
-
if (!target) return [];
|
|
1426
|
-
for (const reminder2 of reminders) {
|
|
1427
|
-
if (reminder2.metadata) {
|
|
1428
|
-
mergeMessageMetadata(message2, reminder2.metadata);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
const added = [];
|
|
1432
|
-
if (typeof target.part.output === "string") {
|
|
1433
|
-
let output = target.part.output;
|
|
1434
|
-
for (const reminder2 of reminders) {
|
|
1435
|
-
const reminderText = formatTaggedReminder(reminder2.text);
|
|
1436
|
-
const start = output.length;
|
|
1437
|
-
output = `${output}${reminderText}`;
|
|
1438
|
-
added.push({
|
|
1439
|
-
id: generateId2(),
|
|
1440
|
-
text: reminder2.text,
|
|
1441
|
-
target: "tool-output",
|
|
1442
|
-
partIndex: target.partIndex,
|
|
1443
|
-
start,
|
|
1444
|
-
end: start + reminderText.length,
|
|
1445
|
-
mode: "tool-output"
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
|
-
message2.parts[target.partIndex] = {
|
|
1449
|
-
...target.part,
|
|
1450
|
-
output
|
|
1451
|
-
};
|
|
1452
|
-
return added;
|
|
1453
|
-
}
|
|
1454
|
-
message2.parts[target.partIndex] = {
|
|
1455
|
-
...target.part,
|
|
1456
|
-
output: {
|
|
1457
|
-
result: target.part.output,
|
|
1458
|
-
systemReminder: reminders.map((reminder2) => reminder2.text).join("\n")
|
|
1459
|
-
}
|
|
1389
|
+
function applyRemindersToToolOutput(output, texts) {
|
|
1390
|
+
if (texts.length === 0) return output;
|
|
1391
|
+
return {
|
|
1392
|
+
result: output === void 0 ? null : output,
|
|
1393
|
+
systemReminder: formatTaggedReminder(texts.join("\n"))
|
|
1460
1394
|
};
|
|
1461
|
-
return reminders.map((reminder2) => ({
|
|
1462
|
-
id: generateId2(),
|
|
1463
|
-
text: reminder2.text,
|
|
1464
|
-
target: "tool-output",
|
|
1465
|
-
partIndex: target.partIndex,
|
|
1466
|
-
start: 0,
|
|
1467
|
-
end: 0,
|
|
1468
|
-
mode: "tool-output"
|
|
1469
|
-
}));
|
|
1470
1395
|
}
|
|
1471
1396
|
function mergeReminderMetadata(message2, addedReminders) {
|
|
1472
1397
|
if (addedReminders.length === 0) return;
|
|
@@ -1497,7 +1422,7 @@ function reminder(textOrFragment, options) {
|
|
|
1497
1422
|
};
|
|
1498
1423
|
}
|
|
1499
1424
|
if (target !== "user") {
|
|
1500
|
-
throw new Error(
|
|
1425
|
+
throw new Error(`Reminder target "${target}" requires a when predicate`);
|
|
1501
1426
|
}
|
|
1502
1427
|
const text = normalizeImmediateReminderText(textOrFragment);
|
|
1503
1428
|
if (typeof text === "string") {
|
|
@@ -1541,6 +1466,48 @@ function user(content, ...reminders) {
|
|
|
1541
1466
|
}
|
|
1542
1467
|
};
|
|
1543
1468
|
}
|
|
1469
|
+
function synthesizeSteerUserMessage(text, firedAt, onceIds = []) {
|
|
1470
|
+
const texts = Array.isArray(text) ? text : [text];
|
|
1471
|
+
for (const value of texts) assertReminderText(value);
|
|
1472
|
+
return {
|
|
1473
|
+
id: generateId2(),
|
|
1474
|
+
role: "user",
|
|
1475
|
+
parts: texts.map((value) => ({
|
|
1476
|
+
type: "text",
|
|
1477
|
+
text: formatTaggedReminder(value)
|
|
1478
|
+
})),
|
|
1479
|
+
metadata: {
|
|
1480
|
+
synthetic: {
|
|
1481
|
+
source: "steer-reminder",
|
|
1482
|
+
firedAt,
|
|
1483
|
+
...onceIds.length > 0 ? { onceIds } : {}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
function isSyntheticSteerMessage(message2) {
|
|
1489
|
+
const meta = message2.metadata;
|
|
1490
|
+
if (!isRecord(meta)) return false;
|
|
1491
|
+
const synthetic = meta.synthetic;
|
|
1492
|
+
if (!isRecord(synthetic)) return false;
|
|
1493
|
+
return synthetic.source === "steer-reminder";
|
|
1494
|
+
}
|
|
1495
|
+
function stripSyntheticSteerMessage(message2) {
|
|
1496
|
+
const next = {
|
|
1497
|
+
...message2,
|
|
1498
|
+
parts: message2.parts.filter((part) => part.type !== "text")
|
|
1499
|
+
};
|
|
1500
|
+
if (isRecord(message2.metadata)) {
|
|
1501
|
+
const metadata = { ...message2.metadata };
|
|
1502
|
+
delete metadata.synthetic;
|
|
1503
|
+
if (Object.keys(metadata).length > 0) {
|
|
1504
|
+
next.metadata = metadata;
|
|
1505
|
+
} else {
|
|
1506
|
+
delete next.metadata;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
return next;
|
|
1510
|
+
}
|
|
1544
1511
|
|
|
1545
1512
|
// packages/context/src/lib/guardrail.ts
|
|
1546
1513
|
function pass(part) {
|
|
@@ -1607,7 +1574,10 @@ var Agent = class _Agent {
|
|
|
1607
1574
|
sandbox;
|
|
1608
1575
|
constructor(options) {
|
|
1609
1576
|
this.#options = options;
|
|
1610
|
-
this.tools =
|
|
1577
|
+
this.tools = wrapToolsWithOutputReminders(
|
|
1578
|
+
{ ...options.sandbox.tools, ...options.tools || {} },
|
|
1579
|
+
options.context
|
|
1580
|
+
);
|
|
1611
1581
|
this.context = options.context;
|
|
1612
1582
|
this.model = options.model;
|
|
1613
1583
|
this.sandbox = options.sandbox;
|
|
@@ -1714,6 +1684,7 @@ var Agent = class _Agent {
|
|
|
1714
1684
|
config?.abortSignal
|
|
1715
1685
|
),
|
|
1716
1686
|
stopWhen: stepCountIs(200),
|
|
1687
|
+
prepareStep: context.createSteerPrepareStep(),
|
|
1717
1688
|
experimental_transform: config?.transform ?? smoothStream(),
|
|
1718
1689
|
tools: this.tools,
|
|
1719
1690
|
experimental_context: contextVariables,
|
|
@@ -1750,9 +1721,14 @@ var Agent = class _Agent {
|
|
|
1750
1721
|
generateId: assistantMsgId ? () => assistantMsgId : generateId3,
|
|
1751
1722
|
onStepFinish: async ({ responseMessage }) => {
|
|
1752
1723
|
if (!stepSaved) return;
|
|
1753
|
-
const
|
|
1754
|
-
|
|
1755
|
-
|
|
1724
|
+
const head = await context.headMessage();
|
|
1725
|
+
if (head?.name === "assistant") {
|
|
1726
|
+
await context.writeAssistantSegment(responseMessage);
|
|
1727
|
+
} else {
|
|
1728
|
+
const message2 = assistantMsgId ? { ...responseMessage, id: assistantMsgId } : responseMessage;
|
|
1729
|
+
context.set(assistant(message2));
|
|
1730
|
+
await context.save({ branch: false });
|
|
1731
|
+
}
|
|
1756
1732
|
stepSaved.resolve();
|
|
1757
1733
|
stepSaved = null;
|
|
1758
1734
|
},
|
|
@@ -1959,6 +1935,29 @@ ${details}
|
|
|
1959
1935
|
});
|
|
1960
1936
|
}
|
|
1961
1937
|
};
|
|
1938
|
+
function wrapToolsWithOutputReminders(tools, context) {
|
|
1939
|
+
if (!context) return tools;
|
|
1940
|
+
const wrapped = {};
|
|
1941
|
+
for (const [name, toolDef] of Object.entries(tools)) {
|
|
1942
|
+
const execute = toolDef.execute;
|
|
1943
|
+
if (typeof execute !== "function") {
|
|
1944
|
+
wrapped[name] = toolDef;
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
wrapped[name] = {
|
|
1948
|
+
...toolDef,
|
|
1949
|
+
execute: async (input, options) => {
|
|
1950
|
+
const result = await execute.call(toolDef, input, options);
|
|
1951
|
+
if (isAsyncIterable(result)) return result;
|
|
1952
|
+
return context.applyToolOutputReminders(result);
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
return wrapped;
|
|
1957
|
+
}
|
|
1958
|
+
function isAsyncIterable(value) {
|
|
1959
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
1960
|
+
}
|
|
1962
1961
|
function agent(options) {
|
|
1963
1962
|
return new Agent(options);
|
|
1964
1963
|
}
|
|
@@ -2062,6 +2061,7 @@ import { z as z2 } from "zod";
|
|
|
2062
2061
|
|
|
2063
2062
|
// packages/context/src/lib/engine.ts
|
|
2064
2063
|
import {
|
|
2064
|
+
convertToModelMessages as convertToModelMessages2,
|
|
2065
2065
|
generateId as generateId4,
|
|
2066
2066
|
validateUIMessages
|
|
2067
2067
|
} from "ai";
|
|
@@ -2097,30 +2097,41 @@ var ChainSummaryBuilder = class {
|
|
|
2097
2097
|
#lastMessage;
|
|
2098
2098
|
#lastAssistantMessage;
|
|
2099
2099
|
#lastAssistantMessages = [];
|
|
2100
|
+
#firedOnceIds = /* @__PURE__ */ new Set();
|
|
2100
2101
|
ingestStored(msg) {
|
|
2101
2102
|
this.#messageCount++;
|
|
2102
2103
|
if (msg.name === "assistant") {
|
|
2103
|
-
const
|
|
2104
|
+
const message3 = requireUIMessage(
|
|
2104
2105
|
msg.data,
|
|
2105
2106
|
`Stored assistant message "${msg.id}"`
|
|
2106
2107
|
);
|
|
2107
|
-
this.#lastAssistantMessage =
|
|
2108
|
-
this.#lastAssistantMessages.push(
|
|
2108
|
+
this.#lastAssistantMessage = message3;
|
|
2109
|
+
this.#lastAssistantMessages.push(message3);
|
|
2109
2110
|
return;
|
|
2110
2111
|
}
|
|
2111
2112
|
if (msg.name !== "user") {
|
|
2112
2113
|
return;
|
|
2113
2114
|
}
|
|
2114
|
-
|
|
2115
|
-
this.#lastMessageAt = msg.createdAt;
|
|
2116
|
-
this.#lastMessage = requireUIMessage(
|
|
2115
|
+
const message2 = requireUIMessage(
|
|
2117
2116
|
msg.data,
|
|
2118
2117
|
`Stored user message "${msg.id}"`
|
|
2119
2118
|
);
|
|
2119
|
+
if (isSyntheticSteerMessage(message2)) {
|
|
2120
|
+
for (const id of message2.metadata.synthetic.onceIds ?? []) {
|
|
2121
|
+
this.#firedOnceIds.add(id);
|
|
2122
|
+
}
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
this.#turn++;
|
|
2126
|
+
this.#lastMessageAt = msg.createdAt;
|
|
2127
|
+
this.#lastMessage = message2;
|
|
2120
2128
|
}
|
|
2121
2129
|
ingestPending(fragment2) {
|
|
2122
2130
|
this.#messageCount++;
|
|
2123
|
-
if (fragment2.name
|
|
2131
|
+
if (fragment2.name !== "user") return;
|
|
2132
|
+
const encoded = fragment2.codec?.encode();
|
|
2133
|
+
if (encoded && isSyntheticSteerMessage(encoded)) return;
|
|
2134
|
+
this.#turn++;
|
|
2124
2135
|
}
|
|
2125
2136
|
build() {
|
|
2126
2137
|
return {
|
|
@@ -2129,7 +2140,8 @@ var ChainSummaryBuilder = class {
|
|
|
2129
2140
|
lastMessageAt: this.#lastMessageAt,
|
|
2130
2141
|
lastMessage: this.#lastMessage,
|
|
2131
2142
|
lastAssistantMessage: this.#lastAssistantMessage,
|
|
2132
|
-
lastAssistantMessages: this.#lastAssistantMessages
|
|
2143
|
+
lastAssistantMessages: this.#lastAssistantMessages,
|
|
2144
|
+
firedOnceIds: this.#firedOnceIds
|
|
2133
2145
|
};
|
|
2134
2146
|
}
|
|
2135
2147
|
};
|
|
@@ -2338,7 +2350,7 @@ var GeneratorResolver = class {
|
|
|
2338
2350
|
async resolve(value, ctx) {
|
|
2339
2351
|
const iterable = value(ctx);
|
|
2340
2352
|
const collected = [];
|
|
2341
|
-
if (
|
|
2353
|
+
if (isAsyncIterable2(iterable)) {
|
|
2342
2354
|
for await (const chunk of iterable) {
|
|
2343
2355
|
pushLimited(collected, chunk, this.#maxItems, this.name);
|
|
2344
2356
|
}
|
|
@@ -2350,7 +2362,7 @@ var GeneratorResolver = class {
|
|
|
2350
2362
|
return collected;
|
|
2351
2363
|
}
|
|
2352
2364
|
};
|
|
2353
|
-
function
|
|
2365
|
+
function isAsyncIterable2(value) {
|
|
2354
2366
|
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
2355
2367
|
}
|
|
2356
2368
|
function pushLimited(collected, chunk, maxItems, resolverName) {
|
|
@@ -2388,7 +2400,7 @@ var IterableResolver = class {
|
|
|
2388
2400
|
async resolve(value, _ctx) {
|
|
2389
2401
|
const iterable = value;
|
|
2390
2402
|
const collected = [];
|
|
2391
|
-
if (
|
|
2403
|
+
if (isAsyncIterable3(iterable)) {
|
|
2392
2404
|
for await (const chunk of iterable) {
|
|
2393
2405
|
pushLimited2(collected, chunk, this.#maxItems, this.name);
|
|
2394
2406
|
}
|
|
@@ -2400,7 +2412,7 @@ var IterableResolver = class {
|
|
|
2400
2412
|
return collected;
|
|
2401
2413
|
}
|
|
2402
2414
|
};
|
|
2403
|
-
function
|
|
2415
|
+
function isAsyncIterable3(value) {
|
|
2404
2416
|
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
2405
2417
|
}
|
|
2406
2418
|
function pushLimited2(collected, chunk, maxItems, resolverName) {
|
|
@@ -2556,6 +2568,54 @@ function defaultResolvers() {
|
|
|
2556
2568
|
];
|
|
2557
2569
|
}
|
|
2558
2570
|
|
|
2571
|
+
// packages/context/src/lib/save/reminder-eval.ts
|
|
2572
|
+
async function evaluateFiredReminders(configs, whenCtx) {
|
|
2573
|
+
if (configs.length === 0) return [];
|
|
2574
|
+
const collectors = configs.map(() => /* @__PURE__ */ new Set());
|
|
2575
|
+
const whenResults = await Promise.allSettled(
|
|
2576
|
+
// Wrap in an async thunk so a synchronously-throwing predicate becomes a
|
|
2577
|
+
// rejected promise (allSettled cannot catch a throw inside the map itself).
|
|
2578
|
+
configs.map(
|
|
2579
|
+
async (config, i) => config.when({ ...whenCtx, onceCollector: collectors[i] })
|
|
2580
|
+
)
|
|
2581
|
+
);
|
|
2582
|
+
const fired = [];
|
|
2583
|
+
for (let i = 0; i < configs.length; i++) {
|
|
2584
|
+
const result = whenResults[i];
|
|
2585
|
+
if (result.status === "rejected") {
|
|
2586
|
+
console.warn("reminder when() predicate threw; treating as not fired", {
|
|
2587
|
+
reason: result.reason
|
|
2588
|
+
});
|
|
2589
|
+
continue;
|
|
2590
|
+
}
|
|
2591
|
+
if (result.value === true) {
|
|
2592
|
+
fired.push({ config: configs[i], onceIds: [...collectors[i]] });
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
if (fired.length === 0) return [];
|
|
2596
|
+
const resolutions = await Promise.allSettled(
|
|
2597
|
+
fired.map(({ config }) => resolveReminderAsync(config, whenCtx))
|
|
2598
|
+
);
|
|
2599
|
+
const matched = [];
|
|
2600
|
+
for (let i = 0; i < fired.length; i++) {
|
|
2601
|
+
const result = resolutions[i];
|
|
2602
|
+
if (result.status === "rejected") {
|
|
2603
|
+
console.warn("reminder text resolver threw; skipping reminder", {
|
|
2604
|
+
reason: result.reason
|
|
2605
|
+
});
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
if (result.value) {
|
|
2609
|
+
matched.push({
|
|
2610
|
+
config: fired[i].config,
|
|
2611
|
+
resolved: result.value,
|
|
2612
|
+
onceIds: fired[i].onceIds
|
|
2613
|
+
});
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
return matched;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2559
2619
|
// packages/context/src/lib/save/save-pipeline.ts
|
|
2560
2620
|
var SavePipeline = class {
|
|
2561
2621
|
#engine;
|
|
@@ -2581,65 +2641,49 @@ var SavePipeline = class {
|
|
|
2581
2641
|
}
|
|
2582
2642
|
return this;
|
|
2583
2643
|
}
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
configsByTarget.set(target, list);
|
|
2594
|
-
}
|
|
2595
|
-
const chain = await this.#engine.getChainSummary();
|
|
2596
|
-
const base = this.#engine.buildBaseWhenCtx(chain);
|
|
2597
|
-
const sharedUserMessage = this.#encodePendingUserMessage();
|
|
2598
|
-
for (const handler of handlers) {
|
|
2599
|
-
const configs = configsByTarget.get(handler.target);
|
|
2600
|
-
if (!configs || configs.length === 0) continue;
|
|
2601
|
-
const prepared = handler.prepare({
|
|
2602
|
-
pending: this.#pending,
|
|
2603
|
-
base,
|
|
2604
|
-
chain,
|
|
2605
|
-
sharedUserMessage
|
|
2606
|
-
});
|
|
2607
|
-
if (!prepared) continue;
|
|
2608
|
-
const whenResults = await Promise.all(
|
|
2609
|
-
configs.map((config) => config.when(prepared.whenCtx))
|
|
2610
|
-
);
|
|
2611
|
-
const fired = configs.filter((_, i) => whenResults[i]);
|
|
2612
|
-
if (fired.length === 0) continue;
|
|
2613
|
-
const resolvedOrNull = await Promise.all(
|
|
2614
|
-
fired.map((config) => resolveReminderAsync(config, prepared.whenCtx))
|
|
2615
|
-
);
|
|
2616
|
-
const matched = [];
|
|
2617
|
-
for (let i = 0; i < resolvedOrNull.length; i++) {
|
|
2618
|
-
const resolution = resolvedOrNull[i];
|
|
2619
|
-
if (resolution)
|
|
2620
|
-
matched.push({ config: fired[i], resolved: resolution });
|
|
2621
|
-
}
|
|
2622
|
-
if (matched.length === 0) continue;
|
|
2623
|
-
handler.apply({
|
|
2624
|
-
pending: this.#pending,
|
|
2625
|
-
carrier: prepared.carrier,
|
|
2626
|
-
fired: matched.map((m) => m.config),
|
|
2627
|
-
resolved: matched.map((m) => m.resolved)
|
|
2628
|
-
});
|
|
2629
|
-
}
|
|
2630
|
-
return this;
|
|
2631
|
-
}
|
|
2632
|
-
#encodePendingUserMessage() {
|
|
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;
|
|
2633
2653
|
const fragmentIndex = this.#pending.findLastIndex(
|
|
2634
2654
|
(fragment3) => fragment3.name === "user"
|
|
2635
2655
|
);
|
|
2636
|
-
if (fragmentIndex < 0) return
|
|
2656
|
+
if (fragmentIndex < 0) return this;
|
|
2637
2657
|
const fragment2 = this.#pending[fragmentIndex];
|
|
2638
|
-
if (!fragment2.codec) return
|
|
2639
|
-
|
|
2658
|
+
if (!fragment2.codec) return this;
|
|
2659
|
+
const message2 = requireUserUIMessage(
|
|
2640
2660
|
fragment2.codec.encode(),
|
|
2641
2661
|
`Pending user fragment "${fragment2.name}"`
|
|
2642
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;
|
|
2643
2687
|
}
|
|
2644
2688
|
async persist() {
|
|
2645
2689
|
let parentId = this.#engine.getActiveBranch().headMessageId;
|
|
@@ -2680,108 +2724,6 @@ var SavePipeline = class {
|
|
|
2680
2724
|
}
|
|
2681
2725
|
};
|
|
2682
2726
|
|
|
2683
|
-
// packages/context/src/lib/save/tool-output-target-handler.ts
|
|
2684
|
-
var ToolOutputTargetHandler = class {
|
|
2685
|
-
target = "tool-output";
|
|
2686
|
-
prepare({
|
|
2687
|
-
pending,
|
|
2688
|
-
base,
|
|
2689
|
-
chain,
|
|
2690
|
-
sharedUserMessage
|
|
2691
|
-
}) {
|
|
2692
|
-
const fragmentIndex = pending.findLastIndex(
|
|
2693
|
-
(fragment3) => fragment3.name === "assistant"
|
|
2694
|
-
);
|
|
2695
|
-
if (fragmentIndex < 0) return null;
|
|
2696
|
-
const fragment2 = pending[fragmentIndex];
|
|
2697
|
-
if (!fragment2.codec) return null;
|
|
2698
|
-
const currentMessage = sharedUserMessage ?? chain.lastMessage;
|
|
2699
|
-
if (!currentMessage) return null;
|
|
2700
|
-
const message2 = requireUIMessage(
|
|
2701
|
-
fragment2.codec.encode(),
|
|
2702
|
-
`Pending assistant fragment "${fragment2.name}"`
|
|
2703
|
-
);
|
|
2704
|
-
if (!findSingleOutputAvailableToolPart(message2)) return null;
|
|
2705
|
-
return {
|
|
2706
|
-
whenCtx: {
|
|
2707
|
-
...base,
|
|
2708
|
-
content: extractPlainText(currentMessage),
|
|
2709
|
-
currentMessage,
|
|
2710
|
-
lastAssistantMessage: message2,
|
|
2711
|
-
lastAssistantMessages: [
|
|
2712
|
-
...chain.lastAssistantMessages ?? [],
|
|
2713
|
-
message2
|
|
2714
|
-
]
|
|
2715
|
-
},
|
|
2716
|
-
carrier: { message: message2, fragmentIndex }
|
|
2717
|
-
};
|
|
2718
|
-
}
|
|
2719
|
-
apply({ pending, carrier, resolved }) {
|
|
2720
|
-
const reminders = resolved.map((resolution) => ({
|
|
2721
|
-
text: resolution.text,
|
|
2722
|
-
metadata: resolution.metadata
|
|
2723
|
-
}));
|
|
2724
|
-
const metadata = applyToolOutputRemindersToMessage(
|
|
2725
|
-
carrier.message,
|
|
2726
|
-
reminders
|
|
2727
|
-
);
|
|
2728
|
-
mergeReminderMetadata(carrier.message, metadata);
|
|
2729
|
-
const originalId = pending[carrier.fragmentIndex].id;
|
|
2730
|
-
const message2 = originalId ? { ...carrier.message, id: originalId } : carrier.message;
|
|
2731
|
-
const updated = assistant(message2);
|
|
2732
|
-
if (originalId) updated.id = originalId;
|
|
2733
|
-
pending[carrier.fragmentIndex] = updated;
|
|
2734
|
-
}
|
|
2735
|
-
};
|
|
2736
|
-
|
|
2737
|
-
// packages/context/src/lib/save/user-target-handler.ts
|
|
2738
|
-
var UserTargetHandler = class {
|
|
2739
|
-
target = "user";
|
|
2740
|
-
prepare({
|
|
2741
|
-
pending,
|
|
2742
|
-
base,
|
|
2743
|
-
chain
|
|
2744
|
-
}) {
|
|
2745
|
-
const fragmentIndex = pending.findLastIndex(
|
|
2746
|
-
(fragment3) => fragment3.name === "user"
|
|
2747
|
-
);
|
|
2748
|
-
if (fragmentIndex < 0) return null;
|
|
2749
|
-
const fragment2 = pending[fragmentIndex];
|
|
2750
|
-
if (!fragment2.codec) return null;
|
|
2751
|
-
const message2 = requireUserUIMessage(
|
|
2752
|
-
fragment2.codec.encode(),
|
|
2753
|
-
`Pending user fragment "${fragment2.name}"`
|
|
2754
|
-
);
|
|
2755
|
-
return {
|
|
2756
|
-
whenCtx: {
|
|
2757
|
-
...base,
|
|
2758
|
-
content: extractPlainText(message2),
|
|
2759
|
-
currentMessage: message2,
|
|
2760
|
-
lastAssistantMessage: chain.lastAssistantMessage,
|
|
2761
|
-
lastAssistantMessages: chain.lastAssistantMessages
|
|
2762
|
-
},
|
|
2763
|
-
carrier: { message: message2, fragmentIndex },
|
|
2764
|
-
sharedUserMessage: message2
|
|
2765
|
-
};
|
|
2766
|
-
}
|
|
2767
|
-
apply({ pending, carrier, fired, resolved }) {
|
|
2768
|
-
const reminders = resolved.map((resolution, i) => ({
|
|
2769
|
-
text: resolution.text,
|
|
2770
|
-
asPart: fired[i].asPart,
|
|
2771
|
-
target: "user",
|
|
2772
|
-
metadata: resolution.metadata
|
|
2773
|
-
}));
|
|
2774
|
-
const originalId = pending[carrier.fragmentIndex].id;
|
|
2775
|
-
const message2 = requireUserUIMessage(
|
|
2776
|
-
originalId ? { ...carrier.message, id: originalId } : carrier.message,
|
|
2777
|
-
"Pending user reminder carrier"
|
|
2778
|
-
);
|
|
2779
|
-
const recreated = user(message2, ...reminders);
|
|
2780
|
-
if (originalId) recreated.id = originalId;
|
|
2781
|
-
pending[carrier.fragmentIndex] = recreated;
|
|
2782
|
-
}
|
|
2783
|
-
};
|
|
2784
|
-
|
|
2785
2727
|
// packages/context/src/lib/store/sqlite.store.ts
|
|
2786
2728
|
import { DatabaseSync } from "node:sqlite";
|
|
2787
2729
|
|
|
@@ -3402,6 +3344,23 @@ function isSkillPathMapping(value) {
|
|
|
3402
3344
|
function isEmptyAssistantPlaceholder(message2) {
|
|
3403
3345
|
return message2.role === "assistant" && message2.parts.length === 0;
|
|
3404
3346
|
}
|
|
3347
|
+
function stepStartPartIndices(parts) {
|
|
3348
|
+
const indices = [];
|
|
3349
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3350
|
+
if (parts[i].type === "step-start") indices.push(i);
|
|
3351
|
+
}
|
|
3352
|
+
return indices;
|
|
3353
|
+
}
|
|
3354
|
+
function spliceSteerMessages(messages, fired) {
|
|
3355
|
+
const ordered = [...fired].sort((a, b) => a.spliceIndex - b.spliceIndex);
|
|
3356
|
+
const out = [...messages];
|
|
3357
|
+
let offset = 0;
|
|
3358
|
+
for (const fire of ordered) {
|
|
3359
|
+
out.splice(fire.spliceIndex + offset, 0, ...fire.synthModel);
|
|
3360
|
+
offset += fire.synthModel.length;
|
|
3361
|
+
}
|
|
3362
|
+
return out;
|
|
3363
|
+
}
|
|
3405
3364
|
var ContextEngine = class _ContextEngine {
|
|
3406
3365
|
/** Non-message fragments (role, hints, etc.) - not persisted in graph */
|
|
3407
3366
|
#fragments = [];
|
|
@@ -3765,15 +3724,211 @@ var ContextEngine = class _ContextEngine {
|
|
|
3765
3724
|
this.#fragments
|
|
3766
3725
|
);
|
|
3767
3726
|
await pipeline.applyUpdateBranching(options?.branch ?? true);
|
|
3768
|
-
await pipeline.
|
|
3727
|
+
await pipeline.evaluateUserReminders();
|
|
3769
3728
|
const result = await pipeline.persist();
|
|
3770
3729
|
this.#pendingMessages = [];
|
|
3771
3730
|
return result;
|
|
3772
3731
|
}
|
|
3773
|
-
#
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3732
|
+
#currentSteerSession;
|
|
3733
|
+
/**
|
|
3734
|
+
* Build the `prepareStep` hook that injects steer reminders mid-loop.
|
|
3735
|
+
*
|
|
3736
|
+
* Semantics are "inject once, persist": when a steer reminder's predicate
|
|
3737
|
+
* fires (only after the model has produced ≥1 step with content — the mid-loop
|
|
3738
|
+
* gate), its `<system-reminder>` user message is spliced into the model prompt
|
|
3739
|
+
* at the step boundary where it fired AND re-spliced on every subsequent step,
|
|
3740
|
+
* so the model keeps seeing it for the rest of the loop.
|
|
3741
|
+
*
|
|
3742
|
+
* Firing is edge-triggered with a post-fire re-sample: each fire resets the
|
|
3743
|
+
* elapsed reference (`lastSyntheticAt`), then the config is immediately
|
|
3744
|
+
* re-evaluated against the reset context — self-resetting predicates like
|
|
3745
|
+
* `elapsedExceeds` read false and re-arm (so they recur every N within one
|
|
3746
|
+
* stream), while constant predicates like `everyNTurns` read true and disarm
|
|
3747
|
+
* (so they fire once per stream). Any later false sample re-arms a config.
|
|
3748
|
+
* All state is closure-local (a fresh SteerSession per call), so overlapping
|
|
3749
|
+
* streams / guardrail retries never share a cursor.
|
|
3750
|
+
*
|
|
3751
|
+
* The session is also consumed by writeAssistantSegment, which carves the
|
|
3752
|
+
* streamed assistant message into the matching `[assistant, steer, assistant]`
|
|
3753
|
+
* split — so the stored chain reproduces exactly the prompt the model saw
|
|
3754
|
+
* (store/prompt parity).
|
|
3755
|
+
*/
|
|
3756
|
+
createSteerPrepareStep() {
|
|
3757
|
+
const session = {
|
|
3758
|
+
firedOnceIds: /* @__PURE__ */ new Set(),
|
|
3759
|
+
fired: [],
|
|
3760
|
+
currentSegStart: 0,
|
|
3761
|
+
materialized: 0
|
|
3762
|
+
};
|
|
3763
|
+
this.#currentSteerSession = session;
|
|
3764
|
+
return async ({ steps, stepNumber, messages }) => {
|
|
3765
|
+
const priorStep = stepNumber >= 1 ? steps[stepNumber - 1] : void 0;
|
|
3766
|
+
const canFire = (priorStep?.content?.length ?? 0) > 0;
|
|
3767
|
+
if (canFire) {
|
|
3768
|
+
const configs = this.#steerConfigs();
|
|
3769
|
+
if (configs.length > 0) {
|
|
3770
|
+
const whenCtx = await this.#steerWhenCtx(session);
|
|
3771
|
+
const matched = await evaluateFiredReminders(configs, whenCtx);
|
|
3772
|
+
if (matched.length > 0) {
|
|
3773
|
+
const onceIds = [...new Set(matched.flatMap((m) => m.onceIds))];
|
|
3774
|
+
for (const id of onceIds) session.firedOnceIds.add(id);
|
|
3775
|
+
const synth = synthesizeSteerUserMessage(
|
|
3776
|
+
matched.map((m) => m.resolved.text),
|
|
3777
|
+
Date.now(),
|
|
3778
|
+
onceIds
|
|
3779
|
+
);
|
|
3780
|
+
const synthModel = await convertToModelMessages2([synth], {
|
|
3781
|
+
ignoreIncompleteToolCalls: true
|
|
3782
|
+
});
|
|
3783
|
+
session.fired.push({
|
|
3784
|
+
afterStep: stepNumber - 1,
|
|
3785
|
+
spliceIndex: messages.length,
|
|
3786
|
+
synth,
|
|
3787
|
+
synthModel
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
if (session.fired.length === 0) return void 0;
|
|
3793
|
+
return {
|
|
3794
|
+
messages: spliceSteerMessages(
|
|
3795
|
+
messages,
|
|
3796
|
+
session.fired
|
|
3797
|
+
)
|
|
3798
|
+
};
|
|
3799
|
+
};
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Persist the streamed assistant message, carving it into the steer split when
|
|
3803
|
+
* steer reminders fired this turn.
|
|
3804
|
+
*
|
|
3805
|
+
* Called from chat()'s onStepFinish/onFinish (and the guardrail path) with the
|
|
3806
|
+
* cumulative response message. Segment boundaries come from the `step-start`
|
|
3807
|
+
* markers in the message itself — no cross-track store read — so the carve is
|
|
3808
|
+
* race-free. Idempotent: finalized segments keep stable ids; the open segment
|
|
3809
|
+
* is updated in place. With no active steer it degrades to a plain in-place
|
|
3810
|
+
* write of the whole message to the reserved head.
|
|
3811
|
+
*/
|
|
3812
|
+
async writeAssistantSegment(message2) {
|
|
3813
|
+
const head = await this.headMessage();
|
|
3814
|
+
if (head?.name !== "assistant") {
|
|
3815
|
+
throw new Error(
|
|
3816
|
+
"writeAssistantSegment: expected an assistant message at chain head."
|
|
3817
|
+
);
|
|
3818
|
+
}
|
|
3819
|
+
const session = this.#currentSteerSession;
|
|
3820
|
+
if (!session || session.fired.length === 0) {
|
|
3821
|
+
this.set(assistant({ ...message2, id: head.id }));
|
|
3822
|
+
await this.save({ branch: false });
|
|
3823
|
+
return;
|
|
3824
|
+
}
|
|
3825
|
+
if (session.currentSegId === void 0) {
|
|
3826
|
+
session.currentSegId = head.id;
|
|
3827
|
+
session.currentSegStart = 0;
|
|
3828
|
+
}
|
|
3829
|
+
const stepStarts = stepStartPartIndices(message2.parts);
|
|
3830
|
+
while (session.materialized < session.fired.length) {
|
|
3831
|
+
const fire = session.fired[session.materialized];
|
|
3832
|
+
const boundary = stepStarts[fire.afterStep + 1];
|
|
3833
|
+
if (boundary === void 0) break;
|
|
3834
|
+
this.set(
|
|
3835
|
+
assistant({
|
|
3836
|
+
id: session.currentSegId,
|
|
3837
|
+
role: "assistant",
|
|
3838
|
+
parts: message2.parts.slice(session.currentSegStart, boundary)
|
|
3839
|
+
})
|
|
3840
|
+
);
|
|
3841
|
+
this.set(user(fire.synth));
|
|
3842
|
+
session.currentSegId = generateId4();
|
|
3843
|
+
session.currentSegStart = boundary;
|
|
3844
|
+
session.materialized++;
|
|
3845
|
+
}
|
|
3846
|
+
this.set(
|
|
3847
|
+
assistant({
|
|
3848
|
+
...message2,
|
|
3849
|
+
id: session.currentSegId,
|
|
3850
|
+
parts: message2.parts.slice(session.currentSegStart)
|
|
3851
|
+
})
|
|
3852
|
+
);
|
|
3853
|
+
await this.save({ branch: false });
|
|
3854
|
+
}
|
|
3855
|
+
#steerConfigs() {
|
|
3856
|
+
return this.#fragments.filter(isConditionalReminder).map((fragment2) => fragment2.metadata.reminder).filter((config) => config.target === "steer");
|
|
3857
|
+
}
|
|
3858
|
+
async #steerWhenCtx(session) {
|
|
3859
|
+
await this.#ensureInitialized();
|
|
3860
|
+
if (!session.whenBase) {
|
|
3861
|
+
const chain = await this.#getChainContext();
|
|
3862
|
+
const base2 = this.#asSavePipelineEngine().buildBaseWhenCtx(chain);
|
|
3863
|
+
const currentMessage2 = chain.lastMessage;
|
|
3864
|
+
if (!currentMessage2) {
|
|
3865
|
+
throw new Error(
|
|
3866
|
+
"steer reminders require a user message earlier in the turn"
|
|
3867
|
+
);
|
|
3868
|
+
}
|
|
3869
|
+
session.whenBase = {
|
|
3870
|
+
base: base2,
|
|
3871
|
+
content: extractPlainText(currentMessage2),
|
|
3872
|
+
currentMessage: currentMessage2,
|
|
3873
|
+
lastAssistantMessage: chain.lastAssistantMessage,
|
|
3874
|
+
lastAssistantMessages: chain.lastAssistantMessages,
|
|
3875
|
+
chainFiredOnceIds: chain.firedOnceIds
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
const {
|
|
3879
|
+
base,
|
|
3880
|
+
content,
|
|
3881
|
+
currentMessage,
|
|
3882
|
+
lastAssistantMessage,
|
|
3883
|
+
lastAssistantMessages,
|
|
3884
|
+
chainFiredOnceIds
|
|
3885
|
+
} = session.whenBase;
|
|
3886
|
+
const elapsed = base.lastMessageAt !== void 0 ? Date.now() - base.lastMessageAt : void 0;
|
|
3887
|
+
return {
|
|
3888
|
+
...base,
|
|
3889
|
+
elapsed,
|
|
3890
|
+
content,
|
|
3891
|
+
currentMessage,
|
|
3892
|
+
lastAssistantMessage,
|
|
3893
|
+
lastAssistantMessages,
|
|
3894
|
+
firedOnceIds: /* @__PURE__ */ new Set([...chainFiredOnceIds, ...session.firedOnceIds])
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* Evaluate `target: 'tool-output'` reminders against a tool's raw result and
|
|
3899
|
+
* return the (possibly wrapped) output.
|
|
3900
|
+
*
|
|
3901
|
+
* Called by the agent's tool wrapper right after each `execute()` resolves —
|
|
3902
|
+
* upstream of both the model and the store, so the next model step and the
|
|
3903
|
+
* persisted chain see the exact same wrapped value (store/prompt parity).
|
|
3904
|
+
*
|
|
3905
|
+
* Returns the output unchanged when no tool-output reminder fires. Without a
|
|
3906
|
+
* persisted user message there is no turn context to evaluate against
|
|
3907
|
+
* (e.g. asTool forks that set a pending user without saving), so the output
|
|
3908
|
+
* passes through untouched.
|
|
3909
|
+
*/
|
|
3910
|
+
async applyToolOutputReminders(output) {
|
|
3911
|
+
const configs = this.#fragments.filter(isConditionalReminder).map((fragment2) => fragment2.metadata.reminder).filter((config) => config.target === "tool-output");
|
|
3912
|
+
if (configs.length === 0) return output;
|
|
3913
|
+
await this.#ensureInitialized();
|
|
3914
|
+
const chain = await this.#getChainContext();
|
|
3915
|
+
const currentMessage = chain.lastMessage;
|
|
3916
|
+
if (!currentMessage) return output;
|
|
3917
|
+
const base = this.#asSavePipelineEngine().buildBaseWhenCtx(chain);
|
|
3918
|
+
const whenCtx = {
|
|
3919
|
+
...base,
|
|
3920
|
+
content: extractPlainText(currentMessage),
|
|
3921
|
+
currentMessage,
|
|
3922
|
+
lastAssistantMessage: chain.lastAssistantMessage,
|
|
3923
|
+
lastAssistantMessages: chain.lastAssistantMessages
|
|
3924
|
+
};
|
|
3925
|
+
const matched = await evaluateFiredReminders(configs, whenCtx);
|
|
3926
|
+
if (matched.length === 0) return output;
|
|
3927
|
+
return applyRemindersToToolOutput(
|
|
3928
|
+
output,
|
|
3929
|
+
matched.map((m) => m.resolved.text)
|
|
3930
|
+
);
|
|
3931
|
+
}
|
|
3777
3932
|
#asSavePipelineEngine() {
|
|
3778
3933
|
return {
|
|
3779
3934
|
store: this.#store,
|
|
@@ -4689,7 +4844,6 @@ var defaultChatMessageMetadata = ({
|
|
|
4689
4844
|
};
|
|
4690
4845
|
async function chat(agent2, options = {}) {
|
|
4691
4846
|
const context = agent2.context;
|
|
4692
|
-
const sandbox = agent2.sandbox;
|
|
4693
4847
|
if (!context) {
|
|
4694
4848
|
throw new Error(
|
|
4695
4849
|
"Agent is missing a context. Provide context when creating the agent."
|
|
@@ -4701,7 +4855,7 @@ async function chat(agent2, options = {}) {
|
|
|
4701
4855
|
"chat: expected an assistant message at head. Call context.continue(input) before chat()."
|
|
4702
4856
|
);
|
|
4703
4857
|
}
|
|
4704
|
-
const
|
|
4858
|
+
const initialAssistantMsgId = head.id;
|
|
4705
4859
|
const uiMessages = await context.getMessages();
|
|
4706
4860
|
const streamContextVariables = options.contextVariables === void 0 ? {} : options.contextVariables;
|
|
4707
4861
|
const [title, result] = await Promise.all([
|
|
@@ -4723,42 +4877,30 @@ async function chat(agent2, options = {}) {
|
|
|
4723
4877
|
sendReasoning: true,
|
|
4724
4878
|
sendSources: true,
|
|
4725
4879
|
originalMessages: uiMessages,
|
|
4726
|
-
generateMessageId: () =>
|
|
4880
|
+
generateMessageId: () => initialAssistantMsgId,
|
|
4727
4881
|
messageMetadata: options.messageMetadata ?? defaultChatMessageMetadata
|
|
4728
4882
|
});
|
|
4729
4883
|
return createUIMessageStream2({
|
|
4730
4884
|
originalMessages: uiMessages,
|
|
4731
|
-
generateId: () =>
|
|
4885
|
+
generateId: () => initialAssistantMsgId,
|
|
4732
4886
|
onStepFinish: async ({ responseMessage }) => {
|
|
4733
|
-
|
|
4734
|
-
...responseMessage,
|
|
4735
|
-
id: assistantMsgId
|
|
4736
|
-
};
|
|
4737
|
-
context.set(assistant(normalizedMessage));
|
|
4738
|
-
await context.save({ branch: false });
|
|
4887
|
+
await context.writeAssistantSegment(responseMessage);
|
|
4739
4888
|
},
|
|
4740
4889
|
onFinish: async ({ responseMessage, isAborted }) => {
|
|
4741
|
-
|
|
4742
|
-
...responseMessage,
|
|
4743
|
-
id: assistantMsgId
|
|
4744
|
-
};
|
|
4890
|
+
let message2 = responseMessage;
|
|
4745
4891
|
if (isAborted) {
|
|
4746
|
-
|
|
4892
|
+
message2 = { ...message2, parts: sanitizeAbortedParts(message2.parts) };
|
|
4747
4893
|
}
|
|
4748
|
-
const
|
|
4749
|
-
const fileEvents = isAborted ? [] : drained;
|
|
4750
|
-
const finalMetadata = await options.finalAssistantMetadata?.(normalizedMessage);
|
|
4894
|
+
const finalMetadata = await options.finalAssistantMetadata?.(message2);
|
|
4751
4895
|
const mergedMetadata = {
|
|
4752
|
-
...
|
|
4753
|
-
...fileEvents.length > 0 ? { fileEvents } : {},
|
|
4896
|
+
...message2.metadata ?? {},
|
|
4754
4897
|
...finalMetadata ?? {}
|
|
4755
4898
|
};
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
await context.
|
|
4760
|
-
|
|
4761
|
-
await context.trackUsage(totalUsage);
|
|
4899
|
+
if (Object.keys(mergedMetadata).length > 0) {
|
|
4900
|
+
message2 = { ...message2, metadata: mergedMetadata };
|
|
4901
|
+
}
|
|
4902
|
+
await context.writeAssistantSegment(message2);
|
|
4903
|
+
await context.trackUsage(await result.totalUsage);
|
|
4762
4904
|
},
|
|
4763
4905
|
execute: async ({ writer }) => {
|
|
4764
4906
|
writer.merge(uiStream);
|
|
@@ -5345,6 +5487,23 @@ function lastAssistantLength(spec) {
|
|
|
5345
5487
|
};
|
|
5346
5488
|
}
|
|
5347
5489
|
|
|
5490
|
+
// packages/context/src/lib/fragments/reminders/once.ts
|
|
5491
|
+
function once(id) {
|
|
5492
|
+
if (id.trim().length === 0) {
|
|
5493
|
+
throw new Error("once(id) requires a non-empty id");
|
|
5494
|
+
}
|
|
5495
|
+
return (ctx) => {
|
|
5496
|
+
if (ctx.firedOnceIds === void 0) {
|
|
5497
|
+
throw new Error(
|
|
5498
|
+
`once('${id}') is only supported on target:'steer' reminders`
|
|
5499
|
+
);
|
|
5500
|
+
}
|
|
5501
|
+
if (ctx.firedOnceIds.has(id)) return false;
|
|
5502
|
+
ctx.onceCollector?.add(id);
|
|
5503
|
+
return true;
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
|
|
5348
5507
|
// packages/context/src/lib/fragments/reminders/temporal/elapsed.ts
|
|
5349
5508
|
function elapsedExceeds(ms) {
|
|
5350
5509
|
return (ctx) => (ctx.elapsed ?? 0) >= ms;
|
|
@@ -5502,15 +5661,15 @@ function dateReminder(options) {
|
|
|
5502
5661
|
const now = /* @__PURE__ */ new Date();
|
|
5503
5662
|
const currentDate = formatDateKey(now, tz);
|
|
5504
5663
|
const currentDay = formatDayOfWeek(now, tz);
|
|
5505
|
-
let
|
|
5664
|
+
let diff = "";
|
|
5506
5665
|
if (ctx.lastMessageAt !== void 0) {
|
|
5507
5666
|
const prev = new Date(ctx.lastMessageAt);
|
|
5508
|
-
|
|
5667
|
+
diff = formatDiff([
|
|
5509
5668
|
diffLine("date", formatDateKey(prev, tz), currentDate),
|
|
5510
5669
|
diffLine("day of week", formatDayOfWeek(prev, tz), currentDay)
|
|
5511
5670
|
]);
|
|
5512
5671
|
}
|
|
5513
|
-
return `${
|
|
5672
|
+
return `${diff}Date: ${currentDate}
|
|
5514
5673
|
Day of Week: ${currentDay}`;
|
|
5515
5674
|
},
|
|
5516
5675
|
{ when: dayChanged(options), asPart: false }
|
|
@@ -5522,14 +5681,14 @@ function timeReminder(options) {
|
|
|
5522
5681
|
const tz = resolveTz(options, ctx);
|
|
5523
5682
|
const now = /* @__PURE__ */ new Date();
|
|
5524
5683
|
const currentTime = formatTime(now, tz);
|
|
5525
|
-
let
|
|
5684
|
+
let diff = "";
|
|
5526
5685
|
if (ctx.lastMessageAt !== void 0) {
|
|
5527
5686
|
const prev = new Date(ctx.lastMessageAt);
|
|
5528
|
-
|
|
5687
|
+
diff = formatDiff([
|
|
5529
5688
|
diffLine("hour", formatHour(prev, tz), formatHour(now, tz))
|
|
5530
5689
|
]);
|
|
5531
5690
|
}
|
|
5532
|
-
return `${
|
|
5691
|
+
return `${diff}Time: ${currentTime}`;
|
|
5533
5692
|
},
|
|
5534
5693
|
{ when: hourChanged(options), asPart: false }
|
|
5535
5694
|
);
|
|
@@ -5540,14 +5699,14 @@ function monthReminder(options) {
|
|
|
5540
5699
|
const tz = resolveTz(options, ctx);
|
|
5541
5700
|
const now = /* @__PURE__ */ new Date();
|
|
5542
5701
|
const currentMonth = formatMonthName(now, tz);
|
|
5543
|
-
let
|
|
5702
|
+
let diff = "";
|
|
5544
5703
|
if (ctx.lastMessageAt !== void 0) {
|
|
5545
5704
|
const prev = new Date(ctx.lastMessageAt);
|
|
5546
|
-
|
|
5705
|
+
diff = formatDiff([
|
|
5547
5706
|
diffLine("month", formatMonthName(prev, tz), currentMonth)
|
|
5548
5707
|
]);
|
|
5549
5708
|
}
|
|
5550
|
-
return `${
|
|
5709
|
+
return `${diff}Month: ${currentMonth}`;
|
|
5551
5710
|
},
|
|
5552
5711
|
{ when: monthChanged(options), asPart: false }
|
|
5553
5712
|
);
|
|
@@ -5558,14 +5717,14 @@ function yearReminder(options) {
|
|
|
5558
5717
|
const tz = resolveTz(options, ctx);
|
|
5559
5718
|
const now = /* @__PURE__ */ new Date();
|
|
5560
5719
|
const currentYear = formatYear(now, tz);
|
|
5561
|
-
let
|
|
5720
|
+
let diff = "";
|
|
5562
5721
|
if (ctx.lastMessageAt !== void 0) {
|
|
5563
5722
|
const prev = new Date(ctx.lastMessageAt);
|
|
5564
|
-
|
|
5723
|
+
diff = formatDiff([
|
|
5565
5724
|
diffLine("year", formatYear(prev, tz), currentYear)
|
|
5566
5725
|
]);
|
|
5567
5726
|
}
|
|
5568
|
-
return `${
|
|
5727
|
+
return `${diff}Year: ${currentYear}`;
|
|
5569
5728
|
},
|
|
5570
5729
|
{ when: yearChanged(options), asPart: false }
|
|
5571
5730
|
);
|
|
@@ -5576,14 +5735,14 @@ function seasonReminder(options) {
|
|
|
5576
5735
|
const tz = resolveTz(options, ctx);
|
|
5577
5736
|
const now = /* @__PURE__ */ new Date();
|
|
5578
5737
|
const currentSeason = getSeason(getMonthIndex(now, tz));
|
|
5579
|
-
let
|
|
5738
|
+
let diff = "";
|
|
5580
5739
|
if (ctx.lastMessageAt !== void 0) {
|
|
5581
5740
|
const prev = new Date(ctx.lastMessageAt);
|
|
5582
|
-
|
|
5741
|
+
diff = formatDiff([
|
|
5583
5742
|
diffLine("season", getSeason(getMonthIndex(prev, tz)), currentSeason)
|
|
5584
5743
|
]);
|
|
5585
5744
|
}
|
|
5586
|
-
return `${
|
|
5745
|
+
return `${diff}Season: ${currentSeason}`;
|
|
5587
5746
|
},
|
|
5588
5747
|
{ when: seasonChanged(options), asPart: false }
|
|
5589
5748
|
);
|
|
@@ -5601,11 +5760,11 @@ function localeReminder() {
|
|
|
5601
5760
|
const current = getLocaleFromMessage(ctx.currentMessage);
|
|
5602
5761
|
if (!current) return "";
|
|
5603
5762
|
const last = getLocaleFromMessage(ctx.lastMessage);
|
|
5604
|
-
const
|
|
5763
|
+
const diff = last ? formatDiff([
|
|
5605
5764
|
diffLine("language", last.language, current.language),
|
|
5606
5765
|
diffLine("timezone", last.timeZone, current.timeZone)
|
|
5607
5766
|
]) : "";
|
|
5608
|
-
return `${
|
|
5767
|
+
return `${diff}Language: ${current.language}
|
|
5609
5768
|
Timezone: ${current.timeZone}`;
|
|
5610
5769
|
},
|
|
5611
5770
|
{ when: whenFn, asPart: false }
|
|
@@ -5692,7 +5851,7 @@ function toolCallCount(name, spec) {
|
|
|
5692
5851
|
function everyNTurns(n) {
|
|
5693
5852
|
return ({ turn }) => turn % n === 0;
|
|
5694
5853
|
}
|
|
5695
|
-
function
|
|
5854
|
+
function first() {
|
|
5696
5855
|
return ({ turn }) => turn === 1;
|
|
5697
5856
|
}
|
|
5698
5857
|
function firstN(n) {
|
|
@@ -6114,158 +6273,878 @@ import {
|
|
|
6114
6273
|
} from "bash-tool";
|
|
6115
6274
|
import z3 from "zod";
|
|
6116
6275
|
|
|
6117
|
-
// packages/context/src/lib/sandbox/file-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6276
|
+
// packages/context/src/lib/sandbox/file-changes.ts
|
|
6277
|
+
import { randomUUID } from "node:crypto";
|
|
6278
|
+
import { posix as posix2 } from "node:path";
|
|
6279
|
+
|
|
6280
|
+
// packages/context/src/lib/sandbox/installers/installer.ts
|
|
6281
|
+
import "bash-tool";
|
|
6282
|
+
import spawn from "nano-spawn";
|
|
6283
|
+
|
|
6284
|
+
// packages/context/src/lib/sandbox/docker-sandbox-errors.ts
|
|
6285
|
+
var DockerSandboxError = class extends Error {
|
|
6286
|
+
containerId;
|
|
6287
|
+
constructor(message2, containerId) {
|
|
6121
6288
|
super(message2);
|
|
6122
|
-
this.name = "
|
|
6123
|
-
this.
|
|
6289
|
+
this.name = "DockerSandboxError";
|
|
6290
|
+
this.containerId = containerId;
|
|
6124
6291
|
}
|
|
6125
6292
|
};
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
const probe = await execute(`[ -d ${shellQuote(destination)} ]`);
|
|
6131
|
-
if (probe.exitCode !== 0) return /* @__PURE__ */ new Map();
|
|
6132
|
-
const list = await execute(
|
|
6133
|
-
`find ${shellQuote(destination)} -type f -exec sha256sum {} +`
|
|
6134
|
-
);
|
|
6135
|
-
if (list.exitCode !== 0) {
|
|
6136
|
-
throw new SnapshotFailedError(
|
|
6137
|
-
`snapshot failed for ${destination}`,
|
|
6138
|
-
list.stderr
|
|
6139
|
-
);
|
|
6140
|
-
}
|
|
6141
|
-
const snap = /* @__PURE__ */ new Map();
|
|
6142
|
-
if (!list.stdout) return snap;
|
|
6143
|
-
for (const line of list.stdout.split("\n")) {
|
|
6144
|
-
if (line.length < 66) continue;
|
|
6145
|
-
snap.set(line.slice(66), line.slice(0, 64));
|
|
6146
|
-
}
|
|
6147
|
-
return snap;
|
|
6148
|
-
}
|
|
6149
|
-
function lazyReadable(innerPromise) {
|
|
6150
|
-
let reader = null;
|
|
6151
|
-
return new ReadableStream({
|
|
6152
|
-
async pull(controller) {
|
|
6153
|
-
if (!reader) {
|
|
6154
|
-
const inner = await innerPromise;
|
|
6155
|
-
reader = inner.getReader();
|
|
6156
|
-
}
|
|
6157
|
-
const { done, value } = await reader.read();
|
|
6158
|
-
if (done) controller.close();
|
|
6159
|
-
else controller.enqueue(value);
|
|
6160
|
-
},
|
|
6161
|
-
async cancel(reason) {
|
|
6162
|
-
if (reader) {
|
|
6163
|
-
await reader.cancel(reason);
|
|
6164
|
-
} else {
|
|
6165
|
-
const inner = await innerPromise;
|
|
6166
|
-
await inner.cancel(reason);
|
|
6167
|
-
}
|
|
6168
|
-
}
|
|
6169
|
-
});
|
|
6170
|
-
}
|
|
6171
|
-
function diff(before, after) {
|
|
6172
|
-
const events = [];
|
|
6173
|
-
const ts = Date.now();
|
|
6174
|
-
for (const [path5, hash] of after) {
|
|
6175
|
-
const prior = before.get(path5);
|
|
6176
|
-
if (prior === void 0) {
|
|
6177
|
-
events.push({ path: path5, op: "write", timestamp: ts });
|
|
6178
|
-
} else if (prior !== hash) {
|
|
6179
|
-
events.push({ path: path5, op: "modify", timestamp: ts });
|
|
6180
|
-
}
|
|
6293
|
+
var DockerNotAvailableError = class extends DockerSandboxError {
|
|
6294
|
+
constructor() {
|
|
6295
|
+
super("Docker is not available. Ensure Docker daemon is running.");
|
|
6296
|
+
this.name = "DockerNotAvailableError";
|
|
6181
6297
|
}
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6298
|
+
};
|
|
6299
|
+
var ContainerCreationError = class extends DockerSandboxError {
|
|
6300
|
+
image;
|
|
6301
|
+
cause;
|
|
6302
|
+
constructor(message2, image, cause) {
|
|
6303
|
+
super(`Failed to create container from image "${image}": ${message2}`);
|
|
6304
|
+
this.name = "ContainerCreationError";
|
|
6305
|
+
this.image = image;
|
|
6306
|
+
this.cause = cause;
|
|
6186
6307
|
}
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6308
|
+
};
|
|
6309
|
+
var PackageInstallError = class extends DockerSandboxError {
|
|
6310
|
+
packages;
|
|
6311
|
+
image;
|
|
6312
|
+
packageManager;
|
|
6313
|
+
stderr;
|
|
6314
|
+
constructor(packages, image, packageManager, stderr, containerId) {
|
|
6315
|
+
super(
|
|
6316
|
+
`Package installation failed for [${packages.join(", ")}] using ${packageManager} on ${image}: ${stderr}`,
|
|
6317
|
+
containerId
|
|
6318
|
+
);
|
|
6319
|
+
this.name = "PackageInstallError";
|
|
6320
|
+
this.packages = packages;
|
|
6321
|
+
this.image = image;
|
|
6322
|
+
this.packageManager = packageManager;
|
|
6323
|
+
this.stderr = stderr;
|
|
6193
6324
|
}
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6325
|
+
};
|
|
6326
|
+
var InstallError = class extends DockerSandboxError {
|
|
6327
|
+
target;
|
|
6328
|
+
source;
|
|
6329
|
+
reason;
|
|
6330
|
+
url;
|
|
6331
|
+
constructor(opts) {
|
|
6332
|
+
const where = opts.url ? `${opts.source} (${opts.url})` : opts.source;
|
|
6333
|
+
super(
|
|
6334
|
+
`Failed to install "${opts.target}" via ${where}: ${opts.reason}`,
|
|
6335
|
+
opts.containerId
|
|
6336
|
+
);
|
|
6337
|
+
this.name = "InstallError";
|
|
6338
|
+
this.target = opts.target;
|
|
6339
|
+
this.source = opts.source;
|
|
6340
|
+
this.reason = opts.reason;
|
|
6341
|
+
this.url = opts.url;
|
|
6342
|
+
}
|
|
6343
|
+
};
|
|
6344
|
+
var MissingRuntimeError = class extends DockerSandboxError {
|
|
6345
|
+
runtime;
|
|
6346
|
+
required;
|
|
6347
|
+
constructor(runtime, required, details, containerId) {
|
|
6348
|
+
const base = `Required runtime "${runtime}" is not installed (needs: ${required.join(", ")}).`;
|
|
6349
|
+
super(details ? `${base} ${details}` : base, containerId);
|
|
6350
|
+
this.name = "MissingRuntimeError";
|
|
6351
|
+
this.runtime = runtime;
|
|
6352
|
+
this.required = required;
|
|
6353
|
+
}
|
|
6354
|
+
};
|
|
6355
|
+
var VolumePathError = class extends DockerSandboxError {
|
|
6356
|
+
source;
|
|
6357
|
+
containerPath;
|
|
6358
|
+
reason;
|
|
6359
|
+
constructor(source, containerPath, reason) {
|
|
6360
|
+
super(
|
|
6361
|
+
`Invalid Docker volume path "${source}" -> "${containerPath}": ${reason}`
|
|
6362
|
+
);
|
|
6363
|
+
this.name = "VolumePathError";
|
|
6364
|
+
this.source = source;
|
|
6365
|
+
this.containerPath = containerPath;
|
|
6366
|
+
this.reason = reason;
|
|
6367
|
+
}
|
|
6368
|
+
};
|
|
6369
|
+
var VolumeInspectError = class extends DockerSandboxError {
|
|
6370
|
+
volume;
|
|
6371
|
+
reason;
|
|
6372
|
+
constructor(volume, reason) {
|
|
6373
|
+
super(`Failed to inspect Docker volume "${volume}": ${reason}`);
|
|
6374
|
+
this.name = "VolumeInspectError";
|
|
6375
|
+
this.volume = volume;
|
|
6376
|
+
this.reason = reason;
|
|
6377
|
+
}
|
|
6378
|
+
};
|
|
6379
|
+
var VolumeCreateError = class extends DockerSandboxError {
|
|
6380
|
+
volume;
|
|
6381
|
+
reason;
|
|
6382
|
+
constructor(volume, reason) {
|
|
6383
|
+
super(`Failed to create Docker volume "${volume}": ${reason}`);
|
|
6384
|
+
this.name = "VolumeCreateError";
|
|
6385
|
+
this.volume = volume;
|
|
6386
|
+
this.reason = reason;
|
|
6387
|
+
}
|
|
6388
|
+
};
|
|
6389
|
+
var VolumeRemoveError = class extends DockerSandboxError {
|
|
6390
|
+
volume;
|
|
6391
|
+
reason;
|
|
6392
|
+
constructor(volume, reason) {
|
|
6393
|
+
super(`Failed to remove Docker volume "${volume}": ${reason}`);
|
|
6394
|
+
this.name = "VolumeRemoveError";
|
|
6395
|
+
this.volume = volume;
|
|
6396
|
+
this.reason = reason;
|
|
6397
|
+
}
|
|
6398
|
+
};
|
|
6399
|
+
var DockerfileBuildError = class extends DockerSandboxError {
|
|
6400
|
+
stderr;
|
|
6401
|
+
constructor(stderr) {
|
|
6402
|
+
super(`Dockerfile build failed: ${stderr}`);
|
|
6403
|
+
this.name = "DockerfileBuildError";
|
|
6404
|
+
this.stderr = stderr;
|
|
6405
|
+
}
|
|
6406
|
+
};
|
|
6407
|
+
var ComposeStartError = class extends DockerSandboxError {
|
|
6408
|
+
composeFile;
|
|
6409
|
+
stderr;
|
|
6410
|
+
constructor(composeFile, stderr) {
|
|
6411
|
+
super(`Docker Compose failed to start: ${stderr}`);
|
|
6412
|
+
this.name = "ComposeStartError";
|
|
6413
|
+
this.composeFile = composeFile;
|
|
6414
|
+
this.stderr = stderr;
|
|
6415
|
+
}
|
|
6416
|
+
};
|
|
6417
|
+
|
|
6418
|
+
// packages/context/src/lib/sandbox/installers/installer.ts
|
|
6419
|
+
var Installer = class {
|
|
6420
|
+
};
|
|
6421
|
+
function isDebianBased(image) {
|
|
6422
|
+
const lower = image.toLowerCase();
|
|
6423
|
+
if (lower.includes("alpine")) return false;
|
|
6424
|
+
const debianPatterns = ["debian", "ubuntu", "node", "python"];
|
|
6425
|
+
return debianPatterns.some((pattern) => lower.includes(pattern));
|
|
6426
|
+
}
|
|
6427
|
+
function shellQuote(s) {
|
|
6428
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
6429
|
+
}
|
|
6430
|
+
function createInstallerContext(containerId, image) {
|
|
6431
|
+
const packageManager = isDebianBased(image) ? "apt-get" : "apk";
|
|
6432
|
+
let archPromise = null;
|
|
6433
|
+
const ensuredTools = /* @__PURE__ */ new Set();
|
|
6434
|
+
let aptUpdated = false;
|
|
6435
|
+
const exec = async (command) => {
|
|
6436
|
+
try {
|
|
6437
|
+
const result = await spawn("docker", [
|
|
6438
|
+
"exec",
|
|
6439
|
+
containerId,
|
|
6440
|
+
"sh",
|
|
6441
|
+
"-c",
|
|
6442
|
+
command
|
|
6443
|
+
]);
|
|
6444
|
+
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
6445
|
+
} catch (error) {
|
|
6446
|
+
const err = error;
|
|
6447
|
+
return {
|
|
6448
|
+
stdout: err.stdout ?? "",
|
|
6449
|
+
stderr: err.stderr ?? err.message ?? "",
|
|
6450
|
+
exitCode: err.exitCode ?? 1
|
|
6451
|
+
};
|
|
6452
|
+
}
|
|
6453
|
+
};
|
|
6454
|
+
const arch = async () => {
|
|
6455
|
+
if (!archPromise) {
|
|
6456
|
+
const attempt = (async () => {
|
|
6457
|
+
const result = await exec("uname -m");
|
|
6458
|
+
if (result.exitCode !== 0) {
|
|
6459
|
+
throw new DockerSandboxError(
|
|
6460
|
+
`Failed to detect container architecture: ${result.stderr}`,
|
|
6461
|
+
containerId
|
|
6462
|
+
);
|
|
6463
|
+
}
|
|
6464
|
+
return result.stdout.trim();
|
|
6465
|
+
})();
|
|
6466
|
+
archPromise = attempt.catch((err) => {
|
|
6467
|
+
archPromise = null;
|
|
6468
|
+
throw err;
|
|
6469
|
+
});
|
|
6470
|
+
}
|
|
6471
|
+
return archPromise;
|
|
6472
|
+
};
|
|
6473
|
+
const installPackages = async (packages) => {
|
|
6474
|
+
if (packages.length === 0) return;
|
|
6475
|
+
const quoted = packages.map(shellQuote).join(" ");
|
|
6476
|
+
let cmd;
|
|
6477
|
+
if (packageManager === "apt-get") {
|
|
6478
|
+
cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
|
|
6479
|
+
} else {
|
|
6480
|
+
cmd = `apk add --no-cache ${quoted}`;
|
|
6481
|
+
}
|
|
6482
|
+
const result = await exec(cmd);
|
|
6483
|
+
if (result.exitCode !== 0) {
|
|
6484
|
+
throw new PackageInstallError(
|
|
6485
|
+
packages,
|
|
6486
|
+
image,
|
|
6487
|
+
packageManager,
|
|
6488
|
+
result.stderr,
|
|
6489
|
+
containerId
|
|
6490
|
+
);
|
|
6491
|
+
}
|
|
6492
|
+
if (packageManager === "apt-get") aptUpdated = true;
|
|
6493
|
+
};
|
|
6494
|
+
const ensureTool = async (checkName, installName) => {
|
|
6495
|
+
const cacheKey = installName ?? checkName;
|
|
6496
|
+
if (ensuredTools.has(cacheKey)) return;
|
|
6497
|
+
const check = await exec(`which ${shellQuote(checkName)}`);
|
|
6498
|
+
if (check.exitCode === 0) {
|
|
6499
|
+
ensuredTools.add(cacheKey);
|
|
6500
|
+
return;
|
|
6501
|
+
}
|
|
6502
|
+
await installPackages([installName ?? checkName]);
|
|
6503
|
+
ensuredTools.add(cacheKey);
|
|
6504
|
+
};
|
|
6505
|
+
return {
|
|
6506
|
+
containerId,
|
|
6507
|
+
image,
|
|
6508
|
+
packageManager,
|
|
6509
|
+
arch,
|
|
6510
|
+
exec,
|
|
6511
|
+
installPackages,
|
|
6512
|
+
ensureTool
|
|
6513
|
+
};
|
|
6514
|
+
}
|
|
6515
|
+
|
|
6516
|
+
// packages/context/src/lib/sandbox/installers/package-manager.ts
|
|
6517
|
+
var PackageInstaller = class extends Installer {
|
|
6518
|
+
kind;
|
|
6519
|
+
packages;
|
|
6520
|
+
constructor(packages) {
|
|
6521
|
+
super();
|
|
6522
|
+
this.packages = packages;
|
|
6523
|
+
this.kind = packages.length === 0 ? "pkg:<empty>" : `pkg:${packages.join(",")}`;
|
|
6524
|
+
}
|
|
6525
|
+
async install(ctx) {
|
|
6526
|
+
if (this.packages.length === 0) return;
|
|
6527
|
+
await ctx.installPackages([...this.packages]);
|
|
6528
|
+
}
|
|
6529
|
+
};
|
|
6530
|
+
function pkg(packages) {
|
|
6531
|
+
return new PackageInstaller(packages);
|
|
6532
|
+
}
|
|
6533
|
+
|
|
6534
|
+
// packages/context/src/lib/sandbox/installers/url-binary.ts
|
|
6535
|
+
var UrlBinaryInstaller = class extends Installer {
|
|
6536
|
+
kind;
|
|
6537
|
+
options;
|
|
6538
|
+
constructor(options) {
|
|
6539
|
+
super();
|
|
6540
|
+
this.options = options;
|
|
6541
|
+
this.kind = `url-binary:${options.name}`;
|
|
6542
|
+
}
|
|
6543
|
+
async install(ctx) {
|
|
6544
|
+
const url = await resolveUrl(ctx, this.options);
|
|
6545
|
+
await downloadAndInstall(
|
|
6546
|
+
ctx,
|
|
6547
|
+
this.options.name,
|
|
6548
|
+
url,
|
|
6549
|
+
this.options.binaryPath
|
|
6550
|
+
);
|
|
6551
|
+
}
|
|
6552
|
+
};
|
|
6553
|
+
function urlBinary(options) {
|
|
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
|
+
});
|
|
6567
|
+
}
|
|
6568
|
+
return archUrl;
|
|
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
|
+
});
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6585
|
+
function buildTarGzInstallCmd(name, url, binaryPathInArchive) {
|
|
6586
|
+
return `
|
|
6587
|
+
set -e
|
|
6588
|
+
NAME=${shellQuote(name)}
|
|
6589
|
+
URL=${shellQuote(url)}
|
|
6590
|
+
BIN_IN_ARCHIVE=${shellQuote(binaryPathInArchive)}
|
|
6591
|
+
TMPDIR=$(mktemp -d)
|
|
6592
|
+
cd "$TMPDIR"
|
|
6593
|
+
curl -fsSL "$URL" -o archive.tar.gz
|
|
6594
|
+
tar -xzf archive.tar.gz
|
|
6595
|
+
BINARY_FILE=$(find . -name "$BIN_IN_ARCHIVE" -o -name "$NAME" | head -1)
|
|
6596
|
+
if [ -z "$BINARY_FILE" ]; then
|
|
6597
|
+
echo "Binary not found in archive. Contents:" >&2
|
|
6598
|
+
find . -type f >&2
|
|
6599
|
+
exit 1
|
|
6600
|
+
fi
|
|
6601
|
+
chmod +x "$BINARY_FILE"
|
|
6602
|
+
mv "$BINARY_FILE" "/usr/local/bin/$NAME"
|
|
6603
|
+
cd /
|
|
6604
|
+
rm -rf "$TMPDIR"
|
|
6605
|
+
`;
|
|
6606
|
+
}
|
|
6607
|
+
function buildRawInstallCmd(name, url) {
|
|
6608
|
+
return `
|
|
6609
|
+
NAME=${shellQuote(name)}
|
|
6610
|
+
URL=${shellQuote(url)}
|
|
6611
|
+
curl -fsSL "$URL" -o "/usr/local/bin/$NAME"
|
|
6612
|
+
chmod +x "/usr/local/bin/$NAME"
|
|
6613
|
+
`;
|
|
6614
|
+
}
|
|
6615
|
+
|
|
6616
|
+
// packages/context/src/lib/sandbox/installers/npm.ts
|
|
6617
|
+
var NODE_BINARIES = ["node", "npm"];
|
|
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}`;
|
|
6628
|
+
}
|
|
6629
|
+
async install(ctx) {
|
|
6630
|
+
await ensureNodeRuntime(ctx, this.options.ensureRuntime ?? false);
|
|
6631
|
+
const spec = this.options.version ? `${this.packageName}@${this.options.version}` : this.packageName;
|
|
6632
|
+
const result = await ctx.exec(`npm install -g ${spec}`);
|
|
6633
|
+
if (result.exitCode !== 0) {
|
|
6634
|
+
throw new InstallError({
|
|
6635
|
+
target: this.packageName,
|
|
6636
|
+
source: "npm",
|
|
6637
|
+
reason: result.stderr,
|
|
6638
|
+
containerId: ctx.containerId
|
|
6639
|
+
});
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
};
|
|
6643
|
+
function npm(packageName, options) {
|
|
6644
|
+
return new NpmInstaller(packageName, options);
|
|
6645
|
+
}
|
|
6646
|
+
async function ensureNodeRuntime(ctx, ensure) {
|
|
6647
|
+
if (ensure) {
|
|
6648
|
+
await ctx.ensureTool("node", "nodejs");
|
|
6649
|
+
await ctx.ensureTool("npm");
|
|
6650
|
+
return;
|
|
6651
|
+
}
|
|
6652
|
+
const check = await ctx.exec("which node && which npm");
|
|
6653
|
+
if (check.exitCode !== 0) {
|
|
6654
|
+
throw new MissingRuntimeError(
|
|
6655
|
+
"npm",
|
|
6656
|
+
NODE_BINARIES,
|
|
6657
|
+
ENSURE_RUNTIME_HINT,
|
|
6658
|
+
ctx.containerId
|
|
6659
|
+
);
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
|
|
6663
|
+
// packages/context/src/lib/sandbox/installers/bin.ts
|
|
6664
|
+
import { basename, posix } from "node:path";
|
|
6665
|
+
var NOT_FOUND_EXIT = 11;
|
|
6666
|
+
var CHMOD_FAILED_EXIT = 12;
|
|
6667
|
+
var BinInstaller = class extends Installer {
|
|
6668
|
+
kind;
|
|
6669
|
+
binary;
|
|
6670
|
+
options;
|
|
6671
|
+
#name;
|
|
6672
|
+
#target;
|
|
6673
|
+
constructor(binary, options = {}) {
|
|
6674
|
+
super();
|
|
6675
|
+
this.binary = binary;
|
|
6676
|
+
this.options = options;
|
|
6677
|
+
this.#name = resolveName(binary, options);
|
|
6678
|
+
this.#target = options.target ?? posix.join("/usr/local/bin", this.#name);
|
|
6679
|
+
this.kind = `bin:${this.#name}`;
|
|
6680
|
+
}
|
|
6681
|
+
async install(ctx) {
|
|
6682
|
+
const b = shellQuote(this.binary);
|
|
6683
|
+
const t = shellQuote(this.#target);
|
|
6684
|
+
const dir = shellQuote(posix.dirname(this.#target));
|
|
6685
|
+
const result = await ctx.exec(
|
|
6686
|
+
`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}`
|
|
6687
|
+
);
|
|
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
|
+
}
|
|
6696
|
+
};
|
|
6697
|
+
function bin(binary, options) {
|
|
6698
|
+
return new BinInstaller(binary, options);
|
|
6699
|
+
}
|
|
6700
|
+
function explainFailure(exitCode, stderr, binary) {
|
|
6701
|
+
if (exitCode === NOT_FOUND_EXIT) {
|
|
6702
|
+
return `binary not found at ${binary}`;
|
|
6703
|
+
}
|
|
6704
|
+
if (exitCode === CHMOD_FAILED_EXIT && /read-only file system/i.test(stderr)) {
|
|
6705
|
+
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()})`;
|
|
6706
|
+
}
|
|
6707
|
+
return stderr || `bin installer failed with exit code ${exitCode}`;
|
|
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}`;
|
|
6727
|
+
}
|
|
6728
|
+
async install(ctx) {
|
|
6729
|
+
await ensurePythonRuntime(ctx, this.options.ensureRuntime ?? false);
|
|
6730
|
+
const spec = this.options.version ? `${this.packageName}${formatVersion(this.options.version)}` : this.packageName;
|
|
6731
|
+
const flags = this.options.breakSystemPackages ?? true ? "--break-system-packages" : "";
|
|
6732
|
+
const result = await ctx.exec(`pip3 install ${flags} ${spec}`.trim());
|
|
6733
|
+
if (result.exitCode !== 0) {
|
|
6734
|
+
throw new InstallError({
|
|
6735
|
+
target: this.packageName,
|
|
6736
|
+
source: "pypi",
|
|
6737
|
+
reason: result.stderr,
|
|
6738
|
+
containerId: ctx.containerId
|
|
6739
|
+
});
|
|
6740
|
+
}
|
|
6741
|
+
}
|
|
6742
|
+
};
|
|
6743
|
+
function pip(packageName, options) {
|
|
6744
|
+
return new PipInstaller(packageName, options);
|
|
6745
|
+
}
|
|
6746
|
+
async function ensurePythonRuntime(ctx, ensure) {
|
|
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
|
+
);
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
function formatVersion(version) {
|
|
6764
|
+
return /^[<>=!~]/.test(version) ? version : `==${version}`;
|
|
6765
|
+
}
|
|
6766
|
+
|
|
6767
|
+
// packages/context/src/lib/sandbox/installers/github-release.ts
|
|
6768
|
+
var GithubReleaseInstaller = class extends Installer {
|
|
6769
|
+
kind;
|
|
6770
|
+
options;
|
|
6771
|
+
constructor(options) {
|
|
6772
|
+
super();
|
|
6773
|
+
this.options = options;
|
|
6774
|
+
this.kind = `github-release:${options.owner}/${options.repo}@${options.version}`;
|
|
6775
|
+
}
|
|
6776
|
+
async install(ctx) {
|
|
6777
|
+
const arch = await ctx.arch();
|
|
6778
|
+
const assetName = this.options.asset(arch);
|
|
6779
|
+
const url = `https://github.com/${this.options.owner}/${this.options.repo}/releases/download/${this.options.version}/${assetName}`;
|
|
6780
|
+
await downloadAndInstall(
|
|
6781
|
+
ctx,
|
|
6782
|
+
this.options.name,
|
|
6783
|
+
url,
|
|
6784
|
+
this.options.binaryPath,
|
|
6785
|
+
"github-release"
|
|
6786
|
+
);
|
|
6787
|
+
}
|
|
6788
|
+
};
|
|
6789
|
+
function githubRelease(options) {
|
|
6790
|
+
return new GithubReleaseInstaller(options);
|
|
6791
|
+
}
|
|
6792
|
+
|
|
6793
|
+
// packages/context/src/lib/sandbox/file-changes.ts
|
|
6794
|
+
var REASON_HINT = {
|
|
6795
|
+
"ptrace-blocked": "ptrace is denied by the sandbox runtime. Modern Docker permits it by default; on a hardened host, relax the runtime seccomp/ptrace policy (allow the ptrace syscall, or add the SYS_PTRACE capability).",
|
|
6796
|
+
"strace-missing": "strace is not installed in the sandbox image. Bake `strace` into the image.",
|
|
6797
|
+
"trace-unparseable": "strace ran but its output is unusable \u2014 the sandbox is likely running under emulation (e.g. amd64 via Rosetta). Use a native-architecture sandbox."
|
|
6798
|
+
};
|
|
6799
|
+
var StraceUnavailableError = class extends Error {
|
|
6800
|
+
reason;
|
|
6801
|
+
diagnostics;
|
|
6802
|
+
constructor(reason, diagnostics) {
|
|
6803
|
+
super(
|
|
6804
|
+
`strace file-change tracking unavailable (${reason}): ${REASON_HINT[reason]}`
|
|
6805
|
+
);
|
|
6806
|
+
this.name = "StraceUnavailableError";
|
|
6807
|
+
this.reason = reason;
|
|
6808
|
+
this.diagnostics = diagnostics;
|
|
6809
|
+
}
|
|
6810
|
+
};
|
|
6811
|
+
var DEFAULT_TRACE_DIR = "/tmp/dat-trace";
|
|
6812
|
+
var STRACE_FLAGS = "-f -y -qq -e trace=%file,write,pwrite64,writev";
|
|
6813
|
+
function buildStraceCommand(command, traceFile, traceDir) {
|
|
6814
|
+
return `mkdir -p ${shellQuote(traceDir)} 2>/dev/null; strace ${STRACE_FLAGS} -o ${shellQuote(traceFile)} -- sh -c ${shellQuote(command)}`;
|
|
6815
|
+
}
|
|
6816
|
+
function stripStraceDiagnostics(stderr) {
|
|
6817
|
+
if (!stderr.includes("strace: ")) return stderr;
|
|
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);
|
|
6853
|
+
}
|
|
6854
|
+
return out;
|
|
6855
|
+
}
|
|
6856
|
+
function parseCall(line) {
|
|
6857
|
+
const m = line.match(
|
|
6858
|
+
/^\s*(?:\d+\s+)?([a-z_][a-z0-9_]*)\((.*)\)\s*=\s*(.+?)\s*$/
|
|
6859
|
+
);
|
|
6860
|
+
if (!m) return null;
|
|
6861
|
+
const [, syscall, args, rest] = m;
|
|
6862
|
+
const errnoMatch = rest.match(/^(-?\d+)\s+([A-Z][A-Z0-9_]*)/);
|
|
6863
|
+
if (errnoMatch) {
|
|
6864
|
+
return { syscall, args, retval: errnoMatch[1], errno: errnoMatch[2] };
|
|
6865
|
+
}
|
|
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
|
+
};
|
|
6877
|
+
function decodeStraceString(s) {
|
|
6878
|
+
const bytes = [];
|
|
6879
|
+
for (let i = 0; i < s.length; ) {
|
|
6880
|
+
if (s[i] === "\\" && i + 1 < s.length) {
|
|
6881
|
+
const octal = s.slice(i + 1, i + 4).match(/^[0-7]{1,3}/)?.[0];
|
|
6882
|
+
if (octal) {
|
|
6883
|
+
bytes.push(parseInt(octal, 8) & 255);
|
|
6884
|
+
i += 1 + octal.length;
|
|
6885
|
+
continue;
|
|
6886
|
+
}
|
|
6887
|
+
const mapped = STRACE_ESCAPE[s[i + 1]];
|
|
6888
|
+
bytes.push(mapped ?? s.charCodeAt(i + 1));
|
|
6889
|
+
i += 2;
|
|
6890
|
+
continue;
|
|
6891
|
+
}
|
|
6892
|
+
const code = s.charCodeAt(i);
|
|
6893
|
+
if (code < 128) bytes.push(code);
|
|
6894
|
+
else for (const b of Buffer.from(s[i], "utf8")) bytes.push(b);
|
|
6895
|
+
i += 1;
|
|
6896
|
+
}
|
|
6897
|
+
return Buffer.from(bytes).toString("utf8");
|
|
6898
|
+
}
|
|
6899
|
+
function quotedStrings(args) {
|
|
6900
|
+
const out = [];
|
|
6901
|
+
const re = /"((?:[^"\\]|\\.)*)"/g;
|
|
6902
|
+
let m;
|
|
6903
|
+
while ((m = re.exec(args)) !== null) {
|
|
6904
|
+
out.push(decodeStraceString(m[1]));
|
|
6905
|
+
}
|
|
6906
|
+
return out;
|
|
6907
|
+
}
|
|
6908
|
+
function dirfdPath(args) {
|
|
6909
|
+
return args.match(/^\s*(?:AT_FDCWD|-?\d+)<([^>]*)>/)?.[1] ?? "/";
|
|
6910
|
+
}
|
|
6911
|
+
function fdPath(args) {
|
|
6912
|
+
return args.match(/^\s*-?\d+<([^>]*)>/)?.[1] ?? null;
|
|
6913
|
+
}
|
|
6914
|
+
function openFlags(args) {
|
|
6915
|
+
return args.match(/\bO_[A-Z_]+(?:\|O_[A-Z_]+)*/)?.[0] ?? "";
|
|
6916
|
+
}
|
|
6917
|
+
function parseStraceTrace(raw, options) {
|
|
6918
|
+
const dest = posix2.normalize(options.destination);
|
|
6919
|
+
const traceDir = options.traceDir;
|
|
6920
|
+
const traceFile = options.traceFile;
|
|
6921
|
+
const now = Date.now();
|
|
6922
|
+
const state = /* @__PURE__ */ new Map();
|
|
6923
|
+
const renames = [];
|
|
6924
|
+
const resolve4 = (dir, p) => posix2.normalize(p.startsWith("/") ? p : posix2.join(dir, p));
|
|
6925
|
+
const write = (path5) => {
|
|
6926
|
+
state.set(path5, "write");
|
|
6927
|
+
};
|
|
6928
|
+
const remove = (path5) => {
|
|
6929
|
+
if (state.get(path5) === "write") state.delete(path5);
|
|
6930
|
+
else state.set(path5, "delete");
|
|
6931
|
+
};
|
|
6932
|
+
for (const line of stitchLines(raw)) {
|
|
6933
|
+
const call = parseCall(line);
|
|
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;
|
|
6955
|
+
}
|
|
6956
|
+
if (LINK_SYSCALLS.has(syscall)) {
|
|
6957
|
+
const strings = quotedStrings(args);
|
|
6958
|
+
if (strings.length) write(resolve4(dir, strings[strings.length - 1]));
|
|
6959
|
+
continue;
|
|
6960
|
+
}
|
|
6961
|
+
if (OPEN_SYSCALLS.has(syscall)) {
|
|
6962
|
+
if (!CREATE_OR_TRUNC_FLAGS.test(openFlags(args))) continue;
|
|
6963
|
+
const strings = quotedStrings(args);
|
|
6964
|
+
if (strings.length) write(resolve4(dir, strings[0]));
|
|
6965
|
+
continue;
|
|
6966
|
+
}
|
|
6967
|
+
if (WRITE_SYSCALLS.has(syscall)) {
|
|
6968
|
+
const p = fdPath(args);
|
|
6969
|
+
if (p && Number(call.retval) > 0) write(posix2.normalize(p));
|
|
6970
|
+
continue;
|
|
6971
|
+
}
|
|
6972
|
+
if (TRUNCATE_SYSCALLS.has(syscall)) {
|
|
6973
|
+
const p = fdPath(args) ?? quotedStrings(args)[0];
|
|
6974
|
+
if (p) write(resolve4(dir, p));
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
const keep = (path5) => {
|
|
6978
|
+
if (traceFile && path5 === traceFile) return false;
|
|
6979
|
+
if (traceDir && (path5 === traceDir || path5.startsWith(`${traceDir}/`))) {
|
|
6980
|
+
return false;
|
|
6981
|
+
}
|
|
6982
|
+
if (/^\/(proc|sys|dev)(\/|$)/.test(path5)) return false;
|
|
6983
|
+
return dest === "/" ? true : path5 === dest || path5.startsWith(`${dest}/`);
|
|
6984
|
+
};
|
|
6985
|
+
const changes = [];
|
|
6986
|
+
const emittedRenameTargets = /* @__PURE__ */ new Set();
|
|
6987
|
+
for (const { from, to } of renames) {
|
|
6988
|
+
if (!keep(to)) continue;
|
|
6989
|
+
if (state.get(to) === "delete") continue;
|
|
6990
|
+
changes.push({ op: "rename", path: to, from, timestamp: now });
|
|
6991
|
+
emittedRenameTargets.add(to);
|
|
6992
|
+
}
|
|
6993
|
+
const tail = [];
|
|
6994
|
+
for (const [path5, op] of state) {
|
|
6995
|
+
if (emittedRenameTargets.has(path5)) continue;
|
|
6996
|
+
if (keep(path5)) tail.push({ op, path: path5, timestamp: now });
|
|
6997
|
+
}
|
|
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
|
+
};
|
|
7008
|
+
function traceFileChanges(sandbox, options) {
|
|
7009
|
+
const { destination, onFileChanges } = options;
|
|
7010
|
+
const onError = options.onError ?? warnOnFileChangesError;
|
|
7011
|
+
const traceDir = options.traceDir ?? DEFAULT_TRACE_DIR;
|
|
7012
|
+
const innerExecute = sandbox.executeCommand.bind(sandbox);
|
|
7013
|
+
const innerReadFile = sandbox.readFile.bind(sandbox);
|
|
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) => {
|
|
6204
7020
|
try {
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
await takeAfter().catch(() => {
|
|
7021
|
+
return parseStraceTrace(await innerReadFile(traceFile), {
|
|
7022
|
+
destination,
|
|
7023
|
+
traceFile,
|
|
7024
|
+
traceDir
|
|
6210
7025
|
});
|
|
6211
|
-
|
|
7026
|
+
} catch {
|
|
7027
|
+
return [];
|
|
7028
|
+
} finally {
|
|
7029
|
+
removeTrace(traceFile);
|
|
7030
|
+
}
|
|
7031
|
+
};
|
|
7032
|
+
const collectCommand = async (traceFile) => {
|
|
7033
|
+
const changes = await readChanges(traceFile);
|
|
7034
|
+
if (!changes.length) return;
|
|
7035
|
+
useBashMeta()?.setHidden({ fileChanges: changes });
|
|
7036
|
+
await onFileChanges?.(changes);
|
|
7037
|
+
};
|
|
7038
|
+
const collectSpawn = async (traceFile) => {
|
|
7039
|
+
const changes = await readChanges(traceFile);
|
|
7040
|
+
if (!changes.length || !onFileChanges) return;
|
|
7041
|
+
try {
|
|
7042
|
+
await onFileChanges(changes);
|
|
7043
|
+
} catch (error) {
|
|
7044
|
+
onError(error);
|
|
6212
7045
|
}
|
|
6213
7046
|
};
|
|
6214
7047
|
const decorated = {
|
|
6215
|
-
async executeCommand(command,
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
7048
|
+
async executeCommand(command, execOptions) {
|
|
7049
|
+
const traceFile = `${traceDir}/${randomUUID()}.strace`;
|
|
7050
|
+
const wrapped = buildStraceCommand(command, traceFile, traceDir);
|
|
7051
|
+
let result;
|
|
7052
|
+
try {
|
|
7053
|
+
const raw = await innerExecute(wrapped, execOptions);
|
|
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;
|
|
6222
7065
|
},
|
|
6223
|
-
|
|
6224
|
-
|
|
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);
|
|
6225
7082
|
},
|
|
6226
|
-
dispose:
|
|
7083
|
+
dispose: async () => {
|
|
7084
|
+
await innerExecute(`rm -rf ${shellQuote(traceDir)}`).catch(() => {
|
|
7085
|
+
});
|
|
7086
|
+
await sandbox.dispose();
|
|
7087
|
+
}
|
|
6227
7088
|
};
|
|
6228
7089
|
if (sandbox.spawn) {
|
|
6229
7090
|
const innerSpawn = sandbox.spawn.bind(sandbox);
|
|
6230
|
-
decorated.spawn = (command,
|
|
6231
|
-
const
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
return { before, child };
|
|
6237
|
-
})();
|
|
7091
|
+
decorated.spawn = (command, spawnOptions) => {
|
|
7092
|
+
const traceFile = `${traceDir}/${randomUUID()}.strace`;
|
|
7093
|
+
const child = innerSpawn(
|
|
7094
|
+
buildStraceCommand(command, traceFile, traceDir),
|
|
7095
|
+
spawnOptions
|
|
7096
|
+
);
|
|
6238
7097
|
const exit = (async () => {
|
|
6239
|
-
const { before, child } = await started;
|
|
6240
7098
|
try {
|
|
6241
7099
|
return await child.exit;
|
|
6242
7100
|
} finally {
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
}
|
|
6249
|
-
})();
|
|
6250
|
-
const stdoutPromise = started.then((s) => s.child.stdout);
|
|
6251
|
-
const stderrPromise = started.then((s) => s.child.stderr);
|
|
6252
|
-
stdoutPromise.catch(() => {
|
|
6253
|
-
});
|
|
6254
|
-
stderrPromise.catch(() => {
|
|
6255
|
-
});
|
|
6256
|
-
return {
|
|
6257
|
-
stdout: lazyReadable(stdoutPromise),
|
|
6258
|
-
stderr: lazyReadable(stderrPromise),
|
|
6259
|
-
exit
|
|
6260
|
-
};
|
|
7101
|
+
await collectSpawn(traceFile).catch(() => {
|
|
7102
|
+
});
|
|
7103
|
+
}
|
|
7104
|
+
})();
|
|
7105
|
+
return { stdout: child.stdout, stderr: child.stderr, exit };
|
|
6261
7106
|
};
|
|
6262
7107
|
}
|
|
6263
|
-
return
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
7108
|
+
return decorated;
|
|
7109
|
+
}
|
|
7110
|
+
var PTRACE_DENIED = /strace:[^\n]*(?:PTRACE_TRACEME|ptrace|Operation not permitted|EPERM)/i;
|
|
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 = "";
|
|
7120
|
+
try {
|
|
7121
|
+
result = await sandbox.executeCommand(wrapped);
|
|
7122
|
+
raw = await sandbox.readFile(traceFile).catch(() => "");
|
|
7123
|
+
const diagnostics = `exit=${result.exitCode}
|
|
7124
|
+
stderr=${result.stderr}
|
|
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);
|
|
6267
7143
|
}
|
|
6268
|
-
}
|
|
7144
|
+
} finally {
|
|
7145
|
+
void sandbox.executeCommand(`rm -rf ${q(probeDir)} ${q(traceFile)}`).catch(() => {
|
|
7146
|
+
});
|
|
7147
|
+
}
|
|
6269
7148
|
}
|
|
6270
7149
|
|
|
6271
7150
|
// packages/context/src/lib/sandbox/upload-skills.ts
|
|
@@ -6432,12 +7311,18 @@ async function createBashTool(options) {
|
|
|
6432
7311
|
extraInstructions,
|
|
6433
7312
|
sandbox: backend,
|
|
6434
7313
|
destination,
|
|
7314
|
+
onFileChanges,
|
|
7315
|
+
onError,
|
|
6435
7316
|
...rest
|
|
6436
7317
|
} = options;
|
|
6437
|
-
const
|
|
6438
|
-
|
|
7318
|
+
const observationRoot = destination ?? "/workspace";
|
|
7319
|
+
await selfTestStrace(backend);
|
|
7320
|
+
const tracked = traceFileChanges(backend, {
|
|
7321
|
+
destination: observationRoot,
|
|
7322
|
+
onFileChanges,
|
|
7323
|
+
onError
|
|
6439
7324
|
});
|
|
6440
|
-
const sandbox = withAbortSignal(withBashExceptionCatch(
|
|
7325
|
+
const sandbox = withAbortSignal(withBashExceptionCatch(tracked));
|
|
6441
7326
|
const combinedInstructions = [extraInstructions, REASONING_INSTRUCTION].filter(Boolean).join("\n\n");
|
|
6442
7327
|
const toolkit = await externalCreateBashTool({
|
|
6443
7328
|
...rest,
|
|
@@ -6485,21 +7370,37 @@ async function createBashTool(options) {
|
|
|
6485
7370
|
return { type: "json", value: visible };
|
|
6486
7371
|
}
|
|
6487
7372
|
});
|
|
6488
|
-
const
|
|
7373
|
+
const upstreamWriteFile = toolkit.tools.writeFile;
|
|
7374
|
+
const originalWriteExecute = upstreamWriteFile.execute;
|
|
7375
|
+
const writeFileBuilder = tool2;
|
|
7376
|
+
const writeFile = writeFileBuilder({
|
|
7377
|
+
...upstreamWriteFile,
|
|
7378
|
+
execute: async (input, options2) => {
|
|
7379
|
+
if (!originalWriteExecute) {
|
|
7380
|
+
throw new Error("writeFile tool execution is not available");
|
|
7381
|
+
}
|
|
7382
|
+
try {
|
|
7383
|
+
return await originalWriteExecute(input, options2);
|
|
7384
|
+
} catch (err) {
|
|
7385
|
+
if (err instanceof BashException) return err.format();
|
|
7386
|
+
throw err;
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
7389
|
+
});
|
|
7390
|
+
const skills2 = await uploadSkills(backend, skillInputs);
|
|
6489
7391
|
return {
|
|
6490
7392
|
...toolkit,
|
|
6491
7393
|
sandbox,
|
|
6492
7394
|
bash,
|
|
6493
|
-
tools: { ...toolkit.tools, bash },
|
|
6494
|
-
skills: skills2
|
|
6495
|
-
drainFileEvents: () => observer.drain()
|
|
7395
|
+
tools: { ...toolkit.tools, bash, writeFile },
|
|
7396
|
+
skills: skills2
|
|
6496
7397
|
};
|
|
6497
7398
|
}
|
|
6498
7399
|
|
|
6499
7400
|
// packages/context/src/lib/sandbox/binary-bridges.ts
|
|
6500
7401
|
import { existsSync as existsSync2 } from "fs";
|
|
6501
7402
|
import { defineCommand } from "just-bash";
|
|
6502
|
-
import
|
|
7403
|
+
import spawn2 from "nano-spawn";
|
|
6503
7404
|
import * as path4 from "path";
|
|
6504
7405
|
function createBinaryBridges(...binaries) {
|
|
6505
7406
|
return binaries.map((input) => {
|
|
@@ -6537,7 +7438,7 @@ function createBinaryBridges(...binaries) {
|
|
|
6537
7438
|
PATH: process.env.PATH
|
|
6538
7439
|
// Always use host PATH for binary bridges
|
|
6539
7440
|
};
|
|
6540
|
-
const result = await
|
|
7441
|
+
const result = await spawn2(binaryPath, resolvedArgs, {
|
|
6541
7442
|
cwd: realCwd,
|
|
6542
7443
|
env: mergedEnv
|
|
6543
7444
|
});
|
|
@@ -6594,7 +7495,7 @@ function resolveRealCwd(ctx) {
|
|
|
6594
7495
|
|
|
6595
7496
|
// packages/context/src/lib/sandbox/daytona-sandbox.ts
|
|
6596
7497
|
import "bash-tool";
|
|
6597
|
-
import { randomUUID } from "node:crypto";
|
|
7498
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
6598
7499
|
var DAYTONA_DEFAULT_DESTINATION = "/home/daytona";
|
|
6599
7500
|
var DAYTONA_EXIT_POLL_INTERVAL_MS = 250;
|
|
6600
7501
|
var DAYTONA_EXIT_POLL_TIMEOUT_MS = 3e4;
|
|
@@ -7027,7 +7928,7 @@ function validateEnvKey(key) {
|
|
|
7027
7928
|
}
|
|
7028
7929
|
}
|
|
7029
7930
|
function createSessionId(kind) {
|
|
7030
|
-
return `deepagents-${kind}-${
|
|
7931
|
+
return `deepagents-${kind}-${randomUUID2()}`;
|
|
7031
7932
|
}
|
|
7032
7933
|
function abortedCommandResult() {
|
|
7033
7934
|
return {
|
|
@@ -7075,243 +7976,7 @@ import spawn3 from "nano-spawn";
|
|
|
7075
7976
|
import { spawn as childSpawn } from "node:child_process";
|
|
7076
7977
|
import { createHash } from "node:crypto";
|
|
7077
7978
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
7078
|
-
import { Readable as Readable2 } from "node:stream";
|
|
7079
|
-
|
|
7080
|
-
// packages/context/src/lib/sandbox/docker-sandbox-errors.ts
|
|
7081
|
-
var DockerSandboxError = class extends Error {
|
|
7082
|
-
containerId;
|
|
7083
|
-
constructor(message2, containerId) {
|
|
7084
|
-
super(message2);
|
|
7085
|
-
this.name = "DockerSandboxError";
|
|
7086
|
-
this.containerId = containerId;
|
|
7087
|
-
}
|
|
7088
|
-
};
|
|
7089
|
-
var DockerNotAvailableError = class extends DockerSandboxError {
|
|
7090
|
-
constructor() {
|
|
7091
|
-
super("Docker is not available. Ensure Docker daemon is running.");
|
|
7092
|
-
this.name = "DockerNotAvailableError";
|
|
7093
|
-
}
|
|
7094
|
-
};
|
|
7095
|
-
var ContainerCreationError = class extends DockerSandboxError {
|
|
7096
|
-
image;
|
|
7097
|
-
cause;
|
|
7098
|
-
constructor(message2, image, cause) {
|
|
7099
|
-
super(`Failed to create container from image "${image}": ${message2}`);
|
|
7100
|
-
this.name = "ContainerCreationError";
|
|
7101
|
-
this.image = image;
|
|
7102
|
-
this.cause = cause;
|
|
7103
|
-
}
|
|
7104
|
-
};
|
|
7105
|
-
var PackageInstallError = class extends DockerSandboxError {
|
|
7106
|
-
packages;
|
|
7107
|
-
image;
|
|
7108
|
-
packageManager;
|
|
7109
|
-
stderr;
|
|
7110
|
-
constructor(packages, image, packageManager, stderr, containerId) {
|
|
7111
|
-
super(
|
|
7112
|
-
`Package installation failed for [${packages.join(", ")}] using ${packageManager} on ${image}: ${stderr}`,
|
|
7113
|
-
containerId
|
|
7114
|
-
);
|
|
7115
|
-
this.name = "PackageInstallError";
|
|
7116
|
-
this.packages = packages;
|
|
7117
|
-
this.image = image;
|
|
7118
|
-
this.packageManager = packageManager;
|
|
7119
|
-
this.stderr = stderr;
|
|
7120
|
-
}
|
|
7121
|
-
};
|
|
7122
|
-
var InstallError = class extends DockerSandboxError {
|
|
7123
|
-
target;
|
|
7124
|
-
source;
|
|
7125
|
-
reason;
|
|
7126
|
-
url;
|
|
7127
|
-
constructor(opts) {
|
|
7128
|
-
const where = opts.url ? `${opts.source} (${opts.url})` : opts.source;
|
|
7129
|
-
super(
|
|
7130
|
-
`Failed to install "${opts.target}" via ${where}: ${opts.reason}`,
|
|
7131
|
-
opts.containerId
|
|
7132
|
-
);
|
|
7133
|
-
this.name = "InstallError";
|
|
7134
|
-
this.target = opts.target;
|
|
7135
|
-
this.source = opts.source;
|
|
7136
|
-
this.reason = opts.reason;
|
|
7137
|
-
this.url = opts.url;
|
|
7138
|
-
}
|
|
7139
|
-
};
|
|
7140
|
-
var MissingRuntimeError = class extends DockerSandboxError {
|
|
7141
|
-
runtime;
|
|
7142
|
-
required;
|
|
7143
|
-
constructor(runtime, required, details, containerId) {
|
|
7144
|
-
const base = `Required runtime "${runtime}" is not installed (needs: ${required.join(", ")}).`;
|
|
7145
|
-
super(details ? `${base} ${details}` : base, containerId);
|
|
7146
|
-
this.name = "MissingRuntimeError";
|
|
7147
|
-
this.runtime = runtime;
|
|
7148
|
-
this.required = required;
|
|
7149
|
-
}
|
|
7150
|
-
};
|
|
7151
|
-
var VolumePathError = class extends DockerSandboxError {
|
|
7152
|
-
source;
|
|
7153
|
-
containerPath;
|
|
7154
|
-
reason;
|
|
7155
|
-
constructor(source, containerPath, reason) {
|
|
7156
|
-
super(
|
|
7157
|
-
`Invalid Docker volume path "${source}" -> "${containerPath}": ${reason}`
|
|
7158
|
-
);
|
|
7159
|
-
this.name = "VolumePathError";
|
|
7160
|
-
this.source = source;
|
|
7161
|
-
this.containerPath = containerPath;
|
|
7162
|
-
this.reason = reason;
|
|
7163
|
-
}
|
|
7164
|
-
};
|
|
7165
|
-
var VolumeInspectError = class extends DockerSandboxError {
|
|
7166
|
-
volume;
|
|
7167
|
-
reason;
|
|
7168
|
-
constructor(volume, reason) {
|
|
7169
|
-
super(`Failed to inspect Docker volume "${volume}": ${reason}`);
|
|
7170
|
-
this.name = "VolumeInspectError";
|
|
7171
|
-
this.volume = volume;
|
|
7172
|
-
this.reason = reason;
|
|
7173
|
-
}
|
|
7174
|
-
};
|
|
7175
|
-
var VolumeCreateError = class extends DockerSandboxError {
|
|
7176
|
-
volume;
|
|
7177
|
-
reason;
|
|
7178
|
-
constructor(volume, reason) {
|
|
7179
|
-
super(`Failed to create Docker volume "${volume}": ${reason}`);
|
|
7180
|
-
this.name = "VolumeCreateError";
|
|
7181
|
-
this.volume = volume;
|
|
7182
|
-
this.reason = reason;
|
|
7183
|
-
}
|
|
7184
|
-
};
|
|
7185
|
-
var VolumeRemoveError = class extends DockerSandboxError {
|
|
7186
|
-
volume;
|
|
7187
|
-
reason;
|
|
7188
|
-
constructor(volume, reason) {
|
|
7189
|
-
super(`Failed to remove Docker volume "${volume}": ${reason}`);
|
|
7190
|
-
this.name = "VolumeRemoveError";
|
|
7191
|
-
this.volume = volume;
|
|
7192
|
-
this.reason = reason;
|
|
7193
|
-
}
|
|
7194
|
-
};
|
|
7195
|
-
var DockerfileBuildError = class extends DockerSandboxError {
|
|
7196
|
-
stderr;
|
|
7197
|
-
constructor(stderr) {
|
|
7198
|
-
super(`Dockerfile build failed: ${stderr}`);
|
|
7199
|
-
this.name = "DockerfileBuildError";
|
|
7200
|
-
this.stderr = stderr;
|
|
7201
|
-
}
|
|
7202
|
-
};
|
|
7203
|
-
var ComposeStartError = class extends DockerSandboxError {
|
|
7204
|
-
composeFile;
|
|
7205
|
-
stderr;
|
|
7206
|
-
constructor(composeFile, stderr) {
|
|
7207
|
-
super(`Docker Compose failed to start: ${stderr}`);
|
|
7208
|
-
this.name = "ComposeStartError";
|
|
7209
|
-
this.composeFile = composeFile;
|
|
7210
|
-
this.stderr = stderr;
|
|
7211
|
-
}
|
|
7212
|
-
};
|
|
7213
|
-
|
|
7214
|
-
// packages/context/src/lib/sandbox/installers/installer.ts
|
|
7215
|
-
import "bash-tool";
|
|
7216
|
-
import spawn2 from "nano-spawn";
|
|
7217
|
-
var Installer = class {
|
|
7218
|
-
};
|
|
7219
|
-
function isDebianBased(image) {
|
|
7220
|
-
const lower = image.toLowerCase();
|
|
7221
|
-
if (lower.includes("alpine")) return false;
|
|
7222
|
-
const debianPatterns = ["debian", "ubuntu", "node", "python"];
|
|
7223
|
-
return debianPatterns.some((pattern) => lower.includes(pattern));
|
|
7224
|
-
}
|
|
7225
|
-
function shellQuote3(s) {
|
|
7226
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
7227
|
-
}
|
|
7228
|
-
function createInstallerContext(containerId, image) {
|
|
7229
|
-
const packageManager = isDebianBased(image) ? "apt-get" : "apk";
|
|
7230
|
-
let archPromise = null;
|
|
7231
|
-
const ensuredTools = /* @__PURE__ */ new Set();
|
|
7232
|
-
let aptUpdated = false;
|
|
7233
|
-
const exec = async (command) => {
|
|
7234
|
-
try {
|
|
7235
|
-
const result = await spawn2("docker", [
|
|
7236
|
-
"exec",
|
|
7237
|
-
containerId,
|
|
7238
|
-
"sh",
|
|
7239
|
-
"-c",
|
|
7240
|
-
command
|
|
7241
|
-
]);
|
|
7242
|
-
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
7243
|
-
} catch (error) {
|
|
7244
|
-
const err = error;
|
|
7245
|
-
return {
|
|
7246
|
-
stdout: err.stdout ?? "",
|
|
7247
|
-
stderr: err.stderr ?? err.message ?? "",
|
|
7248
|
-
exitCode: err.exitCode ?? 1
|
|
7249
|
-
};
|
|
7250
|
-
}
|
|
7251
|
-
};
|
|
7252
|
-
const arch = async () => {
|
|
7253
|
-
if (!archPromise) {
|
|
7254
|
-
const attempt = (async () => {
|
|
7255
|
-
const result = await exec("uname -m");
|
|
7256
|
-
if (result.exitCode !== 0) {
|
|
7257
|
-
throw new DockerSandboxError(
|
|
7258
|
-
`Failed to detect container architecture: ${result.stderr}`,
|
|
7259
|
-
containerId
|
|
7260
|
-
);
|
|
7261
|
-
}
|
|
7262
|
-
return result.stdout.trim();
|
|
7263
|
-
})();
|
|
7264
|
-
archPromise = attempt.catch((err) => {
|
|
7265
|
-
archPromise = null;
|
|
7266
|
-
throw err;
|
|
7267
|
-
});
|
|
7268
|
-
}
|
|
7269
|
-
return archPromise;
|
|
7270
|
-
};
|
|
7271
|
-
const installPackages = async (packages) => {
|
|
7272
|
-
if (packages.length === 0) return;
|
|
7273
|
-
const quoted = packages.map(shellQuote3).join(" ");
|
|
7274
|
-
let cmd;
|
|
7275
|
-
if (packageManager === "apt-get") {
|
|
7276
|
-
cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
|
|
7277
|
-
} else {
|
|
7278
|
-
cmd = `apk add --no-cache ${quoted}`;
|
|
7279
|
-
}
|
|
7280
|
-
const result = await exec(cmd);
|
|
7281
|
-
if (result.exitCode !== 0) {
|
|
7282
|
-
throw new PackageInstallError(
|
|
7283
|
-
packages,
|
|
7284
|
-
image,
|
|
7285
|
-
packageManager,
|
|
7286
|
-
result.stderr,
|
|
7287
|
-
containerId
|
|
7288
|
-
);
|
|
7289
|
-
}
|
|
7290
|
-
if (packageManager === "apt-get") aptUpdated = true;
|
|
7291
|
-
};
|
|
7292
|
-
const ensureTool = async (checkName, installName) => {
|
|
7293
|
-
const cacheKey = installName ?? checkName;
|
|
7294
|
-
if (ensuredTools.has(cacheKey)) return;
|
|
7295
|
-
const check = await exec(`which ${shellQuote3(checkName)}`);
|
|
7296
|
-
if (check.exitCode === 0) {
|
|
7297
|
-
ensuredTools.add(cacheKey);
|
|
7298
|
-
return;
|
|
7299
|
-
}
|
|
7300
|
-
await installPackages([installName ?? checkName]);
|
|
7301
|
-
ensuredTools.add(cacheKey);
|
|
7302
|
-
};
|
|
7303
|
-
return {
|
|
7304
|
-
containerId,
|
|
7305
|
-
image,
|
|
7306
|
-
packageManager,
|
|
7307
|
-
arch,
|
|
7308
|
-
exec,
|
|
7309
|
-
installPackages,
|
|
7310
|
-
ensureTool
|
|
7311
|
-
};
|
|
7312
|
-
}
|
|
7313
|
-
|
|
7314
|
-
// packages/context/src/lib/sandbox/docker-sandbox.ts
|
|
7979
|
+
import { Readable as Readable2 } from "node:stream";
|
|
7315
7980
|
function isDockerfileOptions(opts) {
|
|
7316
7981
|
return "dockerfile" in opts;
|
|
7317
7982
|
}
|
|
@@ -7752,560 +8417,314 @@ function buildDockerExecFlags(options) {
|
|
|
7752
8417
|
flags.push("-w", options.cwd);
|
|
7753
8418
|
}
|
|
7754
8419
|
if (options?.env) {
|
|
7755
|
-
for (const [key, value] of Object.entries(options.env)) {
|
|
7756
|
-
validateEnvKey2(key);
|
|
7757
|
-
flags.push("-e", `${key}=${value}`);
|
|
7758
|
-
}
|
|
7759
|
-
}
|
|
7760
|
-
return flags;
|
|
7761
|
-
}
|
|
7762
|
-
function toSandboxProcess(child, abortSignal) {
|
|
7763
|
-
if (!child.stdout || !child.stderr) {
|
|
7764
|
-
child.kill("SIGKILL");
|
|
7765
|
-
throw new DockerSandboxError("docker exec child missing stdio streams");
|
|
7766
|
-
}
|
|
7767
|
-
const onAbort = abortSignal ? () => child.kill("SIGKILL") : void 0;
|
|
7768
|
-
if (abortSignal && onAbort) {
|
|
7769
|
-
if (abortSignal.aborted) onAbort();
|
|
7770
|
-
else abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
7771
|
-
}
|
|
7772
|
-
return {
|
|
7773
|
-
stdout: Readable2.toWeb(child.stdout),
|
|
7774
|
-
stderr: Readable2.toWeb(child.stderr),
|
|
7775
|
-
exit: new Promise((resolve4, reject) => {
|
|
7776
|
-
const settle = () => {
|
|
7777
|
-
child.removeListener("exit", onExitEvent);
|
|
7778
|
-
child.removeListener("error", onError);
|
|
7779
|
-
if (abortSignal && onAbort) {
|
|
7780
|
-
abortSignal.removeEventListener("abort", onAbort);
|
|
7781
|
-
}
|
|
7782
|
-
};
|
|
7783
|
-
const onError = (err) => {
|
|
7784
|
-
settle();
|
|
7785
|
-
reject(err);
|
|
7786
|
-
};
|
|
7787
|
-
const onExitEvent = (code, exitSignal) => {
|
|
7788
|
-
settle();
|
|
7789
|
-
resolve4({ code, signal: exitSignal, success: code === 0 });
|
|
7790
|
-
};
|
|
7791
|
-
child.on("exit", onExitEvent);
|
|
7792
|
-
child.on("error", onError);
|
|
7793
|
-
})
|
|
7794
|
-
};
|
|
7795
|
-
}
|
|
7796
|
-
var RuntimeStrategy = class extends DockerSandboxStrategy {
|
|
7797
|
-
image;
|
|
7798
|
-
installers;
|
|
7799
|
-
constructor(args = {}) {
|
|
7800
|
-
super({
|
|
7801
|
-
volumes: args.volumes,
|
|
7802
|
-
resources: args.resources,
|
|
7803
|
-
env: args.env,
|
|
7804
|
-
name: args.name,
|
|
7805
|
-
command: args.command
|
|
7806
|
-
});
|
|
7807
|
-
this.image = args.image ?? "alpine:latest";
|
|
7808
|
-
this.installers = args.installers ?? [];
|
|
7809
|
-
}
|
|
7810
|
-
async getImage() {
|
|
7811
|
-
return this.image;
|
|
7812
|
-
}
|
|
7813
|
-
async configure() {
|
|
7814
|
-
const ctx = createInstallerContext(this.context.containerId, this.image);
|
|
7815
|
-
for (const installer of this.installers) {
|
|
7816
|
-
await installer.install(ctx);
|
|
7817
|
-
}
|
|
7818
|
-
}
|
|
7819
|
-
};
|
|
7820
|
-
var DockerfileStrategy = class extends DockerSandboxStrategy {
|
|
7821
|
-
imageTag;
|
|
7822
|
-
dockerfile;
|
|
7823
|
-
dockerContext;
|
|
7824
|
-
constructor(args) {
|
|
7825
|
-
super({
|
|
7826
|
-
volumes: args.volumes,
|
|
7827
|
-
resources: args.resources,
|
|
7828
|
-
env: args.env,
|
|
7829
|
-
name: args.name,
|
|
7830
|
-
command: args.command
|
|
7831
|
-
});
|
|
7832
|
-
this.dockerfile = args.dockerfile;
|
|
7833
|
-
this.dockerContext = args.context ?? ".";
|
|
7834
|
-
this.imageTag = this.computeImageTag();
|
|
7835
|
-
}
|
|
7836
|
-
computeImageTag() {
|
|
7837
|
-
const content = this.isInlineDockerfile() ? this.dockerfile : readFileSync2(this.dockerfile, "utf-8");
|
|
7838
|
-
const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
7839
|
-
return `sandbox-${hash}`;
|
|
7840
|
-
}
|
|
7841
|
-
isInlineDockerfile() {
|
|
7842
|
-
return this.dockerfile.includes("\n");
|
|
7843
|
-
}
|
|
7844
|
-
async getImage() {
|
|
7845
|
-
const exists = await this.imageExists();
|
|
7846
|
-
if (!exists) {
|
|
7847
|
-
await this.buildImage();
|
|
7848
|
-
}
|
|
7849
|
-
return this.imageTag;
|
|
7850
|
-
}
|
|
7851
|
-
async configure() {
|
|
7852
|
-
}
|
|
7853
|
-
async imageExists() {
|
|
7854
|
-
try {
|
|
7855
|
-
await spawn3("docker", ["image", "inspect", this.imageTag]);
|
|
7856
|
-
return true;
|
|
7857
|
-
} catch {
|
|
7858
|
-
return false;
|
|
7859
|
-
}
|
|
7860
|
-
}
|
|
7861
|
-
async buildImage() {
|
|
7862
|
-
try {
|
|
7863
|
-
if (this.isInlineDockerfile()) {
|
|
7864
|
-
const buildCmd = `echo '${this.dockerfile.replace(/'/g, "'\\''")}' | docker build -t ${this.imageTag} -f - ${this.dockerContext}`;
|
|
7865
|
-
await spawn3("sh", ["-c", buildCmd]);
|
|
7866
|
-
} else {
|
|
7867
|
-
await spawn3("docker", [
|
|
7868
|
-
"build",
|
|
7869
|
-
"-t",
|
|
7870
|
-
this.imageTag,
|
|
7871
|
-
"-f",
|
|
7872
|
-
this.dockerfile,
|
|
7873
|
-
this.dockerContext
|
|
7874
|
-
]);
|
|
7875
|
-
}
|
|
7876
|
-
} catch (error) {
|
|
7877
|
-
const err = error;
|
|
7878
|
-
throw new DockerfileBuildError(err.stderr || err.message);
|
|
7879
|
-
}
|
|
7880
|
-
}
|
|
7881
|
-
};
|
|
7882
|
-
var ComposeStrategy = class extends DockerSandboxStrategy {
|
|
7883
|
-
projectName;
|
|
7884
|
-
composeFile;
|
|
7885
|
-
service;
|
|
7886
|
-
constructor(args) {
|
|
7887
|
-
super({ resources: args.resources });
|
|
7888
|
-
this.composeFile = args.compose;
|
|
7889
|
-
this.service = args.service;
|
|
7890
|
-
this.projectName = this.computeProjectName();
|
|
7891
|
-
}
|
|
7892
|
-
computeProjectName() {
|
|
7893
|
-
const content = readFileSync2(this.composeFile, "utf-8");
|
|
7894
|
-
const hash = createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
7895
|
-
return `sandbox-${hash}`;
|
|
7896
|
-
}
|
|
7897
|
-
async getImage() {
|
|
7898
|
-
return "";
|
|
7899
|
-
}
|
|
7900
|
-
async startContainer(_image, _containerId) {
|
|
7901
|
-
try {
|
|
7902
|
-
await spawn3("docker", [
|
|
7903
|
-
"compose",
|
|
7904
|
-
"-f",
|
|
7905
|
-
this.composeFile,
|
|
7906
|
-
"-p",
|
|
7907
|
-
this.projectName,
|
|
7908
|
-
"up",
|
|
7909
|
-
"-d"
|
|
7910
|
-
]);
|
|
7911
|
-
} catch (error) {
|
|
7912
|
-
const err = error;
|
|
7913
|
-
if (err.stderr?.includes("Cannot connect")) {
|
|
7914
|
-
throw new DockerNotAvailableError();
|
|
7915
|
-
}
|
|
7916
|
-
throw new ComposeStartError(this.composeFile, err.stderr || err.message);
|
|
7917
|
-
}
|
|
7918
|
-
}
|
|
7919
|
-
defaultContainerId() {
|
|
7920
|
-
return this.projectName;
|
|
7921
|
-
}
|
|
7922
|
-
async configure() {
|
|
7923
|
-
}
|
|
7924
|
-
async exec(command, options) {
|
|
7925
|
-
try {
|
|
7926
|
-
const result = await spawn3(
|
|
7927
|
-
"docker",
|
|
7928
|
-
[
|
|
7929
|
-
"compose",
|
|
7930
|
-
"-f",
|
|
7931
|
-
this.composeFile,
|
|
7932
|
-
"-p",
|
|
7933
|
-
this.projectName,
|
|
7934
|
-
"exec",
|
|
7935
|
-
"-T",
|
|
7936
|
-
this.service,
|
|
7937
|
-
"sh",
|
|
7938
|
-
"-c",
|
|
7939
|
-
command
|
|
7940
|
-
],
|
|
7941
|
-
{ signal: options?.signal }
|
|
7942
|
-
);
|
|
7943
|
-
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
7944
|
-
} catch (error) {
|
|
7945
|
-
const err = error;
|
|
7946
|
-
return {
|
|
7947
|
-
stdout: err.stdout || "",
|
|
7948
|
-
stderr: err.stderr || err.message || "",
|
|
7949
|
-
exitCode: err.exitCode ?? 1
|
|
7950
|
-
};
|
|
7951
|
-
}
|
|
7952
|
-
}
|
|
7953
|
-
spawnProcess(command, options) {
|
|
7954
|
-
const child = childSpawn("docker", [
|
|
7955
|
-
"compose",
|
|
7956
|
-
"-f",
|
|
7957
|
-
this.composeFile,
|
|
7958
|
-
"-p",
|
|
7959
|
-
this.projectName,
|
|
7960
|
-
"exec",
|
|
7961
|
-
"-T",
|
|
7962
|
-
...buildDockerExecFlags(options),
|
|
7963
|
-
this.service,
|
|
7964
|
-
"sh",
|
|
7965
|
-
"-c",
|
|
7966
|
-
command
|
|
7967
|
-
]);
|
|
7968
|
-
return toSandboxProcess(child, options?.signal);
|
|
7969
|
-
}
|
|
7970
|
-
async stopContainer(_containerId) {
|
|
7971
|
-
try {
|
|
7972
|
-
await spawn3("docker", [
|
|
7973
|
-
"compose",
|
|
7974
|
-
"-f",
|
|
7975
|
-
this.composeFile,
|
|
7976
|
-
"-p",
|
|
7977
|
-
this.projectName,
|
|
7978
|
-
"down"
|
|
7979
|
-
]);
|
|
7980
|
-
} catch {
|
|
7981
|
-
}
|
|
7982
|
-
}
|
|
7983
|
-
};
|
|
7984
|
-
async function createDockerSandbox(options = {}) {
|
|
7985
|
-
let strategy;
|
|
7986
|
-
if (isComposeOptions(options)) {
|
|
7987
|
-
strategy = new ComposeStrategy({
|
|
7988
|
-
compose: options.compose,
|
|
7989
|
-
service: options.service,
|
|
7990
|
-
resources: options.resources
|
|
7991
|
-
});
|
|
7992
|
-
} else if (isDockerfileOptions(options)) {
|
|
7993
|
-
strategy = new DockerfileStrategy({
|
|
7994
|
-
dockerfile: options.dockerfile,
|
|
7995
|
-
context: options.context,
|
|
7996
|
-
volumes: options.volumes,
|
|
7997
|
-
resources: options.resources,
|
|
7998
|
-
env: options.env,
|
|
7999
|
-
name: options.name,
|
|
8000
|
-
command: options.command
|
|
8001
|
-
});
|
|
8002
|
-
} else {
|
|
8003
|
-
strategy = new RuntimeStrategy({
|
|
8004
|
-
image: options.image,
|
|
8005
|
-
installers: options.installers,
|
|
8006
|
-
volumes: options.volumes,
|
|
8007
|
-
resources: options.resources,
|
|
8008
|
-
env: options.env,
|
|
8009
|
-
name: options.name,
|
|
8010
|
-
command: options.command
|
|
8011
|
-
});
|
|
8420
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
8421
|
+
validateEnvKey2(key);
|
|
8422
|
+
flags.push("-e", `${key}=${value}`);
|
|
8423
|
+
}
|
|
8012
8424
|
}
|
|
8013
|
-
return
|
|
8425
|
+
return flags;
|
|
8014
8426
|
}
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
}
|
|
8020
|
-
|
|
8427
|
+
function toSandboxProcess(child, abortSignal) {
|
|
8428
|
+
if (!child.stdout || !child.stderr) {
|
|
8429
|
+
child.kill("SIGKILL");
|
|
8430
|
+
throw new DockerSandboxError("docker exec child missing stdio streams");
|
|
8431
|
+
}
|
|
8432
|
+
const onAbort = abortSignal ? () => child.kill("SIGKILL") : void 0;
|
|
8433
|
+
if (abortSignal && onAbort) {
|
|
8434
|
+
if (abortSignal.aborted) onAbort();
|
|
8435
|
+
else abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
8021
8436
|
}
|
|
8022
|
-
}
|
|
8023
|
-
|
|
8024
|
-
// packages/context/src/lib/sandbox/gcs.ts
|
|
8025
|
-
function gcs(options) {
|
|
8026
8437
|
return {
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
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
|
+
})
|
|
8031
8459
|
};
|
|
8032
8460
|
}
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8461
|
+
var RuntimeStrategy = class extends DockerSandboxStrategy {
|
|
8462
|
+
image;
|
|
8463
|
+
installers;
|
|
8464
|
+
constructor(args = {}) {
|
|
8465
|
+
super({
|
|
8466
|
+
volumes: args.volumes,
|
|
8467
|
+
resources: args.resources,
|
|
8468
|
+
env: args.env,
|
|
8469
|
+
name: args.name,
|
|
8470
|
+
command: args.command
|
|
8471
|
+
});
|
|
8472
|
+
this.image = args.image ?? "alpine:latest";
|
|
8473
|
+
this.installers = args.installers ?? [];
|
|
8046
8474
|
}
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
return new PackageInstaller(packages);
|
|
8050
|
-
}
|
|
8051
|
-
|
|
8052
|
-
// packages/context/src/lib/sandbox/installers/url-binary.ts
|
|
8053
|
-
var UrlBinaryInstaller = class extends Installer {
|
|
8054
|
-
kind;
|
|
8055
|
-
options;
|
|
8056
|
-
constructor(options) {
|
|
8057
|
-
super();
|
|
8058
|
-
this.options = options;
|
|
8059
|
-
this.kind = `url-binary:${options.name}`;
|
|
8475
|
+
async getImage() {
|
|
8476
|
+
return this.image;
|
|
8060
8477
|
}
|
|
8061
|
-
async
|
|
8062
|
-
const
|
|
8063
|
-
|
|
8064
|
-
ctx
|
|
8065
|
-
|
|
8066
|
-
url,
|
|
8067
|
-
this.options.binaryPath
|
|
8068
|
-
);
|
|
8478
|
+
async configure() {
|
|
8479
|
+
const ctx = createInstallerContext(this.context.containerId, this.image);
|
|
8480
|
+
for (const installer of this.installers) {
|
|
8481
|
+
await installer.install(ctx);
|
|
8482
|
+
}
|
|
8069
8483
|
}
|
|
8070
8484
|
};
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
containerId: ctx.containerId
|
|
8485
|
+
var DockerfileStrategy = class extends DockerSandboxStrategy {
|
|
8486
|
+
imageTag;
|
|
8487
|
+
dockerfile;
|
|
8488
|
+
dockerContext;
|
|
8489
|
+
showBuildLogs;
|
|
8490
|
+
constructor(args) {
|
|
8491
|
+
super({
|
|
8492
|
+
volumes: args.volumes,
|
|
8493
|
+
resources: args.resources,
|
|
8494
|
+
env: args.env,
|
|
8495
|
+
name: args.name,
|
|
8496
|
+
command: args.command
|
|
8084
8497
|
});
|
|
8498
|
+
this.dockerfile = args.dockerfile;
|
|
8499
|
+
this.dockerContext = args.context ?? ".";
|
|
8500
|
+
this.showBuildLogs = args.showBuildLogs ?? false;
|
|
8501
|
+
this.imageTag = this.computeImageTag();
|
|
8085
8502
|
}
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
const isTarGz = url.endsWith(".tar.gz") || url.endsWith(".tgz");
|
|
8091
|
-
const installCmd = isTarGz ? buildTarGzInstallCmd(name, url, binaryPath ?? name) : buildRawInstallCmd(name, url);
|
|
8092
|
-
const result = await ctx.exec(installCmd);
|
|
8093
|
-
if (result.exitCode !== 0) {
|
|
8094
|
-
throw new InstallError({
|
|
8095
|
-
target: name,
|
|
8096
|
-
source,
|
|
8097
|
-
url,
|
|
8098
|
-
reason: result.stderr,
|
|
8099
|
-
containerId: ctx.containerId
|
|
8100
|
-
});
|
|
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}`;
|
|
8101
8507
|
}
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
return `
|
|
8105
|
-
set -e
|
|
8106
|
-
NAME=${shellQuote3(name)}
|
|
8107
|
-
URL=${shellQuote3(url)}
|
|
8108
|
-
BIN_IN_ARCHIVE=${shellQuote3(binaryPathInArchive)}
|
|
8109
|
-
TMPDIR=$(mktemp -d)
|
|
8110
|
-
cd "$TMPDIR"
|
|
8111
|
-
curl -fsSL "$URL" -o archive.tar.gz
|
|
8112
|
-
tar -xzf archive.tar.gz
|
|
8113
|
-
BINARY_FILE=$(find . -name "$BIN_IN_ARCHIVE" -o -name "$NAME" | head -1)
|
|
8114
|
-
if [ -z "$BINARY_FILE" ]; then
|
|
8115
|
-
echo "Binary not found in archive. Contents:" >&2
|
|
8116
|
-
find . -type f >&2
|
|
8117
|
-
exit 1
|
|
8118
|
-
fi
|
|
8119
|
-
chmod +x "$BINARY_FILE"
|
|
8120
|
-
mv "$BINARY_FILE" "/usr/local/bin/$NAME"
|
|
8121
|
-
cd /
|
|
8122
|
-
rm -rf "$TMPDIR"
|
|
8123
|
-
`;
|
|
8124
|
-
}
|
|
8125
|
-
function buildRawInstallCmd(name, url) {
|
|
8126
|
-
return `
|
|
8127
|
-
NAME=${shellQuote3(name)}
|
|
8128
|
-
URL=${shellQuote3(url)}
|
|
8129
|
-
curl -fsSL "$URL" -o "/usr/local/bin/$NAME"
|
|
8130
|
-
chmod +x "/usr/local/bin/$NAME"
|
|
8131
|
-
`;
|
|
8132
|
-
}
|
|
8133
|
-
|
|
8134
|
-
// packages/context/src/lib/sandbox/installers/npm.ts
|
|
8135
|
-
var NODE_BINARIES = ["node", "npm"];
|
|
8136
|
-
var ENSURE_RUNTIME_HINT = "Pass `{ ensureRuntime: true }` to auto-install, or add it via `pkg([...])`.";
|
|
8137
|
-
var NpmInstaller = class extends Installer {
|
|
8138
|
-
kind;
|
|
8139
|
-
packageName;
|
|
8140
|
-
options;
|
|
8141
|
-
constructor(packageName, options = {}) {
|
|
8142
|
-
super();
|
|
8143
|
-
this.packageName = packageName;
|
|
8144
|
-
this.options = options;
|
|
8145
|
-
this.kind = `npm:${packageName}`;
|
|
8508
|
+
isInlineDockerfile() {
|
|
8509
|
+
return this.dockerfile.includes("\n");
|
|
8146
8510
|
}
|
|
8147
|
-
async
|
|
8148
|
-
await
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
if (result.exitCode !== 0) {
|
|
8152
|
-
throw new InstallError({
|
|
8153
|
-
target: this.packageName,
|
|
8154
|
-
source: "npm",
|
|
8155
|
-
reason: result.stderr,
|
|
8156
|
-
containerId: ctx.containerId
|
|
8157
|
-
});
|
|
8511
|
+
async getImage() {
|
|
8512
|
+
const exists = await this.imageExists();
|
|
8513
|
+
if (!exists) {
|
|
8514
|
+
await this.buildImage();
|
|
8158
8515
|
}
|
|
8516
|
+
return this.imageTag;
|
|
8159
8517
|
}
|
|
8160
|
-
|
|
8161
|
-
function npm(packageName, options) {
|
|
8162
|
-
return new NpmInstaller(packageName, options);
|
|
8163
|
-
}
|
|
8164
|
-
async function ensureNodeRuntime(ctx, ensure) {
|
|
8165
|
-
if (ensure) {
|
|
8166
|
-
await ctx.ensureTool("node", "nodejs");
|
|
8167
|
-
await ctx.ensureTool("npm");
|
|
8168
|
-
return;
|
|
8518
|
+
async configure() {
|
|
8169
8519
|
}
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
);
|
|
8520
|
+
async imageExists() {
|
|
8521
|
+
try {
|
|
8522
|
+
await spawn3("docker", ["image", "inspect", this.imageTag]);
|
|
8523
|
+
return true;
|
|
8524
|
+
} catch {
|
|
8525
|
+
return false;
|
|
8526
|
+
}
|
|
8178
8527
|
}
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8528
|
+
async buildImage() {
|
|
8529
|
+
try {
|
|
8530
|
+
if (this.isInlineDockerfile()) {
|
|
8531
|
+
const buildCmd = `echo '${this.dockerfile.replace(/'/g, "'\\''")}' | docker build -t ${this.imageTag} -f - ${this.dockerContext}`;
|
|
8532
|
+
if (this.showBuildLogs) {
|
|
8533
|
+
await this.runStreamed("sh", ["-c", buildCmd]);
|
|
8534
|
+
} else {
|
|
8535
|
+
await spawn3("sh", ["-c", buildCmd]);
|
|
8536
|
+
}
|
|
8537
|
+
} else {
|
|
8538
|
+
const args = [
|
|
8539
|
+
"build",
|
|
8540
|
+
"-t",
|
|
8541
|
+
this.imageTag,
|
|
8542
|
+
"-f",
|
|
8543
|
+
this.dockerfile,
|
|
8544
|
+
this.dockerContext
|
|
8545
|
+
];
|
|
8546
|
+
if (this.showBuildLogs) {
|
|
8547
|
+
await this.runStreamed("docker", args);
|
|
8548
|
+
} else {
|
|
8549
|
+
await spawn3("docker", args);
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
} catch (error) {
|
|
8553
|
+
const err = error;
|
|
8554
|
+
throw new DockerfileBuildError(err.stderr || err.message);
|
|
8555
|
+
}
|
|
8198
8556
|
}
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
)
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8557
|
+
/**
|
|
8558
|
+
* Run a build command with stdio inherited so its output streams live to the
|
|
8559
|
+
* parent terminal. On failure the build error is already on screen; the
|
|
8560
|
+
* rejection just carries the exit code.
|
|
8561
|
+
*/
|
|
8562
|
+
runStreamed(command, args) {
|
|
8563
|
+
return new Promise((resolve4, reject) => {
|
|
8564
|
+
const child = childSpawn(command, args, { stdio: "inherit" });
|
|
8565
|
+
child.once("error", reject);
|
|
8566
|
+
child.once(
|
|
8567
|
+
"exit",
|
|
8568
|
+
(code) => code === 0 ? resolve4() : reject(
|
|
8569
|
+
new Error(
|
|
8570
|
+
`docker build exited with code ${code} (see build output above)`
|
|
8571
|
+
)
|
|
8572
|
+
)
|
|
8573
|
+
);
|
|
8212
8574
|
});
|
|
8213
8575
|
}
|
|
8214
8576
|
};
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8577
|
+
var ComposeStrategy = class extends DockerSandboxStrategy {
|
|
8578
|
+
projectName;
|
|
8579
|
+
composeFile;
|
|
8580
|
+
service;
|
|
8581
|
+
constructor(args) {
|
|
8582
|
+
super({ resources: args.resources });
|
|
8583
|
+
this.composeFile = args.compose;
|
|
8584
|
+
this.service = args.service;
|
|
8585
|
+
this.projectName = this.computeProjectName();
|
|
8221
8586
|
}
|
|
8222
|
-
|
|
8223
|
-
|
|
8587
|
+
computeProjectName() {
|
|
8588
|
+
const content = readFileSync2(this.composeFile, "utf-8");
|
|
8589
|
+
const hash = createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
8590
|
+
return `sandbox-${hash}`;
|
|
8224
8591
|
}
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
function resolveName(binary, options) {
|
|
8228
|
-
if (options.name) return options.name;
|
|
8229
|
-
const base = basename(binary);
|
|
8230
|
-
const dot = base.lastIndexOf(".");
|
|
8231
|
-
return dot > 0 ? base.slice(0, dot) : base;
|
|
8232
|
-
}
|
|
8233
|
-
|
|
8234
|
-
// packages/context/src/lib/sandbox/installers/pip.ts
|
|
8235
|
-
var PYTHON_BINARIES = ["python3", "pip3"];
|
|
8236
|
-
var PipInstaller = class extends Installer {
|
|
8237
|
-
kind;
|
|
8238
|
-
packageName;
|
|
8239
|
-
options;
|
|
8240
|
-
constructor(packageName, options = {}) {
|
|
8241
|
-
super();
|
|
8242
|
-
this.packageName = packageName;
|
|
8243
|
-
this.options = options;
|
|
8244
|
-
this.kind = `pip:${packageName}`;
|
|
8592
|
+
async getImage() {
|
|
8593
|
+
return "";
|
|
8245
8594
|
}
|
|
8246
|
-
async
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8595
|
+
async startContainer(_image, _containerId) {
|
|
8596
|
+
try {
|
|
8597
|
+
await spawn3("docker", [
|
|
8598
|
+
"compose",
|
|
8599
|
+
"-f",
|
|
8600
|
+
this.composeFile,
|
|
8601
|
+
"-p",
|
|
8602
|
+
this.projectName,
|
|
8603
|
+
"up",
|
|
8604
|
+
"-d"
|
|
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);
|
|
8258
8612
|
}
|
|
8259
8613
|
}
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
return new PipInstaller(packageName, options);
|
|
8263
|
-
}
|
|
8264
|
-
async function ensurePythonRuntime(ctx, ensure) {
|
|
8265
|
-
if (ensure) {
|
|
8266
|
-
const pipPackage = ctx.packageManager === "apk" ? "py3-pip" : "python3-pip";
|
|
8267
|
-
await ctx.ensureTool("python3");
|
|
8268
|
-
await ctx.ensureTool("pip3", pipPackage);
|
|
8269
|
-
return;
|
|
8614
|
+
defaultContainerId() {
|
|
8615
|
+
return this.projectName;
|
|
8270
8616
|
}
|
|
8271
|
-
|
|
8272
|
-
if (check.exitCode !== 0) {
|
|
8273
|
-
throw new MissingRuntimeError(
|
|
8274
|
-
"pip",
|
|
8275
|
-
PYTHON_BINARIES,
|
|
8276
|
-
"Pass `{ ensureRuntime: true }` to auto-install, or add via `pkg([...])` (Alpine: `python3 py3-pip`; Debian: `python3 python3-pip`).",
|
|
8277
|
-
ctx.containerId
|
|
8278
|
-
);
|
|
8617
|
+
async configure() {
|
|
8279
8618
|
}
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8619
|
+
async exec(command, options) {
|
|
8620
|
+
try {
|
|
8621
|
+
const result = await spawn3(
|
|
8622
|
+
"docker",
|
|
8623
|
+
[
|
|
8624
|
+
"compose",
|
|
8625
|
+
"-f",
|
|
8626
|
+
this.composeFile,
|
|
8627
|
+
"-p",
|
|
8628
|
+
this.projectName,
|
|
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
|
+
}
|
|
8293
8647
|
}
|
|
8294
|
-
|
|
8295
|
-
const
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
this.
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8648
|
+
spawnProcess(command, options) {
|
|
8649
|
+
const child = childSpawn("docker", [
|
|
8650
|
+
"compose",
|
|
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);
|
|
8664
|
+
}
|
|
8665
|
+
async stopContainer(_containerId) {
|
|
8666
|
+
try {
|
|
8667
|
+
await spawn3("docker", [
|
|
8668
|
+
"compose",
|
|
8669
|
+
"-f",
|
|
8670
|
+
this.composeFile,
|
|
8671
|
+
"-p",
|
|
8672
|
+
this.projectName,
|
|
8673
|
+
"down"
|
|
8674
|
+
]);
|
|
8675
|
+
} catch {
|
|
8676
|
+
}
|
|
8305
8677
|
}
|
|
8306
8678
|
};
|
|
8307
|
-
function
|
|
8308
|
-
|
|
8679
|
+
async function createDockerSandbox(options = {}) {
|
|
8680
|
+
let strategy;
|
|
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();
|
|
8710
|
+
}
|
|
8711
|
+
async function useSandbox(options, fn) {
|
|
8712
|
+
const sandbox = await createDockerSandbox(options);
|
|
8713
|
+
try {
|
|
8714
|
+
return await fn(sandbox);
|
|
8715
|
+
} finally {
|
|
8716
|
+
await sandbox.dispose();
|
|
8717
|
+
}
|
|
8718
|
+
}
|
|
8719
|
+
|
|
8720
|
+
// packages/context/src/lib/sandbox/gcs.ts
|
|
8721
|
+
function gcs(options) {
|
|
8722
|
+
return {
|
|
8723
|
+
type: "bind",
|
|
8724
|
+
hostPath: options.hostPath,
|
|
8725
|
+
containerPath: options.mountPath,
|
|
8726
|
+
readOnly: options.readOnly ?? false
|
|
8727
|
+
};
|
|
8309
8728
|
}
|
|
8310
8729
|
|
|
8311
8730
|
// packages/context/src/lib/sandbox/subcommand.ts
|
|
@@ -11654,10 +12073,10 @@ export {
|
|
|
11654
12073
|
PostgresStreamStore,
|
|
11655
12074
|
PromiseResolver,
|
|
11656
12075
|
RuntimeStrategy,
|
|
11657
|
-
SnapshotFailedError,
|
|
11658
12076
|
SqlServerContextStore,
|
|
11659
12077
|
SqliteContextStore,
|
|
11660
12078
|
SqliteStreamStore,
|
|
12079
|
+
StraceUnavailableError,
|
|
11661
12080
|
StreamManager,
|
|
11662
12081
|
StreamStore,
|
|
11663
12082
|
TitleGenerator,
|
|
@@ -11682,7 +12101,7 @@ export {
|
|
|
11682
12101
|
applyInlineReminder,
|
|
11683
12102
|
applyPartReminder,
|
|
11684
12103
|
applyReminderToMessage,
|
|
11685
|
-
|
|
12104
|
+
applyRemindersToToolOutput,
|
|
11686
12105
|
asStaticWordPartText,
|
|
11687
12106
|
asStaticWordText,
|
|
11688
12107
|
assertCountSpec,
|
|
@@ -11726,7 +12145,7 @@ export {
|
|
|
11726
12145
|
executorContext,
|
|
11727
12146
|
explain,
|
|
11728
12147
|
fail,
|
|
11729
|
-
|
|
12148
|
+
first,
|
|
11730
12149
|
firstN,
|
|
11731
12150
|
fragment,
|
|
11732
12151
|
fromFragment,
|
|
@@ -11750,6 +12169,7 @@ export {
|
|
|
11750
12169
|
isFragmentObject,
|
|
11751
12170
|
isMessageFragment,
|
|
11752
12171
|
isRecord,
|
|
12172
|
+
isSyntheticSteerMessage,
|
|
11753
12173
|
lastAssistantLength,
|
|
11754
12174
|
loadSkillMetadata,
|
|
11755
12175
|
localeReminder,
|
|
@@ -11765,10 +12185,10 @@ export {
|
|
|
11765
12185
|
not,
|
|
11766
12186
|
npm,
|
|
11767
12187
|
nullUsage,
|
|
11768
|
-
observeSandboxFileEvents,
|
|
11769
12188
|
once,
|
|
11770
12189
|
or,
|
|
11771
12190
|
parseFrontmatter,
|
|
12191
|
+
parseStraceTrace,
|
|
11772
12192
|
pass,
|
|
11773
12193
|
persistedWriter,
|
|
11774
12194
|
persona,
|
|
@@ -11795,7 +12215,8 @@ export {
|
|
|
11795
12215
|
seasonChanged,
|
|
11796
12216
|
seasonReminder,
|
|
11797
12217
|
selfCritique,
|
|
11798
|
-
|
|
12218
|
+
selfTestStrace,
|
|
12219
|
+
shellQuote,
|
|
11799
12220
|
skills,
|
|
11800
12221
|
skillsReminder,
|
|
11801
12222
|
socraticPrompting,
|
|
@@ -11806,6 +12227,7 @@ export {
|
|
|
11806
12227
|
stripTextByRanges,
|
|
11807
12228
|
structuredOutput,
|
|
11808
12229
|
styleGuide,
|
|
12230
|
+
synthesizeSteerUserMessage,
|
|
11809
12231
|
temporalReminder,
|
|
11810
12232
|
term,
|
|
11811
12233
|
timeReminder,
|
|
@@ -11815,6 +12237,7 @@ export {
|
|
|
11815
12237
|
toolCallCount,
|
|
11816
12238
|
toolCalled,
|
|
11817
12239
|
toolFailed,
|
|
12240
|
+
traceFileChanges,
|
|
11818
12241
|
uploadSkills,
|
|
11819
12242
|
urlBinary,
|
|
11820
12243
|
usageExceeds,
|