@deepagents/context 6.1.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +29 -3
  2. package/dist/browser.js +9 -2
  3. package/dist/browser.js.map +2 -2
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1411 -514
  7. package/dist/index.js.map +4 -4
  8. package/dist/lib/agent.d.ts +78 -37
  9. package/dist/lib/agent.d.ts.map +1 -1
  10. package/dist/lib/chat.d.ts +17 -18
  11. package/dist/lib/chat.d.ts.map +1 -1
  12. package/dist/lib/engine.d.ts.map +1 -1
  13. package/dist/lib/fragments/plan-shared.d.ts +46 -0
  14. package/dist/lib/fragments/plan-shared.d.ts.map +1 -0
  15. package/dist/lib/fragments/plan.d.ts.map +1 -1
  16. package/dist/lib/fragments/socratic-inquiry.d.ts +4 -0
  17. package/dist/lib/fragments/socratic-inquiry.d.ts.map +1 -0
  18. package/dist/lib/fragments/socratic-plan.d.ts +13 -0
  19. package/dist/lib/fragments/socratic-plan.d.ts.map +1 -0
  20. package/dist/lib/fragments/socratic.d.ts +2 -5
  21. package/dist/lib/fragments/socratic.d.ts.map +1 -1
  22. package/dist/lib/sandbox/agent-os-sandbox.d.ts +1 -1
  23. package/dist/lib/sandbox/agent-os-sandbox.d.ts.map +1 -1
  24. package/dist/lib/sandbox/apple-container-sandbox.d.ts +3 -3
  25. package/dist/lib/sandbox/apple-container-sandbox.d.ts.map +1 -1
  26. package/dist/lib/sandbox/bash-tool.d.ts +1 -1
  27. package/dist/lib/sandbox/bash-tool.d.ts.map +1 -1
  28. package/dist/lib/sandbox/cli-process.d.ts +6 -0
  29. package/dist/lib/sandbox/cli-process.d.ts.map +1 -1
  30. package/dist/lib/sandbox/container-engine.d.ts +1 -0
  31. package/dist/lib/sandbox/container-engine.d.ts.map +1 -1
  32. package/dist/lib/sandbox/container-sandbox.d.ts +1 -0
  33. package/dist/lib/sandbox/container-sandbox.d.ts.map +1 -1
  34. package/dist/lib/sandbox/daytona-sandbox.d.ts.map +1 -1
  35. package/dist/lib/sandbox/docker-sandbox.d.ts +10 -3
  36. package/dist/lib/sandbox/docker-sandbox.d.ts.map +1 -1
  37. package/dist/lib/sandbox/index.d.ts +1 -0
  38. package/dist/lib/sandbox/index.d.ts.map +1 -1
  39. package/dist/lib/sandbox/microsandbox-sandbox.d.ts +13 -17
  40. package/dist/lib/sandbox/microsandbox-sandbox.d.ts.map +1 -1
  41. package/dist/lib/sandbox/read-file-tool/binary.d.ts +13 -0
  42. package/dist/lib/sandbox/read-file-tool/binary.d.ts.map +1 -0
  43. package/dist/lib/sandbox/read-file-tool/format.d.ts +17 -0
  44. package/dist/lib/sandbox/read-file-tool/format.d.ts.map +1 -0
  45. package/dist/lib/sandbox/read-file-tool/image.d.ts +4 -0
  46. package/dist/lib/sandbox/read-file-tool/image.d.ts.map +1 -0
  47. package/dist/lib/sandbox/read-file-tool/index.d.ts +6 -0
  48. package/dist/lib/sandbox/read-file-tool/index.d.ts.map +1 -0
  49. package/dist/lib/sandbox/read-file-tool/ooxml.d.ts +4 -0
  50. package/dist/lib/sandbox/read-file-tool/ooxml.d.ts.map +1 -0
  51. package/dist/lib/sandbox/read-file-tool/pdf.d.ts +4 -0
  52. package/dist/lib/sandbox/read-file-tool/pdf.d.ts.map +1 -0
  53. package/dist/lib/sandbox/read-file-tool/text.d.ts +10 -0
  54. package/dist/lib/sandbox/read-file-tool/text.d.ts.map +1 -0
  55. package/dist/lib/sandbox/read-file.d.ts +8 -0
  56. package/dist/lib/sandbox/read-file.d.ts.map +1 -0
  57. package/dist/lib/sandbox/shell-quote.d.ts +1 -1
  58. package/dist/lib/sandbox/strace/index.d.ts +3 -3
  59. package/dist/lib/sandbox/strace/index.d.ts.map +1 -1
  60. package/dist/lib/sandbox/strace/index.js +2 -2
  61. package/dist/lib/sandbox/strace/index.js.map +2 -2
  62. package/dist/lib/sandbox/types.d.ts +24 -5
  63. package/dist/lib/sandbox/types.d.ts.map +1 -1
  64. package/dist/lib/sandbox/virtual-sandbox.d.ts.map +1 -1
  65. package/dist/lib/stream-buffer.d.ts +1 -1
  66. package/dist/lib/stream-buffer.d.ts.map +1 -1
  67. package/dist/lib/telemetry/console-telemetry.d.ts +0 -1
  68. package/dist/lib/telemetry/console-telemetry.d.ts.map +1 -1
  69. package/dist/lib/telemetry/file/file-telemetry.d.ts +5 -3
  70. package/dist/lib/telemetry/file/file-telemetry.d.ts.map +1 -1
  71. package/dist/lib/telemetry/file/index.js +16 -11
  72. package/dist/lib/telemetry/file/index.js.map +2 -2
  73. package/dist/lib/telemetry/index.js +3 -4
  74. package/dist/lib/telemetry/index.js.map +2 -2
  75. package/dist/lib/telemetry/safe-serialize.d.ts +2 -2
  76. package/dist/lib/telemetry/safe-serialize.d.ts.map +1 -1
  77. package/dist/lib/tracing/index.js +4 -4
  78. package/dist/lib/tracing/index.js.map +2 -2
  79. package/package.json +9 -8
package/dist/index.js CHANGED
@@ -313,23 +313,23 @@ import {
313
313
  } from "ai";
314
314
  import chalk from "chalk";
315
315
  function createRepairToolCall(model, abortSignal) {
316
- return async ({ toolCall: toolCall2, tools, inputSchema, error }) => {
316
+ return async ({ toolCall: toolCall2, tools, inputSchema: inputSchema2, error }) => {
317
317
  if (NoSuchToolError.isInstance(error)) {
318
318
  return null;
319
319
  }
320
320
  console.log(
321
321
  `Debug: ${chalk.yellow("RepairingToolCall")}: ${toolCall2.toolName}`
322
322
  );
323
- const tool3 = tools[toolCall2.toolName];
323
+ const tool4 = tools[toolCall2.toolName];
324
324
  const { output } = await generateText({
325
325
  abortSignal,
326
326
  model,
327
- output: Output.object({ schema: tool3.inputSchema }),
327
+ output: Output.object({ schema: tool4.inputSchema }),
328
328
  prompt: [
329
329
  `The model tried to call the tool "${toolCall2.toolName}" with the following inputs:`,
330
330
  JSON.stringify(toolCall2.input),
331
331
  `The tool accepts the following schema:`,
332
- JSON.stringify(inputSchema(toolCall2)),
332
+ JSON.stringify(inputSchema2(toolCall2)),
333
333
  "Please fix the inputs."
334
334
  ].join("\n")
335
335
  });
@@ -348,16 +348,15 @@ var Agent = class _Agent {
348
348
  sandbox;
349
349
  constructor(options) {
350
350
  this.#options = options;
351
- this.tools = withHostOnlyToolMetadata({
352
- ...options.sandbox.tools,
353
- ...options.tools || {}
354
- });
351
+ this.tools = withHostOnlyToolMetadata(
352
+ Object.assign({}, options.sandbox.tools, options.tools)
353
+ );
355
354
  this.context = options.context;
356
355
  this.model = options.model;
357
356
  this.sandbox = options.sandbox;
358
357
  this.#guardrails = options.guardrails || [];
359
358
  }
360
- async generate(contextVariables, config) {
359
+ async generate(...[options]) {
361
360
  if (!this.#options.context) {
362
361
  throw new Error(`Agent ${this.#options.name} is missing a context.`);
363
362
  }
@@ -369,9 +368,10 @@ var Agent = class _Agent {
369
368
  sandbox: this.#options.sandbox
370
369
  });
371
370
  return generateText2({
372
- abortSignal: config?.abortSignal,
371
+ abortSignal: options?.abortSignal,
373
372
  providerOptions: this.#options.providerOptions,
374
373
  telemetry: this.#options.telemetry,
374
+ runtimeContext: this.#options.runtimeContext,
375
375
  model: this.#options.model,
376
376
  instructions: systemPrompt,
377
377
  messages: await convertToModelMessages(messages, {
@@ -379,16 +379,18 @@ var Agent = class _Agent {
379
379
  tools: this.tools
380
380
  }),
381
381
  stopWhen: this.#options.stopWhen ?? DEFAULT_STOP_WHEN,
382
- prepareStep: this.#options.context.createPrepareStep({
383
- steer: false,
384
- sandbox: this.#options.sandbox
385
- }),
382
+ prepareStep: this.#withPrepareStep(
383
+ this.#options.context.createPrepareStep({
384
+ steer: false,
385
+ sandbox: this.#options.sandbox
386
+ })
387
+ ),
386
388
  tools: this.tools,
387
- runtimeContext: contextVariables,
388
- toolsContext: createToolsContext(this.tools, contextVariables),
389
+ toolsContext: options?.toolsContext,
390
+ experimental_toolCallers: this.#options.experimental_toolCallers,
389
391
  repairToolCall: createRepairToolCall(
390
392
  this.#options.model,
391
- config?.abortSignal
393
+ options?.abortSignal
392
394
  ),
393
395
  toolChoice: this.#options.toolChoice
394
396
  });
@@ -401,7 +403,7 @@ var Agent = class _Agent {
401
403
  *
402
404
  * @example
403
405
  * ```typescript
404
- * const stream = await agent.stream({});
406
+ * const stream = await agent.stream();
405
407
  *
406
408
  * // With guardrails - use toUIMessageStream for protection
407
409
  * await printer.readableStream(stream.toUIMessageStream());
@@ -410,20 +412,22 @@ var Agent = class _Agent {
410
412
  * await printer.stdout(stream);
411
413
  * ```
412
414
  */
413
- async stream(contextVariables, config) {
415
+ async stream(...[options]) {
414
416
  if (!this.#options.context) {
415
417
  throw new Error(`Agent ${this.#options.name} is missing a context.`);
416
418
  }
417
419
  if (!this.#options.model) {
418
420
  throw new Error(`Agent ${this.#options.name} is missing a model.`);
419
421
  }
420
- const prepareStep = this.#options.context.createPrepareStep({
421
- additionalInput: this.#options.prepareStepInput,
422
- sandbox: this.#options.sandbox
423
- });
422
+ const prepareStep = this.#withPrepareStep(
423
+ this.#options.context.createPrepareStep({
424
+ additionalInput: this.#options.prepareStepInput,
425
+ sandbox: this.#options.sandbox
426
+ })
427
+ );
424
428
  const result = await this.#createRawStream(
425
- contextVariables,
426
- config,
429
+ options?.toolsContext,
430
+ options,
427
431
  prepareStep
428
432
  );
429
433
  if (this.#guardrails.length === 0) {
@@ -431,15 +435,27 @@ var Agent = class _Agent {
431
435
  }
432
436
  return this.#wrapWithGuardrails(
433
437
  result,
434
- contextVariables,
435
- config,
438
+ options?.toolsContext,
439
+ options,
436
440
  prepareStep
437
441
  );
438
442
  }
443
+ #withPrepareStep(prepareContext) {
444
+ const prepareStep = this.#options.prepareStep;
445
+ if (!prepareStep) return prepareContext;
446
+ return async (input) => {
447
+ const context = await prepareContext(input);
448
+ const overrides = await prepareStep({
449
+ ...input,
450
+ ...context
451
+ });
452
+ return { ...context, ...overrides };
453
+ };
454
+ }
439
455
  /**
440
456
  * Create a raw stream without guardrail processing.
441
457
  */
442
- async #createRawStream(contextVariables, config, prepareStep) {
458
+ async #createRawStream(toolsContext, config, prepareStep) {
443
459
  const context = this.#options.context;
444
460
  if (!context) {
445
461
  throw new Error(`Agent ${this.#options.name} is missing a context.`);
@@ -454,8 +470,13 @@ var Agent = class _Agent {
454
470
  });
455
471
  return streamText({
456
472
  abortSignal: config?.abortSignal,
473
+ onStart: config?.onStart,
474
+ onStepStart: config?.onStepStart,
475
+ onStepEnd: config?.onStepEnd,
476
+ include: config?.include,
457
477
  providerOptions: this.#options.providerOptions,
458
478
  telemetry: this.#options.telemetry,
479
+ runtimeContext: this.#options.runtimeContext,
459
480
  model,
460
481
  instructions: systemPrompt,
461
482
  messages: await convertToModelMessages(messages, {
@@ -464,11 +485,15 @@ var Agent = class _Agent {
464
485
  }),
465
486
  repairToolCall: createRepairToolCall(model, config?.abortSignal),
466
487
  stopWhen: this.#options.stopWhen ?? DEFAULT_STOP_WHEN,
467
- prepareStep: prepareStep ?? context.createPrepareStep({ sandbox: this.#options.sandbox }),
488
+ // Streaming erases the tool-set type; the callback still receives this.tools.
489
+ prepareStep: prepareStep ?? context.createPrepareStep({
490
+ sandbox: this.#options.sandbox
491
+ }),
468
492
  experimental_transform: config?.transform ?? smoothStream(),
469
493
  tools: this.tools,
470
- runtimeContext: contextVariables,
471
- toolsContext: createToolsContext(this.tools, contextVariables),
494
+ // Generic wrappers cannot reduce AI SDK's conditional context or caller maps.
495
+ toolsContext,
496
+ experimental_toolCallers: this.#options.experimental_toolCallers,
472
497
  toolChoice: this.#options.toolChoice
473
498
  });
474
499
  }
@@ -480,7 +505,7 @@ var Agent = class _Agent {
480
505
  * 2. A finish-step is emitted, triggering onStepEnd to persist the self-correction
481
506
  * 3. A new stream is started and the model continues from the correction
482
507
  */
483
- #wrapWithGuardrails(result, contextVariables, config, prepareStep) {
508
+ #wrapWithGuardrails(result, toolsContext, config, prepareStep) {
484
509
  const maxRetries = config?.maxRetries ?? this.#options.maxGuardrailRetries ?? 3;
485
510
  const context = this.#options.context;
486
511
  if (!context) {
@@ -570,7 +595,7 @@ var Agent = class _Agent {
570
595
  writer.write({ type: "finish-step" });
571
596
  await stepSaved.promise;
572
597
  currentResult = await this.#createRawStream(
573
- contextVariables,
598
+ toolsContext,
574
599
  config,
575
600
  prepareStep
576
601
  );
@@ -614,21 +639,10 @@ var Agent = class _Agent {
614
639
  ${output}
615
640
  </OutputInstructions>` : input;
616
641
  ctx.set(user(prompt));
617
- const sub = agent({
618
- name: this.#options.name,
619
- sandbox: this.#options.sandbox,
620
- model: this.model,
621
- context: ctx,
622
- tools: this.#options.tools,
623
- providerOptions: this.#options.providerOptions,
624
- telemetry: this.#options.telemetry
642
+ const result = await this.clone({ context: ctx }).generate({
643
+ toolsContext: options.context,
644
+ abortSignal: options.abortSignal
625
645
  });
626
- const result = await sub.generate(
627
- {},
628
- {
629
- abortSignal: options.abortSignal
630
- }
631
- );
632
646
  if (props?.outputExtractor) {
633
647
  return await props.outputExtractor(result);
634
648
  }
@@ -721,9 +735,9 @@ function agent(options) {
721
735
  return new Agent(options);
722
736
  }
723
737
  function structuredOutput(options) {
724
- const tools = options.tools ? withHostOnlyToolMetadata(options.tools) : void 0;
738
+ const tools = withHostOnlyToolMetadata({ ...options.tools });
725
739
  return {
726
- async generate(contextVariables, config) {
740
+ async generate(...[callOptions]) {
727
741
  if (!options.context) {
728
742
  throw new Error(`structuredOutput is missing a context.`);
729
743
  }
@@ -735,7 +749,7 @@ function structuredOutput(options) {
735
749
  sandbox: options.sandbox
736
750
  });
737
751
  const result = await generateText2({
738
- abortSignal: config?.abortSignal,
752
+ abortSignal: callOptions?.abortSignal,
739
753
  providerOptions: options.providerOptions,
740
754
  telemetry: options.telemetry,
741
755
  model: options.model,
@@ -747,16 +761,15 @@ function structuredOutput(options) {
747
761
  stopWhen: DEFAULT_STOP_WHEN,
748
762
  repairToolCall: createRepairToolCall(
749
763
  options.model,
750
- config?.abortSignal
764
+ callOptions?.abortSignal
751
765
  ),
752
- runtimeContext: contextVariables,
753
- toolsContext: createToolsContext(tools ?? {}, contextVariables),
766
+ toolsContext: callOptions?.toolsContext,
754
767
  output: Output2.object({ schema: options.schema }),
755
768
  tools
756
769
  });
757
770
  return result.output;
758
771
  },
759
- async stream(contextVariables, config) {
772
+ async stream(...[callOptions]) {
760
773
  if (!options.context) {
761
774
  throw new Error(`structuredOutput is missing a context.`);
762
775
  }
@@ -768,34 +781,28 @@ function structuredOutput(options) {
768
781
  sandbox: options.sandbox
769
782
  });
770
783
  return streamText({
771
- abortSignal: config?.abortSignal,
784
+ abortSignal: callOptions?.abortSignal,
772
785
  providerOptions: options.providerOptions,
773
786
  telemetry: options.telemetry,
774
787
  model: options.model,
775
788
  instructions: systemPrompt,
776
789
  repairToolCall: createRepairToolCall(
777
790
  options.model,
778
- config?.abortSignal
791
+ callOptions?.abortSignal
779
792
  ),
780
793
  messages: await convertToModelMessages(messages, {
781
794
  ignoreIncompleteToolCalls: true,
782
795
  tools
783
796
  }),
784
797
  stopWhen: DEFAULT_STOP_WHEN,
785
- experimental_transform: config?.transform ?? smoothStream(),
786
- runtimeContext: contextVariables,
787
- toolsContext: createToolsContext(tools ?? {}, contextVariables),
798
+ experimental_transform: callOptions?.transform ?? smoothStream(),
799
+ toolsContext: callOptions?.toolsContext,
788
800
  output: Output2.object({ schema: options.schema }),
789
801
  tools
790
802
  });
791
803
  }
792
804
  };
793
805
  }
794
- function createToolsContext(tools, context) {
795
- return Object.fromEntries(
796
- Object.keys(tools).map((toolName) => [toolName, context])
797
- );
798
- }
799
806
  function chunkId(chunk) {
800
807
  if (typeof chunk !== "object" || chunk === null || !("id" in chunk)) {
801
808
  return void 0;
@@ -840,6 +847,7 @@ import { getErrorMessage } from "@ai-sdk/provider";
840
847
  import {
841
848
  convertToModelMessages as convertToModelMessages2,
842
849
  generateId as generateId5,
850
+ isStaticToolUIPart as isStaticToolUIPart2,
843
851
  validateUIMessages
844
852
  } from "ai";
845
853
 
@@ -1436,13 +1444,13 @@ var TomlRenderer = class extends ContextRenderer {
1436
1444
  const entries = this.#renderObjectEntries(obj, newPath);
1437
1445
  return ["", `[${newPath.join(".")}]`, ...entries].join("\n");
1438
1446
  }
1439
- #renderObjectEntries(obj, path3) {
1447
+ #renderObjectEntries(obj, path6) {
1440
1448
  return Object.entries(obj).map(([key, value]) => {
1441
1449
  if (value == null) {
1442
1450
  return "";
1443
1451
  }
1444
1452
  if (isFragmentObject(value)) {
1445
- const newPath = [...path3, key];
1453
+ const newPath = [...path6, key];
1446
1454
  const entries = this.#renderObjectEntries(value, newPath);
1447
1455
  return ["", `[${newPath.join(".")}]`, ...entries].join("\n");
1448
1456
  }
@@ -3025,30 +3033,30 @@ var FragmentLoaderResolver = class {
3025
3033
  fragments.map((f) => this.#walkFragment(f, ctx, 0, /* @__PURE__ */ new Set(), f.name))
3026
3034
  );
3027
3035
  }
3028
- async #walkFragment(fragment2, ctx, depth, ancestors, path3) {
3036
+ async #walkFragment(fragment2, ctx, depth, ancestors, path6) {
3029
3037
  if (!("data" in fragment2)) return;
3030
3038
  fragment2.data = await this.#walkData(
3031
3039
  fragment2.data,
3032
3040
  ctx,
3033
3041
  depth,
3034
3042
  ancestors,
3035
- path3
3043
+ path6
3036
3044
  );
3037
3045
  }
3038
- async #walkData(value, ctx, depth, ancestors, path3) {
3046
+ async #walkData(value, ctx, depth, ancestors, path6) {
3039
3047
  if (ctx.signal?.aborted) {
3040
3048
  throw ctx.signal.reason ?? new Error("Resolver aborted");
3041
3049
  }
3042
3050
  if (depth > this.#maxDepth) {
3043
3051
  throw new Error(
3044
- `Resolver recursion exceeded maxDepth=${this.#maxDepth} at fragment '${path3}'`
3052
+ `Resolver recursion exceeded maxDepth=${this.#maxDepth} at fragment '${path6}'`
3045
3053
  );
3046
3054
  }
3047
3055
  const handler = this.#resolvers.find((r) => r.canResolve(value));
3048
3056
  if (handler) {
3049
3057
  if (handler.requiresSandbox && !ctx.sandbox) {
3050
3058
  throw new Error(
3051
- `Fragment '${path3}' is dispatched to ${handler.name}, which requires a sandbox; none was provided`
3059
+ `Fragment '${path6}' is dispatched to ${handler.name}, which requires a sandbox; none was provided`
3052
3060
  );
3053
3061
  }
3054
3062
  let resolved;
@@ -3059,11 +3067,11 @@ var FragmentLoaderResolver = class {
3059
3067
  );
3060
3068
  } catch (cause) {
3061
3069
  throw new Error(
3062
- `Async fragment '${path3}' failed in ${handler.name}: ${cause instanceof Error ? cause.message : String(cause)}`,
3070
+ `Async fragment '${path6}' failed in ${handler.name}: ${cause instanceof Error ? cause.message : String(cause)}`,
3063
3071
  { cause }
3064
3072
  );
3065
3073
  }
3066
- return this.#walkData(resolved, ctx, depth + 1, ancestors, path3);
3074
+ return this.#walkData(resolved, ctx, depth + 1, ancestors, path6);
3067
3075
  }
3068
3076
  if (value === null || value === void 0) {
3069
3077
  return value;
@@ -3072,7 +3080,7 @@ var FragmentLoaderResolver = class {
3072
3080
  if (ancestors.has(value)) return void 0;
3073
3081
  const childAncestors = new Set(ancestors);
3074
3082
  childAncestors.add(value);
3075
- const childPath = `${path3}.${value.name}`;
3083
+ const childPath = `${path6}.${value.name}`;
3076
3084
  await this.#walkFragment(
3077
3085
  value,
3078
3086
  ctx,
@@ -3088,7 +3096,7 @@ var FragmentLoaderResolver = class {
3088
3096
  childAncestors.add(value);
3089
3097
  return Promise.all(
3090
3098
  value.map(
3091
- (item) => this.#walkData(item, ctx, depth, childAncestors, path3)
3099
+ (item) => this.#walkData(item, ctx, depth, childAncestors, path6)
3092
3100
  )
3093
3101
  );
3094
3102
  }
@@ -3100,7 +3108,7 @@ var FragmentLoaderResolver = class {
3100
3108
  const entries = Object.entries(value);
3101
3109
  const resolvedValues = await Promise.all(
3102
3110
  entries.map(
3103
- ([, v]) => this.#walkData(v, ctx, depth, childAncestors, path3)
3111
+ ([, v]) => this.#walkData(v, ctx, depth, childAncestors, path6)
3104
3112
  )
3105
3113
  );
3106
3114
  for (let i = 0; i < entries.length; i += 1) {
@@ -3959,6 +3967,58 @@ function toolOutcomesFromStep(content) {
3959
3967
  }
3960
3968
  return outcomes;
3961
3969
  }
3970
+ function terminalToolPartsFromStep(content) {
3971
+ return content.flatMap((part) => {
3972
+ if (part.type === "tool-result" && part.dynamic !== true) {
3973
+ return [
3974
+ {
3975
+ type: `tool-${part.toolName}`,
3976
+ toolCallId: part.toolCallId,
3977
+ state: "output-available",
3978
+ input: part.input,
3979
+ output: part.output === void 0 ? null : part.output
3980
+ }
3981
+ ];
3982
+ }
3983
+ if (part.type === "tool-error" && part.dynamic !== true) {
3984
+ return [
3985
+ {
3986
+ type: `tool-${part.toolName}`,
3987
+ toolCallId: part.toolCallId,
3988
+ state: "output-error",
3989
+ input: part.input,
3990
+ errorText: getErrorMessage(part.error)
3991
+ }
3992
+ ];
3993
+ }
3994
+ if (part.type === "tool-approval-response" && part.approved === false && part.toolCall.dynamic !== true) {
3995
+ return [
3996
+ {
3997
+ type: `tool-${part.toolCall.toolName}`,
3998
+ toolCallId: part.toolCall.toolCallId,
3999
+ state: "output-denied",
4000
+ input: part.toolCall.input,
4001
+ approval: {
4002
+ id: part.approvalId,
4003
+ approved: false,
4004
+ ...part.reason === void 0 ? {} : { reason: part.reason }
4005
+ }
4006
+ }
4007
+ ];
4008
+ }
4009
+ return [];
4010
+ });
4011
+ }
4012
+ function mergeTerminalToolParts(message2, liveParts) {
4013
+ const remaining = new Map(liveParts);
4014
+ const parts = message2.parts.map((part) => {
4015
+ if (!isStaticToolUIPart2(part)) return part;
4016
+ remaining.delete(part.toolCallId);
4017
+ return part;
4018
+ });
4019
+ parts.push(...remaining.values());
4020
+ return { ...message2, parts };
4021
+ }
3962
4022
  var ContextEngine = class _ContextEngine {
3963
4023
  /** Non-message fragments (role, hints, etc.) - not persisted in graph */
3964
4024
  #fragments = [];
@@ -4037,6 +4097,7 @@ var ContextEngine = class _ContextEngine {
4037
4097
  );
4038
4098
  }
4039
4099
  this.#branch = branch;
4100
+ this.#branchName = branch.name;
4040
4101
  this.#initialized = true;
4041
4102
  }
4042
4103
  /**
@@ -4371,12 +4432,24 @@ var ContextEngine = class _ContextEngine {
4371
4432
  const enableSteer = options.steer ?? true;
4372
4433
  const session = {
4373
4434
  firedOnceIds: /* @__PURE__ */ new Set(),
4374
- currentSegStart: 0
4435
+ terminalToolParts: /* @__PURE__ */ new Map(),
4436
+ segments: [],
4437
+ initialStepStarts: null,
4438
+ stepOffset: 0,
4439
+ lastStepNumber: null
4375
4440
  };
4376
4441
  this.#currentReminderSession = session;
4377
4442
  return async ({ steps, stepNumber, messages }) => {
4443
+ if (stepNumber === 0 && session.lastStepNumber !== null) {
4444
+ session.stepOffset += session.lastStepNumber + 1;
4445
+ }
4446
+ session.lastStepNumber = stepNumber;
4447
+ const completedSteps = session.stepOffset + stepNumber;
4378
4448
  const priorStep = stepNumber >= 1 ? steps[stepNumber - 1] : void 0;
4379
4449
  const hasSafeBoundary = (priorStep?.content?.length ?? 0) > 0;
4450
+ for (const part of terminalToolPartsFromStep(priorStep?.content ?? [])) {
4451
+ session.terminalToolParts.set(part.toolCallId, part);
4452
+ }
4380
4453
  const canFire = enableSteer && hasSafeBoundary;
4381
4454
  const steerConfigs = canFire ? this.#remindersFor("steer") : [];
4382
4455
  const outcomes = toolOutcomesFromStep(steps.at(-1)?.content ?? []);
@@ -4435,18 +4508,24 @@ var ContextEngine = class _ContextEngine {
4435
4508
  "createPrepareStep: expected an assistant message at chain head."
4436
4509
  );
4437
4510
  }
4438
- const currentSegmentId = session.currentSegId ?? head.id;
4511
+ const currentSegmentId = head.id;
4439
4512
  const currentSegment = await this.#store.getMessage(currentSegmentId);
4440
4513
  if (!currentSegment || currentSegment.name !== "assistant") {
4441
4514
  throw new Error(
4442
4515
  "createPrepareStep: expected the open assistant segment to be persisted."
4443
4516
  );
4444
4517
  }
4445
- const currentMessage = requireUIMessage(
4518
+ requireUIMessage(
4446
4519
  currentSegment.data,
4447
4520
  `Stored assistant message "${currentSegment.id}"`
4448
4521
  );
4449
4522
  const nextSegmentId = generateId5();
4523
+ if (session.segments.length === 0) {
4524
+ session.segments.push({
4525
+ id: currentSegmentId,
4526
+ startStep: 0
4527
+ });
4528
+ }
4450
4529
  for (const input of inputs) this.set(user(input));
4451
4530
  this.set(
4452
4531
  assistant({
@@ -4456,8 +4535,11 @@ var ContextEngine = class _ContextEngine {
4456
4535
  })
4457
4536
  );
4458
4537
  await this.save({ branch: false });
4459
- session.currentSegId = nextSegmentId;
4460
- session.currentSegStart += currentMessage.parts.length;
4538
+ session.segments.push({
4539
+ id: nextSegmentId,
4540
+ startStep: completedSteps
4541
+ });
4542
+ session.terminalToolParts.clear();
4461
4543
  return {
4462
4544
  messages: [...messages, ...inputModel]
4463
4545
  };
@@ -4482,14 +4564,81 @@ var ContextEngine = class _ContextEngine {
4482
4564
  );
4483
4565
  }
4484
4566
  const session = this.#currentReminderSession;
4485
- this.set(
4486
- assistant({
4487
- ...message2,
4488
- id: session?.currentSegId ?? head.id,
4489
- parts: message2.parts.slice(session?.currentSegStart ?? 0)
4490
- })
4491
- );
4492
- await this.save({ branch: false });
4567
+ let id = head.id;
4568
+ let parts = message2.parts;
4569
+ if (session) {
4570
+ if (session.initialStepStarts === null) {
4571
+ const initialId = session.segments[0]?.id ?? head.id;
4572
+ const initial = await this.#store.getMessage(initialId);
4573
+ if (!initial || initial.name !== "assistant") {
4574
+ throw new Error(
4575
+ `writeAssistantSegment: initial segment "${initialId}" was not found`
4576
+ );
4577
+ }
4578
+ session.initialStepStarts = requireUIMessage(
4579
+ initial.data,
4580
+ `Stored assistant message "${initial.id}"`
4581
+ ).parts.filter((part) => part.type === "step-start").length;
4582
+ }
4583
+ const streamStepStarts = message2.parts.flatMap(
4584
+ (part, index) => part.type === "step-start" ? [index] : []
4585
+ );
4586
+ const completedSteps = streamStepStarts.length - session.initialStepStarts;
4587
+ if (completedSteps < 0) {
4588
+ throw new Error(
4589
+ "writeAssistantSegment: response lost an existing step boundary"
4590
+ );
4591
+ }
4592
+ const segmentIndex = session.segments.findLastIndex(
4593
+ (candidate) => candidate.startStep < completedSteps
4594
+ );
4595
+ if (segmentIndex >= 0) {
4596
+ const segment = session.segments[segmentIndex];
4597
+ id = segment.id;
4598
+ if (segmentIndex > 0) {
4599
+ const start = streamStepStarts[session.initialStepStarts + segment.startStep];
4600
+ if (start === void 0) {
4601
+ throw new Error(
4602
+ `writeAssistantSegment: response is missing step ${segment.startStep + 1}`
4603
+ );
4604
+ }
4605
+ parts = message2.parts.slice(start);
4606
+ }
4607
+ } else {
4608
+ const lastSegment = session.segments.at(-1);
4609
+ if (lastSegment) {
4610
+ id = lastSegment.id;
4611
+ parts = [];
4612
+ }
4613
+ }
4614
+ }
4615
+ const fragment2 = assistant({
4616
+ ...message2,
4617
+ id,
4618
+ parts
4619
+ });
4620
+ if (!session) {
4621
+ this.set(fragment2);
4622
+ await this.save({ branch: false });
4623
+ return;
4624
+ }
4625
+ const stored = await this.#store.getMessage(id);
4626
+ if (!stored) {
4627
+ throw new Error(
4628
+ `writeAssistantSegment: reserved segment "${id}" was not found`
4629
+ );
4630
+ }
4631
+ if (!fragment2.codec) {
4632
+ throw new Error("writeAssistantSegment: assistant codec is missing");
4633
+ }
4634
+ await this.#store.addMessages([
4635
+ {
4636
+ ...stored,
4637
+ name: fragment2.name,
4638
+ type: fragment2.type,
4639
+ data: fragment2.codec.encode()
4640
+ }
4641
+ ]);
4493
4642
  }
4494
4643
  #remindersFor(target) {
4495
4644
  return this.#fragments.filter(isConditionalReminder).map((fragment2) => fragment2.metadata.reminder).filter((config) => config.target === target);
@@ -4590,8 +4739,16 @@ var ContextEngine = class _ContextEngine {
4590
4739
  "steer reminders require a user message earlier in the turn"
4591
4740
  );
4592
4741
  }
4742
+ const lastAssistantMessage = chain.lastAssistantMessage;
4743
+ if (!lastAssistantMessage) {
4744
+ throw new Error("steer reminders require a completed assistant step");
4745
+ }
4593
4746
  return {
4594
4747
  ...this.#buildWhenCtx(chain, currentMessage),
4748
+ lastAssistantMessage: mergeTerminalToolParts(
4749
+ lastAssistantMessage,
4750
+ session.terminalToolParts
4751
+ ),
4595
4752
  sandbox,
4596
4753
  firedOnceIds: /* @__PURE__ */ new Set([...chain.firedOnceIds, ...session.firedOnceIds])
4597
4754
  };
@@ -4998,10 +5155,10 @@ var ContextEngine = class _ContextEngine {
4998
5155
  for (const fragment2 of this.#fragments) {
4999
5156
  const skills2 = fragment2.metadata?.skills;
5000
5157
  if (fragment2.name === "available_skills" && Array.isArray(skills2) && skills2.every(isAvailableSkill)) {
5001
- return skills2.map(({ name, description, path: path3 }) => ({
5158
+ return skills2.map(({ name, description, path: path6 }) => ({
5002
5159
  name,
5003
5160
  description,
5004
- path: path3
5161
+ path: path6
5005
5162
  }));
5006
5163
  }
5007
5164
  }
@@ -5489,7 +5646,7 @@ var TitleGenerator = class {
5489
5646
  context,
5490
5647
  model: options.model,
5491
5648
  schema: titleSchema
5492
- }).generate({}, { abortSignal: options.abortSignal });
5649
+ }).generate({ abortSignal: options.abortSignal });
5493
5650
  if (!title) {
5494
5651
  throw new Error("Title generation returned an empty string.");
5495
5652
  }
@@ -5524,7 +5681,8 @@ var defaultChatMessageMetadata = ({
5524
5681
  }
5525
5682
  return void 0;
5526
5683
  };
5527
- async function chat(agent2, options = {}) {
5684
+ async function chat(agent2, ...args) {
5685
+ const [options] = args;
5528
5686
  const context = agent2.context;
5529
5687
  if (!context) {
5530
5688
  throw new Error(
@@ -5539,32 +5697,11 @@ async function chat(agent2, options = {}) {
5539
5697
  }
5540
5698
  const initialAssistantMsgId = head.id;
5541
5699
  const uiMessages = await context.getMessages();
5542
- const streamContextVariables = options.contextVariables === void 0 ? {} : options.contextVariables;
5543
- const [title, result] = await Promise.all([
5544
- makeTitle({
5545
- context,
5546
- model: agent2.model,
5547
- generateTitle: options.generateTitle,
5548
- abortSignal: options.abortSignal
5549
- }),
5550
- agent2.stream(streamContextVariables, {
5551
- transform: options.transform,
5552
- abortSignal: options.abortSignal
5553
- })
5554
- ]);
5555
- const uiStream = result.toUIMessageStream({
5556
- onError: options.onError ?? formatChatError,
5557
- sendStart: true,
5558
- sendFinish: true,
5559
- sendReasoning: true,
5560
- sendSources: true,
5561
- originalMessages: uiMessages,
5562
- generateMessageId: () => initialAssistantMsgId,
5563
- messageMetadata: options.messageMetadata ?? defaultChatMessageMetadata
5564
- });
5700
+ let result;
5565
5701
  return createUIMessageStream2({
5566
5702
  originalMessages: uiMessages,
5567
5703
  generateId: () => initialAssistantMsgId,
5704
+ onError: options?.onError ?? formatChatError,
5568
5705
  onStepEnd: async ({ responseMessage }) => {
5569
5706
  await context.writeAssistantSegment(responseMessage);
5570
5707
  },
@@ -5573,7 +5710,7 @@ async function chat(agent2, options = {}) {
5573
5710
  if (isAborted) {
5574
5711
  message2 = { ...message2, parts: sanitizeAbortedParts(message2.parts) };
5575
5712
  }
5576
- const finalMetadata = await options.finalAssistantMetadata?.(message2);
5713
+ const finalMetadata = await options?.finalAssistantMetadata?.(message2);
5577
5714
  const mergedMetadata = {
5578
5715
  ...message2.metadata ?? {},
5579
5716
  ...finalMetadata ?? {}
@@ -5582,13 +5719,35 @@ async function chat(agent2, options = {}) {
5582
5719
  message2 = { ...message2, metadata: mergedMetadata };
5583
5720
  }
5584
5721
  await context.writeAssistantSegment(message2);
5585
- const usage = await result.usage;
5722
+ const usage = await result?.usage;
5586
5723
  if (usage !== void 0) {
5587
5724
  await context.trackUsage(usage);
5588
5725
  }
5589
5726
  },
5590
5727
  execute: async ({ writer }) => {
5591
- writer.merge(uiStream);
5728
+ writer.write({ type: "start", messageId: initialAssistantMsgId });
5729
+ options?.onStream?.(writer);
5730
+ const [title, streamed] = await Promise.all([
5731
+ makeTitle({
5732
+ context,
5733
+ model: agent2.model,
5734
+ generateTitle: options?.generateTitle,
5735
+ abortSignal: options?.abortSignal
5736
+ }),
5737
+ agent2.stream(...args)
5738
+ ]);
5739
+ result = streamed;
5740
+ writer.merge(
5741
+ result.toUIMessageStream({
5742
+ onError: options?.onError ?? formatChatError,
5743
+ sendStart: false,
5744
+ sendReasoning: true,
5745
+ sendSources: true,
5746
+ originalMessages: uiMessages,
5747
+ generateMessageId: () => initialAssistantMsgId,
5748
+ messageMetadata: options?.messageMetadata ?? defaultChatMessageMetadata
5749
+ })
5750
+ );
5592
5751
  if (title) {
5593
5752
  writer.write({ type: "data-chat-title", data: title, transient: true });
5594
5753
  }
@@ -5634,7 +5793,7 @@ function formatChatError(error) {
5634
5793
  console.error("Upstream API call failed:", error);
5635
5794
  return `Upstream API call failed with status ${error.statusCode}: ${error.message}`;
5636
5795
  }
5637
- return JSON.stringify(error);
5796
+ return error instanceof Error ? "An error occurred." : JSON.stringify(error);
5638
5797
  }
5639
5798
  async function makeTitle(options) {
5640
5799
  const titler = new TitleGenerator({ context: options.context });
@@ -5930,10 +6089,14 @@ function fromFragment(fragment2, options) {
5930
6089
  }
5931
6090
 
5932
6091
  // packages/context/src/lib/fragments/plan.ts
6092
+ import dedent2 from "dedent";
6093
+ import z4 from "zod";
6094
+
6095
+ // packages/context/src/lib/fragments/plan-shared.ts
5933
6096
  import dedent from "dedent";
5934
6097
  import z3 from "zod";
5935
6098
  var PLAN_FILE_PATH = "/workspace/.deepagents/plan.json";
5936
- var PLAN_INSTRUCTIONS = dedent`
6099
+ var PLAN_FOUNDATION_INSTRUCTIONS = dedent`
5937
6100
  You own the plan. The user is not required to provide a structured objective, success criteria, constraints, or task breakdown.
5938
6101
 
5939
6102
  Build and maintain the plan from the best available evidence, in this order:
@@ -5957,28 +6120,11 @@ var PLAN_INSTRUCTIONS = dedent`
5957
6120
 
5958
6121
  Never silently promote an assumption into a requirement or constraint. Ask the user only when an unresolved choice materially changes the outcome, cannot be answered from the workspace or runtime, or requires authority beyond the request. Do not ask merely because the user did not provide structured plan fields.
5959
6122
 
5960
- Maintain the authoritative plan as JSON at ${PLAN_FILE_PATH}. Read the latest file before changing it and write the complete document in one operation. The plan has this shape:
5961
- - revision: a positive integer, incremented whenever the objective, criteria, constraints, assumptions, tasks, or dependencies change; recording lastReview alone does not increment it;
5962
- - objective: { text, basis, source };
5963
- - successCriteria: [{ id, text, basis, source, evidence: [{ summary, source }] }];
5964
- - constraints and assumptions: [{ text, basis, source }];
5965
- - tasks: [{ id, title, status, blockedBy, evidence }], where status is pending, in_progress, or completed;
5966
- - lastReview, when present: { revision, decision, summary }.
5967
-
5968
- Use basis values explicit_requirement, discovered_constraint, inference, or assumption. Store only blockedBy dependency edges. Never store blocks, readiness, or a blocked task status; those are derived when the plan is read. A task may be in_progress only when every blocker is completed, and a completed task must include concrete evidence.
5969
-
6123
+ Maintain the authoritative plan as JSON at ${PLAN_FILE_PATH}. Read the latest file before changing it and write the complete document in one operation.
6124
+ `;
6125
+ var PLAN_REVIEW_INSTRUCTIONS = dedent`
5970
6126
  Whenever plan review fires, record lastReview before continuing. Its revision is the plan revision that was evaluated. If the decision is revise or replace, increment the plan revision when applying the resulting changes while preserving the evaluated revision in lastReview.
5971
-
5972
- Revise the plan when new evidence invalidates its objective, criteria, constraints, assumptions, sequencing, or dependencies. Before recording a complete decision or claiming completion, map every success criterion to concrete evidence. Completed task statuses alone do not prove completion.
5973
6127
  `;
5974
- var REVIEW_REMINDER = [
5975
- "Re-read the current plan and consider the evidence gathered since the previous review.",
5976
- "",
5977
- "Is the current plan still valid given the latest evidence?",
5978
- "",
5979
- "If yes, continue. If not, revise it before taking another action.",
5980
- "Before claiming completion, verify the success criteria against evidence."
5981
- ].join("\n");
5982
6128
  var nonEmptyString = z3.string().trim().min(1);
5983
6129
  var statementBasis = z3.enum([
5984
6130
  "explicit_requirement",
@@ -5986,40 +6132,24 @@ var statementBasis = z3.enum([
5986
6132
  "inference",
5987
6133
  "assumption"
5988
6134
  ]);
5989
- var statement = z3.strictObject({
5990
- text: nonEmptyString,
5991
- basis: statementBasis,
5992
- source: nonEmptyString
5993
- });
5994
6135
  var evidence = z3.strictObject({
5995
6136
  summary: nonEmptyString,
5996
6137
  source: nonEmptyString
5997
6138
  });
5998
- var successCriterion = statement.extend({
5999
- id: nonEmptyString,
6000
- evidence: z3.array(evidence)
6001
- });
6002
- var task = z3.strictObject({
6003
- id: nonEmptyString,
6004
- title: nonEmptyString,
6005
- status: z3.enum(["pending", "in_progress", "completed"]),
6006
- blockedBy: z3.array(nonEmptyString),
6007
- evidence: z3.array(evidence)
6008
- });
6009
6139
  var lastReview = z3.strictObject({
6010
6140
  revision: z3.number().int().positive(),
6011
6141
  decision: z3.enum(["continue", "revise", "replace", "complete", "blocked"]),
6012
6142
  summary: nonEmptyString
6013
6143
  });
6014
- var planState = z3.strictObject({
6015
- revision: z3.number().int().positive(),
6016
- objective: statement,
6017
- successCriteria: z3.array(successCriterion).min(1),
6018
- constraints: z3.array(statement),
6019
- assumptions: z3.array(statement),
6020
- tasks: z3.array(task),
6021
- lastReview: lastReview.optional()
6022
- });
6144
+ function parsePlanSchema(schema, value) {
6145
+ const result = schema.safeParse(value);
6146
+ if (result.success) return result.data;
6147
+ const issues = result.error.issues.slice(0, 5).map(({ path: path6, message: message2 }) => `${path6.join(".") || "plan"}: ${message2}`);
6148
+ const remaining = result.error.issues.length - issues.length;
6149
+ throw new Error(
6150
+ `schema errors: ${issues.join("; ")}${remaining > 0 ? `; ${remaining} more` : ""}`
6151
+ );
6152
+ }
6023
6153
  function uniqueIds(items, label) {
6024
6154
  const byId = /* @__PURE__ */ new Map();
6025
6155
  for (const item of items) {
@@ -6030,23 +6160,22 @@ function uniqueIds(items, label) {
6030
6160
  }
6031
6161
  return byId;
6032
6162
  }
6033
- function validatePlan(state) {
6034
- uniqueIds(state.successCriteria, "success criterion");
6035
- const tasks = uniqueIds(state.tasks, "task");
6036
- const blocks = new Map(state.tasks.map(({ id }) => [id, []]));
6037
- for (const item of state.tasks) {
6163
+ function validateDependencies(items, label) {
6164
+ const byId = uniqueIds(items, label);
6165
+ const blocks = new Map(items.map(({ id }) => [id, []]));
6166
+ for (const item of items) {
6038
6167
  const blockers = /* @__PURE__ */ new Set();
6039
6168
  for (const blocker of item.blockedBy) {
6040
6169
  if (blocker === item.id) {
6041
- throw new Error(`task "${item.id}" cannot block itself`);
6170
+ throw new Error(`${label} "${item.id}" cannot block itself`);
6042
6171
  }
6043
- if (!tasks.has(blocker)) {
6172
+ if (!byId.has(blocker)) {
6044
6173
  throw new Error(
6045
- `task "${item.id}" references missing blocker "${blocker}"`
6174
+ `${label} "${item.id}" references missing blocker "${blocker}"`
6046
6175
  );
6047
6176
  }
6048
6177
  if (blockers.has(blocker)) {
6049
- throw new Error(`task "${item.id}" repeats blocker "${blocker}"`);
6178
+ throw new Error(`${label} "${item.id}" repeats blocker "${blocker}"`);
6050
6179
  }
6051
6180
  blockers.add(blocker);
6052
6181
  blocks.get(blocker)?.push(item.id);
@@ -6065,12 +6194,112 @@ function validatePlan(state) {
6065
6194
  }
6066
6195
  visiting.add(id);
6067
6196
  stack.push(id);
6068
- for (const blocker of tasks.get(id)?.blockedBy ?? []) visit(blocker);
6197
+ for (const blocker of byId.get(id)?.blockedBy ?? []) visit(blocker);
6069
6198
  stack.pop();
6070
6199
  visiting.delete(id);
6071
6200
  visited.add(id);
6072
6201
  };
6073
- for (const { id } of state.tasks) visit(id);
6202
+ for (const { id } of items) visit(id);
6203
+ return { byId, blocks };
6204
+ }
6205
+ function lineItems(items) {
6206
+ return items.length > 0 ? items.map((item) => `- ${item}`) : ["- none"];
6207
+ }
6208
+ function latestEvidence(items) {
6209
+ const latest = items.at(-1);
6210
+ return latest ? ` Evidence: ${latest.summary} (${latest.source})` : "";
6211
+ }
6212
+ function errorMessage(error) {
6213
+ const message2 = error instanceof Error ? error.message : String(error);
6214
+ return message2.length > 500 ? `${message2.slice(0, 497)}...` : message2;
6215
+ }
6216
+ function parsePlanJson(raw) {
6217
+ try {
6218
+ return JSON.parse(raw);
6219
+ } catch (error) {
6220
+ const message2 = error instanceof Error ? error.message : String(error);
6221
+ throw new Error(`invalid JSON: ${message2}`, { cause: error });
6222
+ }
6223
+ }
6224
+ function createPlanReview(options, formatPlanReview, reviewReminder) {
6225
+ return reminder(
6226
+ async ({ sandbox }) => {
6227
+ let raw;
6228
+ try {
6229
+ raw = await sandbox.sandbox.readFile(PLAN_FILE_PATH);
6230
+ } catch (error) {
6231
+ return [
6232
+ `The plan file at ${PLAN_FILE_PATH} could not be read: ${errorMessage(error)}`,
6233
+ "Create or repair it before taking another action.",
6234
+ "",
6235
+ reviewReminder
6236
+ ].join("\n");
6237
+ }
6238
+ try {
6239
+ return formatPlanReview(parsePlanJson(raw));
6240
+ } catch (error) {
6241
+ return [
6242
+ `The plan file at ${PLAN_FILE_PATH} is invalid: ${errorMessage(error)}`,
6243
+ "Repair the plan before taking another action.",
6244
+ "",
6245
+ reviewReminder
6246
+ ].join("\n");
6247
+ }
6248
+ },
6249
+ { when: options.when, target: "steer" }
6250
+ );
6251
+ }
6252
+
6253
+ // packages/context/src/lib/fragments/plan.ts
6254
+ var EXECUTION_PLAN_INSTRUCTIONS = dedent2`
6255
+ Use an execution plan with this shape:
6256
+ - revision: a positive integer, incremented whenever the objective, criteria, constraints, assumptions, tasks, or dependencies change; recording lastReview alone does not increment it;
6257
+ - objective: { text, basis, source };
6258
+ - successCriteria: [{ id, text, basis, source, evidence: [{ summary, source }] }];
6259
+ - constraints and assumptions: [{ text, basis, source }];
6260
+ - tasks: [{ id, title, status, blockedBy, evidence }], where status is pending, in_progress, or completed;
6261
+ - lastReview, when present: { revision, decision, summary }.
6262
+
6263
+ Use basis values explicit_requirement, discovered_constraint, inference, or assumption. Store only blockedBy dependency edges. Never store blocks, readiness, or a blocked task status; those are derived when the plan is read. A task may be in_progress only when every blocker is completed, and a completed task must include concrete evidence.
6264
+
6265
+ Revise the plan when new evidence invalidates its objective, criteria, constraints, assumptions, sequencing, or dependencies. Before recording a complete decision or claiming completion, map every success criterion to concrete evidence. Completed task statuses alone do not prove completion.
6266
+ `;
6267
+ var EXECUTION_REVIEW_REMINDER = [
6268
+ "Re-read the current plan and consider the evidence gathered since the previous review.",
6269
+ "",
6270
+ "Is the current plan still valid given the latest evidence?",
6271
+ "",
6272
+ "If yes, continue. If not, revise it before taking another action.",
6273
+ "Before claiming completion, verify the success criteria against evidence."
6274
+ ].join("\n");
6275
+ var statement = z4.strictObject({
6276
+ text: nonEmptyString,
6277
+ basis: statementBasis,
6278
+ source: nonEmptyString
6279
+ });
6280
+ var successCriterion = statement.extend({
6281
+ id: nonEmptyString,
6282
+ evidence: z4.array(evidence)
6283
+ });
6284
+ var task = z4.strictObject({
6285
+ id: nonEmptyString,
6286
+ title: nonEmptyString,
6287
+ status: z4.enum(["pending", "in_progress", "completed"]),
6288
+ blockedBy: z4.array(nonEmptyString),
6289
+ evidence: z4.array(evidence)
6290
+ });
6291
+ var executionPlanState = z4.strictObject({
6292
+ revision: z4.number().int().positive(),
6293
+ objective: statement,
6294
+ successCriteria: z4.array(successCriterion).min(1),
6295
+ constraints: z4.array(statement),
6296
+ assumptions: z4.array(statement),
6297
+ tasks: z4.array(task),
6298
+ lastReview: lastReview.optional()
6299
+ });
6300
+ function validateExecutionPlan(state) {
6301
+ uniqueIds(state.successCriteria, "success criterion");
6302
+ const { byId: tasks, blocks } = validateDependencies(state.tasks, "task");
6074
6303
  for (const item of state.tasks) {
6075
6304
  const incompleteBlockers = item.blockedBy.filter(
6076
6305
  (id) => tasks.get(id)?.status !== "completed"
@@ -6096,37 +6325,19 @@ function validatePlan(state) {
6096
6325
  }
6097
6326
  return blocks;
6098
6327
  }
6099
- function parsePlan(raw) {
6100
- let parsed;
6101
- try {
6102
- parsed = JSON.parse(raw);
6103
- } catch (error) {
6104
- const message2 = error instanceof Error ? error.message : String(error);
6105
- throw new Error(`invalid JSON: ${message2}`, { cause: error });
6106
- }
6107
- const result = planState.safeParse(parsed);
6108
- if (!result.success) {
6109
- const issues = result.error.issues.slice(0, 5).map(({ path: path3, message: message2 }) => `${path3.join(".") || "plan"}: ${message2}`);
6110
- const remaining = result.error.issues.length - issues.length;
6111
- throw new Error(
6112
- `schema errors: ${issues.join("; ")}${remaining > 0 ? `; ${remaining} more` : ""}`
6113
- );
6114
- }
6115
- return { state: result.data, blocks: validatePlan(result.data) };
6116
- }
6117
- function lineItems(items) {
6118
- return items.length > 0 ? items.map((item) => `- ${item}`) : ["- none"];
6119
- }
6120
- function latestEvidence(items) {
6121
- const latest = items.at(-1);
6122
- return latest ? ` Evidence: ${latest.summary} (${latest.source})` : "";
6328
+ function parseExecutionPlan(value) {
6329
+ const state = parsePlanSchema(executionPlanState, value);
6330
+ return { state, blocks: validateExecutionPlan(state) };
6123
6331
  }
6124
6332
  function formatTask(item, blocks) {
6125
6333
  const blocked = blocks.get(item.id) ?? [];
6126
6334
  const suffix = blocked.length > 0 ? `; blocks ${blocked.join(", ")}` : "";
6127
6335
  return `${item.id}: ${item.title}${suffix}${latestEvidence(item.evidence)}`;
6128
6336
  }
6129
- function formatPlanReview(state, blocks) {
6337
+ function formatExecutionPlanReview({
6338
+ state,
6339
+ blocks
6340
+ }) {
6130
6341
  const tasks = new Map(state.tasks.map((item) => [item.id, item]));
6131
6342
  const active = state.tasks.filter(({ status }) => status === "in_progress");
6132
6343
  const ready = state.tasks.filter(
@@ -6181,45 +6392,23 @@ function formatPlanReview(state, blocks) {
6181
6392
  `Last review: ${state.lastReview.decision} at revision ${state.lastReview.revision} \u2014 ${state.lastReview.summary}`
6182
6393
  ] : [],
6183
6394
  "",
6184
- REVIEW_REMINDER
6395
+ EXECUTION_REVIEW_REMINDER
6185
6396
  ].join("\n");
6186
6397
  }
6187
- function errorMessage(error) {
6188
- const message2 = error instanceof Error ? error.message : String(error);
6189
- return message2.length > 500 ? `${message2.slice(0, 497)}...` : message2;
6190
- }
6191
- async function resolvePlanReview(sandbox) {
6192
- let raw;
6193
- try {
6194
- raw = await sandbox.sandbox.readFile(PLAN_FILE_PATH);
6195
- } catch (error) {
6196
- return [
6197
- `The plan file at ${PLAN_FILE_PATH} could not be read: ${errorMessage(error)}`,
6198
- "Create or repair it before taking another action.",
6199
- "",
6200
- REVIEW_REMINDER
6201
- ].join("\n");
6202
- }
6203
- try {
6204
- const { state, blocks } = parsePlan(raw);
6205
- return formatPlanReview(state, blocks);
6206
- } catch (error) {
6207
- return [
6208
- `The plan file at ${PLAN_FILE_PATH} is invalid: ${errorMessage(error)}`,
6209
- "Repair the plan before taking another action.",
6210
- "",
6211
- REVIEW_REMINDER
6212
- ].join("\n");
6213
- }
6214
- }
6215
6398
  function instructions() {
6216
- return fragment("plan_instructions", PLAN_INSTRUCTIONS);
6399
+ return fragment(
6400
+ "plan_instructions",
6401
+ PLAN_FOUNDATION_INSTRUCTIONS,
6402
+ EXECUTION_PLAN_INSTRUCTIONS,
6403
+ PLAN_REVIEW_INSTRUCTIONS
6404
+ );
6217
6405
  }
6218
6406
  function review(options) {
6219
- return reminder(({ sandbox }) => resolvePlanReview(sandbox), {
6220
- when: options.when,
6221
- target: "steer"
6222
- });
6407
+ return createPlanReview(
6408
+ options,
6409
+ (value) => formatExecutionPlanReview(parseExecutionPlan(value)),
6410
+ EXECUTION_REVIEW_REMINDER
6411
+ );
6223
6412
  }
6224
6413
  var plan = { path: PLAN_FILE_PATH, instructions, review };
6225
6414
 
@@ -6347,67 +6536,304 @@ function selfCritique(checks = DEFAULT_SELF_CRITIQUE_CHECKS) {
6347
6536
  });
6348
6537
  }
6349
6538
 
6539
+ // packages/context/src/lib/fragments/socratic-inquiry.ts
6540
+ var SOCRATIC_ROLE = "You are a deep, methodical thinker. For substantive work, build understanding through explicit questions and evidence before executing. Scale the inquiry to the task instead of manufacturing ceremony.";
6541
+ function socraticInquiry() {
6542
+ return fragment(
6543
+ "socratic_inquiry",
6544
+ hint(
6545
+ "Use Socratic inquiry when uncertainty, tradeoffs, or quality judgment matter. Skip Socratic inquiry for mechanical tasks with one obvious path, or when the user explicitly says to proceed directly, unless another active contract explicitly requires it."
6546
+ ),
6547
+ principle({
6548
+ title: "Interrogate the domain",
6549
+ description: "Surface the principles that separate excellent work from merely plausible output before choosing an approach.",
6550
+ policies: [
6551
+ "Ask what the user or audience actually needs from the result.",
6552
+ "Ask which frameworks, mental models, and quality criteria govern this domain.",
6553
+ "Ask which common failure modes would produce convincing but wrong results.",
6554
+ "Explore the logical, practical, user-specific, and risk dimensions that materially affect the outcome."
6555
+ ]
6556
+ }),
6557
+ principle({
6558
+ title: "Apply to the specific case",
6559
+ description: "Translate the domain principles into questions about the actual request, source, runtime, and constraints.",
6560
+ policies: [
6561
+ "Ask which evidence in this case changes which general principles matter most.",
6562
+ "Ask which assumptions remain unresolved and what observation could answer them.",
6563
+ "Ask what an expert would inspect, test, or rule out before committing to an answer."
6564
+ ]
6565
+ }),
6566
+ principle({
6567
+ title: "Bridge and execute",
6568
+ description: "Synthesize the answers into the concrete action before producing the result.",
6569
+ policies: [
6570
+ 'State "Applying to this task:" and connect the inquiry answers to the chosen action.',
6571
+ "Execute from that synthesis rather than reverting to a generic answer or checklist.",
6572
+ "Verify the result against the criteria discovered during inquiry."
6573
+ ]
6574
+ }),
6575
+ principle({
6576
+ title: "Adaptive and evolving inquiry",
6577
+ description: "Match inquiry depth to task complexity and let evidence determine follow-up questions.",
6578
+ policies: [
6579
+ "Use about 2 focused questions for light work, 3-4 for medium work, and 5-7 for heavy work.",
6580
+ "Do not precompute every follow-up question; answers should create, revise, or eliminate later questions.",
6581
+ "Retain questions and answers that explain consequential decisions."
6582
+ ]
6583
+ }),
6584
+ workflow({
6585
+ task: "Socratic reasoning process",
6586
+ steps: [
6587
+ "Interrogate the domain and answer the foundational questions.",
6588
+ "Apply those answers to the specific request and available evidence.",
6589
+ "Add or revise follow-up questions based on what the answers changed.",
6590
+ 'Bridge the answers into the concrete action with "Applying to this task:".',
6591
+ "Execute and verify the result against the discovered criteria."
6592
+ ]
6593
+ })
6594
+ );
6595
+ }
6596
+
6350
6597
  // packages/context/src/lib/fragments/socratic.ts
6351
6598
  function socraticPrompting() {
6352
- return [
6353
- role(
6354
- "You are a deep, methodical thinker. Before producing any output, you reason through the problem by asking and answering foundational questions. You never treat a request as a simple task to complete \u2014 you first build understanding through inquiry, then apply that understanding to produce high-quality output."
6355
- ),
6356
- fragment(
6357
- "socratic_prompting",
6358
- hint(
6359
- "When given a task, do not jump straight to producing output. Instead, decompose the task into foundational questions that, once answered, will make the output significantly better. Answer those questions first, then synthesize your answers into the final output."
6360
- ),
6361
- principle({
6362
- title: "Question-first decomposition",
6363
- description: 'Every task hides assumptions about what "good" looks like. Surface those assumptions by asking what makes the output effective before attempting to produce it.',
6364
- policies: [
6365
- 'Ask "What makes X effective/compelling/useful?" before producing X.',
6366
- "Identify the criteria, frameworks, or principles that govern quality for this type of output.",
6367
- "Do not skip this step even when the task seems straightforward \u2014 obvious tasks often have non-obvious quality dimensions."
6368
- ]
6369
- }),
6370
- principle({
6371
- title: "Multi-dimensional inquiry",
6372
- description: "Explore the problem from multiple angles \u2014 emotional, logical, practical, audience-specific \u2014 through targeted questions.",
6373
- policies: [
6374
- "Ask about the audience: Who is this for? What do they care about?",
6375
- "Ask about constraints: What must this include or avoid?",
6376
- "Ask about effectiveness: What separates great output from mediocre output in this domain?",
6377
- "Ask about structure: How should this be organized for maximum impact?"
6378
- ]
6379
- }),
6380
- principle({
6381
- title: "Framework discovery before application",
6382
- description: "Build or recall a relevant framework by reasoning through questions, then explicitly apply that framework to the specific task.",
6383
- policies: [
6384
- "First derive the framework: What principles govern this type of work?",
6385
- 'Then apply it: "Now, using these principles, produce the specific output."',
6386
- "The framework should emerge from your reasoning, not from a template."
6387
- ]
6388
- }),
6389
- workflow({
6390
- task: "Socratic reasoning process",
6391
- steps: [
6392
- "Identify the core task and desired output type.",
6393
- "Formulate 2-5 foundational questions about what makes this output effective.",
6394
- "Answer each question, drawing on relevant knowledge and frameworks.",
6395
- "Synthesize answers into a coherent set of principles or criteria.",
6396
- "Apply the discovered framework to produce the specific output.",
6397
- "Verify the output satisfies the criteria you identified."
6398
- ]
6399
- }),
6400
- example({
6401
- question: "How should I prompt for a value proposition for my AI analytics tool?",
6402
- answer: "What makes a value proposition compelling to B2B buyers? What emotional and logical triggers should it hit? Now apply that framework to an AI analytics tool that helps teams make faster data-driven decisions."
6403
- }),
6404
- example({
6405
- question: "How should I prompt for a 30-day LinkedIn content calendar for B2B SaaS?",
6406
- answer: "What types of LinkedIn content generate the most engagement in B2B SaaS? What posting frequency avoids audience fatigue? How should topics build on each other? Now design a 30-day calendar using these principles."
6407
- })
6408
- )
6409
- ];
6599
+ return [role(SOCRATIC_ROLE), socraticInquiry()];
6600
+ }
6601
+
6602
+ // packages/context/src/lib/fragments/socratic-plan.ts
6603
+ import dedent3 from "dedent";
6604
+ import z5 from "zod";
6605
+ var SOCRATIC_PLAN_INSTRUCTIONS = dedent3`
6606
+ Use a Socratic plan as an evolving question-and-answer inquiry, not an execution task list whose titles merely end in question marks. It has this shape:
6607
+ The only top-level keys are revision, objective, successCriteria, constraints, assumptions, inquiries, and lastReview.
6608
+ - revision: a positive integer, incremented whenever a question, answer, dependency, or review-driven conclusion changes; recording lastReview alone does not increment it;
6609
+ - objective: one question entry;
6610
+ - successCriteria: question entries with an id;
6611
+ - constraints: question entries;
6612
+ - assumptions: question entries;
6613
+ - inquiries: question entries with id, phase, status, and blockedBy;
6614
+ - lastReview: null before the first review, then { revision, decision, summary, nextQuestionId }.
6615
+
6616
+ A question entry is { question, basis, source, answer }. Its answer is null until answered, then { text, basis, source, evidence: [{ summary, source }] }. Use basis values explicit_requirement, discovered_constraint, inference, or assumption for every basis field. Every question must end with a question mark and include an answer field. Never store an answer as a bare string. Use phase values domain, case, or bridge. Use inquiry status values open, answering, or answered. Store only blockedBy dependencies; readiness and reverse edges are derived. An inquiry may be answering only after every blocker is answered. An answered inquiry must have an evidence-backed answer. An open inquiry must have answer: null. nextQuestionId records the inquiry selected by that review; use null when the review selects none.
6617
+
6618
+ After reading any required skill or workspace instructions, write the initial inquiry before substantive investigation. Begin with the governing objective question and verification questions. Create an initial inquiry proportional to complexity: about 2 questions for light work, 3-4 for medium work, and 5-7 for heavy work. Do not precompute every follow-up question. After each answer, ask what it changed, then add, revise, or remove later questions. Preserve consequential questions and answers as the decision log.
6619
+
6620
+ Domain questions discover governing principles, relevant frameworks, and common failure modes. Case questions apply them to the actual source, runtime, constraints, and evidence. A bridge answer must begin with "Applying to this task:" and state the concrete action implied by the earlier answers. Execute from that bridge, then answer the success questions with verification evidence.
6621
+
6622
+ Before recording a complete decision, every objective, success, constraint, assumption, and inquiry question must have an evidence-backed answer. Completion means the answers collectively establish the requested outcome; the presence of answered questions alone does not prove completion. Do not return a final answer until lastReview records a complete decision at the current revision with nextQuestionId: null.
6623
+ `;
6624
+ var SOCRATIC_REVIEW_REMINDER = [
6625
+ "Re-read the current Socratic plan and the evidence gathered since the previous review.",
6626
+ "",
6627
+ "What did the latest evidence answer?",
6628
+ "Does that answer invalidate another answer, assumption, dependency, or question?",
6629
+ "Which unanswered question would reduce the most consequential uncertainty next?",
6630
+ "",
6631
+ "Record the review and its nextQuestionId, then revise the inquiry before continuing when needed.",
6632
+ "Before claiming completion, verify that every required question has an evidence-backed answer and that the answers establish the requested outcome."
6633
+ ].join("\n");
6634
+ var questionText = nonEmptyString.refine((text) => text.endsWith("?"), {
6635
+ message: "must end with a question mark"
6636
+ });
6637
+ var answer = z5.strictObject({
6638
+ text: nonEmptyString,
6639
+ basis: statementBasis,
6640
+ source: nonEmptyString,
6641
+ evidence: z5.array(evidence).min(1)
6642
+ });
6643
+ var question = z5.strictObject({
6644
+ question: questionText,
6645
+ basis: statementBasis,
6646
+ source: nonEmptyString,
6647
+ answer: answer.nullable()
6648
+ });
6649
+ var successQuestion = question.extend({ id: nonEmptyString });
6650
+ var inquiry = question.extend({
6651
+ id: nonEmptyString,
6652
+ phase: z5.enum(["domain", "case", "bridge"]),
6653
+ status: z5.enum(["open", "answering", "answered"]),
6654
+ blockedBy: z5.array(nonEmptyString)
6655
+ });
6656
+ var socraticLastReview = lastReview.extend({
6657
+ nextQuestionId: nonEmptyString.nullable()
6658
+ });
6659
+ var socraticPlanState = z5.strictObject({
6660
+ revision: z5.number().int().positive(),
6661
+ objective: question,
6662
+ successCriteria: z5.array(successQuestion).min(1),
6663
+ constraints: z5.array(question),
6664
+ assumptions: z5.array(question),
6665
+ inquiries: z5.array(inquiry).min(1),
6666
+ lastReview: socraticLastReview.nullable().optional()
6667
+ });
6668
+ function validateSocraticPlan(state) {
6669
+ uniqueIds(state.successCriteria, "success question");
6670
+ const { byId: inquiries, blocks } = validateDependencies(
6671
+ state.inquiries,
6672
+ "inquiry"
6673
+ );
6674
+ for (const item of state.inquiries) {
6675
+ const incompleteBlockers = item.blockedBy.filter(
6676
+ (id) => inquiries.get(id)?.status !== "answered"
6677
+ );
6678
+ if (item.status !== "open" && incompleteBlockers.length > 0) {
6679
+ throw new Error(
6680
+ `inquiry "${item.id}" is ${item.status} while blocked by ${incompleteBlockers.join(", ")}`
6681
+ );
6682
+ }
6683
+ if (item.status === "open" && item.answer) {
6684
+ throw new Error(`open inquiry "${item.id}" already has an answer`);
6685
+ }
6686
+ if (item.status === "answered" && !item.answer) {
6687
+ throw new Error(`inquiry "${item.id}" is answered without an answer`);
6688
+ }
6689
+ if (item.phase === "bridge" && item.answer && !item.answer.text.startsWith("Applying to this task:")) {
6690
+ throw new Error(
6691
+ `bridge inquiry "${item.id}" answer must start with "Applying to this task:"`
6692
+ );
6693
+ }
6694
+ }
6695
+ if (state.lastReview && state.lastReview.revision > state.revision) {
6696
+ throw new Error(
6697
+ `last review references future revision ${state.lastReview.revision}`
6698
+ );
6699
+ }
6700
+ const nextQuestionId = state.lastReview?.nextQuestionId;
6701
+ if (nextQuestionId) {
6702
+ const next = inquiries.get(nextQuestionId);
6703
+ if (!next) {
6704
+ throw new Error(
6705
+ `last review references missing next question "${nextQuestionId}"`
6706
+ );
6707
+ }
6708
+ if (state.lastReview?.revision === state.revision) {
6709
+ if (next.status === "answered") {
6710
+ throw new Error(
6711
+ `last review next question "${nextQuestionId}" is already answered`
6712
+ );
6713
+ }
6714
+ const incompleteBlockers = next.blockedBy.filter(
6715
+ (id) => inquiries.get(id)?.status !== "answered"
6716
+ );
6717
+ if (incompleteBlockers.length > 0) {
6718
+ throw new Error(
6719
+ `last review next question "${nextQuestionId}" is blocked by ${incompleteBlockers.join(", ")}`
6720
+ );
6721
+ }
6722
+ }
6723
+ }
6724
+ if (state.lastReview?.decision === "complete") {
6725
+ if (state.lastReview.revision !== state.revision) {
6726
+ throw new Error(
6727
+ "a complete Socratic review must reference the current revision"
6728
+ );
6729
+ }
6730
+ const unansweredStatements = [
6731
+ state.objective,
6732
+ ...state.successCriteria,
6733
+ ...state.constraints,
6734
+ ...state.assumptions
6735
+ ].some(({ answer: answer2 }) => answer2 === null);
6736
+ if (unansweredStatements || state.inquiries.some(({ status }) => status !== "answered")) {
6737
+ throw new Error(
6738
+ "a complete Socratic review requires an answer for every required question"
6739
+ );
6740
+ }
6741
+ if (state.lastReview.nextQuestionId !== null) {
6742
+ throw new Error("a complete Socratic review cannot name a next question");
6743
+ }
6744
+ }
6745
+ return blocks;
6746
+ }
6747
+ function parseSocraticPlan(value) {
6748
+ const state = parsePlanSchema(socraticPlanState, value);
6749
+ return { state, blocks: validateSocraticPlan(state) };
6750
+ }
6751
+ function formatAnswer(answer2) {
6752
+ return answer2 ? `Answer: ${answer2.text} [${answer2.basis}; ${answer2.source}]${latestEvidence(answer2.evidence)}` : "Answer: unanswered";
6753
+ }
6754
+ function formatQuestion(item) {
6755
+ return `${item.question} [${item.basis}; ${item.source}]
6756
+ ${formatAnswer(item.answer)}`;
6757
+ }
6758
+ function formatInquiry(item, blocks) {
6759
+ const blocked = blocks.get(item.id) ?? [];
6760
+ const suffix = blocked.length > 0 ? `; blocks ${blocked.join(", ")}` : "";
6761
+ return `${item.id} [${item.phase}]: ${item.question} [${item.basis}; ${item.source}]${suffix}
6762
+ ${formatAnswer(item.answer)}`;
6763
+ }
6764
+ function formatSocraticPlanReview({
6765
+ state,
6766
+ blocks
6767
+ }) {
6768
+ const inquiries = new Map(state.inquiries.map((item) => [item.id, item]));
6769
+ const active = state.inquiries.filter(({ status }) => status === "answering");
6770
+ const ready = state.inquiries.filter(
6771
+ ({ status, blockedBy }) => status === "open" && blockedBy.every((id) => inquiries.get(id)?.status === "answered")
6772
+ );
6773
+ const waiting = state.inquiries.filter(
6774
+ ({ status, blockedBy }) => status === "open" && blockedBy.some((id) => inquiries.get(id)?.status !== "answered")
6775
+ );
6776
+ const answered = state.inquiries.filter(
6777
+ ({ status }) => status === "answered"
6778
+ );
6779
+ return [
6780
+ `Current Socratic plan (revision ${state.revision})`,
6781
+ `Governing question: ${formatQuestion(state.objective)}`,
6782
+ "",
6783
+ "Success questions:",
6784
+ ...lineItems(
6785
+ state.successCriteria.map(
6786
+ (item) => `${item.id}: ${formatQuestion(item)}`
6787
+ )
6788
+ ),
6789
+ "",
6790
+ "Constraint questions:",
6791
+ ...lineItems(state.constraints.map(formatQuestion)),
6792
+ "",
6793
+ "Assumption questions:",
6794
+ ...lineItems(state.assumptions.map(formatQuestion)),
6795
+ "",
6796
+ "Active inquiries:",
6797
+ ...lineItems(active.map((item) => formatInquiry(item, blocks))),
6798
+ "",
6799
+ "Ready inquiries:",
6800
+ ...lineItems(ready.map((item) => formatInquiry(item, blocks))),
6801
+ "",
6802
+ "Waiting inquiries:",
6803
+ ...lineItems(
6804
+ waiting.map(
6805
+ (item) => `${formatInquiry(item, blocks)}; waiting for ${item.blockedBy.filter((id) => inquiries.get(id)?.status !== "answered").join(", ")}`
6806
+ )
6807
+ ),
6808
+ "",
6809
+ "Answered inquiries:",
6810
+ ...lineItems(answered.map((item) => formatInquiry(item, blocks))),
6811
+ ...state.lastReview ? [
6812
+ "",
6813
+ `Last review: ${state.lastReview.decision} at revision ${state.lastReview.revision} \u2014 ${state.lastReview.summary}`,
6814
+ `Next question: ${state.lastReview.nextQuestionId ?? "none"}`
6815
+ ] : [],
6816
+ "",
6817
+ SOCRATIC_REVIEW_REMINDER
6818
+ ].join("\n");
6819
+ }
6820
+ function instructions2() {
6821
+ return fragment(
6822
+ "plan_instructions",
6823
+ PLAN_FOUNDATION_INSTRUCTIONS,
6824
+ socraticInquiry(),
6825
+ SOCRATIC_PLAN_INSTRUCTIONS,
6826
+ PLAN_REVIEW_INSTRUCTIONS
6827
+ );
6410
6828
  }
6829
+ function review2(options) {
6830
+ return createPlanReview(
6831
+ options,
6832
+ (value) => formatSocraticPlanReview(parseSocraticPlan(value)),
6833
+ SOCRATIC_REVIEW_REMINDER
6834
+ );
6835
+ }
6836
+ var socraticPlan = { path: PLAN_FILE_PATH, instructions: instructions2, review: review2 };
6411
6837
 
6412
6838
  // packages/context/src/lib/guardrails/error-recovery.guardrail.ts
6413
6839
  import chalk3 from "chalk";
@@ -6519,7 +6945,15 @@ function render(tag, ...fragments) {
6519
6945
 
6520
6946
  // packages/context/src/lib/sandbox/agent-os-sandbox.ts
6521
6947
  import { PassThrough, Readable } from "node:stream";
6948
+
6949
+ // packages/context/src/lib/sandbox/read-file.ts
6522
6950
  var decoder = new TextDecoder();
6951
+ function readFileContent(bytes, options) {
6952
+ const content = options?.encoding === "binary" ? bytes : decoder.decode(bytes);
6953
+ return content;
6954
+ }
6955
+
6956
+ // packages/context/src/lib/sandbox/agent-os-sandbox.ts
6523
6957
  var AgentOsSandboxError = class extends Error {
6524
6958
  constructor(message2) {
6525
6959
  super(message2);
@@ -6557,7 +6991,7 @@ function startKernelProcess(os, command, options) {
6557
6991
  const stderr = new PassThrough();
6558
6992
  const onStdout = (chunk) => stdout.write(chunk);
6559
6993
  const onStderr = (chunk) => stderr.write(chunk);
6560
- const { pid } = os.spawn("sh", ["-c", command], {
6994
+ const { pid } = os.spawn("bash", ["-lc", command], {
6561
6995
  ...options,
6562
6996
  onStdout,
6563
6997
  onStderr
@@ -6610,6 +7044,25 @@ async function createAgentOsSandbox(options = {}) {
6610
7044
  const err = error instanceof Error ? error : new Error(String(error));
6611
7045
  throw new AgentOsCreationError(err.message, err);
6612
7046
  }
7047
+ try {
7048
+ const probe = startKernelProcess(os, ":", {});
7049
+ const [, stderr, exitCode] = await Promise.all([
7050
+ readAll(probe.stdout),
7051
+ readAll(probe.stderr),
7052
+ probe.exit
7053
+ ]);
7054
+ if (exitCode !== 0) {
7055
+ throw new Error(stderr.trim() || `exit code ${exitCode}`);
7056
+ }
7057
+ } catch (error) {
7058
+ await os.dispose().catch(() => {
7059
+ });
7060
+ const err = error instanceof Error ? error : new Error(String(error));
7061
+ throw new AgentOsCreationError(
7062
+ `Bash is required to execute sandbox commands but could not be started: ${err.message}`,
7063
+ err
7064
+ );
7065
+ }
6613
7066
  const sandbox = {
6614
7067
  async executeCommand(command, { signal } = {}) {
6615
7068
  if (signal?.aborted) {
@@ -6657,16 +7110,19 @@ async function createAgentOsSandbox(options = {}) {
6657
7110
  exit
6658
7111
  };
6659
7112
  },
6660
- async readFile(path3) {
7113
+ async readFile(path6, options2) {
6661
7114
  try {
6662
- const bytes = await os.readFile(path3);
6663
- return decoder.decode(bytes);
7115
+ const bytes = await os.readFile(path6);
7116
+ return readFileContent(bytes, options2);
6664
7117
  } catch (error) {
6665
7118
  throw new Error(
6666
- `Failed to read file "${path3}": ${error instanceof Error ? error.message : String(error)}`
7119
+ `Failed to read file "${path6}": ${error instanceof Error ? error.message : String(error)}`
6667
7120
  );
6668
7121
  }
6669
7122
  },
7123
+ async exists(path6) {
7124
+ return os.exists(path6);
7125
+ },
6670
7126
  async writeFiles(files) {
6671
7127
  const results = await os.writeFiles(
6672
7128
  files.map((f) => ({
@@ -6853,9 +7309,9 @@ function toSandboxProcess(child, abortSignal) {
6853
7309
  };
6854
7310
  }
6855
7311
  var BASE64_WRITE_CHUNK = 32768;
6856
- function base64WriteCommands(path3, content) {
7312
+ function base64WriteCommands(path6, content) {
6857
7313
  const base64 = Buffer.from(content).toString("base64");
6858
- const quotedPath = shellQuote(path3);
7314
+ const quotedPath = shellQuote(path6);
6859
7315
  const commands = [];
6860
7316
  for (let offset = 0; offset < base64.length; offset += BASE64_WRITE_CHUNK) {
6861
7317
  const chunk = base64.slice(offset, offset + BASE64_WRITE_CHUNK);
@@ -6869,8 +7325,11 @@ function base64WriteCommands(path3, content) {
6869
7325
  }
6870
7326
  return commands;
6871
7327
  }
6872
- function base64ReadCommand(path3) {
6873
- return `base64 ${shellQuote(path3)}`;
7328
+ function base64ReadCommand(path6) {
7329
+ return `base64 ${shellQuote(path6)}`;
7330
+ }
7331
+ function existsCommand(path6) {
7332
+ return `test -e ${shellQuote(path6)} && printf true || printf false`;
6874
7333
  }
6875
7334
 
6876
7335
  // packages/context/src/lib/sandbox/installers/installer.ts
@@ -7143,6 +7602,7 @@ var ContainerSandboxStrategy = class {
7143
7602
  await this.engine.ensureWorkdir(this.context.containerId, this.workdir);
7144
7603
  await this.configure();
7145
7604
  }
7605
+ await this.assertBashAvailable();
7146
7606
  } catch (error) {
7147
7607
  if (acquired && !acquired.attached) {
7148
7608
  await this.stopContainer(acquired.containerId);
@@ -7375,7 +7835,7 @@ var ContainerSandboxStrategy = class {
7375
7835
  const volumes = [...this.createdVolumes].reverse();
7376
7836
  for (const volume of volumes) {
7377
7837
  try {
7378
- await spawn2(this.engine.cli, ["volume", "rm", volume]);
7838
+ await this.engine.removeVolume(volume);
7379
7839
  this.createdVolumes.delete(volume);
7380
7840
  } catch (error) {
7381
7841
  const reason = this.engineErrorMessage(error);
@@ -7441,6 +7901,15 @@ var ContainerSandboxStrategy = class {
7441
7901
  };
7442
7902
  }
7443
7903
  }
7904
+ async assertBashAvailable() {
7905
+ const result = await this.exec(":");
7906
+ if (result.exitCode === 0) return;
7907
+ const detail = result.stderr.trim() || result.stdout.trim();
7908
+ throw this.engine.errors.generic(
7909
+ `Bash is required to execute sandbox commands but could not be started${detail ? `: ${detail}` : "."}`,
7910
+ this.context.containerId
7911
+ );
7912
+ }
7444
7913
  spawnProcess(command, options) {
7445
7914
  const child = childSpawn(
7446
7915
  this.engine.cli,
@@ -7453,12 +7922,18 @@ var ContainerSandboxStrategy = class {
7453
7922
  const sandbox = {
7454
7923
  executeCommand: async (command, options) => this.exec(command, options),
7455
7924
  spawn: (command, options) => this.spawnProcess(command, options),
7456
- readFile: async (path3) => {
7457
- const result = await sandbox.executeCommand(base64ReadCommand(path3));
7925
+ readFile: async (path6, options) => {
7926
+ const result = await sandbox.executeCommand(base64ReadCommand(path6));
7458
7927
  if (result.exitCode !== 0) {
7459
- throw new Error(`Failed to read file "${path3}": ${result.stderr}`);
7928
+ throw new Error(`Failed to read file "${path6}": ${result.stderr}`);
7460
7929
  }
7461
- return Buffer.from(result.stdout, "base64").toString("utf-8");
7930
+ return readFileContent(Buffer.from(result.stdout, "base64"), options);
7931
+ },
7932
+ exists: async (path6) => {
7933
+ const result = await sandbox.executeCommand(existsCommand(path6));
7934
+ if (result.stdout === "true") return true;
7935
+ if (result.stdout === "false") return false;
7936
+ throw new Error(`Failed to check "${path6}": ${result.stderr}`);
7462
7937
  },
7463
7938
  writeFiles: async (files) => {
7464
7939
  for (const file of files) {
@@ -7632,7 +8107,7 @@ var appleEngine = {
7632
8107
  flags.push("--env", `${key}=${value}`);
7633
8108
  }
7634
8109
  }
7635
- return ["exec", ...flags, containerId, "sh", "-c", command];
8110
+ return ["exec", ...flags, containerId, "bash", "-lc", command];
7636
8111
  },
7637
8112
  inspectArgs(containerId) {
7638
8113
  return ["inspect", containerId];
@@ -7645,6 +8120,9 @@ var appleEngine = {
7645
8120
  volumeCreateArgs(volume) {
7646
8121
  return ["volume", "create", volume.name];
7647
8122
  },
8123
+ async removeVolume(name) {
8124
+ await spawn3(CLI, ["volume", "rm", name]);
8125
+ },
7648
8126
  errorMessage: getCliErrorMessage,
7649
8127
  isServiceDown(message2) {
7650
8128
  return /apiserver|not running|connection refused|could not connect|xpc/i.test(
@@ -7661,9 +8139,15 @@ var appleEngine = {
7661
8139
  return /already exists/i.test(message2);
7662
8140
  },
7663
8141
  async ensureWorkdir(containerId, workdir) {
7664
- await spawn3(CLI, ["exec", containerId, "sh", "-c", `mkdir -p ${workdir}`]);
8142
+ await spawn3(CLI, [
8143
+ "exec",
8144
+ containerId,
8145
+ "bash",
8146
+ "-lc",
8147
+ `mkdir -p ${shellQuote(workdir)}`
8148
+ ]);
7665
8149
  },
7666
- defaultImage: "docker.io/library/alpine:latest",
8150
+ defaultImage: "docker.io/library/bash:5.3-alpine3.24",
7667
8151
  createInstallerContext: createAppleInstallerContext,
7668
8152
  async imageExists(tag) {
7669
8153
  try {
@@ -7850,13 +8334,200 @@ var BashException = class extends Error {
7850
8334
  };
7851
8335
 
7852
8336
  // packages/context/src/lib/sandbox/bash-tool.ts
7853
- import { tool as tool2 } from "ai";
8337
+ import { tool as tool3 } from "ai";
7854
8338
  import fg from "fast-glob";
7855
8339
  import { BashTransformPipeline, TeePlugin } from "just-bash";
7856
8340
  import { readFile } from "node:fs/promises";
7857
- import * as path from "node:path";
7858
- import z4 from "zod";
8341
+ import * as path4 from "node:path";
8342
+ import z7 from "zod";
7859
8343
  import { withHostOnlyToolMetadata as withHostOnlyToolMetadata2 } from "@deepagents/agent";
8344
+
8345
+ // packages/context/src/lib/sandbox/read-file-tool/index.ts
8346
+ import { tool as tool2 } from "ai";
8347
+ import path3 from "node:path";
8348
+ import z6 from "zod";
8349
+
8350
+ // packages/context/src/lib/sandbox/read-file-tool/binary.ts
8351
+ import { detectMediaType } from "@ai-sdk/provider-utils";
8352
+ import path from "node:path";
8353
+ var SNIFF_BYTES = 8 * 1024;
8354
+ var NUL = 0;
8355
+ var mediaKinds = /* @__PURE__ */ new Set(["image", "video", "audio"]);
8356
+ var binaryFileFormat = {
8357
+ read({ bytes, path: filePath, options }) {
8358
+ options.abortSignal?.throwIfAborted();
8359
+ const mediaType = detectMediaType({ data: bytes });
8360
+ const kind = mediaType?.split("/")[0];
8361
+ const binary = kind !== void 0 && mediaKinds.has(kind) || bytes.subarray(0, SNIFF_BYTES).includes(NUL);
8362
+ if (!binary) return void 0;
8363
+ return {
8364
+ type: "error-text",
8365
+ value: `${path.posix.basename(filePath)} is a binary ${mediaType ?? "file"} that readFile cannot display. Convert it in the sandbox first and read the result: HEIC stills with \`heif-dec in.heic out.jpg\`, video frames with \`ffmpeg -i in.mov -frames:v 1 frame.jpg\`, audio through a transcript or metadata tool. UTF-16 text counts as binary too; re-encode it as UTF-8.`
8366
+ };
8367
+ }
8368
+ };
8369
+
8370
+ // packages/context/src/lib/sandbox/read-file-tool/format.ts
8371
+ import path2 from "node:path";
8372
+ function formatBytes(bytes) {
8373
+ return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
8374
+ }
8375
+ function attachment(input, {
8376
+ label,
8377
+ maxBytes,
8378
+ mediaType
8379
+ }) {
8380
+ input.options.abortSignal?.throwIfAborted();
8381
+ if (input.bytes.byteLength > maxBytes) {
8382
+ return {
8383
+ type: "error-text",
8384
+ value: `${label} exceeds ${formatBytes(maxBytes)} limit (got ${formatBytes(input.bytes.byteLength)})`
8385
+ };
8386
+ }
8387
+ return {
8388
+ type: "content",
8389
+ value: [
8390
+ {
8391
+ type: "file",
8392
+ data: {
8393
+ type: "data",
8394
+ data: Buffer.from(input.bytes).toString("base64")
8395
+ },
8396
+ mediaType,
8397
+ filename: path2.posix.basename(input.path)
8398
+ }
8399
+ ]
8400
+ };
8401
+ }
8402
+
8403
+ // packages/context/src/lib/sandbox/read-file-tool/image.ts
8404
+ import { detectMediaType as detectMediaType2 } from "@ai-sdk/provider-utils";
8405
+ var IMAGE_MAX_BYTES = 5 * 1024 * 1024;
8406
+ var supportedImageMediaTypes = /* @__PURE__ */ new Set([
8407
+ "image/png",
8408
+ "image/jpeg",
8409
+ "image/gif",
8410
+ "image/webp"
8411
+ ]);
8412
+ var imageFileFormat = {
8413
+ read(input) {
8414
+ const mediaType = detectMediaType2({
8415
+ data: input.bytes,
8416
+ topLevelType: "image"
8417
+ });
8418
+ return mediaType && supportedImageMediaTypes.has(mediaType) ? attachment(input, {
8419
+ label: "Image",
8420
+ maxBytes: IMAGE_MAX_BYTES,
8421
+ mediaType
8422
+ }) : void 0;
8423
+ }
8424
+ };
8425
+
8426
+ // packages/context/src/lib/sandbox/read-file-tool/ooxml.ts
8427
+ import { extname } from "node:path";
8428
+ var OFFICE_MAX_BYTES = 50 * 1024 * 1024;
8429
+ var mediaTypeByExtension = {
8430
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
8431
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
8432
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
8433
+ };
8434
+ function hasZipSignature(bytes) {
8435
+ return bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
8436
+ }
8437
+ var ooxmlFileFormat = {
8438
+ read(input) {
8439
+ if (!hasZipSignature(input.bytes)) return void 0;
8440
+ const mediaType = mediaTypeByExtension[extname(input.path).toLowerCase()];
8441
+ return mediaType ? attachment(input, {
8442
+ label: "Office file",
8443
+ maxBytes: OFFICE_MAX_BYTES,
8444
+ mediaType
8445
+ }) : void 0;
8446
+ }
8447
+ };
8448
+
8449
+ // packages/context/src/lib/sandbox/read-file-tool/pdf.ts
8450
+ import { detectMediaType as detectMediaType3 } from "@ai-sdk/provider-utils";
8451
+ var PDF_MAX_BYTES = 32 * 1024 * 1024;
8452
+ var pdfFileFormat = {
8453
+ read(input) {
8454
+ return detectMediaType3({
8455
+ data: input.bytes,
8456
+ topLevelType: "application"
8457
+ }) === "application/pdf" ? attachment(input, {
8458
+ label: "PDF",
8459
+ maxBytes: PDF_MAX_BYTES,
8460
+ mediaType: "application/pdf"
8461
+ }) : void 0;
8462
+ }
8463
+ };
8464
+
8465
+ // packages/context/src/lib/sandbox/read-file-tool/text.ts
8466
+ var TEXT_MAX_BYTES = 10 * 1024 * 1024;
8467
+ var textFileFormat = {
8468
+ read({ bytes, limit, offset, options }) {
8469
+ options.abortSignal?.throwIfAborted();
8470
+ if (offset === void 0 && limit === void 0 && bytes.byteLength > TEXT_MAX_BYTES) {
8471
+ return {
8472
+ type: "error-text",
8473
+ value: `Text file exceeds ${formatBytes(TEXT_MAX_BYTES)} limit (got ${formatBytes(bytes.byteLength)}). Use offset/limit to slice.`
8474
+ };
8475
+ }
8476
+ const content = readFileContent(bytes, { encoding: "utf-8" });
8477
+ if (offset === void 0 && limit === void 0) {
8478
+ return { type: "text", value: content };
8479
+ }
8480
+ const start = (offset ?? 1) - 1;
8481
+ const end = limit === void 0 ? void 0 : start + limit;
8482
+ return {
8483
+ type: "text",
8484
+ value: content.split(/\r\n|\n|\r/u).slice(start, end).join("\n")
8485
+ };
8486
+ }
8487
+ };
8488
+
8489
+ // packages/context/src/lib/sandbox/read-file-tool/index.ts
8490
+ var formats = [
8491
+ imageFileFormat,
8492
+ pdfFileFormat,
8493
+ ooxmlFileFormat,
8494
+ binaryFileFormat,
8495
+ textFileFormat
8496
+ ];
8497
+ var inputSchema = z6.strictObject({
8498
+ path: z6.string().min(1).describe("The path to the file to read"),
8499
+ offset: z6.number().int().min(1).optional().describe("1-based line number to start at for text files"),
8500
+ limit: z6.number().int().min(1).optional().describe("Number of text lines to return from offset")
8501
+ });
8502
+ function createReadFileTool({
8503
+ sandbox,
8504
+ destination
8505
+ }) {
8506
+ return tool2({
8507
+ description: "Read a sandbox file. Supports text, PNG, JPEG, GIF, WebP, PDF, XLSX, DOCX, and PPTX; other binary files such as HEIC, video, and audio are refused with conversion guidance. Use offset/limit with large text files.",
8508
+ inputExamples: [
8509
+ { input: { path: "README.md" } },
8510
+ { input: { path: "logs/trace.jsonl", offset: 1, limit: 200 } }
8511
+ ],
8512
+ inputSchema,
8513
+ execute: async (input, options) => {
8514
+ options.abortSignal?.throwIfAborted();
8515
+ const bytes = await sandbox.readFile(
8516
+ path3.posix.resolve(destination, input.path),
8517
+ { encoding: "binary" }
8518
+ );
8519
+ options.abortSignal?.throwIfAborted();
8520
+ for (const format of formats) {
8521
+ const result = format.read({ ...input, bytes, options });
8522
+ if (result !== void 0) return result;
8523
+ }
8524
+ throw new Error(`No file format accepted ${input.path}`);
8525
+ },
8526
+ toModelOutput: ({ output }) => output
8527
+ });
8528
+ }
8529
+
8530
+ // packages/context/src/lib/sandbox/bash-tool.ts
7860
8531
  var DEFAULT_DESTINATION = "/workspace";
7861
8532
  var DEFAULT_MAX_FILES = 1e3;
7862
8533
  var DEFAULT_MAX_OUTPUT_LENGTH = 3e4;
@@ -7948,8 +8619,9 @@ function withBashExceptionCatch(sandbox) {
7948
8619
  throw error;
7949
8620
  }
7950
8621
  },
7951
- readFile: (filePath) => sandbox.readFile(filePath),
8622
+ readFile: (filePath, options) => sandbox.readFile(filePath, options),
7952
8623
  writeFiles: (files) => sandbox.writeFiles(files),
8624
+ exists: (filePath) => sandbox.exists(filePath),
7953
8625
  dispose: () => sandbox.dispose(),
7954
8626
  [Symbol.asyncDispose]() {
7955
8627
  return decorated.dispose();
@@ -7979,7 +8651,7 @@ async function listInitialFiles(options) {
7979
8651
  const files = /* @__PURE__ */ new Map();
7980
8652
  if (options.uploadDirectory) {
7981
8653
  const { source, include = "**/*" } = options.uploadDirectory;
7982
- const absoluteSource = path.resolve(source);
8654
+ const absoluteSource = path4.resolve(source);
7983
8655
  const uploaded = await fg(include, {
7984
8656
  cwd: absoluteSource,
7985
8657
  dot: true,
@@ -7989,7 +8661,7 @@ async function listInitialFiles(options) {
7989
8661
  for (const relativePath of uploaded) {
7990
8662
  files.set(relativePath, {
7991
8663
  path: relativePath,
7992
- sourcePath: path.join(absoluteSource, relativePath)
8664
+ sourcePath: path4.join(absoluteSource, relativePath)
7993
8665
  });
7994
8666
  }
7995
8667
  }
@@ -8008,7 +8680,7 @@ async function uploadInitialFiles(sandbox, destination, files) {
8008
8680
  for (let index = 0; index < files.length; index += WRITE_BATCH_SIZE) {
8009
8681
  const batch = await Promise.all(
8010
8682
  files.slice(index, index + WRITE_BATCH_SIZE).map(async (file) => ({
8011
- path: path.posix.join(destination, file.path),
8683
+ path: path4.posix.join(destination, file.path),
8012
8684
  content: "content" in file ? file.content : await readFile(file.sourcePath)
8013
8685
  }))
8014
8686
  );
@@ -8028,12 +8700,12 @@ async function createToolPrompt(sandbox, filenames, customPrompt) {
8028
8700
  const lines = [
8029
8701
  `Available tools: ${[...available].sort().join(", ")}, and more`
8030
8702
  ];
8031
- const formats = new Set(
8703
+ const formats2 = new Set(
8032
8704
  filenames.map(
8033
- (filename) => EXTENSION_FORMATS[path.extname(filename).toLowerCase()]
8705
+ (filename) => EXTENSION_FORMATS[path4.extname(filename).toLowerCase()]
8034
8706
  ).filter((format) => format !== void 0)
8035
8707
  );
8036
- for (const format of formats) {
8708
+ for (const format of formats2) {
8037
8709
  const tools = FORMAT_TOOLS[format].filter((name) => available.has(name));
8038
8710
  if (tools.length > 0) {
8039
8711
  lines.push(`For ${FORMAT_LABELS[format]}: ${tools.join(", ")}`);
@@ -8093,7 +8765,7 @@ async function createBashTool(options) {
8093
8765
  );
8094
8766
  const extraInstructions = [options.extraInstructions, REASONING_INSTRUCTION].filter(Boolean).join("\n\n");
8095
8767
  const experimentalTeeTransform = options.experimentalTeeTransform ?? false;
8096
- const bash = tool2({
8768
+ const bash = tool3({
8097
8769
  description: createBashDescription({
8098
8770
  destination,
8099
8771
  filenames: initialFiles.map((file) => file.path),
@@ -8101,9 +8773,9 @@ async function createBashTool(options) {
8101
8773
  extraInstructions,
8102
8774
  experimentalTeeTransform
8103
8775
  }),
8104
- inputSchema: z4.object({
8105
- command: z4.string().describe("The bash command to execute"),
8106
- reasoning: z4.string().trim().min(1).describe("Brief reason for executing this command")
8776
+ inputSchema: z7.object({
8777
+ command: z7.string().describe("The bash command to execute"),
8778
+ reasoning: z7.string().trim().min(1).describe("Brief reason for executing this command")
8107
8779
  }),
8108
8780
  execute: async ({ command: originalCommand }, executionOptions) => {
8109
8781
  let command = originalCommand;
@@ -8143,28 +8815,18 @@ async function createBashTool(options) {
8143
8815
  return Object.keys(meta).length > 0 ? { ...result, meta } : result;
8144
8816
  }
8145
8817
  });
8146
- const readFile2 = tool2({
8147
- description: "Read the contents of a file from the sandbox.",
8148
- inputSchema: z4.object({
8149
- path: z4.string().describe("The path to the file to read")
8150
- }),
8151
- execute: async ({ path: filePath }) => ({
8152
- content: await sandbox.readFile(
8153
- path.posix.resolve(destination, filePath)
8154
- )
8155
- })
8156
- });
8157
- const writeFile2 = tool2({
8818
+ const readFile2 = createReadFileTool({ sandbox, destination });
8819
+ const writeFile2 = tool3({
8158
8820
  description: "Write content to a file in the sandbox. Creates parent directories if needed.",
8159
- inputSchema: z4.object({
8160
- path: z4.string().describe("The path where the file should be written"),
8161
- content: z4.string().describe("The content to write to the file")
8821
+ inputSchema: z7.object({
8822
+ path: z7.string().describe("The path where the file should be written"),
8823
+ content: z7.string().describe("The content to write to the file")
8162
8824
  }),
8163
8825
  execute: async ({ path: filePath, content }) => {
8164
8826
  try {
8165
8827
  await sandbox.writeFiles([
8166
8828
  {
8167
- path: path.posix.resolve(destination, filePath),
8829
+ path: path4.posix.resolve(destination, filePath),
8168
8830
  content
8169
8831
  }
8170
8832
  ]);
@@ -8183,7 +8845,7 @@ async function createBashTool(options) {
8183
8845
  import { existsSync as existsSync2 } from "fs";
8184
8846
  import { defineCommand } from "just-bash";
8185
8847
  import spawn4 from "nano-spawn";
8186
- import * as path2 from "path";
8848
+ import * as path5 from "path";
8187
8849
  function createBinaryBridges(...binaries) {
8188
8850
  return binaries.map((input) => {
8189
8851
  const config = typeof input === "string" ? { name: input } : input;
@@ -8205,11 +8867,11 @@ function createBinaryBridges(...binaries) {
8205
8867
  if (arg.startsWith("-")) {
8206
8868
  return arg;
8207
8869
  }
8208
- const hasExtension = path2.extname(arg) !== "";
8209
- const hasPathSep = arg.includes(path2.sep) || arg.includes("/");
8870
+ const hasExtension = path5.extname(arg) !== "";
8871
+ const hasPathSep = arg.includes(path5.sep) || arg.includes("/");
8210
8872
  const isRelative = arg.startsWith(".");
8211
8873
  if (hasExtension || hasPathSep || isRelative) {
8212
- return path2.resolve(realCwd, arg);
8874
+ return path5.resolve(realCwd, arg);
8213
8875
  }
8214
8876
  return arg;
8215
8877
  });
@@ -8262,7 +8924,7 @@ function resolveRealCwd(ctx) {
8262
8924
  const fs = ctx.fs;
8263
8925
  let realCwd;
8264
8926
  if (fs.root) {
8265
- realCwd = path2.join(fs.root, ctx.cwd);
8927
+ realCwd = path5.join(fs.root, ctx.cwd);
8266
8928
  } else if (typeof fs.getMountPoint === "function" && typeof fs.toRealPath === "function") {
8267
8929
  const real = fs.toRealPath(ctx.cwd);
8268
8930
  realCwd = real ?? process.cwd();
@@ -8313,7 +8975,9 @@ async function createDaytonaSandbox(client, options = {}) {
8313
8975
  } catch (error) {
8314
8976
  throw normalizeDaytonaError(error, sdk);
8315
8977
  }
8978
+ await assertDaytonaBash(sandbox, options.commandTimeout);
8316
8979
  const backend = createDaytonaSandboxMethods({
8980
+ sdk,
8317
8981
  sandbox,
8318
8982
  commandTimeout: options.commandTimeout
8319
8983
  });
@@ -8328,6 +8992,24 @@ async function createDaytonaSandbox(client, options = {}) {
8328
8992
  }
8329
8993
  return backend;
8330
8994
  }
8995
+ async function assertDaytonaBash(sandbox, commandTimeout) {
8996
+ try {
8997
+ const result = await sandbox.process.executeCommand(
8998
+ "bash -lc ':'",
8999
+ void 0,
9000
+ void 0,
9001
+ commandTimeout
9002
+ );
9003
+ if (result.exitCode === void 0 || result.exitCode === 0) return;
9004
+ throw new Error(result.result || `exit code ${result.exitCode}`);
9005
+ } catch (error) {
9006
+ const err = toError(error);
9007
+ throw new DaytonaCreationError(
9008
+ `Bash is required to execute sandbox commands but could not be started: ${err.message}`,
9009
+ err
9010
+ );
9011
+ }
9012
+ }
8331
9013
  var UNRECOVERABLE_SANDBOX_STATES = /* @__PURE__ */ new Set([
8332
9014
  "error",
8333
9015
  "build_failed",
@@ -8449,7 +9131,7 @@ function validateDaytonaOptions(options) {
8449
9131
  }
8450
9132
  }
8451
9133
  function createDaytonaSandboxMethods(args) {
8452
- const { sandbox, commandTimeout } = args;
9134
+ const { sdk, sandbox, commandTimeout } = args;
8453
9135
  const executeCommand = async (command, options) => {
8454
9136
  if (options?.signal?.aborted) {
8455
9137
  return abortedCommandResult();
@@ -8464,7 +9146,7 @@ function createDaytonaSandboxMethods(args) {
8464
9146
  return abortedCommandResult();
8465
9147
  }
8466
9148
  const response = await sandbox.process.executeCommand(
8467
- command,
9149
+ `bash -lc ${shellQuote(command)}`,
8468
9150
  void 0,
8469
9151
  void 0,
8470
9152
  commandTimeout
@@ -8495,13 +9177,25 @@ function createDaytonaSandboxMethods(args) {
8495
9177
  commandTimeout
8496
9178
  });
8497
9179
  },
8498
- async readFile(path3) {
9180
+ async readFile(path6, options) {
8499
9181
  try {
8500
- const bytes = await sandbox.fs.downloadFile(path3);
8501
- return Buffer.from(bytes).toString("utf-8");
9182
+ const bytes = await sandbox.fs.downloadFile(path6);
9183
+ return readFileContent(bytes, options);
9184
+ } catch (error) {
9185
+ throw new DaytonaCommandError(
9186
+ `Failed to read file "${path6}": ${toError(error).message}`,
9187
+ toError(error)
9188
+ );
9189
+ }
9190
+ },
9191
+ async exists(path6) {
9192
+ try {
9193
+ await sandbox.fs.getFileDetails(path6);
9194
+ return true;
8502
9195
  } catch (error) {
9196
+ if (error instanceof sdk.DaytonaNotFoundError) return false;
8503
9197
  throw new DaytonaCommandError(
8504
- `Failed to read file "${path3}": ${toError(error).message}`,
9198
+ `Failed to check "${path6}": ${toError(error).message}`,
8505
9199
  toError(error)
8506
9200
  );
8507
9201
  }
@@ -8706,13 +9400,13 @@ function buildSessionCommand(command, options) {
8706
9400
  const env = options.env ?? {};
8707
9401
  const entries = Object.entries(env);
8708
9402
  if (entries.length === 0) {
8709
- return `sh -lc ${shellQuote(`${cwdPrefix}${command}`)}`;
9403
+ return `bash -lc ${shellQuote(`${cwdPrefix}${command}`)}`;
8710
9404
  }
8711
9405
  for (const [key] of entries) {
8712
9406
  validateEnvKey(key);
8713
9407
  }
8714
9408
  const exports = entries.map(([key, value]) => `export ${key}=${shellQuote(value)}`).join("; ");
8715
- return `sh -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
9409
+ return `bash -lc ${shellQuote(`${exports}; ${cwdPrefix}${command}`)}`;
8716
9410
  }
8717
9411
  function validateEnvKey(key) {
8718
9412
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
@@ -8740,10 +9434,10 @@ function abortedExitInfo() {
8740
9434
  }
8741
9435
  function uniqueParentDirectories(paths) {
8742
9436
  const dirs = /* @__PURE__ */ new Set();
8743
- for (const path3 of paths) {
8744
- const index = path3.lastIndexOf("/");
9437
+ for (const path6 of paths) {
9438
+ const index = path6.lastIndexOf("/");
8745
9439
  if (index > 0) {
8746
- dirs.add(path3.slice(0, index));
9440
+ dirs.add(path6.slice(0, index));
8747
9441
  }
8748
9442
  }
8749
9443
  return [...dirs];
@@ -8764,8 +9458,9 @@ function toError(error) {
8764
9458
  // packages/context/src/lib/sandbox/docker-sandbox.ts
8765
9459
  import spawn5 from "nano-spawn";
8766
9460
  import { spawn as childSpawn3 } from "node:child_process";
8767
- import { createHash as createHash2 } from "node:crypto";
9461
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
8768
9462
  import { readFileSync as readFileSync2 } from "node:fs";
9463
+ import { setTimeout as delay2 } from "node:timers/promises";
8769
9464
  function isDockerfileOptions(opts) {
8770
9465
  return "dockerfile" in opts;
8771
9466
  }
@@ -8952,7 +9647,7 @@ var dockerEngine = {
8952
9647
  flags.push("-e", `${key}=${value}`);
8953
9648
  }
8954
9649
  }
8955
- return ["exec", ...flags, containerId, "sh", "-c", command];
9650
+ return ["exec", ...flags, containerId, "bash", "-lc", command];
8956
9651
  },
8957
9652
  inspectArgs(containerId) {
8958
9653
  return [
@@ -8981,6 +9676,20 @@ var dockerEngine = {
8981
9676
  const err = error;
8982
9677
  return err.stderr || err.stdout || err.message || String(error);
8983
9678
  },
9679
+ async removeVolume(name) {
9680
+ const deadline = performance.now() + 3e4;
9681
+ while (true) {
9682
+ try {
9683
+ await spawn5("docker", ["volume", "rm", name]);
9684
+ return;
9685
+ } catch (error) {
9686
+ if (!this.errorMessage(error).includes("volume is in use") || performance.now() >= deadline) {
9687
+ throw error;
9688
+ }
9689
+ await delay2(25);
9690
+ }
9691
+ }
9692
+ },
8984
9693
  isServiceDown(message2) {
8985
9694
  return message2.includes("Cannot connect") || message2.includes("docker daemon");
8986
9695
  },
@@ -8995,7 +9704,7 @@ var dockerEngine = {
8995
9704
  },
8996
9705
  async ensureWorkdir() {
8997
9706
  },
8998
- defaultImage: "alpine:latest",
9707
+ defaultImage: "bash:5.3-alpine3.24",
8999
9708
  createInstallerContext,
9000
9709
  async imageExists(tag) {
9001
9710
  try {
@@ -9050,14 +9759,190 @@ function buildDockerExecFlags(options) {
9050
9759
  }
9051
9760
  return flags;
9052
9761
  }
9762
+ function managedDockerCommand(command, controlPath) {
9763
+ return [
9764
+ "set -m",
9765
+ `control=${shellQuote(controlPath)}`,
9766
+ ': > "$control" || exit 1',
9767
+ `trap 'rm -f "$control"' EXIT`,
9768
+ `bash -lc ${shellQuote(command)} &`,
9769
+ "pid=$!",
9770
+ `printf '%s\\n' "$pid" > "$control"`,
9771
+ 'wait "$pid"',
9772
+ 'exit "$?"'
9773
+ ].join("\n");
9774
+ }
9775
+ async function waitForDockerExecPid(execArgs, controlPath, localExit) {
9776
+ const exited = localExit.then(
9777
+ () => true,
9778
+ () => true
9779
+ );
9780
+ const readPid = `if IFS= read -r pid < ${shellQuote(controlPath)}; then printf '%s' "$pid"; fi`;
9781
+ while (true) {
9782
+ const result = await spawn5("docker", execArgs(readPid));
9783
+ if (result.stdout) return Number(result.stdout);
9784
+ if (await Promise.race([exited, delay2(10).then(() => false)])) {
9785
+ return void 0;
9786
+ }
9787
+ }
9788
+ }
9789
+ async function terminateDockerExec(execArgs, controlPath, localExit) {
9790
+ const pid = await waitForDockerExecPid(execArgs, controlPath, localExit);
9791
+ if (pid === void 0) return false;
9792
+ const result = await spawn5(
9793
+ "docker",
9794
+ execArgs(
9795
+ [
9796
+ `pid=${pid}`,
9797
+ `IFS= read -r control_pid < ${shellQuote(controlPath)} || exit 0`,
9798
+ '[ "$control_pid" = "$pid" ] || exit 0',
9799
+ 'kill -KILL -- "-$pid" 2>/dev/null || exit 0',
9800
+ "group_alive() {",
9801
+ " local stat rest state pgrp",
9802
+ " for stat_path in /proc/[0-9]*/stat; do",
9803
+ ' IFS= read -r stat < "$stat_path" || continue',
9804
+ " rest=${stat##*) }",
9805
+ ' read -r state _ pgrp _ <<< "$rest"',
9806
+ ' if [ "$pgrp" = "$pid" ] && [ "$state" != Z ]; then return 0; fi',
9807
+ " done",
9808
+ " return 1",
9809
+ "}",
9810
+ "while group_alive; do sleep 0.01; done",
9811
+ "printf terminated"
9812
+ ].join("\n")
9813
+ )
9814
+ );
9815
+ return result.stdout === "terminated";
9816
+ }
9817
+ function spawnDockerProcess(execArgs, command, options = {}, commandTimeout) {
9818
+ if (!commandTimeout && options.signal === void 0) {
9819
+ return toSandboxProcess(
9820
+ childSpawn3("docker", execArgs(command, options)),
9821
+ void 0
9822
+ );
9823
+ }
9824
+ const controlPath = `/dev/shm/deepagents-exec-${randomUUID2()}.pid`;
9825
+ const localProcess = toSandboxProcess(
9826
+ childSpawn3(
9827
+ "docker",
9828
+ execArgs(managedDockerCommand(command, controlPath), options)
9829
+ ),
9830
+ void 0
9831
+ );
9832
+ let termination;
9833
+ const terminate = (reason) => {
9834
+ termination ??= {
9835
+ reason,
9836
+ confirmed: terminateDockerExec(execArgs, controlPath, localProcess.exit)
9837
+ };
9838
+ };
9839
+ const onAbort = () => terminate("abort");
9840
+ if (options.signal?.aborted) onAbort();
9841
+ else options.signal?.addEventListener("abort", onAbort, { once: true });
9842
+ let timeout;
9843
+ if (commandTimeout) {
9844
+ timeout = setTimeout(() => terminate("timeout"), commandTimeout);
9845
+ timeout.unref();
9846
+ }
9847
+ return {
9848
+ stdout: localProcess.stdout,
9849
+ stderr: localProcess.stderr,
9850
+ exit: localProcess.exit.then(async (info) => {
9851
+ const activeTermination = termination;
9852
+ if (!activeTermination || !await activeTermination.confirmed) {
9853
+ return info;
9854
+ }
9855
+ return activeTermination.reason === "timeout" ? { code: 124, signal: null, success: false } : { code: null, signal: "SIGKILL", success: false };
9856
+ }).finally(() => {
9857
+ clearTimeout(timeout);
9858
+ options.signal?.removeEventListener("abort", onAbort);
9859
+ })
9860
+ };
9861
+ }
9862
+ async function executeDockerCommand(execArgs, command, options = {}, commandTimeout) {
9863
+ if (!commandTimeout && options.signal === void 0) {
9864
+ try {
9865
+ const result2 = await spawn5("docker", execArgs(command));
9866
+ return { stdout: result2.stdout, stderr: result2.stderr, exitCode: 0 };
9867
+ } catch (error) {
9868
+ const err = error;
9869
+ return {
9870
+ stdout: err.stdout || "",
9871
+ stderr: err.stderr || err.message || "",
9872
+ exitCode: err.exitCode ?? 1
9873
+ };
9874
+ }
9875
+ }
9876
+ const process2 = spawnDockerProcess(
9877
+ execArgs,
9878
+ command,
9879
+ { signal: options.signal },
9880
+ commandTimeout
9881
+ );
9882
+ const [stdout, stderr, info] = await Promise.all([
9883
+ new Response(process2.stdout).text(),
9884
+ new Response(process2.stderr).text(),
9885
+ process2.exit
9886
+ ]);
9887
+ const result = {
9888
+ stdout: stdout.replace(/\r?\n$/, ""),
9889
+ stderr: stderr.replace(/\r?\n$/, "")
9890
+ };
9891
+ if (info.signal === "SIGKILL") {
9892
+ return {
9893
+ ...result,
9894
+ stderr: result.stderr || "Command aborted",
9895
+ exitCode: 1
9896
+ };
9897
+ }
9898
+ return { ...result, exitCode: info.code ?? 1 };
9899
+ }
9900
+ var DockerRuntimeStrategy = class extends RuntimeStrategy {
9901
+ exec(command, options) {
9902
+ return executeDockerCommand(
9903
+ (value, spawnOptions) => this.engine.execArgs(this.context.containerId, value, spawnOptions),
9904
+ command,
9905
+ options,
9906
+ this.opts.commandTimeout
9907
+ );
9908
+ }
9909
+ spawnProcess(command, options) {
9910
+ return spawnDockerProcess(
9911
+ (value, spawnOptions) => this.engine.execArgs(this.context.containerId, value, spawnOptions),
9912
+ command,
9913
+ options,
9914
+ this.opts.commandTimeout
9915
+ );
9916
+ }
9917
+ };
9918
+ var DockerContainerfileStrategy = class extends ContainerfileStrategy {
9919
+ exec(command, options) {
9920
+ return executeDockerCommand(
9921
+ (value, spawnOptions) => this.engine.execArgs(this.context.containerId, value, spawnOptions),
9922
+ command,
9923
+ options,
9924
+ this.opts.commandTimeout
9925
+ );
9926
+ }
9927
+ spawnProcess(command, options) {
9928
+ return spawnDockerProcess(
9929
+ (value, spawnOptions) => this.engine.execArgs(this.context.containerId, value, spawnOptions),
9930
+ command,
9931
+ options,
9932
+ this.opts.commandTimeout
9933
+ );
9934
+ }
9935
+ };
9053
9936
  var ComposeStrategy = class extends ContainerSandboxStrategy {
9054
9937
  projectName;
9055
9938
  composeFile;
9056
9939
  service;
9940
+ commandTimeout;
9057
9941
  constructor(args) {
9058
9942
  super({ resources: args.resources }, dockerEngine);
9059
9943
  this.composeFile = args.compose;
9060
9944
  this.service = args.service;
9945
+ this.commandTimeout = args.commandTimeout;
9061
9946
  this.projectName = this.computeProjectName();
9062
9947
  }
9063
9948
  computeProjectName() {
@@ -9093,36 +9978,23 @@ var ComposeStrategy = class extends ContainerSandboxStrategy {
9093
9978
  async configure() {
9094
9979
  }
9095
9980
  async exec(command, options) {
9096
- try {
9097
- const result = await spawn5(
9098
- "docker",
9099
- [
9100
- "compose",
9101
- "-f",
9102
- this.composeFile,
9103
- "-p",
9104
- this.projectName,
9105
- "exec",
9106
- "-T",
9107
- this.service,
9108
- "sh",
9109
- "-c",
9110
- command
9111
- ],
9112
- { signal: options?.signal }
9113
- );
9114
- return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
9115
- } catch (error) {
9116
- const err = error;
9117
- return {
9118
- stdout: err.stdout || "",
9119
- stderr: err.stderr || err.message || "",
9120
- exitCode: err.exitCode ?? 1
9121
- };
9122
- }
9981
+ return executeDockerCommand(
9982
+ (value, spawnOptions) => this.execArgs(value, spawnOptions),
9983
+ command,
9984
+ options,
9985
+ this.commandTimeout
9986
+ );
9123
9987
  }
9124
9988
  spawnProcess(command, options) {
9125
- const child = childSpawn3("docker", [
9989
+ return spawnDockerProcess(
9990
+ (value, spawnOptions) => this.execArgs(value, spawnOptions),
9991
+ command,
9992
+ options,
9993
+ this.commandTimeout
9994
+ );
9995
+ }
9996
+ execArgs(command, options) {
9997
+ return [
9126
9998
  "compose",
9127
9999
  "-f",
9128
10000
  this.composeFile,
@@ -9132,11 +10004,10 @@ var ComposeStrategy = class extends ContainerSandboxStrategy {
9132
10004
  "-T",
9133
10005
  ...buildDockerExecFlags(options),
9134
10006
  this.service,
9135
- "sh",
9136
- "-c",
10007
+ "bash",
10008
+ "-lc",
9137
10009
  command
9138
- ]);
9139
- return toSandboxProcess(child, options?.signal);
10010
+ ];
9140
10011
  }
9141
10012
  async stopContainer(_containerId) {
9142
10013
  try {
@@ -9157,18 +10028,19 @@ async function createDockerSandbox(options = {}) {
9157
10028
  return new ComposeStrategy({
9158
10029
  compose: options.compose,
9159
10030
  service: options.service,
9160
- resources: options.resources
10031
+ resources: options.resources,
10032
+ commandTimeout: options.commandTimeout
9161
10033
  }).create();
9162
10034
  }
9163
10035
  if (isDockerfileOptions(options)) {
9164
- return new ContainerfileStrategy(options, dockerEngine, {
10036
+ return new DockerContainerfileStrategy(options, dockerEngine, {
9165
10037
  dockerfile: options.dockerfile,
9166
10038
  context: options.context ?? ".",
9167
10039
  showBuildLogs: options.showBuildLogs ?? false,
9168
10040
  identity: options.platform
9169
10041
  }).create();
9170
10042
  }
9171
- return new RuntimeStrategy(options, dockerEngine, {
10043
+ return new DockerRuntimeStrategy(options, dockerEngine, {
9172
10044
  image: options.image ?? dockerEngine.defaultImage,
9173
10045
  installers: options.installers ?? []
9174
10046
  }).create();
@@ -9183,7 +10055,7 @@ async function useSandbox(options, fn) {
9183
10055
  }
9184
10056
 
9185
10057
  // packages/context/src/lib/sandbox/strace/file-changes.ts
9186
- import { randomUUID as randomUUID2 } from "node:crypto";
10058
+ import { randomUUID as randomUUID3 } from "node:crypto";
9187
10059
  import { posix as posix3 } from "node:path";
9188
10060
 
9189
10061
  // packages/context/src/lib/sandbox/strace/index.ts
@@ -9191,10 +10063,10 @@ import spawn6 from "nano-spawn";
9191
10063
  import { posix as posix2 } from "node:path";
9192
10064
  var STRACE_FLAGS = "-f -y -qq -e trace=%file,write,pwrite64,writev";
9193
10065
  function buildStraceCommand(command, traceFile, traceDir) {
9194
- return `mkdir -p ${shellQuote(traceDir)} 2>/dev/null; strace ${STRACE_FLAGS} -o ${shellQuote(traceFile)} -- sh -c ${shellQuote(command)}`;
10066
+ return `mkdir -p ${shellQuote(traceDir)} 2>/dev/null; strace ${STRACE_FLAGS} -o ${shellQuote(traceFile)} -- bash -lc ${shellQuote(command)}`;
9195
10067
  }
9196
10068
  function buildTracedCommand(command, traceFile, traceDir, sentinel) {
9197
- 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`;
10069
+ return `mkdir -p ${shellQuote(traceDir)} 2>/dev/null; strace ${STRACE_FLAGS} -o ${shellQuote(traceFile)} -- bash -lc ${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`;
9198
10070
  }
9199
10071
  function splitTracedOutput(stdout, sentinel) {
9200
10072
  const marker = `
@@ -9305,8 +10177,8 @@ function fdPath(args) {
9305
10177
  function openFlags(args) {
9306
10178
  return args.match(/\bO_[A-Z_]+(?:\|O_[A-Z_]+)*/)?.[0] ?? "";
9307
10179
  }
9308
- function matchesGlobs(path3, include, exclude) {
9309
- return include.some((glob) => posix2.matchesGlob(path3, glob)) && !exclude?.some((glob) => posix2.matchesGlob(path3, glob));
10180
+ function matchesGlobs(path6, include, exclude) {
10181
+ return include.some((glob) => posix2.matchesGlob(path6, glob)) && !exclude?.some((glob) => posix2.matchesGlob(path6, glob));
9310
10182
  }
9311
10183
  function parseStraceTrace(raw, options) {
9312
10184
  const { include, exclude, traceDir, traceFile } = options;
@@ -9314,12 +10186,12 @@ function parseStraceTrace(raw, options) {
9314
10186
  const state = /* @__PURE__ */ new Map();
9315
10187
  const renames = [];
9316
10188
  const resolve3 = (dir, p) => posix2.normalize(p.startsWith("/") ? p : posix2.join(dir, p));
9317
- const write = (path3) => {
9318
- state.set(path3, "write");
10189
+ const write = (path6) => {
10190
+ state.set(path6, "write");
9319
10191
  };
9320
- const remove = (path3) => {
9321
- if (state.get(path3) === "write") state.delete(path3);
9322
- else state.set(path3, "delete");
10192
+ const remove = (path6) => {
10193
+ if (state.get(path6) === "write") state.delete(path6);
10194
+ else state.set(path6, "delete");
9323
10195
  };
9324
10196
  for (const line of stitchLines(raw)) {
9325
10197
  const call = parseCall(line);
@@ -9366,13 +10238,13 @@ function parseStraceTrace(raw, options) {
9366
10238
  if (p) write(resolve3(dir, p));
9367
10239
  }
9368
10240
  }
9369
- const keep = (path3) => {
9370
- if (traceFile && path3 === traceFile) return false;
9371
- if (traceDir && (path3 === traceDir || path3.startsWith(`${traceDir}/`))) {
10241
+ const keep = (path6) => {
10242
+ if (traceFile && path6 === traceFile) return false;
10243
+ if (traceDir && (path6 === traceDir || path6.startsWith(`${traceDir}/`))) {
9372
10244
  return false;
9373
10245
  }
9374
- if (/^\/(proc|sys|dev)(\/|$)/.test(path3)) return false;
9375
- return matchesGlobs(path3, include, exclude);
10246
+ if (/^\/(proc|sys|dev)(\/|$)/.test(path6)) return false;
10247
+ return matchesGlobs(path6, include, exclude);
9376
10248
  };
9377
10249
  const changes = [];
9378
10250
  const emittedRenameTargets = /* @__PURE__ */ new Set();
@@ -9383,9 +10255,9 @@ function parseStraceTrace(raw, options) {
9383
10255
  emittedRenameTargets.add(to);
9384
10256
  }
9385
10257
  const tail = [];
9386
- for (const [path3, op] of state) {
9387
- if (emittedRenameTargets.has(path3)) continue;
9388
- if (keep(path3)) tail.push({ op, path: path3, timestamp: now });
10258
+ for (const [path6, op] of state) {
10259
+ if (emittedRenameTargets.has(path6)) continue;
10260
+ if (keep(path6)) tail.push({ op, path: path6, timestamp: now });
9389
10261
  }
9390
10262
  tail.sort((a, b) => {
9391
10263
  if (a.path < b.path) return -1;
@@ -9413,13 +10285,12 @@ async function withStraceFileChanges(sandbox, options) {
9413
10285
  const onError = options.onError ?? warnOnFileChangesError;
9414
10286
  const traceDir = options.traceDir ?? DEFAULT_TRACE_DIR;
9415
10287
  const innerExecute = sandbox.executeCommand.bind(sandbox);
9416
- const innerReadFile = sandbox.readFile.bind(sandbox);
9417
10288
  const innerWriteFiles = sandbox.writeFiles.bind(sandbox);
9418
10289
  const removeTrace = (traceFile) => void innerExecute(`rm -f ${shellQuote(traceFile)}`).catch(() => {
9419
10290
  });
9420
10291
  const readChanges = async (traceFile) => {
9421
10292
  try {
9422
- return parseStraceTrace(await innerReadFile(traceFile), {
10293
+ return parseStraceTrace(await sandbox.readFile(traceFile), {
9423
10294
  include,
9424
10295
  exclude,
9425
10296
  traceFile,
@@ -9442,8 +10313,8 @@ async function withStraceFileChanges(sandbox, options) {
9442
10313
  };
9443
10314
  const decorated = {
9444
10315
  async executeCommand(command, execOptions) {
9445
- const traceFile = `${traceDir}/${randomUUID2()}.strace`;
9446
- const sentinel = `__dat_trace_${randomUUID2()}__`;
10316
+ const traceFile = `${traceDir}/${randomUUID3()}.strace`;
10317
+ const sentinel = `__dat_trace_${randomUUID3()}__`;
9447
10318
  const wrapped = buildTracedCommand(
9448
10319
  command,
9449
10320
  traceFile,
@@ -9483,7 +10354,8 @@ async function withStraceFileChanges(sandbox, options) {
9483
10354
  }
9484
10355
  return result;
9485
10356
  },
9486
- readFile: innerReadFile,
10357
+ readFile: (path6, options2) => sandbox.readFile(path6, options2),
10358
+ exists: (path6) => sandbox.exists(path6),
9487
10359
  // The writeFile tool mutates the filesystem outside strace's view, so
9488
10360
  // observe it directly: synthesize a `write` change per file (under the
9489
10361
  // observation root) and run it through onFileChanges. A throw propagates to
@@ -9493,7 +10365,7 @@ async function withStraceFileChanges(sandbox, options) {
9493
10365
  writeFiles: async (files) => {
9494
10366
  await innerWriteFiles(files);
9495
10367
  const now = Date.now();
9496
- const changes = files.map((f) => posix3.normalize(f.path)).filter((path3) => matchesGlobs(path3, include, exclude)).map((path3) => ({ op: "write", path: path3, timestamp: now }));
10368
+ const changes = files.map((f) => posix3.normalize(f.path)).filter((path6) => matchesGlobs(path6, include, exclude)).map((path6) => ({ op: "write", path: path6, timestamp: now }));
9497
10369
  if (changes.length) await onFileChanges?.(changes);
9498
10370
  },
9499
10371
  dispose: async () => {
@@ -9508,7 +10380,7 @@ async function withStraceFileChanges(sandbox, options) {
9508
10380
  if (sandbox.spawn) {
9509
10381
  const innerSpawn = sandbox.spawn.bind(sandbox);
9510
10382
  decorated.spawn = (command, spawnOptions) => {
9511
- const traceFile = `${traceDir}/${randomUUID2()}.strace`;
10383
+ const traceFile = `${traceDir}/${randomUUID3()}.strace`;
9512
10384
  const child = innerSpawn(
9513
10385
  buildStraceCommand(command, traceFile, traceDir),
9514
10386
  spawnOptions
@@ -9815,9 +10687,9 @@ function formatVersion(version) {
9815
10687
  }
9816
10688
 
9817
10689
  // packages/context/src/lib/sandbox/microsandbox-sandbox.ts
9818
- import { randomUUID as randomUUID3 } from "node:crypto";
10690
+ import { randomUUID as randomUUID4 } from "node:crypto";
9819
10691
  var MICROSANDBOX_DEFAULT_DESTINATION = "/workspace";
9820
- var MICROSANDBOX_DEFAULT_IMAGE = "alpine";
10692
+ var MICROSANDBOX_DEFAULT_IMAGE = "bash:5.3-alpine3.24";
9821
10693
  var MICROSANDBOX_MAX_NAME_BYTES = 128;
9822
10694
  var COMMAND_TIMEOUT_EXIT_CODE = 124;
9823
10695
  var MicrosandboxSandboxError = class extends Error {
@@ -9852,13 +10724,29 @@ async function createMicrosandboxSandbox(options = {}) {
9852
10724
  validateMicrosandboxOptions(options);
9853
10725
  const sdk = await importMicrosandbox();
9854
10726
  const ephemeral = options.name === void 0;
9855
- const name = options.name ?? `deepagents-msb-${randomUUID3()}`;
10727
+ const name = options.name ?? `deepagents-msb-${randomUUID4()}`;
9856
10728
  const workdir = options.workdir ?? MICROSANDBOX_DEFAULT_DESTINATION;
10729
+ const sandboxBuilder = (replace) => {
10730
+ let builder = sdk.Sandbox.builder(name).image(MICROSANDBOX_DEFAULT_IMAGE).patch((patch) => patch.mkdir(workdir));
10731
+ if (options.configure) builder = options.configure(builder);
10732
+ builder = builder.workdir(workdir).ephemeral(ephemeral).shell("bash");
10733
+ return replace ? builder.replace() : builder;
10734
+ };
9857
10735
  let vm;
9858
10736
  try {
9859
- vm = await acquireSandbox(sdk, { ...options, name, ephemeral, workdir });
10737
+ const builder = sandboxBuilder(options.replace === true);
10738
+ vm = await (ephemeral || options.replace ? builder.create() : builder.connectOrCreate());
10739
+ } catch (error) {
10740
+ throw normalizeMicrosandboxError(error, sdk);
10741
+ }
10742
+ try {
9860
10743
  await vm.fs().mkdir(workdir);
10744
+ await assertMicrosandboxBash(vm);
9861
10745
  } catch (error) {
10746
+ await vm[Symbol.asyncDispose]().catch(() => {
10747
+ });
10748
+ if (ephemeral) await sdk.Sandbox.remove(name).catch(() => {
10749
+ });
9862
10750
  throw normalizeMicrosandboxError(error, sdk);
9863
10751
  }
9864
10752
  const backend = createMicrosandboxMethods({
@@ -9866,7 +10754,8 @@ async function createMicrosandboxSandbox(options = {}) {
9866
10754
  vm,
9867
10755
  name,
9868
10756
  ephemeral,
9869
- commandTimeout: options.commandTimeout
10757
+ commandTimeout: options.commandTimeout,
10758
+ reconnect: ephemeral ? void 0 : () => sandboxBuilder(false).connectOrCreate()
9870
10759
  });
9871
10760
  if (options.readiness) {
9872
10761
  try {
@@ -9886,56 +10775,22 @@ async function importMicrosandbox() {
9886
10775
  throw new MicrosandboxNotAvailableError(toError2(error));
9887
10776
  }
9888
10777
  }
9889
- async function acquireSandbox(sdk, options) {
9890
- if (options.ephemeral || options.replace) {
9891
- return buildSandbox(sdk, options);
9892
- }
9893
- let handle;
9894
- try {
9895
- handle = await sdk.Sandbox.get(options.name);
9896
- } catch (error) {
9897
- if (error instanceof sdk.SandboxNotFoundError) {
9898
- return createFreshSandbox(sdk, options);
9899
- }
9900
- throw error;
9901
- }
9902
- if (handle.status === "running") {
9903
- return handle.connect();
9904
- }
9905
- if (handle.status === "draining") {
9906
- await handle.waitUntilStopped();
9907
- }
9908
- try {
9909
- return await handle.start();
9910
- } catch {
9911
- await sdk.Sandbox.remove(options.name).catch(() => {
9912
- });
9913
- return buildSandbox(sdk, options);
9914
- }
9915
- }
9916
- async function createFreshSandbox(sdk, options) {
10778
+ async function assertMicrosandboxBash(vm) {
9917
10779
  try {
9918
- return await buildSandbox(sdk, options);
10780
+ const result = await vm.exec("bash", ["-lc", ":"]);
10781
+ if (result.code === 0) return;
10782
+ throw new Error(result.stderr() || `exit code ${result.code}`);
9919
10783
  } catch (error) {
9920
- if (error instanceof sdk.SandboxAlreadyExistsError) {
9921
- const handle = await sdk.Sandbox.get(options.name);
9922
- return handle.status === "running" ? handle.connect() : handle.start();
9923
- }
9924
- throw error;
10784
+ const err = toError2(error);
10785
+ throw new MicrosandboxCreationError(
10786
+ `Bash is required to execute sandbox commands but could not be started: ${err.message}`,
10787
+ err
10788
+ );
9925
10789
  }
9926
10790
  }
9927
- function buildSandbox(sdk, options) {
9928
- let builder = sdk.Sandbox.builder(options.name).image(options.image ?? MICROSANDBOX_DEFAULT_IMAGE).patch((patch) => patch.mkdir(options.workdir)).workdir(options.workdir);
9929
- if (options.ephemeral) builder = builder.ephemeral(true);
9930
- if (options.replace) builder = builder.replace();
9931
- if (options.cpus !== void 0) builder = builder.cpus(options.cpus);
9932
- if (options.memory !== void 0) builder = builder.memory(options.memory);
9933
- if (options.env) builder = builder.envs(options.env);
9934
- if (options.configure) builder = options.configure(builder);
9935
- return builder.create();
9936
- }
9937
10791
  function normalizeMicrosandboxError(error, sdk) {
9938
10792
  const err = toError2(error);
10793
+ if (err instanceof MicrosandboxSandboxError) return err;
9939
10794
  if (err instanceof sdk.LibkrunfwNotFoundError) {
9940
10795
  return new MicrosandboxNotAvailableError(err);
9941
10796
  }
@@ -9957,9 +10812,25 @@ function validateMicrosandboxOptions(options) {
9957
10812
  }
9958
10813
  }
9959
10814
  function createMicrosandboxMethods(args) {
9960
- const { sdk, vm, name, ephemeral, commandTimeout } = args;
10815
+ const { sdk, name, ephemeral, commandTimeout, reconnect } = args;
10816
+ let vm = args.vm;
10817
+ let reconnecting;
10818
+ const run = async (operation) => {
10819
+ try {
10820
+ return await operation(vm);
10821
+ } catch (error) {
10822
+ if (!reconnect || !(error instanceof sdk.SandboxNotRunningError)) {
10823
+ throw error;
10824
+ }
10825
+ reconnecting ??= reconnect().finally(() => {
10826
+ reconnecting = void 0;
10827
+ });
10828
+ vm = await reconnecting;
10829
+ return operation(vm);
10830
+ }
10831
+ };
9961
10832
  const spawn7 = (command, options = {}) => {
9962
- return spawnMicrosandboxProcess(sdk, vm, command, {
10833
+ return spawnMicrosandboxProcess(sdk, run, command, {
9963
10834
  ...options,
9964
10835
  commandTimeout
9965
10836
  });
@@ -9983,25 +10854,38 @@ function createMicrosandboxMethods(args) {
9983
10854
  return { stdout, stderr, exitCode: info.code ?? 1 };
9984
10855
  },
9985
10856
  spawn: spawn7,
9986
- async readFile(path3) {
10857
+ async readFile(path6, options) {
9987
10858
  try {
9988
- return await vm.fs().readToString(path3);
10859
+ const bytes = await run((vm2) => vm2.fs().read(path6));
10860
+ return readFileContent(bytes, options);
9989
10861
  } catch (error) {
9990
10862
  throw new MicrosandboxCommandError(
9991
- `Failed to read file "${path3}": ${toError2(error).message}`,
10863
+ `Failed to read file "${path6}": ${toError2(error).message}`,
10864
+ toError2(error)
10865
+ );
10866
+ }
10867
+ },
10868
+ async exists(path6) {
10869
+ try {
10870
+ return await run((vm2) => vm2.fs().exists(path6));
10871
+ } catch (error) {
10872
+ throw new MicrosandboxCommandError(
10873
+ `Failed to check "${path6}": ${toError2(error).message}`,
9992
10874
  toError2(error)
9993
10875
  );
9994
10876
  }
9995
10877
  },
9996
10878
  async writeFiles(files) {
9997
10879
  try {
9998
- const fs = vm.fs();
9999
- for (const dir of uniqueParentDirectories2(files.map((f) => f.path))) {
10000
- await fs.mkdir(dir);
10001
- }
10002
- for (const file of files) {
10003
- await fs.write(file.path, file.content);
10004
- }
10880
+ await run(async (vm2) => {
10881
+ const fs = vm2.fs();
10882
+ for (const dir of uniqueParentDirectories2(files.map((f) => f.path))) {
10883
+ await fs.mkdir(dir);
10884
+ }
10885
+ for (const file of files) {
10886
+ await fs.write(file.path, file.content);
10887
+ }
10888
+ });
10005
10889
  } catch (error) {
10006
10890
  const err = toError2(error);
10007
10891
  throw new MicrosandboxCommandError(
@@ -10011,10 +10895,7 @@ function createMicrosandboxMethods(args) {
10011
10895
  }
10012
10896
  },
10013
10897
  async dispose() {
10014
- try {
10015
- await vm.stop();
10016
- } catch {
10017
- }
10898
+ await vm[Symbol.asyncDispose]();
10018
10899
  if (ephemeral) {
10019
10900
  await sdk.Sandbox.remove(name).catch(() => {
10020
10901
  });
@@ -10025,14 +10906,14 @@ function createMicrosandboxMethods(args) {
10025
10906
  }
10026
10907
  };
10027
10908
  }
10028
- function spawnMicrosandboxProcess(sdk, vm, command, options) {
10909
+ function spawnMicrosandboxProcess(sdk, run, command, options) {
10029
10910
  const stdout = createByteReadable();
10030
10911
  const stderr = createByteReadable();
10031
- const exit = pumpExecStream({ sdk, vm, command, options, stdout, stderr });
10912
+ const exit = pumpExecStream({ sdk, run, command, options, stdout, stderr });
10032
10913
  return { stdout: stdout.stream, stderr: stderr.stream, exit };
10033
10914
  }
10034
10915
  async function pumpExecStream(args) {
10035
- const { sdk, vm, command, options, stdout, stderr } = args;
10916
+ const { sdk, run, command, options, stdout, stderr } = args;
10036
10917
  const { signal } = options;
10037
10918
  let handle;
10038
10919
  let aborted = signal?.aborted ?? false;
@@ -10050,12 +10931,14 @@ async function pumpExecStream(args) {
10050
10931
  }
10051
10932
  signal?.addEventListener("abort", abort, { once: true });
10052
10933
  try {
10053
- handle = await vm.execStreamWith("sh", (builder) => {
10054
- builder.args(["-lc", command]).stdinNull();
10055
- if (options.cwd) builder.cwd(options.cwd);
10056
- if (options.env) builder.envs(options.env);
10057
- return builder;
10058
- });
10934
+ handle = await run(
10935
+ (vm) => vm.execStreamWith("bash", (builder) => {
10936
+ builder.args(["-lc", command]).stdinNull();
10937
+ if (options.cwd) builder.cwd(options.cwd);
10938
+ if (options.env) builder.envs(options.env);
10939
+ return builder;
10940
+ })
10941
+ );
10059
10942
  if (aborted) {
10060
10943
  return abortedExitInfo2();
10061
10944
  }
@@ -10154,10 +11037,10 @@ function abortedExitInfo2() {
10154
11037
  }
10155
11038
  function uniqueParentDirectories2(paths) {
10156
11039
  const dirs = /* @__PURE__ */ new Set();
10157
- for (const path3 of paths) {
10158
- const index = path3.lastIndexOf("/");
11040
+ for (const path6 of paths) {
11041
+ const index = path6.lastIndexOf("/");
10159
11042
  if (index > 0) {
10160
- dirs.add(path3.slice(0, index));
11043
+ dirs.add(path6.slice(0, index));
10161
11044
  }
10162
11045
  }
10163
11046
  return [...dirs];
@@ -10243,13 +11126,15 @@ import {
10243
11126
  Bash
10244
11127
  } from "just-bash";
10245
11128
  async function createVirtualSandbox(options) {
11129
+ const { fs } = options;
10246
11130
  const bash = new Bash({
10247
- fs: options.fs,
11131
+ fs,
10248
11132
  cwd: options.cwd,
10249
11133
  env: options.env,
10250
11134
  javascript: options.javascript,
10251
11135
  customCommands: options.customCommands
10252
11136
  });
11137
+ const resolve3 = (path6) => fs.resolvePath(bash.getCwd(), path6);
10253
11138
  const sandbox = {
10254
11139
  async executeCommand(command, options2) {
10255
11140
  const result = await bash.exec(
@@ -10262,17 +11147,20 @@ async function createVirtualSandbox(options) {
10262
11147
  exitCode: result.exitCode
10263
11148
  };
10264
11149
  },
10265
- async readFile(path3) {
10266
- return bash.readFile(path3);
11150
+ async readFile(path6, readOptions) {
11151
+ return readFileContent(
11152
+ await fs.readFileBuffer(resolve3(path6)),
11153
+ readOptions
11154
+ );
10267
11155
  },
10268
11156
  async writeFiles(files) {
10269
11157
  for (const file of files) {
10270
- await bash.writeFile(
10271
- file.path,
10272
- typeof file.content === "string" ? file.content : Buffer.from(file.content).toString("utf-8")
10273
- );
11158
+ await fs.writeFile(resolve3(file.path), file.content);
10274
11159
  }
10275
11160
  },
11161
+ async exists(path6) {
11162
+ return fs.exists(resolve3(path6));
11163
+ },
10276
11164
  async dispose() {
10277
11165
  },
10278
11166
  [Symbol.asyncDispose]() {
@@ -10292,12 +11180,12 @@ async function createVirtualSandbox(options) {
10292
11180
  }
10293
11181
 
10294
11182
  // packages/context/src/lib/skills/fragments.ts
10295
- import dedent2 from "dedent";
11183
+ import dedent4 from "dedent";
10296
11184
  function skills(availableSkills) {
10297
- const skillSnapshot = availableSkills.map(({ name, description, path: path3 }) => ({
11185
+ const skillSnapshot = availableSkills.map(({ name, description, path: path6 }) => ({
10298
11186
  name,
10299
11187
  description,
10300
- path: path3
11188
+ path: path6
10301
11189
  }));
10302
11190
  if (skillSnapshot.length === 0) {
10303
11191
  return {
@@ -10323,7 +11211,7 @@ function skills(availableSkills) {
10323
11211
  metadata: { skills: skillSnapshot }
10324
11212
  };
10325
11213
  }
10326
- var SKILLS_INSTRUCTIONS = dedent2`A skill is a set of local instructions to follow that is stored in a \`SKILL.md\` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.
11214
+ var SKILLS_INSTRUCTIONS = dedent4`A skill is a set of local instructions to follow that is stored in a \`SKILL.md\` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.
10327
11215
 
10328
11216
  ### How to use skills
10329
11217
  - Discovery: The list below shows the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.
@@ -12045,6 +12933,7 @@ async function persistedWriter(options) {
12045
12933
  let seq = 0;
12046
12934
  let buffer = [];
12047
12935
  let failedByErrorChunk = false;
12936
+ let abortedByChunk = false;
12048
12937
  async function flush() {
12049
12938
  if (buffer.length === 0) return;
12050
12939
  const batch = buffer;
@@ -12070,11 +12959,14 @@ async function persistedWriter(options) {
12070
12959
  }
12071
12960
  }
12072
12961
  async function persistChunk(chunk) {
12962
+ if (chunk.data.type === "abort") {
12963
+ abortedByChunk = true;
12964
+ }
12073
12965
  if (strategy === "immediate") {
12074
12966
  await appendBatch([chunk]);
12075
12967
  } else {
12076
12968
  buffer.push(chunk);
12077
- if (buffer.length >= flushSize) {
12969
+ if (buffer.length >= flushSize || chunk.data.type.startsWith("data-")) {
12078
12970
  await flush();
12079
12971
  }
12080
12972
  }
@@ -12102,7 +12994,10 @@ async function persistedWriter(options) {
12102
12994
  async complete() {
12103
12995
  await flush();
12104
12996
  if (failedByErrorChunk) return;
12105
- await store.updateStreamStatus(streamId, "completed");
12997
+ await store.updateStreamStatus(
12998
+ streamId,
12999
+ abortedByChunk ? "cancelled" : "completed"
13000
+ );
12106
13001
  },
12107
13002
  async fail(error) {
12108
13003
  await flush();
@@ -12116,7 +13011,7 @@ async function persistedWriter(options) {
12116
13011
  }
12117
13012
 
12118
13013
  // packages/context/src/lib/stream/polling-change-source.ts
12119
- import { setTimeout as delay2 } from "node:timers/promises";
13014
+ import { setTimeout as delay3 } from "node:timers/promises";
12120
13015
 
12121
13016
  // packages/context/src/lib/stream/polling-policy.ts
12122
13017
  var DEFAULT_WATCH_POLLING = {
@@ -12161,7 +13056,7 @@ function nextAdaptivePollingDelay(state) {
12161
13056
  state.config.minMs,
12162
13057
  state.config.maxMs
12163
13058
  );
12164
- const delay3 = applyJitter(
13059
+ const delay4 = applyJitter(
12165
13060
  current,
12166
13061
  state.config.jitterRatio,
12167
13062
  state.config.minMs,
@@ -12172,7 +13067,7 @@ function nextAdaptivePollingDelay(state) {
12172
13067
  state.config.minMs,
12173
13068
  state.config.maxMs
12174
13069
  );
12175
- return delay3;
13070
+ return delay4;
12176
13071
  }
12177
13072
  function normalizeAdaptivePolling(polling, fallback) {
12178
13073
  const merged = {
@@ -12272,7 +13167,7 @@ function isTerminal(status) {
12272
13167
  return status !== "queued" && status !== "running";
12273
13168
  }
12274
13169
  function waitForDelay(ms, signal) {
12275
- return delay2(ms, true, { signal }).catch((err) => {
13170
+ return delay3(ms, true, { signal }).catch((err) => {
12276
13171
  if (signal.aborted) return false;
12277
13172
  throw err;
12278
13173
  });
@@ -13975,6 +14870,7 @@ export {
13975
14870
  preference,
13976
14871
  principle,
13977
14872
  quirk,
14873
+ readFileContent,
13978
14874
  reasoningFramework,
13979
14875
  reminder,
13980
14876
  render,
@@ -13991,6 +14887,7 @@ export {
13991
14887
  shellQuote,
13992
14888
  skills,
13993
14889
  skillsReminder,
14890
+ socraticPlan,
13994
14891
  socraticPrompting,
13995
14892
  soul,
13996
14893
  stop,