@deepagents/context 3.1.0 → 4.1.0

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