@deepagents/context 3.0.1 → 4.0.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 +94 -4
- package/dist/browser.js +88 -146
- 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 +1085 -687
- 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 +89 -95
- 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/agent-os-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/bash-tool.d.ts +9 -4
- package/dist/lib/sandbox/bash-tool.d.ts.map +1 -1
- package/dist/lib/sandbox/daytona-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/docker-sandbox.d.ts +43 -0
- package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox/index.d.ts +2 -1
- package/dist/lib/sandbox/index.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/bin.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/index.d.ts +7 -7
- package/dist/lib/sandbox/installers/index.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/installer.d.ts +0 -5
- package/dist/lib/sandbox/installers/installer.d.ts.map +1 -1
- package/dist/lib/sandbox/installers/url-binary.d.ts.map +1 -1
- package/dist/lib/sandbox/shell-quote.d.ts +10 -0
- package/dist/lib/sandbox/shell-quote.d.ts.map +1 -0
- package/dist/lib/sandbox/strace/file-change.d.ts +21 -0
- package/dist/lib/sandbox/strace/file-change.d.ts.map +1 -0
- package/dist/lib/sandbox/strace/file-changes.d.ts +66 -0
- package/dist/lib/sandbox/strace/file-changes.d.ts.map +1 -0
- package/dist/lib/sandbox/strace/index.d.ts +89 -0
- package/dist/lib/sandbox/strace/index.d.ts.map +1 -0
- package/dist/lib/sandbox/strace/index.js +285 -0
- package/dist/lib/sandbox/strace/index.js.map +7 -0
- package/dist/lib/sandbox/types.d.ts +6 -9
- package/dist/lib/sandbox/types.d.ts.map +1 -1
- package/dist/lib/sandbox/virtual-sandbox.d.ts.map +1 -1
- package/dist/lib/save/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 +1 -7
- package/dist/lib/save/save-pipeline.d.ts.map +1 -1
- package/dist/lib/skills/skill-reminder.d.ts +2 -2
- package/dist/lib/skills/skill-reminder.d.ts.map +1 -1
- package/package.json +7 -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
|
}
|
|
@@ -1161,6 +1161,11 @@ function getReminderRanges(metadata) {
|
|
|
1161
1161
|
end: record.end
|
|
1162
1162
|
}));
|
|
1163
1163
|
}
|
|
1164
|
+
function getReminderOnceIds(message2) {
|
|
1165
|
+
const meta = message2.metadata;
|
|
1166
|
+
if (!isRecord(meta) || !Array.isArray(meta.onceIds)) return [];
|
|
1167
|
+
return meta.onceIds.filter((id) => typeof id === "string");
|
|
1168
|
+
}
|
|
1164
1169
|
function getReminderMetadataRecords(metadata) {
|
|
1165
1170
|
const reminders = metadata?.reminders;
|
|
1166
1171
|
if (!Array.isArray(reminders)) return [];
|
|
@@ -1168,35 +1173,12 @@ function getReminderMetadataRecords(metadata) {
|
|
|
1168
1173
|
(item) => isRecord(item) && typeof item.partIndex === "number" && typeof item.start === "number" && typeof item.end === "number"
|
|
1169
1174
|
);
|
|
1170
1175
|
}
|
|
1171
|
-
function reminderTargetOf(record) {
|
|
1172
|
-
return record.target === "tool-output" ? "tool-output" : "user";
|
|
1173
|
-
}
|
|
1174
1176
|
function normalizeReminderTarget(target) {
|
|
1175
1177
|
if (target === void 0 || target === "user") return "user";
|
|
1176
1178
|
if (target === "tool-output") return "tool-output";
|
|
1179
|
+
if (target === "steer") return "steer";
|
|
1177
1180
|
throw new Error(`Unsupported reminder target: ${String(target)}`);
|
|
1178
1181
|
}
|
|
1179
|
-
function isConditionalReminderOptions(options) {
|
|
1180
|
-
return options !== void 0 && "when" in options;
|
|
1181
|
-
}
|
|
1182
|
-
function isPromiseLike(value) {
|
|
1183
|
-
return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
|
|
1184
|
-
}
|
|
1185
|
-
function normalizeImmediateReminderText(textOrFragment) {
|
|
1186
|
-
if (isFragment(textOrFragment)) {
|
|
1187
|
-
return new XmlRenderer().render([textOrFragment]);
|
|
1188
|
-
}
|
|
1189
|
-
if (typeof textOrFragment === "string") {
|
|
1190
|
-
return textOrFragment;
|
|
1191
|
-
}
|
|
1192
|
-
return (ctx) => {
|
|
1193
|
-
const resolved = textOrFragment(ctx);
|
|
1194
|
-
if (isPromiseLike(resolved)) {
|
|
1195
|
-
throw new Error("Async reminder text requires a when predicate");
|
|
1196
|
-
}
|
|
1197
|
-
return resolved;
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
1182
|
function normalizeConditionalReminderText(textOrFragment) {
|
|
1201
1183
|
return isFragment(textOrFragment) ? new XmlRenderer().render([textOrFragment]) : textOrFragment;
|
|
1202
1184
|
}
|
|
@@ -1204,7 +1186,7 @@ function isOutputAvailableToolPart(part) {
|
|
|
1204
1186
|
return isStaticToolUIPart(part) && part.state === "output-available";
|
|
1205
1187
|
}
|
|
1206
1188
|
function isToolOutputReminderEnvelope(value) {
|
|
1207
|
-
return isRecord(value) && typeof value.systemReminder === "string";
|
|
1189
|
+
return isRecord(value) && "result" in value && typeof value.systemReminder === "string" && value.systemReminder.startsWith(SYSTEM_REMINDER_OPEN_TAG);
|
|
1208
1190
|
}
|
|
1209
1191
|
function stripTextByRanges(text, ranges) {
|
|
1210
1192
|
if (ranges.length === 0) {
|
|
@@ -1233,43 +1215,22 @@ function stripTextByRanges(text, ranges) {
|
|
|
1233
1215
|
return output.trimEnd();
|
|
1234
1216
|
}
|
|
1235
1217
|
function stripReminders(message2) {
|
|
1218
|
+
if (isSyntheticSteerMessage(message2)) {
|
|
1219
|
+
return stripSyntheticSteerMessage(message2);
|
|
1220
|
+
}
|
|
1236
1221
|
const reminderRecords = getReminderMetadataRecords(
|
|
1237
1222
|
isRecord(message2.metadata) ? message2.metadata : void 0
|
|
1238
1223
|
);
|
|
1239
1224
|
const rangesByPartIndex = /* @__PURE__ */ new Map();
|
|
1240
|
-
const toolRemindersByPartIndex = /* @__PURE__ */ new Map();
|
|
1241
1225
|
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
1226
|
const partRanges = rangesByPartIndex.get(range.partIndex) ?? [];
|
|
1249
1227
|
partRanges.push({ start: range.start, end: range.end });
|
|
1250
1228
|
rangesByPartIndex.set(range.partIndex, partRanges);
|
|
1251
1229
|
}
|
|
1252
1230
|
const strippedParts = message2.parts.flatMap((part, partIndex) => {
|
|
1253
1231
|
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
|
-
}
|
|
1232
|
+
if (isOutputAvailableToolPart(clonedPart) && isToolOutputReminderEnvelope(clonedPart.output)) {
|
|
1233
|
+
return [{ ...clonedPart, output: clonedPart.output.result }];
|
|
1273
1234
|
}
|
|
1274
1235
|
const ranges = rangesByPartIndex.get(partIndex);
|
|
1275
1236
|
if (clonedPart.type !== "text" || ranges === void 0) {
|
|
@@ -1409,64 +1370,12 @@ function applyReminderToMessage(message2, item, ctx) {
|
|
|
1409
1370
|
}
|
|
1410
1371
|
return item.asPart ? applyPartReminder(message2, resolved.text) : applyInlineReminder(message2, resolved.text);
|
|
1411
1372
|
}
|
|
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
|
-
}
|
|
1373
|
+
function applyRemindersToToolOutput(output, texts) {
|
|
1374
|
+
if (texts.length === 0) return output;
|
|
1375
|
+
return {
|
|
1376
|
+
result: output === void 0 ? null : output,
|
|
1377
|
+
systemReminder: formatTaggedReminder(texts.join("\n"))
|
|
1460
1378
|
};
|
|
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
1379
|
}
|
|
1471
1380
|
function mergeReminderMetadata(message2, addedReminders) {
|
|
1472
1381
|
if (addedReminders.length === 0) return;
|
|
@@ -1478,54 +1387,32 @@ function mergeReminderMetadata(message2, addedReminders) {
|
|
|
1478
1387
|
function reminder(textOrFragment, options) {
|
|
1479
1388
|
const target = normalizeReminderTarget(options?.target);
|
|
1480
1389
|
const asPart = target === "user" ? options?.asPart ?? false : false;
|
|
1481
|
-
if (
|
|
1482
|
-
|
|
1483
|
-
if (typeof text2 === "string") {
|
|
1484
|
-
assertReminderText(text2);
|
|
1485
|
-
}
|
|
1486
|
-
return {
|
|
1487
|
-
name: "reminder",
|
|
1488
|
-
data: null,
|
|
1489
|
-
metadata: {
|
|
1490
|
-
reminder: {
|
|
1491
|
-
text: text2,
|
|
1492
|
-
when: options.when,
|
|
1493
|
-
asPart,
|
|
1494
|
-
target
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
};
|
|
1390
|
+
if (options?.when === void 0 && target !== "user") {
|
|
1391
|
+
throw new Error(`Reminder target "${target}" requires a when predicate`);
|
|
1498
1392
|
}
|
|
1499
|
-
|
|
1500
|
-
throw new Error('Reminder target "tool-output" requires a when predicate');
|
|
1501
|
-
}
|
|
1502
|
-
const text = normalizeImmediateReminderText(textOrFragment);
|
|
1393
|
+
const text = normalizeConditionalReminderText(textOrFragment);
|
|
1503
1394
|
if (typeof text === "string") {
|
|
1504
1395
|
assertReminderText(text);
|
|
1505
1396
|
}
|
|
1506
1397
|
return {
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1398
|
+
name: "reminder",
|
|
1399
|
+
data: null,
|
|
1400
|
+
metadata: {
|
|
1401
|
+
reminder: {
|
|
1402
|
+
text,
|
|
1403
|
+
when: options?.when ?? (() => true),
|
|
1404
|
+
asPart,
|
|
1405
|
+
target
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1510
1408
|
};
|
|
1511
1409
|
}
|
|
1512
|
-
function user(content
|
|
1410
|
+
function user(content) {
|
|
1513
1411
|
const message2 = typeof content === "string" ? {
|
|
1514
1412
|
id: generateId2(),
|
|
1515
1413
|
role: "user",
|
|
1516
1414
|
parts: [{ type: "text", text: content }]
|
|
1517
1415
|
} : { ...content, role: "user", parts: [...content.parts] };
|
|
1518
|
-
if (reminders.length > 0) {
|
|
1519
|
-
const plainText = extractPlainText(message2);
|
|
1520
|
-
const added = [];
|
|
1521
|
-
for (const item of reminders) {
|
|
1522
|
-
const meta = applyReminderToMessage(message2, item, {
|
|
1523
|
-
content: plainText
|
|
1524
|
-
});
|
|
1525
|
-
if (meta) added.push(meta);
|
|
1526
|
-
}
|
|
1527
|
-
mergeReminderMetadata(message2, added);
|
|
1528
|
-
}
|
|
1529
1416
|
return {
|
|
1530
1417
|
id: message2.id,
|
|
1531
1418
|
name: "user",
|
|
@@ -1541,6 +1428,58 @@ function user(content, ...reminders) {
|
|
|
1541
1428
|
}
|
|
1542
1429
|
};
|
|
1543
1430
|
}
|
|
1431
|
+
function applyUserRemindersToMessage(message2, reminders) {
|
|
1432
|
+
if (reminders.length === 0) return;
|
|
1433
|
+
const plainText = extractPlainText(message2);
|
|
1434
|
+
const added = [];
|
|
1435
|
+
for (const item of reminders) {
|
|
1436
|
+
const meta = applyReminderToMessage(message2, item, { content: plainText });
|
|
1437
|
+
if (meta) added.push(meta);
|
|
1438
|
+
}
|
|
1439
|
+
mergeReminderMetadata(message2, added);
|
|
1440
|
+
}
|
|
1441
|
+
function synthesizeSteerUserMessage(text, firedAt, onceIds = []) {
|
|
1442
|
+
const texts = Array.isArray(text) ? text : [text];
|
|
1443
|
+
for (const value of texts) assertReminderText(value);
|
|
1444
|
+
return {
|
|
1445
|
+
id: generateId2(),
|
|
1446
|
+
role: "user",
|
|
1447
|
+
parts: texts.map((value) => ({
|
|
1448
|
+
type: "text",
|
|
1449
|
+
text: formatTaggedReminder(value)
|
|
1450
|
+
})),
|
|
1451
|
+
metadata: {
|
|
1452
|
+
synthetic: {
|
|
1453
|
+
source: "steer-reminder",
|
|
1454
|
+
firedAt,
|
|
1455
|
+
...onceIds.length > 0 ? { onceIds } : {}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
function isSyntheticSteerMessage(message2) {
|
|
1461
|
+
const meta = message2.metadata;
|
|
1462
|
+
if (!isRecord(meta)) return false;
|
|
1463
|
+
const synthetic = meta.synthetic;
|
|
1464
|
+
if (!isRecord(synthetic)) return false;
|
|
1465
|
+
return synthetic.source === "steer-reminder";
|
|
1466
|
+
}
|
|
1467
|
+
function stripSyntheticSteerMessage(message2) {
|
|
1468
|
+
const next = {
|
|
1469
|
+
...message2,
|
|
1470
|
+
parts: message2.parts.filter((part) => part.type !== "text")
|
|
1471
|
+
};
|
|
1472
|
+
if (isRecord(message2.metadata)) {
|
|
1473
|
+
const metadata = { ...message2.metadata };
|
|
1474
|
+
delete metadata.synthetic;
|
|
1475
|
+
if (Object.keys(metadata).length > 0) {
|
|
1476
|
+
next.metadata = metadata;
|
|
1477
|
+
} else {
|
|
1478
|
+
delete next.metadata;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return next;
|
|
1482
|
+
}
|
|
1544
1483
|
|
|
1545
1484
|
// packages/context/src/lib/guardrail.ts
|
|
1546
1485
|
function pass(part) {
|
|
@@ -1607,7 +1546,10 @@ var Agent = class _Agent {
|
|
|
1607
1546
|
sandbox;
|
|
1608
1547
|
constructor(options) {
|
|
1609
1548
|
this.#options = options;
|
|
1610
|
-
this.tools =
|
|
1549
|
+
this.tools = wrapToolsWithOutputReminders(
|
|
1550
|
+
{ ...options.sandbox.tools, ...options.tools || {} },
|
|
1551
|
+
options.context
|
|
1552
|
+
);
|
|
1611
1553
|
this.context = options.context;
|
|
1612
1554
|
this.model = options.model;
|
|
1613
1555
|
this.sandbox = options.sandbox;
|
|
@@ -1714,6 +1656,7 @@ var Agent = class _Agent {
|
|
|
1714
1656
|
config?.abortSignal
|
|
1715
1657
|
),
|
|
1716
1658
|
stopWhen: stepCountIs(200),
|
|
1659
|
+
prepareStep: context.createSteerPrepareStep(),
|
|
1717
1660
|
experimental_transform: config?.transform ?? smoothStream(),
|
|
1718
1661
|
tools: this.tools,
|
|
1719
1662
|
experimental_context: contextVariables,
|
|
@@ -1750,9 +1693,14 @@ var Agent = class _Agent {
|
|
|
1750
1693
|
generateId: assistantMsgId ? () => assistantMsgId : generateId3,
|
|
1751
1694
|
onStepFinish: async ({ responseMessage }) => {
|
|
1752
1695
|
if (!stepSaved) return;
|
|
1753
|
-
const
|
|
1754
|
-
|
|
1755
|
-
|
|
1696
|
+
const head = await context.headMessage();
|
|
1697
|
+
if (head?.name === "assistant") {
|
|
1698
|
+
await context.writeAssistantSegment(responseMessage);
|
|
1699
|
+
} else {
|
|
1700
|
+
const message2 = assistantMsgId ? { ...responseMessage, id: assistantMsgId } : responseMessage;
|
|
1701
|
+
context.set(assistant(message2));
|
|
1702
|
+
await context.save({ branch: false });
|
|
1703
|
+
}
|
|
1756
1704
|
stepSaved.resolve();
|
|
1757
1705
|
stepSaved = null;
|
|
1758
1706
|
},
|
|
@@ -1959,6 +1907,29 @@ ${details}
|
|
|
1959
1907
|
});
|
|
1960
1908
|
}
|
|
1961
1909
|
};
|
|
1910
|
+
function wrapToolsWithOutputReminders(tools, context) {
|
|
1911
|
+
if (!context) return tools;
|
|
1912
|
+
const wrapped = {};
|
|
1913
|
+
for (const [name, toolDef] of Object.entries(tools)) {
|
|
1914
|
+
const execute = toolDef.execute;
|
|
1915
|
+
if (typeof execute !== "function") {
|
|
1916
|
+
wrapped[name] = toolDef;
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
wrapped[name] = {
|
|
1920
|
+
...toolDef,
|
|
1921
|
+
execute: async (input, options) => {
|
|
1922
|
+
const result = await execute.call(toolDef, input, options);
|
|
1923
|
+
if (isAsyncIterable(result)) return result;
|
|
1924
|
+
return context.applyToolOutputReminders(result);
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
return wrapped;
|
|
1929
|
+
}
|
|
1930
|
+
function isAsyncIterable(value) {
|
|
1931
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
1932
|
+
}
|
|
1962
1933
|
function agent(options) {
|
|
1963
1934
|
return new Agent(options);
|
|
1964
1935
|
}
|
|
@@ -2062,6 +2033,7 @@ import { z as z2 } from "zod";
|
|
|
2062
2033
|
|
|
2063
2034
|
// packages/context/src/lib/engine.ts
|
|
2064
2035
|
import {
|
|
2036
|
+
convertToModelMessages as convertToModelMessages2,
|
|
2065
2037
|
generateId as generateId4,
|
|
2066
2038
|
validateUIMessages
|
|
2067
2039
|
} from "ai";
|
|
@@ -2097,30 +2069,42 @@ var ChainSummaryBuilder = class {
|
|
|
2097
2069
|
#lastMessage;
|
|
2098
2070
|
#lastAssistantMessage;
|
|
2099
2071
|
#lastAssistantMessages = [];
|
|
2072
|
+
#firedOnceIds = /* @__PURE__ */ new Set();
|
|
2100
2073
|
ingestStored(msg) {
|
|
2101
2074
|
this.#messageCount++;
|
|
2102
2075
|
if (msg.name === "assistant") {
|
|
2103
|
-
const
|
|
2076
|
+
const message3 = requireUIMessage(
|
|
2104
2077
|
msg.data,
|
|
2105
2078
|
`Stored assistant message "${msg.id}"`
|
|
2106
2079
|
);
|
|
2107
|
-
this.#lastAssistantMessage =
|
|
2108
|
-
this.#lastAssistantMessages.push(
|
|
2080
|
+
this.#lastAssistantMessage = message3;
|
|
2081
|
+
this.#lastAssistantMessages.push(message3);
|
|
2109
2082
|
return;
|
|
2110
2083
|
}
|
|
2111
2084
|
if (msg.name !== "user") {
|
|
2112
2085
|
return;
|
|
2113
2086
|
}
|
|
2114
|
-
|
|
2115
|
-
this.#lastMessageAt = msg.createdAt;
|
|
2116
|
-
this.#lastMessage = requireUIMessage(
|
|
2087
|
+
const message2 = requireUIMessage(
|
|
2117
2088
|
msg.data,
|
|
2118
2089
|
`Stored user message "${msg.id}"`
|
|
2119
2090
|
);
|
|
2091
|
+
if (isSyntheticSteerMessage(message2)) {
|
|
2092
|
+
for (const id of message2.metadata.synthetic.onceIds ?? []) {
|
|
2093
|
+
this.#firedOnceIds.add(id);
|
|
2094
|
+
}
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
for (const id of getReminderOnceIds(message2)) this.#firedOnceIds.add(id);
|
|
2098
|
+
this.#turn++;
|
|
2099
|
+
this.#lastMessageAt = msg.createdAt;
|
|
2100
|
+
this.#lastMessage = message2;
|
|
2120
2101
|
}
|
|
2121
2102
|
ingestPending(fragment2) {
|
|
2122
2103
|
this.#messageCount++;
|
|
2123
|
-
if (fragment2.name
|
|
2104
|
+
if (fragment2.name !== "user") return;
|
|
2105
|
+
const encoded = fragment2.codec?.encode();
|
|
2106
|
+
if (encoded && isSyntheticSteerMessage(encoded)) return;
|
|
2107
|
+
this.#turn++;
|
|
2124
2108
|
}
|
|
2125
2109
|
build() {
|
|
2126
2110
|
return {
|
|
@@ -2129,7 +2113,8 @@ var ChainSummaryBuilder = class {
|
|
|
2129
2113
|
lastMessageAt: this.#lastMessageAt,
|
|
2130
2114
|
lastMessage: this.#lastMessage,
|
|
2131
2115
|
lastAssistantMessage: this.#lastAssistantMessage,
|
|
2132
|
-
lastAssistantMessages: this.#lastAssistantMessages
|
|
2116
|
+
lastAssistantMessages: this.#lastAssistantMessages,
|
|
2117
|
+
firedOnceIds: this.#firedOnceIds
|
|
2133
2118
|
};
|
|
2134
2119
|
}
|
|
2135
2120
|
};
|
|
@@ -2338,7 +2323,7 @@ var GeneratorResolver = class {
|
|
|
2338
2323
|
async resolve(value, ctx) {
|
|
2339
2324
|
const iterable = value(ctx);
|
|
2340
2325
|
const collected = [];
|
|
2341
|
-
if (
|
|
2326
|
+
if (isAsyncIterable2(iterable)) {
|
|
2342
2327
|
for await (const chunk of iterable) {
|
|
2343
2328
|
pushLimited(collected, chunk, this.#maxItems, this.name);
|
|
2344
2329
|
}
|
|
@@ -2350,7 +2335,7 @@ var GeneratorResolver = class {
|
|
|
2350
2335
|
return collected;
|
|
2351
2336
|
}
|
|
2352
2337
|
};
|
|
2353
|
-
function
|
|
2338
|
+
function isAsyncIterable2(value) {
|
|
2354
2339
|
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
2355
2340
|
}
|
|
2356
2341
|
function pushLimited(collected, chunk, maxItems, resolverName) {
|
|
@@ -2388,7 +2373,7 @@ var IterableResolver = class {
|
|
|
2388
2373
|
async resolve(value, _ctx) {
|
|
2389
2374
|
const iterable = value;
|
|
2390
2375
|
const collected = [];
|
|
2391
|
-
if (
|
|
2376
|
+
if (isAsyncIterable3(iterable)) {
|
|
2392
2377
|
for await (const chunk of iterable) {
|
|
2393
2378
|
pushLimited2(collected, chunk, this.#maxItems, this.name);
|
|
2394
2379
|
}
|
|
@@ -2400,7 +2385,7 @@ var IterableResolver = class {
|
|
|
2400
2385
|
return collected;
|
|
2401
2386
|
}
|
|
2402
2387
|
};
|
|
2403
|
-
function
|
|
2388
|
+
function isAsyncIterable3(value) {
|
|
2404
2389
|
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
2405
2390
|
}
|
|
2406
2391
|
function pushLimited2(collected, chunk, maxItems, resolverName) {
|
|
@@ -2556,16 +2541,62 @@ function defaultResolvers() {
|
|
|
2556
2541
|
];
|
|
2557
2542
|
}
|
|
2558
2543
|
|
|
2544
|
+
// packages/context/src/lib/save/reminder-eval.ts
|
|
2545
|
+
async function evaluateFiredReminders(configs, whenCtx) {
|
|
2546
|
+
if (configs.length === 0) return [];
|
|
2547
|
+
const collectors = configs.map(() => /* @__PURE__ */ new Set());
|
|
2548
|
+
const whenResults = await Promise.allSettled(
|
|
2549
|
+
// Wrap in an async thunk so a synchronously-throwing predicate becomes a
|
|
2550
|
+
// rejected promise (allSettled cannot catch a throw inside the map itself).
|
|
2551
|
+
configs.map(
|
|
2552
|
+
async (config, i) => config.when({ ...whenCtx, onceCollector: collectors[i] })
|
|
2553
|
+
)
|
|
2554
|
+
);
|
|
2555
|
+
const fired = [];
|
|
2556
|
+
for (let i = 0; i < configs.length; i++) {
|
|
2557
|
+
const result = whenResults[i];
|
|
2558
|
+
if (result.status === "rejected") {
|
|
2559
|
+
console.warn("reminder when() predicate threw; treating as not fired", {
|
|
2560
|
+
reason: result.reason
|
|
2561
|
+
});
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
if (result.value === true) {
|
|
2565
|
+
fired.push({ config: configs[i], onceIds: [...collectors[i]] });
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
if (fired.length === 0) return [];
|
|
2569
|
+
const resolutions = await Promise.allSettled(
|
|
2570
|
+
fired.map(({ config }) => resolveReminderAsync(config, whenCtx))
|
|
2571
|
+
);
|
|
2572
|
+
const matched = [];
|
|
2573
|
+
for (let i = 0; i < fired.length; i++) {
|
|
2574
|
+
const result = resolutions[i];
|
|
2575
|
+
if (result.status === "rejected") {
|
|
2576
|
+
console.warn("reminder text resolver threw; skipping reminder", {
|
|
2577
|
+
reason: result.reason
|
|
2578
|
+
});
|
|
2579
|
+
continue;
|
|
2580
|
+
}
|
|
2581
|
+
if (result.value) {
|
|
2582
|
+
matched.push({
|
|
2583
|
+
config: fired[i].config,
|
|
2584
|
+
resolved: result.value,
|
|
2585
|
+
onceIds: fired[i].onceIds
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
return matched;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2559
2592
|
// packages/context/src/lib/save/save-pipeline.ts
|
|
2560
2593
|
var SavePipeline = class {
|
|
2561
2594
|
#engine;
|
|
2562
2595
|
#pending;
|
|
2563
|
-
#fragments;
|
|
2564
2596
|
#shouldBranch = true;
|
|
2565
|
-
constructor(engine, pending
|
|
2597
|
+
constructor(engine, pending) {
|
|
2566
2598
|
this.#engine = engine;
|
|
2567
2599
|
this.#pending = pending;
|
|
2568
|
-
this.#fragments = fragments;
|
|
2569
2600
|
}
|
|
2570
2601
|
async applyUpdateBranching(shouldBranch) {
|
|
2571
2602
|
this.#shouldBranch = shouldBranch;
|
|
@@ -2581,66 +2612,6 @@ var SavePipeline = class {
|
|
|
2581
2612
|
}
|
|
2582
2613
|
return this;
|
|
2583
2614
|
}
|
|
2584
|
-
async evaluateReminders(handlers) {
|
|
2585
|
-
const conditional = this.#fragments.filter(isConditionalReminder);
|
|
2586
|
-
if (conditional.length === 0) return this;
|
|
2587
|
-
const configsByTarget = /* @__PURE__ */ new Map();
|
|
2588
|
-
for (const fragment2 of conditional) {
|
|
2589
|
-
const config = fragment2.metadata.reminder;
|
|
2590
|
-
const target = config.target;
|
|
2591
|
-
const list = configsByTarget.get(target) ?? [];
|
|
2592
|
-
list.push(config);
|
|
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() {
|
|
2633
|
-
const fragmentIndex = this.#pending.findLastIndex(
|
|
2634
|
-
(fragment3) => fragment3.name === "user"
|
|
2635
|
-
);
|
|
2636
|
-
if (fragmentIndex < 0) return void 0;
|
|
2637
|
-
const fragment2 = this.#pending[fragmentIndex];
|
|
2638
|
-
if (!fragment2.codec) return void 0;
|
|
2639
|
-
return requireUserUIMessage(
|
|
2640
|
-
fragment2.codec.encode(),
|
|
2641
|
-
`Pending user fragment "${fragment2.name}"`
|
|
2642
|
-
);
|
|
2643
|
-
}
|
|
2644
2615
|
async persist() {
|
|
2645
2616
|
let parentId = this.#engine.getActiveBranch().headMessageId;
|
|
2646
2617
|
const now = Date.now();
|
|
@@ -2680,108 +2651,6 @@ var SavePipeline = class {
|
|
|
2680
2651
|
}
|
|
2681
2652
|
};
|
|
2682
2653
|
|
|
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
2654
|
// packages/context/src/lib/store/sqlite.store.ts
|
|
2786
2655
|
import { DatabaseSync } from "node:sqlite";
|
|
2787
2656
|
|
|
@@ -3402,6 +3271,23 @@ function isSkillPathMapping(value) {
|
|
|
3402
3271
|
function isEmptyAssistantPlaceholder(message2) {
|
|
3403
3272
|
return message2.role === "assistant" && message2.parts.length === 0;
|
|
3404
3273
|
}
|
|
3274
|
+
function stepStartPartIndices(parts) {
|
|
3275
|
+
const indices = [];
|
|
3276
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3277
|
+
if (parts[i].type === "step-start") indices.push(i);
|
|
3278
|
+
}
|
|
3279
|
+
return indices;
|
|
3280
|
+
}
|
|
3281
|
+
function spliceSteerMessages(messages, fired) {
|
|
3282
|
+
const ordered = [...fired].sort((a, b) => a.spliceIndex - b.spliceIndex);
|
|
3283
|
+
const out = [...messages];
|
|
3284
|
+
let offset = 0;
|
|
3285
|
+
for (const fire of ordered) {
|
|
3286
|
+
out.splice(fire.spliceIndex + offset, 0, ...fire.synthModel);
|
|
3287
|
+
offset += fire.synthModel.length;
|
|
3288
|
+
}
|
|
3289
|
+
return out;
|
|
3290
|
+
}
|
|
3405
3291
|
var ContextEngine = class _ContextEngine {
|
|
3406
3292
|
/** Non-message fragments (role, hints, etc.) - not persisted in graph */
|
|
3407
3293
|
#fragments = [];
|
|
@@ -3759,26 +3645,286 @@ var ContextEngine = class _ContextEngine {
|
|
|
3759
3645
|
if (this.#pendingMessages.length === 0) {
|
|
3760
3646
|
return { headMessageId: this.#branch?.headMessageId ?? void 0 };
|
|
3761
3647
|
}
|
|
3762
|
-
const
|
|
3763
|
-
|
|
3764
|
-
this.#pendingMessages,
|
|
3765
|
-
this.#fragments
|
|
3766
|
-
);
|
|
3648
|
+
const pending = this.#pendingMessages;
|
|
3649
|
+
const pipeline = new SavePipeline(this.#asSavePipelineEngine(), pending);
|
|
3767
3650
|
await pipeline.applyUpdateBranching(options?.branch ?? true);
|
|
3768
|
-
await
|
|
3651
|
+
await this.#foldUserReminders(pending);
|
|
3769
3652
|
const result = await pipeline.persist();
|
|
3770
3653
|
this.#pendingMessages = [];
|
|
3771
3654
|
return result;
|
|
3772
3655
|
}
|
|
3773
|
-
#
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3656
|
+
#currentSteerSession;
|
|
3657
|
+
/**
|
|
3658
|
+
* Build the `prepareStep` hook that injects steer reminders mid-loop.
|
|
3659
|
+
*
|
|
3660
|
+
* Semantics are "inject once, persist": when a steer reminder's predicate
|
|
3661
|
+
* fires (only after the model has produced ≥1 step with content — the mid-loop
|
|
3662
|
+
* gate), its `<system-reminder>` user message is spliced into the model prompt
|
|
3663
|
+
* at the step boundary where it fired AND re-spliced on every subsequent step,
|
|
3664
|
+
* so the model keeps seeing it for the rest of the loop.
|
|
3665
|
+
*
|
|
3666
|
+
* Firing is edge-triggered with a post-fire re-sample: each fire resets the
|
|
3667
|
+
* elapsed reference (`lastSyntheticAt`), then the config is immediately
|
|
3668
|
+
* re-evaluated against the reset context — self-resetting predicates like
|
|
3669
|
+
* `elapsedExceeds` read false and re-arm (so they recur every N within one
|
|
3670
|
+
* stream), while constant predicates like `everyNTurns` read true and disarm
|
|
3671
|
+
* (so they fire once per stream). Any later false sample re-arms a config.
|
|
3672
|
+
* All state is closure-local (a fresh SteerSession per call), so overlapping
|
|
3673
|
+
* streams / guardrail retries never share a cursor.
|
|
3674
|
+
*
|
|
3675
|
+
* The session is also consumed by writeAssistantSegment, which carves the
|
|
3676
|
+
* streamed assistant message into the matching `[assistant, steer, assistant]`
|
|
3677
|
+
* split — so the stored chain reproduces exactly the prompt the model saw
|
|
3678
|
+
* (store/prompt parity).
|
|
3679
|
+
*/
|
|
3680
|
+
createSteerPrepareStep() {
|
|
3681
|
+
const session = {
|
|
3682
|
+
firedOnceIds: /* @__PURE__ */ new Set(),
|
|
3683
|
+
fired: [],
|
|
3684
|
+
currentSegStart: 0,
|
|
3685
|
+
materialized: 0
|
|
3686
|
+
};
|
|
3687
|
+
this.#currentSteerSession = session;
|
|
3688
|
+
return async ({ steps, stepNumber, messages }) => {
|
|
3689
|
+
const priorStep = stepNumber >= 1 ? steps[stepNumber - 1] : void 0;
|
|
3690
|
+
const canFire = (priorStep?.content?.length ?? 0) > 0;
|
|
3691
|
+
if (canFire) {
|
|
3692
|
+
const configs = this.#remindersFor("steer");
|
|
3693
|
+
if (configs.length > 0) {
|
|
3694
|
+
const whenCtx = await this.#steerWhenCtx(session);
|
|
3695
|
+
const matched = await evaluateFiredReminders(configs, whenCtx);
|
|
3696
|
+
if (matched.length > 0) {
|
|
3697
|
+
const onceIds = [...new Set(matched.flatMap((m) => m.onceIds))];
|
|
3698
|
+
for (const id of onceIds) session.firedOnceIds.add(id);
|
|
3699
|
+
const synth = synthesizeSteerUserMessage(
|
|
3700
|
+
matched.map((m) => m.resolved.text),
|
|
3701
|
+
Date.now(),
|
|
3702
|
+
onceIds
|
|
3703
|
+
);
|
|
3704
|
+
const synthModel = await convertToModelMessages2([synth], {
|
|
3705
|
+
ignoreIncompleteToolCalls: true
|
|
3706
|
+
});
|
|
3707
|
+
session.fired.push({
|
|
3708
|
+
afterStep: stepNumber - 1,
|
|
3709
|
+
spliceIndex: messages.length,
|
|
3710
|
+
synth,
|
|
3711
|
+
synthModel
|
|
3712
|
+
});
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
if (session.fired.length === 0) return void 0;
|
|
3717
|
+
return {
|
|
3718
|
+
messages: spliceSteerMessages(
|
|
3719
|
+
messages,
|
|
3720
|
+
session.fired
|
|
3721
|
+
)
|
|
3722
|
+
};
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
/**
|
|
3726
|
+
* Persist the streamed assistant message, carving it into the steer split when
|
|
3727
|
+
* steer reminders fired this turn.
|
|
3728
|
+
*
|
|
3729
|
+
* Called from chat()'s onStepFinish/onFinish (and the guardrail path) with the
|
|
3730
|
+
* cumulative response message. Segment boundaries come from the `step-start`
|
|
3731
|
+
* markers in the message itself — no cross-track store read — so the carve is
|
|
3732
|
+
* race-free. Idempotent: finalized segments keep stable ids; the open segment
|
|
3733
|
+
* is updated in place. With no active steer it degrades to a plain in-place
|
|
3734
|
+
* write of the whole message to the reserved head.
|
|
3735
|
+
*/
|
|
3736
|
+
async writeAssistantSegment(message2) {
|
|
3737
|
+
const head = await this.headMessage();
|
|
3738
|
+
if (head?.name !== "assistant") {
|
|
3739
|
+
throw new Error(
|
|
3740
|
+
"writeAssistantSegment: expected an assistant message at chain head."
|
|
3741
|
+
);
|
|
3742
|
+
}
|
|
3743
|
+
const session = this.#currentSteerSession;
|
|
3744
|
+
if (!session || session.fired.length === 0) {
|
|
3745
|
+
this.set(assistant({ ...message2, id: head.id }));
|
|
3746
|
+
await this.save({ branch: false });
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
if (session.currentSegId === void 0) {
|
|
3750
|
+
session.currentSegId = head.id;
|
|
3751
|
+
session.currentSegStart = 0;
|
|
3752
|
+
}
|
|
3753
|
+
const stepStarts = stepStartPartIndices(message2.parts);
|
|
3754
|
+
while (session.materialized < session.fired.length) {
|
|
3755
|
+
const fire = session.fired[session.materialized];
|
|
3756
|
+
const boundary = stepStarts[fire.afterStep + 1];
|
|
3757
|
+
if (boundary === void 0) break;
|
|
3758
|
+
this.set(
|
|
3759
|
+
assistant({
|
|
3760
|
+
id: session.currentSegId,
|
|
3761
|
+
role: "assistant",
|
|
3762
|
+
parts: message2.parts.slice(session.currentSegStart, boundary)
|
|
3763
|
+
})
|
|
3764
|
+
);
|
|
3765
|
+
this.set(user(fire.synth));
|
|
3766
|
+
session.currentSegId = generateId4();
|
|
3767
|
+
session.currentSegStart = boundary;
|
|
3768
|
+
session.materialized++;
|
|
3769
|
+
}
|
|
3770
|
+
this.set(
|
|
3771
|
+
assistant({
|
|
3772
|
+
...message2,
|
|
3773
|
+
id: session.currentSegId,
|
|
3774
|
+
parts: message2.parts.slice(session.currentSegStart)
|
|
3775
|
+
})
|
|
3776
|
+
);
|
|
3777
|
+
await this.save({ branch: false });
|
|
3778
|
+
}
|
|
3779
|
+
#remindersFor(target) {
|
|
3780
|
+
return this.#fragments.filter(isConditionalReminder).map((fragment2) => fragment2.metadata.reminder).filter((config) => config.target === target);
|
|
3781
|
+
}
|
|
3782
|
+
#buildBaseWhenCtx(chain) {
|
|
3783
|
+
const rawUsage = this.#chatData?.metadata?.usage;
|
|
3784
|
+
const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
|
|
3785
|
+
const elapsed = chain.lastMessageAt !== void 0 ? Date.now() - chain.lastMessageAt : void 0;
|
|
3786
|
+
const chatData = this.#chatData;
|
|
3787
|
+
if (!chatData) {
|
|
3788
|
+
throw new Error("ContextEngine must be initialized before reminders run");
|
|
3789
|
+
}
|
|
3790
|
+
return {
|
|
3791
|
+
turn: chain.turn,
|
|
3792
|
+
messageCount: chain.messageCount,
|
|
3793
|
+
lastMessageAt: chain.lastMessageAt,
|
|
3794
|
+
lastMessage: chain.lastMessage,
|
|
3795
|
+
chat: chatData,
|
|
3796
|
+
usage,
|
|
3797
|
+
branch: this.#branchName,
|
|
3798
|
+
elapsed
|
|
3799
|
+
};
|
|
3800
|
+
}
|
|
3801
|
+
#buildWhenCtx(chain, currentMessage) {
|
|
3802
|
+
return {
|
|
3803
|
+
...this.#buildBaseWhenCtx(chain),
|
|
3804
|
+
content: extractPlainText(currentMessage),
|
|
3805
|
+
currentMessage,
|
|
3806
|
+
lastAssistantMessage: chain.lastAssistantMessage,
|
|
3807
|
+
lastAssistantMessages: chain.lastAssistantMessages
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
/**
|
|
3811
|
+
* `steer` reminders fire mid-loop via prepareStep and `tool-output` reminders
|
|
3812
|
+
* wrap at tool-execution time, so by the time save() runs only user-target
|
|
3813
|
+
* reminders remain to fold into the last pending user message.
|
|
3814
|
+
*/
|
|
3815
|
+
async #foldUserReminders(pending) {
|
|
3816
|
+
const configs = this.#remindersFor("user");
|
|
3817
|
+
if (configs.length === 0) return;
|
|
3818
|
+
const fragmentIndex = pending.findLastIndex(
|
|
3819
|
+
(fragment3) => fragment3.name === "user"
|
|
3820
|
+
);
|
|
3821
|
+
if (fragmentIndex < 0) return;
|
|
3822
|
+
const fragment2 = pending[fragmentIndex];
|
|
3823
|
+
if (!fragment2.codec) return;
|
|
3824
|
+
const message2 = requireUserUIMessage(
|
|
3825
|
+
fragment2.codec.encode(),
|
|
3826
|
+
`Pending user fragment "${fragment2.name}"`
|
|
3827
|
+
);
|
|
3828
|
+
const chain = await this.#getChainContext();
|
|
3829
|
+
const matched = await evaluateFiredReminders(configs, {
|
|
3830
|
+
...this.#buildWhenCtx(chain, message2),
|
|
3831
|
+
firedOnceIds: chain.firedOnceIds
|
|
3832
|
+
});
|
|
3833
|
+
if (matched.length === 0) return;
|
|
3834
|
+
const onceIds = [...new Set(matched.flatMap((m) => m.onceIds))];
|
|
3835
|
+
const reminders = matched.map((m) => ({
|
|
3836
|
+
text: m.resolved.text,
|
|
3837
|
+
asPart: m.config.asPart,
|
|
3838
|
+
target: "user",
|
|
3839
|
+
metadata: m.resolved.metadata
|
|
3840
|
+
}));
|
|
3841
|
+
const carrier = {
|
|
3842
|
+
...message2,
|
|
3843
|
+
id: fragment2.id ?? message2.id,
|
|
3844
|
+
parts: [...message2.parts]
|
|
3845
|
+
};
|
|
3846
|
+
if (onceIds.length > 0) {
|
|
3847
|
+
carrier.metadata = {
|
|
3848
|
+
...carrier.metadata,
|
|
3849
|
+
onceIds
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
applyUserRemindersToMessage(carrier, reminders);
|
|
3853
|
+
pending[fragmentIndex] = user(carrier);
|
|
3854
|
+
}
|
|
3855
|
+
async #steerWhenCtx(session) {
|
|
3856
|
+
await this.#ensureInitialized();
|
|
3857
|
+
if (!session.whenBase) {
|
|
3858
|
+
const chain = await this.#getChainContext();
|
|
3859
|
+
const base2 = this.#buildBaseWhenCtx(chain);
|
|
3860
|
+
const currentMessage2 = chain.lastMessage;
|
|
3861
|
+
if (!currentMessage2) {
|
|
3862
|
+
throw new Error(
|
|
3863
|
+
"steer reminders require a user message earlier in the turn"
|
|
3864
|
+
);
|
|
3865
|
+
}
|
|
3866
|
+
session.whenBase = {
|
|
3867
|
+
base: base2,
|
|
3868
|
+
content: extractPlainText(currentMessage2),
|
|
3869
|
+
currentMessage: currentMessage2,
|
|
3870
|
+
lastAssistantMessage: chain.lastAssistantMessage,
|
|
3871
|
+
lastAssistantMessages: chain.lastAssistantMessages,
|
|
3872
|
+
chainFiredOnceIds: chain.firedOnceIds
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
const {
|
|
3876
|
+
base,
|
|
3877
|
+
content,
|
|
3878
|
+
currentMessage,
|
|
3879
|
+
lastAssistantMessage,
|
|
3880
|
+
lastAssistantMessages,
|
|
3881
|
+
chainFiredOnceIds
|
|
3882
|
+
} = session.whenBase;
|
|
3883
|
+
const elapsed = base.lastMessageAt !== void 0 ? Date.now() - base.lastMessageAt : void 0;
|
|
3884
|
+
return {
|
|
3885
|
+
...base,
|
|
3886
|
+
elapsed,
|
|
3887
|
+
content,
|
|
3888
|
+
currentMessage,
|
|
3889
|
+
lastAssistantMessage,
|
|
3890
|
+
lastAssistantMessages,
|
|
3891
|
+
firedOnceIds: /* @__PURE__ */ new Set([...chainFiredOnceIds, ...session.firedOnceIds])
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
/**
|
|
3895
|
+
* Evaluate `target: 'tool-output'` reminders against a tool's raw result and
|
|
3896
|
+
* return the (possibly wrapped) output.
|
|
3897
|
+
*
|
|
3898
|
+
* Called by the agent's tool wrapper right after each `execute()` resolves —
|
|
3899
|
+
* upstream of both the model and the store, so the next model step and the
|
|
3900
|
+
* persisted chain see the exact same wrapped value (store/prompt parity).
|
|
3901
|
+
*
|
|
3902
|
+
* Returns the output unchanged when no tool-output reminder fires. Without a
|
|
3903
|
+
* persisted user message there is no turn context to evaluate against
|
|
3904
|
+
* (e.g. asTool forks that set a pending user without saving), so the output
|
|
3905
|
+
* passes through untouched.
|
|
3906
|
+
*/
|
|
3907
|
+
async applyToolOutputReminders(output) {
|
|
3908
|
+
const configs = this.#remindersFor("tool-output");
|
|
3909
|
+
if (configs.length === 0) return output;
|
|
3910
|
+
await this.#ensureInitialized();
|
|
3911
|
+
const chain = await this.#getChainContext();
|
|
3912
|
+
const currentMessage = chain.lastMessage;
|
|
3913
|
+
if (!currentMessage) return output;
|
|
3914
|
+
const matched = await evaluateFiredReminders(
|
|
3915
|
+
configs,
|
|
3916
|
+
this.#buildWhenCtx(chain, currentMessage)
|
|
3917
|
+
);
|
|
3918
|
+
if (matched.length === 0) return output;
|
|
3919
|
+
return applyRemindersToToolOutput(
|
|
3920
|
+
output,
|
|
3921
|
+
matched.map((m) => m.resolved.text)
|
|
3922
|
+
);
|
|
3923
|
+
}
|
|
3777
3924
|
#asSavePipelineEngine() {
|
|
3778
3925
|
return {
|
|
3779
3926
|
store: this.#store,
|
|
3780
3927
|
chatId: this.#chatId,
|
|
3781
|
-
branchName: this.#branchName,
|
|
3782
3928
|
getActiveBranch: () => ({
|
|
3783
3929
|
id: this.#activeBranch.id,
|
|
3784
3930
|
headMessageId: this.#activeBranch.headMessageId
|
|
@@ -3790,29 +3936,7 @@ var ContextEngine = class _ContextEngine {
|
|
|
3790
3936
|
);
|
|
3791
3937
|
this.#activeBranch.headMessageId = headMessageId;
|
|
3792
3938
|
},
|
|
3793
|
-
rewindForUpdate: (parentId) => this.#rewindForUpdate(parentId)
|
|
3794
|
-
getChainSummary: () => this.#getChainContext(),
|
|
3795
|
-
buildBaseWhenCtx: (chain) => {
|
|
3796
|
-
const rawUsage = this.#chatData?.metadata?.usage;
|
|
3797
|
-
const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
|
|
3798
|
-
const elapsed = chain.lastMessageAt !== void 0 ? Date.now() - chain.lastMessageAt : void 0;
|
|
3799
|
-
const chatData = this.#chatData;
|
|
3800
|
-
if (!chatData) {
|
|
3801
|
-
throw new Error(
|
|
3802
|
-
"ContextEngine must be initialized before reminders run"
|
|
3803
|
-
);
|
|
3804
|
-
}
|
|
3805
|
-
return {
|
|
3806
|
-
turn: chain.turn,
|
|
3807
|
-
messageCount: chain.messageCount,
|
|
3808
|
-
lastMessageAt: chain.lastMessageAt,
|
|
3809
|
-
lastMessage: chain.lastMessage,
|
|
3810
|
-
chat: chatData,
|
|
3811
|
-
usage,
|
|
3812
|
-
branch: this.#branchName,
|
|
3813
|
-
elapsed
|
|
3814
|
-
};
|
|
3815
|
-
}
|
|
3939
|
+
rewindForUpdate: (parentId) => this.#rewindForUpdate(parentId)
|
|
3816
3940
|
};
|
|
3817
3941
|
}
|
|
3818
3942
|
/**
|
|
@@ -4689,7 +4813,6 @@ var defaultChatMessageMetadata = ({
|
|
|
4689
4813
|
};
|
|
4690
4814
|
async function chat(agent2, options = {}) {
|
|
4691
4815
|
const context = agent2.context;
|
|
4692
|
-
const sandbox = agent2.sandbox;
|
|
4693
4816
|
if (!context) {
|
|
4694
4817
|
throw new Error(
|
|
4695
4818
|
"Agent is missing a context. Provide context when creating the agent."
|
|
@@ -4701,7 +4824,7 @@ async function chat(agent2, options = {}) {
|
|
|
4701
4824
|
"chat: expected an assistant message at head. Call context.continue(input) before chat()."
|
|
4702
4825
|
);
|
|
4703
4826
|
}
|
|
4704
|
-
const
|
|
4827
|
+
const initialAssistantMsgId = head.id;
|
|
4705
4828
|
const uiMessages = await context.getMessages();
|
|
4706
4829
|
const streamContextVariables = options.contextVariables === void 0 ? {} : options.contextVariables;
|
|
4707
4830
|
const [title, result] = await Promise.all([
|
|
@@ -4723,42 +4846,30 @@ async function chat(agent2, options = {}) {
|
|
|
4723
4846
|
sendReasoning: true,
|
|
4724
4847
|
sendSources: true,
|
|
4725
4848
|
originalMessages: uiMessages,
|
|
4726
|
-
generateMessageId: () =>
|
|
4849
|
+
generateMessageId: () => initialAssistantMsgId,
|
|
4727
4850
|
messageMetadata: options.messageMetadata ?? defaultChatMessageMetadata
|
|
4728
4851
|
});
|
|
4729
4852
|
return createUIMessageStream2({
|
|
4730
4853
|
originalMessages: uiMessages,
|
|
4731
|
-
generateId: () =>
|
|
4854
|
+
generateId: () => initialAssistantMsgId,
|
|
4732
4855
|
onStepFinish: async ({ responseMessage }) => {
|
|
4733
|
-
|
|
4734
|
-
...responseMessage,
|
|
4735
|
-
id: assistantMsgId
|
|
4736
|
-
};
|
|
4737
|
-
context.set(assistant(normalizedMessage));
|
|
4738
|
-
await context.save({ branch: false });
|
|
4856
|
+
await context.writeAssistantSegment(responseMessage);
|
|
4739
4857
|
},
|
|
4740
4858
|
onFinish: async ({ responseMessage, isAborted }) => {
|
|
4741
|
-
|
|
4742
|
-
...responseMessage,
|
|
4743
|
-
id: assistantMsgId
|
|
4744
|
-
};
|
|
4859
|
+
let message2 = responseMessage;
|
|
4745
4860
|
if (isAborted) {
|
|
4746
|
-
|
|
4861
|
+
message2 = { ...message2, parts: sanitizeAbortedParts(message2.parts) };
|
|
4747
4862
|
}
|
|
4748
|
-
const
|
|
4749
|
-
const fileEvents = isAborted ? [] : drained;
|
|
4750
|
-
const finalMetadata = await options.finalAssistantMetadata?.(normalizedMessage);
|
|
4863
|
+
const finalMetadata = await options.finalAssistantMetadata?.(message2);
|
|
4751
4864
|
const mergedMetadata = {
|
|
4752
|
-
...
|
|
4753
|
-
...fileEvents.length > 0 ? { fileEvents } : {},
|
|
4865
|
+
...message2.metadata ?? {},
|
|
4754
4866
|
...finalMetadata ?? {}
|
|
4755
4867
|
};
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
await context.
|
|
4760
|
-
|
|
4761
|
-
await context.trackUsage(totalUsage);
|
|
4868
|
+
if (Object.keys(mergedMetadata).length > 0) {
|
|
4869
|
+
message2 = { ...message2, metadata: mergedMetadata };
|
|
4870
|
+
}
|
|
4871
|
+
await context.writeAssistantSegment(message2);
|
|
4872
|
+
await context.trackUsage(await result.totalUsage);
|
|
4762
4873
|
},
|
|
4763
4874
|
execute: async ({ writer }) => {
|
|
4764
4875
|
writer.merge(uiStream);
|
|
@@ -5345,6 +5456,23 @@ function lastAssistantLength(spec) {
|
|
|
5345
5456
|
};
|
|
5346
5457
|
}
|
|
5347
5458
|
|
|
5459
|
+
// packages/context/src/lib/fragments/reminders/once.ts
|
|
5460
|
+
function once(id) {
|
|
5461
|
+
if (id.trim().length === 0) {
|
|
5462
|
+
throw new Error("once(id) requires a non-empty id");
|
|
5463
|
+
}
|
|
5464
|
+
return (ctx) => {
|
|
5465
|
+
if (ctx.firedOnceIds === void 0) {
|
|
5466
|
+
throw new Error(
|
|
5467
|
+
`once('${id}') is not supported on target:'tool-output' reminders`
|
|
5468
|
+
);
|
|
5469
|
+
}
|
|
5470
|
+
if (ctx.firedOnceIds.has(id)) return false;
|
|
5471
|
+
ctx.onceCollector?.add(id);
|
|
5472
|
+
return true;
|
|
5473
|
+
};
|
|
5474
|
+
}
|
|
5475
|
+
|
|
5348
5476
|
// packages/context/src/lib/fragments/reminders/temporal/elapsed.ts
|
|
5349
5477
|
function elapsedExceeds(ms) {
|
|
5350
5478
|
return (ctx) => (ctx.elapsed ?? 0) >= ms;
|
|
@@ -5502,15 +5630,15 @@ function dateReminder(options) {
|
|
|
5502
5630
|
const now = /* @__PURE__ */ new Date();
|
|
5503
5631
|
const currentDate = formatDateKey(now, tz);
|
|
5504
5632
|
const currentDay = formatDayOfWeek(now, tz);
|
|
5505
|
-
let
|
|
5633
|
+
let diff = "";
|
|
5506
5634
|
if (ctx.lastMessageAt !== void 0) {
|
|
5507
5635
|
const prev = new Date(ctx.lastMessageAt);
|
|
5508
|
-
|
|
5636
|
+
diff = formatDiff([
|
|
5509
5637
|
diffLine("date", formatDateKey(prev, tz), currentDate),
|
|
5510
5638
|
diffLine("day of week", formatDayOfWeek(prev, tz), currentDay)
|
|
5511
5639
|
]);
|
|
5512
5640
|
}
|
|
5513
|
-
return `${
|
|
5641
|
+
return `${diff}Date: ${currentDate}
|
|
5514
5642
|
Day of Week: ${currentDay}`;
|
|
5515
5643
|
},
|
|
5516
5644
|
{ when: dayChanged(options), asPart: false }
|
|
@@ -5522,14 +5650,14 @@ function timeReminder(options) {
|
|
|
5522
5650
|
const tz = resolveTz(options, ctx);
|
|
5523
5651
|
const now = /* @__PURE__ */ new Date();
|
|
5524
5652
|
const currentTime = formatTime(now, tz);
|
|
5525
|
-
let
|
|
5653
|
+
let diff = "";
|
|
5526
5654
|
if (ctx.lastMessageAt !== void 0) {
|
|
5527
5655
|
const prev = new Date(ctx.lastMessageAt);
|
|
5528
|
-
|
|
5656
|
+
diff = formatDiff([
|
|
5529
5657
|
diffLine("hour", formatHour(prev, tz), formatHour(now, tz))
|
|
5530
5658
|
]);
|
|
5531
5659
|
}
|
|
5532
|
-
return `${
|
|
5660
|
+
return `${diff}Time: ${currentTime}`;
|
|
5533
5661
|
},
|
|
5534
5662
|
{ when: hourChanged(options), asPart: false }
|
|
5535
5663
|
);
|
|
@@ -5540,14 +5668,14 @@ function monthReminder(options) {
|
|
|
5540
5668
|
const tz = resolveTz(options, ctx);
|
|
5541
5669
|
const now = /* @__PURE__ */ new Date();
|
|
5542
5670
|
const currentMonth = formatMonthName(now, tz);
|
|
5543
|
-
let
|
|
5671
|
+
let diff = "";
|
|
5544
5672
|
if (ctx.lastMessageAt !== void 0) {
|
|
5545
5673
|
const prev = new Date(ctx.lastMessageAt);
|
|
5546
|
-
|
|
5674
|
+
diff = formatDiff([
|
|
5547
5675
|
diffLine("month", formatMonthName(prev, tz), currentMonth)
|
|
5548
5676
|
]);
|
|
5549
5677
|
}
|
|
5550
|
-
return `${
|
|
5678
|
+
return `${diff}Month: ${currentMonth}`;
|
|
5551
5679
|
},
|
|
5552
5680
|
{ when: monthChanged(options), asPart: false }
|
|
5553
5681
|
);
|
|
@@ -5558,14 +5686,14 @@ function yearReminder(options) {
|
|
|
5558
5686
|
const tz = resolveTz(options, ctx);
|
|
5559
5687
|
const now = /* @__PURE__ */ new Date();
|
|
5560
5688
|
const currentYear = formatYear(now, tz);
|
|
5561
|
-
let
|
|
5689
|
+
let diff = "";
|
|
5562
5690
|
if (ctx.lastMessageAt !== void 0) {
|
|
5563
5691
|
const prev = new Date(ctx.lastMessageAt);
|
|
5564
|
-
|
|
5692
|
+
diff = formatDiff([
|
|
5565
5693
|
diffLine("year", formatYear(prev, tz), currentYear)
|
|
5566
5694
|
]);
|
|
5567
5695
|
}
|
|
5568
|
-
return `${
|
|
5696
|
+
return `${diff}Year: ${currentYear}`;
|
|
5569
5697
|
},
|
|
5570
5698
|
{ when: yearChanged(options), asPart: false }
|
|
5571
5699
|
);
|
|
@@ -5576,14 +5704,14 @@ function seasonReminder(options) {
|
|
|
5576
5704
|
const tz = resolveTz(options, ctx);
|
|
5577
5705
|
const now = /* @__PURE__ */ new Date();
|
|
5578
5706
|
const currentSeason = getSeason(getMonthIndex(now, tz));
|
|
5579
|
-
let
|
|
5707
|
+
let diff = "";
|
|
5580
5708
|
if (ctx.lastMessageAt !== void 0) {
|
|
5581
5709
|
const prev = new Date(ctx.lastMessageAt);
|
|
5582
|
-
|
|
5710
|
+
diff = formatDiff([
|
|
5583
5711
|
diffLine("season", getSeason(getMonthIndex(prev, tz)), currentSeason)
|
|
5584
5712
|
]);
|
|
5585
5713
|
}
|
|
5586
|
-
return `${
|
|
5714
|
+
return `${diff}Season: ${currentSeason}`;
|
|
5587
5715
|
},
|
|
5588
5716
|
{ when: seasonChanged(options), asPart: false }
|
|
5589
5717
|
);
|
|
@@ -5601,11 +5729,11 @@ function localeReminder() {
|
|
|
5601
5729
|
const current = getLocaleFromMessage(ctx.currentMessage);
|
|
5602
5730
|
if (!current) return "";
|
|
5603
5731
|
const last = getLocaleFromMessage(ctx.lastMessage);
|
|
5604
|
-
const
|
|
5732
|
+
const diff = last ? formatDiff([
|
|
5605
5733
|
diffLine("language", last.language, current.language),
|
|
5606
5734
|
diffLine("timezone", last.timeZone, current.timeZone)
|
|
5607
5735
|
]) : "";
|
|
5608
|
-
return `${
|
|
5736
|
+
return `${diff}Language: ${current.language}
|
|
5609
5737
|
Timezone: ${current.timeZone}`;
|
|
5610
5738
|
},
|
|
5611
5739
|
{ when: whenFn, asPart: false }
|
|
@@ -5692,7 +5820,7 @@ function toolCallCount(name, spec) {
|
|
|
5692
5820
|
function everyNTurns(n) {
|
|
5693
5821
|
return ({ turn }) => turn % n === 0;
|
|
5694
5822
|
}
|
|
5695
|
-
function
|
|
5823
|
+
function first() {
|
|
5696
5824
|
return ({ turn }) => turn === 1;
|
|
5697
5825
|
}
|
|
5698
5826
|
function firstN(n) {
|
|
@@ -6066,6 +6194,9 @@ async function createAgentOsSandbox(options = {}) {
|
|
|
6066
6194
|
await os.dispose();
|
|
6067
6195
|
} catch {
|
|
6068
6196
|
}
|
|
6197
|
+
},
|
|
6198
|
+
[Symbol.asyncDispose]() {
|
|
6199
|
+
return this.dispose();
|
|
6069
6200
|
}
|
|
6070
6201
|
};
|
|
6071
6202
|
}
|
|
@@ -6114,160 +6245,6 @@ import {
|
|
|
6114
6245
|
} from "bash-tool";
|
|
6115
6246
|
import z3 from "zod";
|
|
6116
6247
|
|
|
6117
|
-
// packages/context/src/lib/sandbox/file-events.ts
|
|
6118
|
-
var SnapshotFailedError = class extends Error {
|
|
6119
|
-
stderr;
|
|
6120
|
-
constructor(message2, stderr) {
|
|
6121
|
-
super(message2);
|
|
6122
|
-
this.name = "SnapshotFailedError";
|
|
6123
|
-
this.stderr = stderr;
|
|
6124
|
-
}
|
|
6125
|
-
};
|
|
6126
|
-
function shellQuote(value) {
|
|
6127
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
6128
|
-
}
|
|
6129
|
-
async function snapshot(execute, destination) {
|
|
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
|
-
}
|
|
6181
|
-
}
|
|
6182
|
-
for (const path5 of before.keys()) {
|
|
6183
|
-
if (!after.has(path5)) {
|
|
6184
|
-
events.push({ path: path5, op: "delete", timestamp: ts });
|
|
6185
|
-
}
|
|
6186
|
-
}
|
|
6187
|
-
return events;
|
|
6188
|
-
}
|
|
6189
|
-
function observeSandboxFileEvents(sandbox, options) {
|
|
6190
|
-
const { destination } = options;
|
|
6191
|
-
if (!destination) {
|
|
6192
|
-
throw new Error("observeSandboxFileEvents: destination is required");
|
|
6193
|
-
}
|
|
6194
|
-
const innerExecute = sandbox.executeCommand.bind(sandbox);
|
|
6195
|
-
const innerReadFile = sandbox.readFile.bind(sandbox);
|
|
6196
|
-
const innerWriteFiles = sandbox.writeFiles.bind(sandbox);
|
|
6197
|
-
const buffer = [];
|
|
6198
|
-
const observe = async (fn) => {
|
|
6199
|
-
const before = await snapshot(innerExecute, destination);
|
|
6200
|
-
const takeAfter = async () => {
|
|
6201
|
-
const after = await snapshot(innerExecute, destination);
|
|
6202
|
-
buffer.push(...diff(before, after));
|
|
6203
|
-
};
|
|
6204
|
-
try {
|
|
6205
|
-
const result = await fn();
|
|
6206
|
-
await takeAfter();
|
|
6207
|
-
return result;
|
|
6208
|
-
} catch (err) {
|
|
6209
|
-
await takeAfter().catch(() => {
|
|
6210
|
-
});
|
|
6211
|
-
throw err;
|
|
6212
|
-
}
|
|
6213
|
-
};
|
|
6214
|
-
const decorated = {
|
|
6215
|
-
async executeCommand(command, options2) {
|
|
6216
|
-
return observe(() => innerExecute(command, options2));
|
|
6217
|
-
},
|
|
6218
|
-
async readFile(path5) {
|
|
6219
|
-
const content = await innerReadFile(path5);
|
|
6220
|
-
buffer.push({ path: path5, op: "read", timestamp: Date.now() });
|
|
6221
|
-
return content;
|
|
6222
|
-
},
|
|
6223
|
-
async writeFiles(files) {
|
|
6224
|
-
await observe(() => innerWriteFiles(files));
|
|
6225
|
-
},
|
|
6226
|
-
dispose: sandbox.dispose.bind(sandbox)
|
|
6227
|
-
};
|
|
6228
|
-
if (sandbox.spawn) {
|
|
6229
|
-
const innerSpawn = sandbox.spawn.bind(sandbox);
|
|
6230
|
-
decorated.spawn = (command, options2) => {
|
|
6231
|
-
const started = (async () => {
|
|
6232
|
-
const before = await snapshot(innerExecute, destination).catch(
|
|
6233
|
-
() => /* @__PURE__ */ new Map()
|
|
6234
|
-
);
|
|
6235
|
-
const child = innerSpawn(command, options2);
|
|
6236
|
-
return { before, child };
|
|
6237
|
-
})();
|
|
6238
|
-
const exit = (async () => {
|
|
6239
|
-
const { before, child } = await started;
|
|
6240
|
-
try {
|
|
6241
|
-
return await child.exit;
|
|
6242
|
-
} finally {
|
|
6243
|
-
try {
|
|
6244
|
-
const after = await snapshot(innerExecute, destination);
|
|
6245
|
-
buffer.push(...diff(before, after));
|
|
6246
|
-
} catch {
|
|
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
|
-
};
|
|
6261
|
-
};
|
|
6262
|
-
}
|
|
6263
|
-
return {
|
|
6264
|
-
sandbox: decorated,
|
|
6265
|
-
drain() {
|
|
6266
|
-
return buffer.splice(0, buffer.length);
|
|
6267
|
-
}
|
|
6268
|
-
};
|
|
6269
|
-
}
|
|
6270
|
-
|
|
6271
6248
|
// packages/context/src/lib/sandbox/upload-skills.ts
|
|
6272
6249
|
import * as path3 from "node:path";
|
|
6273
6250
|
|
|
@@ -6434,10 +6411,7 @@ async function createBashTool(options) {
|
|
|
6434
6411
|
destination,
|
|
6435
6412
|
...rest
|
|
6436
6413
|
} = options;
|
|
6437
|
-
const
|
|
6438
|
-
destination: destination ?? "/workspace"
|
|
6439
|
-
});
|
|
6440
|
-
const sandbox = withAbortSignal(withBashExceptionCatch(observer.sandbox));
|
|
6414
|
+
const sandbox = withAbortSignal(withBashExceptionCatch(backend));
|
|
6441
6415
|
const combinedInstructions = [extraInstructions, REASONING_INSTRUCTION].filter(Boolean).join("\n\n");
|
|
6442
6416
|
const toolkit = await externalCreateBashTool({
|
|
6443
6417
|
...rest,
|
|
@@ -6485,14 +6459,30 @@ async function createBashTool(options) {
|
|
|
6485
6459
|
return { type: "json", value: visible };
|
|
6486
6460
|
}
|
|
6487
6461
|
});
|
|
6488
|
-
const
|
|
6462
|
+
const upstreamWriteFile = toolkit.tools.writeFile;
|
|
6463
|
+
const originalWriteExecute = upstreamWriteFile.execute;
|
|
6464
|
+
const writeFileBuilder = tool2;
|
|
6465
|
+
const writeFile = writeFileBuilder({
|
|
6466
|
+
...upstreamWriteFile,
|
|
6467
|
+
execute: async (input, options2) => {
|
|
6468
|
+
if (!originalWriteExecute) {
|
|
6469
|
+
throw new Error("writeFile tool execution is not available");
|
|
6470
|
+
}
|
|
6471
|
+
try {
|
|
6472
|
+
return await originalWriteExecute(input, options2);
|
|
6473
|
+
} catch (err) {
|
|
6474
|
+
if (err instanceof BashException) return err.format();
|
|
6475
|
+
throw err;
|
|
6476
|
+
}
|
|
6477
|
+
}
|
|
6478
|
+
});
|
|
6479
|
+
const skills2 = await uploadSkills(backend, skillInputs);
|
|
6489
6480
|
return {
|
|
6490
6481
|
...toolkit,
|
|
6491
6482
|
sandbox,
|
|
6492
6483
|
bash,
|
|
6493
|
-
tools: { ...toolkit.tools, bash },
|
|
6494
|
-
skills: skills2
|
|
6495
|
-
drainFileEvents: () => observer.drain()
|
|
6484
|
+
tools: { ...toolkit.tools, bash, writeFile },
|
|
6485
|
+
skills: skills2
|
|
6496
6486
|
};
|
|
6497
6487
|
}
|
|
6498
6488
|
|
|
@@ -6595,6 +6585,13 @@ function resolveRealCwd(ctx) {
|
|
|
6595
6585
|
// packages/context/src/lib/sandbox/daytona-sandbox.ts
|
|
6596
6586
|
import "bash-tool";
|
|
6597
6587
|
import { randomUUID } from "node:crypto";
|
|
6588
|
+
|
|
6589
|
+
// packages/context/src/lib/sandbox/shell-quote.ts
|
|
6590
|
+
function shellQuote(s) {
|
|
6591
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
// packages/context/src/lib/sandbox/daytona-sandbox.ts
|
|
6598
6595
|
var DAYTONA_DEFAULT_DESTINATION = "/home/daytona";
|
|
6599
6596
|
var DAYTONA_EXIT_POLL_INTERVAL_MS = 250;
|
|
6600
6597
|
var DAYTONA_EXIT_POLL_TIMEOUT_MS = 3e4;
|
|
@@ -6817,7 +6814,7 @@ function createDaytonaSandboxMethods(args) {
|
|
|
6817
6814
|
async writeFiles(files) {
|
|
6818
6815
|
try {
|
|
6819
6816
|
for (const dir of uniqueParentDirectories(files.map((f) => f.path))) {
|
|
6820
|
-
const result = await executeCommand(`mkdir -p ${
|
|
6817
|
+
const result = await executeCommand(`mkdir -p ${shellQuote(dir)}`);
|
|
6821
6818
|
if (result.exitCode !== 0) {
|
|
6822
6819
|
throw new DaytonaCommandError(
|
|
6823
6820
|
`Failed to create directory "${dir}": ${result.stderr}`
|
|
@@ -6840,6 +6837,9 @@ function createDaytonaSandboxMethods(args) {
|
|
|
6840
6837
|
}
|
|
6841
6838
|
},
|
|
6842
6839
|
async dispose() {
|
|
6840
|
+
},
|
|
6841
|
+
[Symbol.asyncDispose]() {
|
|
6842
|
+
return this.dispose();
|
|
6843
6843
|
}
|
|
6844
6844
|
};
|
|
6845
6845
|
}
|
|
@@ -7007,17 +7007,17 @@ function createTextReadable() {
|
|
|
7007
7007
|
};
|
|
7008
7008
|
}
|
|
7009
7009
|
function buildSessionCommand(command, options) {
|
|
7010
|
-
const cwdPrefix = options.cwd ? `cd ${
|
|
7010
|
+
const cwdPrefix = options.cwd ? `cd ${shellQuote(options.cwd)} && ` : "";
|
|
7011
7011
|
const env = options.env ?? {};
|
|
7012
7012
|
const entries = Object.entries(env);
|
|
7013
7013
|
if (entries.length === 0) {
|
|
7014
|
-
return `sh -lc ${
|
|
7014
|
+
return `sh -lc ${shellQuote(`${cwdPrefix}${command}`)}`;
|
|
7015
7015
|
}
|
|
7016
7016
|
for (const [key] of entries) {
|
|
7017
7017
|
validateEnvKey(key);
|
|
7018
7018
|
}
|
|
7019
|
-
const exports = entries.map(([key, value]) => `export ${key}=${
|
|
7020
|
-
return `sh -lc ${
|
|
7019
|
+
const exports = entries.map(([key, value]) => `export ${key}=${shellQuote(value)}`).join("; ");
|
|
7020
|
+
return `sh -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
|
|
7021
7021
|
}
|
|
7022
7022
|
function validateEnvKey(key) {
|
|
7023
7023
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
@@ -7043,9 +7043,6 @@ function abortedExitInfo() {
|
|
|
7043
7043
|
success: false
|
|
7044
7044
|
};
|
|
7045
7045
|
}
|
|
7046
|
-
function shellQuote2(value) {
|
|
7047
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
7048
|
-
}
|
|
7049
7046
|
function uniqueParentDirectories(paths) {
|
|
7050
7047
|
const dirs = /* @__PURE__ */ new Set();
|
|
7051
7048
|
for (const path5 of paths) {
|
|
@@ -7222,9 +7219,6 @@ function isDebianBased(image) {
|
|
|
7222
7219
|
const debianPatterns = ["debian", "ubuntu", "node", "python"];
|
|
7223
7220
|
return debianPatterns.some((pattern) => lower.includes(pattern));
|
|
7224
7221
|
}
|
|
7225
|
-
function shellQuote3(s) {
|
|
7226
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
7227
|
-
}
|
|
7228
7222
|
function createInstallerContext(containerId, image) {
|
|
7229
7223
|
const packageManager = isDebianBased(image) ? "apt-get" : "apk";
|
|
7230
7224
|
let archPromise = null;
|
|
@@ -7270,7 +7264,7 @@ function createInstallerContext(containerId, image) {
|
|
|
7270
7264
|
};
|
|
7271
7265
|
const installPackages = async (packages) => {
|
|
7272
7266
|
if (packages.length === 0) return;
|
|
7273
|
-
const quoted = packages.map(
|
|
7267
|
+
const quoted = packages.map(shellQuote).join(" ");
|
|
7274
7268
|
let cmd;
|
|
7275
7269
|
if (packageManager === "apt-get") {
|
|
7276
7270
|
cmd = aptUpdated ? `apt-get install -y ${quoted}` : `apt-get update && apt-get install -y ${quoted}`;
|
|
@@ -7292,7 +7286,7 @@ function createInstallerContext(containerId, image) {
|
|
|
7292
7286
|
const ensureTool = async (checkName, installName) => {
|
|
7293
7287
|
const cacheKey = installName ?? checkName;
|
|
7294
7288
|
if (ensuredTools.has(cacheKey)) return;
|
|
7295
|
-
const check = await exec(`which ${
|
|
7289
|
+
const check = await exec(`which ${shellQuote(checkName)}`);
|
|
7296
7290
|
if (check.exitCode === 0) {
|
|
7297
7291
|
ensuredTools.add(cacheKey);
|
|
7298
7292
|
return;
|
|
@@ -7326,9 +7320,19 @@ var DockerSandboxStrategy = class {
|
|
|
7326
7320
|
env;
|
|
7327
7321
|
name;
|
|
7328
7322
|
command;
|
|
7323
|
+
securityOpt;
|
|
7324
|
+
platform;
|
|
7329
7325
|
createdVolumes = /* @__PURE__ */ new Set();
|
|
7330
7326
|
constructor(args = {}) {
|
|
7331
|
-
const {
|
|
7327
|
+
const {
|
|
7328
|
+
volumes = [],
|
|
7329
|
+
resources = {},
|
|
7330
|
+
env = {},
|
|
7331
|
+
name,
|
|
7332
|
+
command,
|
|
7333
|
+
securityOpt = [],
|
|
7334
|
+
platform
|
|
7335
|
+
} = args;
|
|
7332
7336
|
for (const key of Object.keys(env)) {
|
|
7333
7337
|
validateEnvKey2(key);
|
|
7334
7338
|
}
|
|
@@ -7342,6 +7346,8 @@ var DockerSandboxStrategy = class {
|
|
|
7342
7346
|
this.env = env;
|
|
7343
7347
|
this.name = name;
|
|
7344
7348
|
this.command = command;
|
|
7349
|
+
this.securityOpt = securityOpt;
|
|
7350
|
+
this.platform = platform;
|
|
7345
7351
|
}
|
|
7346
7352
|
async create() {
|
|
7347
7353
|
const image = await this.getImage();
|
|
@@ -7543,6 +7549,12 @@ var DockerSandboxStrategy = class {
|
|
|
7543
7549
|
"-w",
|
|
7544
7550
|
"/workspace"
|
|
7545
7551
|
];
|
|
7552
|
+
if (this.platform) {
|
|
7553
|
+
args.push("--platform", this.platform);
|
|
7554
|
+
}
|
|
7555
|
+
for (const opt of this.securityOpt) {
|
|
7556
|
+
args.push("--security-opt", opt);
|
|
7557
|
+
}
|
|
7546
7558
|
for (const [key, value] of Object.entries(this.env)) {
|
|
7547
7559
|
args.push("-e", `${key}=${value}`);
|
|
7548
7560
|
}
|
|
@@ -7736,6 +7748,9 @@ var DockerSandboxStrategy = class {
|
|
|
7736
7748
|
dispose: async () => {
|
|
7737
7749
|
await this.stopContainer(containerId);
|
|
7738
7750
|
await this.cleanupCreatedVolumes();
|
|
7751
|
+
},
|
|
7752
|
+
[Symbol.asyncDispose]() {
|
|
7753
|
+
return this.dispose();
|
|
7739
7754
|
}
|
|
7740
7755
|
};
|
|
7741
7756
|
return sandbox;
|
|
@@ -7802,7 +7817,9 @@ var RuntimeStrategy = class extends DockerSandboxStrategy {
|
|
|
7802
7817
|
resources: args.resources,
|
|
7803
7818
|
env: args.env,
|
|
7804
7819
|
name: args.name,
|
|
7805
|
-
command: args.command
|
|
7820
|
+
command: args.command,
|
|
7821
|
+
securityOpt: args.securityOpt,
|
|
7822
|
+
platform: args.platform
|
|
7806
7823
|
});
|
|
7807
7824
|
this.image = args.image ?? "alpine:latest";
|
|
7808
7825
|
this.installers = args.installers ?? [];
|
|
@@ -7821,21 +7838,25 @@ var DockerfileStrategy = class extends DockerSandboxStrategy {
|
|
|
7821
7838
|
imageTag;
|
|
7822
7839
|
dockerfile;
|
|
7823
7840
|
dockerContext;
|
|
7841
|
+
showBuildLogs;
|
|
7824
7842
|
constructor(args) {
|
|
7825
7843
|
super({
|
|
7826
7844
|
volumes: args.volumes,
|
|
7827
7845
|
resources: args.resources,
|
|
7828
7846
|
env: args.env,
|
|
7829
7847
|
name: args.name,
|
|
7830
|
-
command: args.command
|
|
7848
|
+
command: args.command,
|
|
7849
|
+
securityOpt: args.securityOpt,
|
|
7850
|
+
platform: args.platform
|
|
7831
7851
|
});
|
|
7832
7852
|
this.dockerfile = args.dockerfile;
|
|
7833
7853
|
this.dockerContext = args.context ?? ".";
|
|
7854
|
+
this.showBuildLogs = args.showBuildLogs ?? false;
|
|
7834
7855
|
this.imageTag = this.computeImageTag();
|
|
7835
7856
|
}
|
|
7836
7857
|
computeImageTag() {
|
|
7837
7858
|
const content = this.isInlineDockerfile() ? this.dockerfile : readFileSync2(this.dockerfile, "utf-8");
|
|
7838
|
-
const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
7859
|
+
const hash = createHash("sha256").update(content).update(this.platform ?? "").digest("hex").slice(0, 12);
|
|
7839
7860
|
return `sandbox-${hash}`;
|
|
7840
7861
|
}
|
|
7841
7862
|
isInlineDockerfile() {
|
|
@@ -7860,24 +7881,54 @@ var DockerfileStrategy = class extends DockerSandboxStrategy {
|
|
|
7860
7881
|
}
|
|
7861
7882
|
async buildImage() {
|
|
7862
7883
|
try {
|
|
7884
|
+
const platformFlag = this.platform ? `--platform ${this.platform} ` : "";
|
|
7863
7885
|
if (this.isInlineDockerfile()) {
|
|
7864
|
-
const buildCmd = `echo '${this.dockerfile.replace(/'/g, "'\\''")}' | docker build -t ${this.imageTag} -f - ${this.dockerContext}`;
|
|
7865
|
-
|
|
7886
|
+
const buildCmd = `echo '${this.dockerfile.replace(/'/g, "'\\''")}' | docker build ${platformFlag}-t ${this.imageTag} -f - ${this.dockerContext}`;
|
|
7887
|
+
if (this.showBuildLogs) {
|
|
7888
|
+
await this.runStreamed("sh", ["-c", buildCmd]);
|
|
7889
|
+
} else {
|
|
7890
|
+
await spawn3("sh", ["-c", buildCmd]);
|
|
7891
|
+
}
|
|
7866
7892
|
} else {
|
|
7867
|
-
|
|
7893
|
+
const args = [
|
|
7868
7894
|
"build",
|
|
7895
|
+
...this.platform ? ["--platform", this.platform] : [],
|
|
7869
7896
|
"-t",
|
|
7870
7897
|
this.imageTag,
|
|
7871
7898
|
"-f",
|
|
7872
7899
|
this.dockerfile,
|
|
7873
7900
|
this.dockerContext
|
|
7874
|
-
]
|
|
7901
|
+
];
|
|
7902
|
+
if (this.showBuildLogs) {
|
|
7903
|
+
await this.runStreamed("docker", args);
|
|
7904
|
+
} else {
|
|
7905
|
+
await spawn3("docker", args);
|
|
7906
|
+
}
|
|
7875
7907
|
}
|
|
7876
7908
|
} catch (error) {
|
|
7877
7909
|
const err = error;
|
|
7878
7910
|
throw new DockerfileBuildError(err.stderr || err.message);
|
|
7879
7911
|
}
|
|
7880
7912
|
}
|
|
7913
|
+
/**
|
|
7914
|
+
* Run a build command with stdio inherited so its output streams live to the
|
|
7915
|
+
* parent terminal. On failure the build error is already on screen; the
|
|
7916
|
+
* rejection just carries the exit code.
|
|
7917
|
+
*/
|
|
7918
|
+
runStreamed(command, args) {
|
|
7919
|
+
return new Promise((resolve4, reject) => {
|
|
7920
|
+
const child = childSpawn(command, args, { stdio: "inherit" });
|
|
7921
|
+
child.once("error", reject);
|
|
7922
|
+
child.once(
|
|
7923
|
+
"exit",
|
|
7924
|
+
(code) => code === 0 ? resolve4() : reject(
|
|
7925
|
+
new Error(
|
|
7926
|
+
`docker build exited with code ${code} (see build output above)`
|
|
7927
|
+
)
|
|
7928
|
+
)
|
|
7929
|
+
);
|
|
7930
|
+
});
|
|
7931
|
+
}
|
|
7881
7932
|
};
|
|
7882
7933
|
var ComposeStrategy = class extends DockerSandboxStrategy {
|
|
7883
7934
|
projectName;
|
|
@@ -7993,11 +8044,14 @@ async function createDockerSandbox(options = {}) {
|
|
|
7993
8044
|
strategy = new DockerfileStrategy({
|
|
7994
8045
|
dockerfile: options.dockerfile,
|
|
7995
8046
|
context: options.context,
|
|
8047
|
+
showBuildLogs: options.showBuildLogs,
|
|
7996
8048
|
volumes: options.volumes,
|
|
7997
8049
|
resources: options.resources,
|
|
7998
8050
|
env: options.env,
|
|
7999
8051
|
name: options.name,
|
|
8000
|
-
command: options.command
|
|
8052
|
+
command: options.command,
|
|
8053
|
+
securityOpt: options.securityOpt,
|
|
8054
|
+
platform: options.platform
|
|
8001
8055
|
});
|
|
8002
8056
|
} else {
|
|
8003
8057
|
strategy = new RuntimeStrategy({
|
|
@@ -8007,7 +8061,9 @@ async function createDockerSandbox(options = {}) {
|
|
|
8007
8061
|
resources: options.resources,
|
|
8008
8062
|
env: options.env,
|
|
8009
8063
|
name: options.name,
|
|
8010
|
-
command: options.command
|
|
8064
|
+
command: options.command,
|
|
8065
|
+
securityOpt: options.securityOpt,
|
|
8066
|
+
platform: options.platform
|
|
8011
8067
|
});
|
|
8012
8068
|
}
|
|
8013
8069
|
return strategy.create();
|
|
@@ -8021,6 +8077,338 @@ async function useSandbox(options, fn) {
|
|
|
8021
8077
|
}
|
|
8022
8078
|
}
|
|
8023
8079
|
|
|
8080
|
+
// packages/context/src/lib/sandbox/strace/file-changes.ts
|
|
8081
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8082
|
+
import { posix as posix2 } from "node:path";
|
|
8083
|
+
|
|
8084
|
+
// packages/context/src/lib/sandbox/strace/index.ts
|
|
8085
|
+
import { posix } from "node:path";
|
|
8086
|
+
var STRACE_FLAGS = "-f -y -qq -e trace=%file,write,pwrite64,writev";
|
|
8087
|
+
function buildStraceCommand(command, traceFile, traceDir) {
|
|
8088
|
+
return `mkdir -p ${shellQuote(traceDir)} 2>/dev/null; strace ${STRACE_FLAGS} -o ${shellQuote(traceFile)} -- sh -c ${shellQuote(command)}`;
|
|
8089
|
+
}
|
|
8090
|
+
function buildTracedCommand(command, traceFile, traceDir, sentinel) {
|
|
8091
|
+
return `mkdir -p ${shellQuote(traceDir)} 2>/dev/null; strace ${STRACE_FLAGS} -o ${shellQuote(traceFile)} -- sh -c ${shellQuote(command)}; __dat_rc=$?; printf '\\n%s\\n' ${shellQuote(sentinel)}; base64 ${shellQuote(traceFile)} 2>/dev/null; rm -f ${shellQuote(traceFile)} 2>/dev/null; exit $__dat_rc`;
|
|
8092
|
+
}
|
|
8093
|
+
function splitTracedOutput(stdout, sentinel) {
|
|
8094
|
+
const marker = `
|
|
8095
|
+
${sentinel}
|
|
8096
|
+
`;
|
|
8097
|
+
const at = stdout.indexOf(marker);
|
|
8098
|
+
if (at === -1) return { stdout, trace: "" };
|
|
8099
|
+
const base64 = stdout.slice(at + marker.length);
|
|
8100
|
+
return {
|
|
8101
|
+
stdout: stdout.slice(0, at),
|
|
8102
|
+
trace: base64 ? Buffer.from(base64, "base64").toString("utf8") : ""
|
|
8103
|
+
};
|
|
8104
|
+
}
|
|
8105
|
+
var CREATE_OR_TRUNC_FLAGS = /O_(CREAT|TRUNC)/;
|
|
8106
|
+
var RENAME_SYSCALLS = /* @__PURE__ */ new Set(["rename", "renameat", "renameat2"]);
|
|
8107
|
+
var DELETE_SYSCALLS = /* @__PURE__ */ new Set(["unlink", "unlinkat", "rmdir"]);
|
|
8108
|
+
var MKDIR_SYSCALLS = /* @__PURE__ */ new Set(["mkdir", "mkdirat"]);
|
|
8109
|
+
var LINK_SYSCALLS = /* @__PURE__ */ new Set(["link", "linkat", "symlink", "symlinkat"]);
|
|
8110
|
+
var OPEN_SYSCALLS = /* @__PURE__ */ new Set(["open", "openat", "openat2", "creat"]);
|
|
8111
|
+
var WRITE_SYSCALLS = /* @__PURE__ */ new Set([
|
|
8112
|
+
"write",
|
|
8113
|
+
"pwrite64",
|
|
8114
|
+
"pwrite",
|
|
8115
|
+
"writev",
|
|
8116
|
+
"pwritev2"
|
|
8117
|
+
]);
|
|
8118
|
+
var TRUNCATE_SYSCALLS = /* @__PURE__ */ new Set(["truncate", "ftruncate"]);
|
|
8119
|
+
function stitchLines(raw) {
|
|
8120
|
+
const pending = /* @__PURE__ */ new Map();
|
|
8121
|
+
const out = [];
|
|
8122
|
+
for (const original of raw.split("\n")) {
|
|
8123
|
+
const line = original.trimEnd();
|
|
8124
|
+
if (!line) continue;
|
|
8125
|
+
const pid = line.match(/^\s*(\d+)\s+/)?.[1] ?? "0";
|
|
8126
|
+
if (/<unfinished \.\.\.>\s*$/.test(line)) {
|
|
8127
|
+
pending.set(pid, line.replace(/<unfinished \.\.\.>\s*$/, ""));
|
|
8128
|
+
continue;
|
|
8129
|
+
}
|
|
8130
|
+
const resumed = line.match(/<\.\.\.\s+\S+\s+resumed>(.*)$/);
|
|
8131
|
+
if (resumed) {
|
|
8132
|
+
const head = pending.get(pid);
|
|
8133
|
+
pending.delete(pid);
|
|
8134
|
+
if (head !== void 0) out.push(head + resumed[1]);
|
|
8135
|
+
continue;
|
|
8136
|
+
}
|
|
8137
|
+
out.push(line);
|
|
8138
|
+
}
|
|
8139
|
+
return out;
|
|
8140
|
+
}
|
|
8141
|
+
function parseCall(line) {
|
|
8142
|
+
const m = line.match(
|
|
8143
|
+
/^\s*(?:\d+\s+)?([a-z_][a-z0-9_]*)\((.*)\)\s*=\s*(.+?)\s*$/
|
|
8144
|
+
);
|
|
8145
|
+
if (!m) return null;
|
|
8146
|
+
const [, syscall, args, rest] = m;
|
|
8147
|
+
const errnoMatch = rest.match(/^(-?\d+)\s+([A-Z][A-Z0-9_]*)/);
|
|
8148
|
+
if (errnoMatch) {
|
|
8149
|
+
return { syscall, args, retval: errnoMatch[1], errno: errnoMatch[2] };
|
|
8150
|
+
}
|
|
8151
|
+
return { syscall, args, retval: rest.split(/\s+/)[0] };
|
|
8152
|
+
}
|
|
8153
|
+
var STRACE_ESCAPE = {
|
|
8154
|
+
n: 10,
|
|
8155
|
+
t: 9,
|
|
8156
|
+
r: 13,
|
|
8157
|
+
v: 11,
|
|
8158
|
+
f: 12,
|
|
8159
|
+
'"': 34,
|
|
8160
|
+
"\\": 92
|
|
8161
|
+
};
|
|
8162
|
+
function decodeStraceString(s) {
|
|
8163
|
+
const bytes = [];
|
|
8164
|
+
for (let i = 0; i < s.length; ) {
|
|
8165
|
+
if (s[i] === "\\" && i + 1 < s.length) {
|
|
8166
|
+
const octal = s.slice(i + 1, i + 4).match(/^[0-7]{1,3}/)?.[0];
|
|
8167
|
+
if (octal) {
|
|
8168
|
+
bytes.push(parseInt(octal, 8) & 255);
|
|
8169
|
+
i += 1 + octal.length;
|
|
8170
|
+
continue;
|
|
8171
|
+
}
|
|
8172
|
+
const mapped = STRACE_ESCAPE[s[i + 1]];
|
|
8173
|
+
bytes.push(mapped ?? s.charCodeAt(i + 1));
|
|
8174
|
+
i += 2;
|
|
8175
|
+
continue;
|
|
8176
|
+
}
|
|
8177
|
+
const code = s.charCodeAt(i);
|
|
8178
|
+
if (code < 128) bytes.push(code);
|
|
8179
|
+
else for (const b of Buffer.from(s[i], "utf8")) bytes.push(b);
|
|
8180
|
+
i += 1;
|
|
8181
|
+
}
|
|
8182
|
+
return Buffer.from(bytes).toString("utf8");
|
|
8183
|
+
}
|
|
8184
|
+
function quotedStrings(args) {
|
|
8185
|
+
const out = [];
|
|
8186
|
+
const re = /"((?:[^"\\]|\\.)*)"/g;
|
|
8187
|
+
let m;
|
|
8188
|
+
while ((m = re.exec(args)) !== null) {
|
|
8189
|
+
out.push(decodeStraceString(m[1]));
|
|
8190
|
+
}
|
|
8191
|
+
return out;
|
|
8192
|
+
}
|
|
8193
|
+
function dirfdPath(args) {
|
|
8194
|
+
return args.match(/^\s*(?:AT_FDCWD|-?\d+)<([^>]*)>/)?.[1] ?? "/";
|
|
8195
|
+
}
|
|
8196
|
+
function fdPath(args) {
|
|
8197
|
+
return args.match(/^\s*-?\d+<([^>]*)>/)?.[1] ?? null;
|
|
8198
|
+
}
|
|
8199
|
+
function openFlags(args) {
|
|
8200
|
+
return args.match(/\bO_[A-Z_]+(?:\|O_[A-Z_]+)*/)?.[0] ?? "";
|
|
8201
|
+
}
|
|
8202
|
+
function matchesGlobs(path5, include, exclude) {
|
|
8203
|
+
return include.some((glob) => posix.matchesGlob(path5, glob)) && !exclude?.some((glob) => posix.matchesGlob(path5, glob));
|
|
8204
|
+
}
|
|
8205
|
+
function parseStraceTrace(raw, options) {
|
|
8206
|
+
const { include, exclude, traceDir, traceFile } = options;
|
|
8207
|
+
const now = Date.now();
|
|
8208
|
+
const state = /* @__PURE__ */ new Map();
|
|
8209
|
+
const renames = [];
|
|
8210
|
+
const resolve4 = (dir, p) => posix.normalize(p.startsWith("/") ? p : posix.join(dir, p));
|
|
8211
|
+
const write = (path5) => {
|
|
8212
|
+
state.set(path5, "write");
|
|
8213
|
+
};
|
|
8214
|
+
const remove = (path5) => {
|
|
8215
|
+
if (state.get(path5) === "write") state.delete(path5);
|
|
8216
|
+
else state.set(path5, "delete");
|
|
8217
|
+
};
|
|
8218
|
+
for (const line of stitchLines(raw)) {
|
|
8219
|
+
const call = parseCall(line);
|
|
8220
|
+
if (!call || call.errno || call.retval === "-1") continue;
|
|
8221
|
+
const { syscall, args } = call;
|
|
8222
|
+
const dir = dirfdPath(args);
|
|
8223
|
+
if (RENAME_SYSCALLS.has(syscall)) {
|
|
8224
|
+
const strings = quotedStrings(args);
|
|
8225
|
+
if (strings.length < 2) continue;
|
|
8226
|
+
const from = resolve4(dir, strings[0]);
|
|
8227
|
+
const to = resolve4(dir, strings[strings.length - 1]);
|
|
8228
|
+
state.delete(from);
|
|
8229
|
+
renames.push({ from, to });
|
|
8230
|
+
continue;
|
|
8231
|
+
}
|
|
8232
|
+
if (DELETE_SYSCALLS.has(syscall)) {
|
|
8233
|
+
const strings = quotedStrings(args);
|
|
8234
|
+
if (strings.length) remove(resolve4(dir, strings[0]));
|
|
8235
|
+
continue;
|
|
8236
|
+
}
|
|
8237
|
+
if (MKDIR_SYSCALLS.has(syscall)) {
|
|
8238
|
+
const strings = quotedStrings(args);
|
|
8239
|
+
if (strings.length) write(resolve4(dir, strings[0]));
|
|
8240
|
+
continue;
|
|
8241
|
+
}
|
|
8242
|
+
if (LINK_SYSCALLS.has(syscall)) {
|
|
8243
|
+
const strings = quotedStrings(args);
|
|
8244
|
+
if (strings.length) write(resolve4(dir, strings[strings.length - 1]));
|
|
8245
|
+
continue;
|
|
8246
|
+
}
|
|
8247
|
+
if (OPEN_SYSCALLS.has(syscall)) {
|
|
8248
|
+
if (!CREATE_OR_TRUNC_FLAGS.test(openFlags(args))) continue;
|
|
8249
|
+
const strings = quotedStrings(args);
|
|
8250
|
+
if (strings.length) write(resolve4(dir, strings[0]));
|
|
8251
|
+
continue;
|
|
8252
|
+
}
|
|
8253
|
+
if (WRITE_SYSCALLS.has(syscall)) {
|
|
8254
|
+
const p = fdPath(args);
|
|
8255
|
+
if (p && Number(call.retval) > 0) write(posix.normalize(p));
|
|
8256
|
+
continue;
|
|
8257
|
+
}
|
|
8258
|
+
if (TRUNCATE_SYSCALLS.has(syscall)) {
|
|
8259
|
+
const p = fdPath(args) ?? quotedStrings(args)[0];
|
|
8260
|
+
if (p) write(resolve4(dir, p));
|
|
8261
|
+
}
|
|
8262
|
+
}
|
|
8263
|
+
const keep = (path5) => {
|
|
8264
|
+
if (traceFile && path5 === traceFile) return false;
|
|
8265
|
+
if (traceDir && (path5 === traceDir || path5.startsWith(`${traceDir}/`))) {
|
|
8266
|
+
return false;
|
|
8267
|
+
}
|
|
8268
|
+
if (/^\/(proc|sys|dev)(\/|$)/.test(path5)) return false;
|
|
8269
|
+
return matchesGlobs(path5, include, exclude);
|
|
8270
|
+
};
|
|
8271
|
+
const changes = [];
|
|
8272
|
+
const emittedRenameTargets = /* @__PURE__ */ new Set();
|
|
8273
|
+
for (const { from, to } of renames) {
|
|
8274
|
+
if (!keep(to)) continue;
|
|
8275
|
+
if (state.get(to) === "delete") continue;
|
|
8276
|
+
changes.push({ op: "rename", path: to, from, timestamp: now });
|
|
8277
|
+
emittedRenameTargets.add(to);
|
|
8278
|
+
}
|
|
8279
|
+
const tail = [];
|
|
8280
|
+
for (const [path5, op] of state) {
|
|
8281
|
+
if (emittedRenameTargets.has(path5)) continue;
|
|
8282
|
+
if (keep(path5)) tail.push({ op, path: path5, timestamp: now });
|
|
8283
|
+
}
|
|
8284
|
+
tail.sort((a, b) => {
|
|
8285
|
+
if (a.path < b.path) return -1;
|
|
8286
|
+
if (a.path > b.path) return 1;
|
|
8287
|
+
return 0;
|
|
8288
|
+
});
|
|
8289
|
+
changes.push(...tail);
|
|
8290
|
+
return changes;
|
|
8291
|
+
}
|
|
8292
|
+
|
|
8293
|
+
// packages/context/src/lib/sandbox/strace/file-changes.ts
|
|
8294
|
+
var DEFAULT_TRACE_DIR = "/tmp/dat-trace";
|
|
8295
|
+
function stripStraceDiagnostics(stderr) {
|
|
8296
|
+
if (!stderr.includes("strace: ")) return stderr;
|
|
8297
|
+
return stderr.split("\n").filter((line) => !line.startsWith("strace: ")).join("\n");
|
|
8298
|
+
}
|
|
8299
|
+
function warnOnFileChangesError(error) {
|
|
8300
|
+
console.warn(
|
|
8301
|
+
"[withStraceFileChanges] onFileChanges threw on spawn; isolated",
|
|
8302
|
+
error
|
|
8303
|
+
);
|
|
8304
|
+
}
|
|
8305
|
+
async function withStraceFileChanges(sandbox, options) {
|
|
8306
|
+
const { include, exclude, onFileChanges } = options;
|
|
8307
|
+
const onError = options.onError ?? warnOnFileChangesError;
|
|
8308
|
+
const traceDir = options.traceDir ?? DEFAULT_TRACE_DIR;
|
|
8309
|
+
const innerExecute = sandbox.executeCommand.bind(sandbox);
|
|
8310
|
+
const innerReadFile = sandbox.readFile.bind(sandbox);
|
|
8311
|
+
const innerWriteFiles = sandbox.writeFiles.bind(sandbox);
|
|
8312
|
+
const removeTrace = (traceFile) => void innerExecute(`rm -f ${shellQuote(traceFile)}`).catch(() => {
|
|
8313
|
+
});
|
|
8314
|
+
const readChanges = async (traceFile) => {
|
|
8315
|
+
try {
|
|
8316
|
+
return parseStraceTrace(await innerReadFile(traceFile), {
|
|
8317
|
+
include,
|
|
8318
|
+
exclude,
|
|
8319
|
+
traceFile,
|
|
8320
|
+
traceDir
|
|
8321
|
+
});
|
|
8322
|
+
} catch {
|
|
8323
|
+
return [];
|
|
8324
|
+
} finally {
|
|
8325
|
+
removeTrace(traceFile);
|
|
8326
|
+
}
|
|
8327
|
+
};
|
|
8328
|
+
const collectSpawn = async (traceFile) => {
|
|
8329
|
+
const changes = await readChanges(traceFile);
|
|
8330
|
+
if (!changes.length || !onFileChanges) return;
|
|
8331
|
+
try {
|
|
8332
|
+
await onFileChanges(changes);
|
|
8333
|
+
} catch (error) {
|
|
8334
|
+
onError(error);
|
|
8335
|
+
}
|
|
8336
|
+
};
|
|
8337
|
+
const decorated = {
|
|
8338
|
+
async executeCommand(command, execOptions) {
|
|
8339
|
+
const traceFile = `${traceDir}/${randomUUID2()}.strace`;
|
|
8340
|
+
const sentinel = `__dat_trace_${randomUUID2()}__`;
|
|
8341
|
+
const wrapped = buildTracedCommand(
|
|
8342
|
+
command,
|
|
8343
|
+
traceFile,
|
|
8344
|
+
traceDir,
|
|
8345
|
+
sentinel
|
|
8346
|
+
);
|
|
8347
|
+
let raw;
|
|
8348
|
+
try {
|
|
8349
|
+
raw = await innerExecute(wrapped, execOptions);
|
|
8350
|
+
} catch (error) {
|
|
8351
|
+
removeTrace(traceFile);
|
|
8352
|
+
throw error;
|
|
8353
|
+
}
|
|
8354
|
+
const { stdout, trace } = splitTracedOutput(raw.stdout, sentinel);
|
|
8355
|
+
const result = {
|
|
8356
|
+
...raw,
|
|
8357
|
+
stdout,
|
|
8358
|
+
stderr: stripStraceDiagnostics(raw.stderr)
|
|
8359
|
+
};
|
|
8360
|
+
if (execOptions?.signal?.aborted) return result;
|
|
8361
|
+
const changes = trace ? parseStraceTrace(trace, { include, exclude, traceFile, traceDir }) : [];
|
|
8362
|
+
if (changes.length) {
|
|
8363
|
+
useBashMeta()?.setHidden({ fileChanges: changes });
|
|
8364
|
+
await onFileChanges?.(changes);
|
|
8365
|
+
}
|
|
8366
|
+
return result;
|
|
8367
|
+
},
|
|
8368
|
+
readFile: innerReadFile,
|
|
8369
|
+
// The writeFile tool mutates the filesystem outside strace's view, so
|
|
8370
|
+
// observe it directly: synthesize a `write` change per file (under the
|
|
8371
|
+
// observation root) and run it through onFileChanges. A throw propagates to
|
|
8372
|
+
// the writeFile tool's execute (which rejects), the same gate as the bash
|
|
8373
|
+
// path — except there's no CommandResult here, so throw a plain Error to
|
|
8374
|
+
// reject; BashException.format() is meaningful only for bash commands.
|
|
8375
|
+
writeFiles: async (files) => {
|
|
8376
|
+
await innerWriteFiles(files);
|
|
8377
|
+
const now = Date.now();
|
|
8378
|
+
const changes = files.map((f) => posix2.normalize(f.path)).filter((path5) => matchesGlobs(path5, include, exclude)).map((path5) => ({ op: "write", path: path5, timestamp: now }));
|
|
8379
|
+
if (changes.length) await onFileChanges?.(changes);
|
|
8380
|
+
},
|
|
8381
|
+
dispose: async () => {
|
|
8382
|
+
await innerExecute(`rm -rf ${shellQuote(traceDir)}`).catch(() => {
|
|
8383
|
+
});
|
|
8384
|
+
await sandbox.dispose();
|
|
8385
|
+
},
|
|
8386
|
+
[Symbol.asyncDispose]() {
|
|
8387
|
+
return this.dispose();
|
|
8388
|
+
}
|
|
8389
|
+
};
|
|
8390
|
+
if (sandbox.spawn) {
|
|
8391
|
+
const innerSpawn = sandbox.spawn.bind(sandbox);
|
|
8392
|
+
decorated.spawn = (command, spawnOptions) => {
|
|
8393
|
+
const traceFile = `${traceDir}/${randomUUID2()}.strace`;
|
|
8394
|
+
const child = innerSpawn(
|
|
8395
|
+
buildStraceCommand(command, traceFile, traceDir),
|
|
8396
|
+
spawnOptions
|
|
8397
|
+
);
|
|
8398
|
+
const exit = (async () => {
|
|
8399
|
+
try {
|
|
8400
|
+
return await child.exit;
|
|
8401
|
+
} finally {
|
|
8402
|
+
await collectSpawn(traceFile).catch(() => {
|
|
8403
|
+
});
|
|
8404
|
+
}
|
|
8405
|
+
})();
|
|
8406
|
+
return { stdout: child.stdout, stderr: child.stderr, exit };
|
|
8407
|
+
};
|
|
8408
|
+
}
|
|
8409
|
+
return decorated;
|
|
8410
|
+
}
|
|
8411
|
+
|
|
8024
8412
|
// packages/context/src/lib/sandbox/gcs.ts
|
|
8025
8413
|
function gcs(options) {
|
|
8026
8414
|
return {
|
|
@@ -8031,22 +8419,57 @@ function gcs(options) {
|
|
|
8031
8419
|
};
|
|
8032
8420
|
}
|
|
8033
8421
|
|
|
8034
|
-
// packages/context/src/lib/sandbox/installers/
|
|
8035
|
-
|
|
8422
|
+
// packages/context/src/lib/sandbox/installers/bin.ts
|
|
8423
|
+
import { basename, posix as posix3 } from "node:path";
|
|
8424
|
+
var NOT_FOUND_EXIT = 11;
|
|
8425
|
+
var CHMOD_FAILED_EXIT = 12;
|
|
8426
|
+
var BinInstaller = class extends Installer {
|
|
8036
8427
|
kind;
|
|
8037
|
-
|
|
8038
|
-
|
|
8428
|
+
binary;
|
|
8429
|
+
options;
|
|
8430
|
+
#name;
|
|
8431
|
+
#target;
|
|
8432
|
+
constructor(binary, options = {}) {
|
|
8039
8433
|
super();
|
|
8040
|
-
this.
|
|
8041
|
-
this.
|
|
8434
|
+
this.binary = binary;
|
|
8435
|
+
this.options = options;
|
|
8436
|
+
this.#name = resolveName(binary, options);
|
|
8437
|
+
this.#target = options.target ?? posix3.join("/usr/local/bin", this.#name);
|
|
8438
|
+
this.kind = `bin:${this.#name}`;
|
|
8042
8439
|
}
|
|
8043
8440
|
async install(ctx) {
|
|
8044
|
-
|
|
8045
|
-
|
|
8441
|
+
const b = shellQuote(this.binary);
|
|
8442
|
+
const t = shellQuote(this.#target);
|
|
8443
|
+
const dir = shellQuote(posix3.dirname(this.#target));
|
|
8444
|
+
const result = await ctx.exec(
|
|
8445
|
+
`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}`
|
|
8446
|
+
);
|
|
8447
|
+
if (result.exitCode === 0) return;
|
|
8448
|
+
throw new InstallError({
|
|
8449
|
+
target: this.#name,
|
|
8450
|
+
source: "bin",
|
|
8451
|
+
reason: explainFailure(result.exitCode, result.stderr, this.binary),
|
|
8452
|
+
containerId: ctx.containerId
|
|
8453
|
+
});
|
|
8046
8454
|
}
|
|
8047
8455
|
};
|
|
8048
|
-
function
|
|
8049
|
-
return new
|
|
8456
|
+
function bin(binary, options) {
|
|
8457
|
+
return new BinInstaller(binary, options);
|
|
8458
|
+
}
|
|
8459
|
+
function explainFailure(exitCode, stderr, binary) {
|
|
8460
|
+
if (exitCode === NOT_FOUND_EXIT) {
|
|
8461
|
+
return `binary not found at ${binary}`;
|
|
8462
|
+
}
|
|
8463
|
+
if (exitCode === CHMOD_FAILED_EXIT && /read-only file system/i.test(stderr)) {
|
|
8464
|
+
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()})`;
|
|
8465
|
+
}
|
|
8466
|
+
return stderr || `bin installer failed with exit code ${exitCode}`;
|
|
8467
|
+
}
|
|
8468
|
+
function resolveName(binary, options) {
|
|
8469
|
+
if (options.name) return options.name;
|
|
8470
|
+
const base = basename(binary);
|
|
8471
|
+
const dot = base.lastIndexOf(".");
|
|
8472
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
8050
8473
|
}
|
|
8051
8474
|
|
|
8052
8475
|
// packages/context/src/lib/sandbox/installers/url-binary.ts
|
|
@@ -8103,9 +8526,9 @@ async function downloadAndInstall(ctx, name, url, binaryPath, source = "url") {
|
|
|
8103
8526
|
function buildTarGzInstallCmd(name, url, binaryPathInArchive) {
|
|
8104
8527
|
return `
|
|
8105
8528
|
set -e
|
|
8106
|
-
NAME=${
|
|
8107
|
-
URL=${
|
|
8108
|
-
BIN_IN_ARCHIVE=${
|
|
8529
|
+
NAME=${shellQuote(name)}
|
|
8530
|
+
URL=${shellQuote(url)}
|
|
8531
|
+
BIN_IN_ARCHIVE=${shellQuote(binaryPathInArchive)}
|
|
8109
8532
|
TMPDIR=$(mktemp -d)
|
|
8110
8533
|
cd "$TMPDIR"
|
|
8111
8534
|
curl -fsSL "$URL" -o archive.tar.gz
|
|
@@ -8124,13 +8547,39 @@ function buildTarGzInstallCmd(name, url, binaryPathInArchive) {
|
|
|
8124
8547
|
}
|
|
8125
8548
|
function buildRawInstallCmd(name, url) {
|
|
8126
8549
|
return `
|
|
8127
|
-
NAME=${
|
|
8128
|
-
URL=${
|
|
8550
|
+
NAME=${shellQuote(name)}
|
|
8551
|
+
URL=${shellQuote(url)}
|
|
8129
8552
|
curl -fsSL "$URL" -o "/usr/local/bin/$NAME"
|
|
8130
8553
|
chmod +x "/usr/local/bin/$NAME"
|
|
8131
8554
|
`;
|
|
8132
8555
|
}
|
|
8133
8556
|
|
|
8557
|
+
// packages/context/src/lib/sandbox/installers/github-release.ts
|
|
8558
|
+
var GithubReleaseInstaller = class extends Installer {
|
|
8559
|
+
kind;
|
|
8560
|
+
options;
|
|
8561
|
+
constructor(options) {
|
|
8562
|
+
super();
|
|
8563
|
+
this.options = options;
|
|
8564
|
+
this.kind = `github-release:${options.owner}/${options.repo}@${options.version}`;
|
|
8565
|
+
}
|
|
8566
|
+
async install(ctx) {
|
|
8567
|
+
const arch = await ctx.arch();
|
|
8568
|
+
const assetName = this.options.asset(arch);
|
|
8569
|
+
const url = `https://github.com/${this.options.owner}/${this.options.repo}/releases/download/${this.options.version}/${assetName}`;
|
|
8570
|
+
await downloadAndInstall(
|
|
8571
|
+
ctx,
|
|
8572
|
+
this.options.name,
|
|
8573
|
+
url,
|
|
8574
|
+
this.options.binaryPath,
|
|
8575
|
+
"github-release"
|
|
8576
|
+
);
|
|
8577
|
+
}
|
|
8578
|
+
};
|
|
8579
|
+
function githubRelease(options) {
|
|
8580
|
+
return new GithubReleaseInstaller(options);
|
|
8581
|
+
}
|
|
8582
|
+
|
|
8134
8583
|
// packages/context/src/lib/sandbox/installers/npm.ts
|
|
8135
8584
|
var NODE_BINARIES = ["node", "npm"];
|
|
8136
8585
|
var ENSURE_RUNTIME_HINT = "Pass `{ ensureRuntime: true }` to auto-install, or add it via `pkg([...])`.";
|
|
@@ -8178,57 +8627,22 @@ async function ensureNodeRuntime(ctx, ensure) {
|
|
|
8178
8627
|
}
|
|
8179
8628
|
}
|
|
8180
8629
|
|
|
8181
|
-
// packages/context/src/lib/sandbox/installers/
|
|
8182
|
-
|
|
8183
|
-
var NOT_FOUND_EXIT = 11;
|
|
8184
|
-
var CHMOD_FAILED_EXIT = 12;
|
|
8185
|
-
var BinInstaller = class extends Installer {
|
|
8630
|
+
// packages/context/src/lib/sandbox/installers/package-manager.ts
|
|
8631
|
+
var PackageInstaller = class extends Installer {
|
|
8186
8632
|
kind;
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
#name;
|
|
8190
|
-
#target;
|
|
8191
|
-
constructor(binary, options = {}) {
|
|
8633
|
+
packages;
|
|
8634
|
+
constructor(packages) {
|
|
8192
8635
|
super();
|
|
8193
|
-
this.
|
|
8194
|
-
this.
|
|
8195
|
-
this.#name = resolveName(binary, options);
|
|
8196
|
-
this.#target = options.target ?? posix.join("/usr/local/bin", this.#name);
|
|
8197
|
-
this.kind = `bin:${this.#name}`;
|
|
8636
|
+
this.packages = packages;
|
|
8637
|
+
this.kind = packages.length === 0 ? "pkg:<empty>" : `pkg:${packages.join(",")}`;
|
|
8198
8638
|
}
|
|
8199
8639
|
async install(ctx) {
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
const dir = shellQuote3(posix.dirname(this.#target));
|
|
8203
|
-
const result = await ctx.exec(
|
|
8204
|
-
`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}`
|
|
8205
|
-
);
|
|
8206
|
-
if (result.exitCode === 0) return;
|
|
8207
|
-
throw new InstallError({
|
|
8208
|
-
target: this.#name,
|
|
8209
|
-
source: "bin",
|
|
8210
|
-
reason: explainFailure(result.exitCode, result.stderr, this.binary),
|
|
8211
|
-
containerId: ctx.containerId
|
|
8212
|
-
});
|
|
8640
|
+
if (this.packages.length === 0) return;
|
|
8641
|
+
await ctx.installPackages([...this.packages]);
|
|
8213
8642
|
}
|
|
8214
8643
|
};
|
|
8215
|
-
function
|
|
8216
|
-
return new
|
|
8217
|
-
}
|
|
8218
|
-
function explainFailure(exitCode, stderr, binary) {
|
|
8219
|
-
if (exitCode === NOT_FOUND_EXIT) {
|
|
8220
|
-
return `binary not found at ${binary}`;
|
|
8221
|
-
}
|
|
8222
|
-
if (exitCode === CHMOD_FAILED_EXIT && /read-only file system/i.test(stderr)) {
|
|
8223
|
-
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()})`;
|
|
8224
|
-
}
|
|
8225
|
-
return stderr || `bin installer failed with exit code ${exitCode}`;
|
|
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;
|
|
8644
|
+
function pkg(packages) {
|
|
8645
|
+
return new PackageInstaller(packages);
|
|
8232
8646
|
}
|
|
8233
8647
|
|
|
8234
8648
|
// packages/context/src/lib/sandbox/installers/pip.ts
|
|
@@ -8282,32 +8696,6 @@ function formatVersion(version) {
|
|
|
8282
8696
|
return /^[<>=!~]/.test(version) ? version : `==${version}`;
|
|
8283
8697
|
}
|
|
8284
8698
|
|
|
8285
|
-
// packages/context/src/lib/sandbox/installers/github-release.ts
|
|
8286
|
-
var GithubReleaseInstaller = class extends Installer {
|
|
8287
|
-
kind;
|
|
8288
|
-
options;
|
|
8289
|
-
constructor(options) {
|
|
8290
|
-
super();
|
|
8291
|
-
this.options = options;
|
|
8292
|
-
this.kind = `github-release:${options.owner}/${options.repo}@${options.version}`;
|
|
8293
|
-
}
|
|
8294
|
-
async install(ctx) {
|
|
8295
|
-
const arch = await ctx.arch();
|
|
8296
|
-
const assetName = this.options.asset(arch);
|
|
8297
|
-
const url = `https://github.com/${this.options.owner}/${this.options.repo}/releases/download/${this.options.version}/${assetName}`;
|
|
8298
|
-
await downloadAndInstall(
|
|
8299
|
-
ctx,
|
|
8300
|
-
this.options.name,
|
|
8301
|
-
url,
|
|
8302
|
-
this.options.binaryPath,
|
|
8303
|
-
"github-release"
|
|
8304
|
-
);
|
|
8305
|
-
}
|
|
8306
|
-
};
|
|
8307
|
-
function githubRelease(options) {
|
|
8308
|
-
return new GithubReleaseInstaller(options);
|
|
8309
|
-
}
|
|
8310
|
-
|
|
8311
8699
|
// packages/context/src/lib/sandbox/subcommand.ts
|
|
8312
8700
|
import {
|
|
8313
8701
|
defineCommand as defineCommand2,
|
|
@@ -8413,6 +8801,9 @@ async function createVirtualSandbox(options) {
|
|
|
8413
8801
|
}
|
|
8414
8802
|
},
|
|
8415
8803
|
async dispose() {
|
|
8804
|
+
},
|
|
8805
|
+
[Symbol.asyncDispose]() {
|
|
8806
|
+
return this.dispose();
|
|
8416
8807
|
}
|
|
8417
8808
|
};
|
|
8418
8809
|
}
|
|
@@ -8477,11 +8868,14 @@ ${lines.join("\n")}`;
|
|
|
8477
8868
|
}
|
|
8478
8869
|
function skillsReminder(skillsOrClassifier, options) {
|
|
8479
8870
|
const classifier = Array.isArray(skillsOrClassifier) ? new BM25Classifier(skillsOrClassifier) : skillsOrClassifier;
|
|
8480
|
-
return reminder(
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8871
|
+
return reminder(
|
|
8872
|
+
(ctx) => {
|
|
8873
|
+
const matches = classifier.match(ctx.content, options);
|
|
8874
|
+
if (matches.length === 0) return "";
|
|
8875
|
+
return formatSkillReminder(matches);
|
|
8876
|
+
},
|
|
8877
|
+
{ target: "user" }
|
|
8878
|
+
);
|
|
8485
8879
|
}
|
|
8486
8880
|
|
|
8487
8881
|
// packages/context/src/lib/soul/protocol.md
|
|
@@ -11654,7 +12048,6 @@ export {
|
|
|
11654
12048
|
PostgresStreamStore,
|
|
11655
12049
|
PromiseResolver,
|
|
11656
12050
|
RuntimeStrategy,
|
|
11657
|
-
SnapshotFailedError,
|
|
11658
12051
|
SqlServerContextStore,
|
|
11659
12052
|
SqliteContextStore,
|
|
11660
12053
|
SqliteStreamStore,
|
|
@@ -11682,7 +12075,8 @@ export {
|
|
|
11682
12075
|
applyInlineReminder,
|
|
11683
12076
|
applyPartReminder,
|
|
11684
12077
|
applyReminderToMessage,
|
|
11685
|
-
|
|
12078
|
+
applyRemindersToToolOutput,
|
|
12079
|
+
applyUserRemindersToMessage,
|
|
11686
12080
|
asStaticWordPartText,
|
|
11687
12081
|
asStaticWordText,
|
|
11688
12082
|
assertCountSpec,
|
|
@@ -11716,6 +12110,7 @@ export {
|
|
|
11716
12110
|
defineSubcommandGroup,
|
|
11717
12111
|
discoverSkillMappings,
|
|
11718
12112
|
discoverSkillsInDirectory,
|
|
12113
|
+
downloadAndInstall,
|
|
11719
12114
|
elapsedExceeds,
|
|
11720
12115
|
encodeSerializedValue,
|
|
11721
12116
|
errorRecoveryGuardrail,
|
|
@@ -11726,7 +12121,7 @@ export {
|
|
|
11726
12121
|
executorContext,
|
|
11727
12122
|
explain,
|
|
11728
12123
|
fail,
|
|
11729
|
-
|
|
12124
|
+
first,
|
|
11730
12125
|
firstN,
|
|
11731
12126
|
fragment,
|
|
11732
12127
|
fromFragment,
|
|
@@ -11734,6 +12129,7 @@ export {
|
|
|
11734
12129
|
getFragmentData,
|
|
11735
12130
|
getLocaleFromMessage,
|
|
11736
12131
|
getModelsRegistry,
|
|
12132
|
+
getReminderOnceIds,
|
|
11737
12133
|
getReminderRanges,
|
|
11738
12134
|
getSeason,
|
|
11739
12135
|
githubRelease,
|
|
@@ -11750,6 +12146,7 @@ export {
|
|
|
11750
12146
|
isFragmentObject,
|
|
11751
12147
|
isMessageFragment,
|
|
11752
12148
|
isRecord,
|
|
12149
|
+
isSyntheticSteerMessage,
|
|
11753
12150
|
lastAssistantLength,
|
|
11754
12151
|
loadSkillMetadata,
|
|
11755
12152
|
localeReminder,
|
|
@@ -11765,7 +12162,6 @@ export {
|
|
|
11765
12162
|
not,
|
|
11766
12163
|
npm,
|
|
11767
12164
|
nullUsage,
|
|
11768
|
-
observeSandboxFileEvents,
|
|
11769
12165
|
once,
|
|
11770
12166
|
or,
|
|
11771
12167
|
parseFrontmatter,
|
|
@@ -11795,7 +12191,7 @@ export {
|
|
|
11795
12191
|
seasonChanged,
|
|
11796
12192
|
seasonReminder,
|
|
11797
12193
|
selfCritique,
|
|
11798
|
-
|
|
12194
|
+
shellQuote,
|
|
11799
12195
|
skills,
|
|
11800
12196
|
skillsReminder,
|
|
11801
12197
|
socraticPrompting,
|
|
@@ -11806,6 +12202,7 @@ export {
|
|
|
11806
12202
|
stripTextByRanges,
|
|
11807
12203
|
structuredOutput,
|
|
11808
12204
|
styleGuide,
|
|
12205
|
+
synthesizeSteerUserMessage,
|
|
11809
12206
|
temporalReminder,
|
|
11810
12207
|
term,
|
|
11811
12208
|
timeReminder,
|
|
@@ -11825,6 +12222,7 @@ export {
|
|
|
11825
12222
|
visualizeGraph,
|
|
11826
12223
|
weekChanged,
|
|
11827
12224
|
withAbortSignal,
|
|
12225
|
+
withStraceFileChanges,
|
|
11828
12226
|
withinLastN,
|
|
11829
12227
|
workflow,
|
|
11830
12228
|
yearChanged,
|