@foldrun/core 2.4.0 → 2.6.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 (149) hide show
  1. package/dist/src/agent-activity.d.ts +5 -0
  2. package/dist/src/agent-activity.js +31 -0
  3. package/dist/src/agent-import.js +2 -2
  4. package/dist/src/agent-tools.js +2 -2
  5. package/dist/src/agents-md.d.ts +5 -1
  6. package/dist/src/agents-md.js +42 -18
  7. package/dist/src/alerts.d.ts +60 -0
  8. package/dist/src/alerts.js +161 -0
  9. package/dist/src/browser-pod.js +4 -0
  10. package/dist/src/capture.d.ts +23 -0
  11. package/dist/src/capture.js +80 -0
  12. package/dist/src/compare.d.ts +57 -0
  13. package/dist/src/compare.js +116 -0
  14. package/dist/src/completions.js +26 -3
  15. package/dist/src/deploy.d.ts +36 -1
  16. package/dist/src/deploy.js +237 -13
  17. package/dist/src/egress.d.ts +16 -1
  18. package/dist/src/evals.d.ts +27 -2
  19. package/dist/src/evals.js +62 -19
  20. package/dist/src/flow-lint.d.ts +14 -0
  21. package/dist/src/flow-lint.js +35 -0
  22. package/dist/src/flow-patterns.js +29 -14
  23. package/dist/src/frontmatter-fields.d.ts +27 -0
  24. package/dist/src/frontmatter-fields.js +60 -3
  25. package/dist/src/frontmatter-guard.d.ts +19 -0
  26. package/dist/src/frontmatter-guard.js +67 -0
  27. package/dist/src/frontmatter-schema.d.ts +2 -2
  28. package/dist/src/frontmatter-schema.js +33 -2
  29. package/dist/src/frontmatter-text.d.ts +31 -0
  30. package/dist/src/frontmatter-text.js +102 -0
  31. package/dist/src/history.d.ts +4 -0
  32. package/dist/src/history.js +15 -0
  33. package/dist/src/hooks.js +1 -1
  34. package/dist/src/insights.d.ts +30 -0
  35. package/dist/src/insights.js +152 -0
  36. package/dist/src/issues.d.ts +140 -0
  37. package/dist/src/issues.js +302 -0
  38. package/dist/src/judge-verdict.d.ts +19 -0
  39. package/dist/src/judge-verdict.js +54 -0
  40. package/dist/src/library.js +1 -1
  41. package/dist/src/limits.d.ts +8 -0
  42. package/dist/src/limits.js +36 -5
  43. package/dist/src/linkable.js +1 -1
  44. package/dist/src/matter.d.ts +42 -0
  45. package/dist/src/matter.js +132 -0
  46. package/dist/src/model-credential.d.ts +14 -0
  47. package/dist/src/model-credential.js +40 -0
  48. package/dist/src/network.d.ts +12 -1
  49. package/dist/src/network.js +35 -8
  50. package/dist/src/notify.d.ts +16 -0
  51. package/dist/src/notify.js +17 -7
  52. package/dist/src/okf.js +1 -1
  53. package/dist/src/online-checks.d.ts +76 -0
  54. package/dist/src/online-checks.js +432 -0
  55. package/dist/src/operator.d.ts +277 -0
  56. package/dist/src/operator.js +176 -2
  57. package/dist/src/otel-config.d.ts +18 -0
  58. package/dist/src/otel-config.js +69 -0
  59. package/dist/src/otel.d.ts +38 -0
  60. package/dist/src/otel.js +85 -0
  61. package/dist/src/outcome.d.ts +40 -0
  62. package/dist/src/outcome.js +77 -0
  63. package/dist/src/package.js +9 -2
  64. package/dist/src/platform.d.ts +1 -1
  65. package/dist/src/prompt-layers.d.ts +30 -0
  66. package/dist/src/prompt-layers.js +77 -0
  67. package/dist/src/provider-health.js +2 -2
  68. package/dist/src/providers.d.ts +5 -0
  69. package/dist/src/providers.js +2 -2
  70. package/dist/src/run-container.d.ts +25 -2
  71. package/dist/src/run-container.js +61 -6
  72. package/dist/src/runner.d.ts +53 -2
  73. package/dist/src/runner.js +471 -102
  74. package/dist/src/runs-filter.d.ts +48 -0
  75. package/dist/src/runs-filter.js +73 -0
  76. package/dist/src/scan.d.ts +30 -1
  77. package/dist/src/scan.js +145 -14
  78. package/dist/src/starter.js +27 -2
  79. package/dist/src/step-exec.d.ts +39 -3
  80. package/dist/src/step-exec.js +118 -15
  81. package/dist/src/store.d.ts +144 -3
  82. package/dist/src/store.js +150 -7
  83. package/dist/src/tokens.d.ts +38 -0
  84. package/dist/src/tokens.js +57 -0
  85. package/dist/src/tool-names.js +1 -1
  86. package/dist/src/tool-programs.js +1 -1
  87. package/dist/src/trace-tree.d.ts +40 -0
  88. package/dist/src/trace-tree.js +156 -0
  89. package/dist/src/translator.d.ts +8 -0
  90. package/dist/src/translator.js +7 -2
  91. package/dist/src/trigger-log.d.ts +1 -1
  92. package/dist/src/workspace-check.d.ts +18 -0
  93. package/dist/src/workspace-check.js +34 -0
  94. package/package.json +2 -2
  95. package/src/agent-activity.ts +32 -0
  96. package/src/agent-import.ts +2 -2
  97. package/src/agent-tools.ts +2 -2
  98. package/src/agents-md.ts +39 -14
  99. package/src/alerts.ts +190 -0
  100. package/src/browser-pod.ts +3 -0
  101. package/src/capture.ts +88 -0
  102. package/src/compare.ts +146 -0
  103. package/src/completions.ts +27 -3
  104. package/src/deploy.ts +238 -12
  105. package/src/egress.ts +12 -2
  106. package/src/evals.ts +64 -19
  107. package/src/flow-lint.ts +36 -0
  108. package/src/flow-patterns.ts +26 -13
  109. package/src/frontmatter-fields.ts +83 -3
  110. package/src/frontmatter-guard.ts +70 -0
  111. package/src/frontmatter-schema.ts +33 -4
  112. package/src/frontmatter-text.ts +109 -0
  113. package/src/history.ts +13 -0
  114. package/src/hooks.ts +1 -1
  115. package/src/insights.ts +174 -0
  116. package/src/issues.ts +396 -0
  117. package/src/judge-verdict.ts +52 -0
  118. package/src/library.ts +1 -1
  119. package/src/limits.ts +38 -5
  120. package/src/linkable.ts +1 -1
  121. package/src/matter.ts +145 -0
  122. package/src/model-credential.ts +45 -0
  123. package/src/network.ts +35 -7
  124. package/src/notify.ts +22 -7
  125. package/src/okf.ts +1 -1
  126. package/src/online-checks.ts +450 -0
  127. package/src/operator.ts +213 -3
  128. package/src/otel-config.ts +67 -0
  129. package/src/otel.ts +100 -0
  130. package/src/outcome.ts +90 -0
  131. package/src/package.ts +8 -2
  132. package/src/platform.ts +1 -1
  133. package/src/prompt-layers.ts +90 -0
  134. package/src/provider-health.ts +2 -2
  135. package/src/providers.ts +7 -2
  136. package/src/run-container.ts +77 -8
  137. package/src/runner.ts +477 -79
  138. package/src/runs-filter.ts +88 -0
  139. package/src/scan.ts +153 -14
  140. package/src/starter.ts +27 -2
  141. package/src/step-exec.ts +168 -17
  142. package/src/store.ts +268 -9
  143. package/src/tokens.ts +68 -0
  144. package/src/tool-names.ts +1 -1
  145. package/src/tool-programs.ts +1 -1
  146. package/src/trace-tree.ts +187 -0
  147. package/src/translator.ts +13 -2
  148. package/src/trigger-log.ts +1 -0
  149. package/src/workspace-check.ts +41 -0
@@ -21,7 +21,7 @@
21
21
  import { accountModelBlock } from "./model-credential.ts";
22
22
  import fs from "node:fs";
23
23
  import path from "node:path";
24
- import matter from "gray-matter";
24
+ import { configMatter } from "./matter.ts";
25
25
  import { getSecret } from "./secrets.ts";
26
26
  import { noteSecretUse, healthKey } from "./secret-health.ts";
27
27
  import { accountDir, workspaceDir, listWorkspaces, listAgents, parseProvider, type ProviderSpec } from "./store.ts";
@@ -205,7 +205,7 @@ function cheapestModel(spec: ProviderSpec): string {
205
205
 
206
206
  function frontmatterOf(file: string): Record<string, unknown> {
207
207
  try {
208
- return (matter(fs.readFileSync(file, "utf8")).data ?? {}) as Record<string, unknown>;
208
+ return (configMatter(fs.readFileSync(file, "utf8")).data ?? {}) as Record<string, unknown>;
209
209
  } catch {
210
210
  return {};
211
211
  }
package/src/providers.ts CHANGED
@@ -54,6 +54,11 @@ export interface ProviderPreset {
54
54
  * Off means the translator drops thinking rather than send a parameter
55
55
  * the endpoint would reject. */
56
56
  reasoningEffort?: boolean;
57
+ /** Whether the endpoint takes `prompt_cache_key` — a hint that requests
58
+ * with the same key share a prompt prefix, so its cache can be routed
59
+ * to. Only set where the parameter is documented; an endpoint that does
60
+ * not know it may refuse the request. */
61
+ promptCacheKey?: boolean;
57
62
  /** One line a person should read before relying on it. */
58
63
  note?: string;
59
64
  /** A tool loop was driven through this endpoint from this runtime. */
@@ -427,7 +432,7 @@ export const PROVIDERS: readonly ProviderPreset[] = [
427
432
 
428
433
  // ------------------------------------------- Chat-Completions, translated
429
434
  { name: "openai", title: "OpenAI", format: "openai", baseUrl: "https://api.openai.com/v1", auth: "bearer", verified: true,
430
- maxTokensParam: "max_completion_tokens", reasoningEffort: true,
435
+ maxTokensParam: "max_completion_tokens", reasoningEffort: true, promptCacheKey: true,
431
436
  note: "Reached over Chat Completions, which OpenAI keeps supporting; the Responses API is not spoken here, so a reasoning model's reasoning does not carry across tool calls." , search: "openai" },
432
437
  { name: "gemini", title: "Google Gemini", format: "openai", baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai", auth: "bearer",
433
438
  note: "Google's OpenAI-compatible route. Unknown parameters are ignored silently; reasoning cannot be switched off on the newest models." },
@@ -435,7 +440,7 @@ export const PROVIDERS: readonly ProviderPreset[] = [
435
440
  maxTokensParam: "max_completion_tokens", reasoningEffort: true },
436
441
  { name: "groq", title: "Groq", format: "openai", baseUrl: "https://api.groq.com/openai/v1", auth: "bearer",
437
442
  maxTokensParam: "max_completion_tokens", reasoningEffort: true },
438
- { name: "mistral", title: "Mistral", format: "openai", baseUrl: "https://api.mistral.ai/v1", auth: "bearer" },
443
+ { name: "mistral", title: "Mistral", format: "openai", baseUrl: "https://api.mistral.ai/v1", auth: "bearer", promptCacheKey: true },
439
444
  { name: "together", title: "Together AI", format: "openai", baseUrl: "https://api.together.ai/v1", auth: "bearer" },
440
445
  { name: "cerebras", title: "Cerebras", format: "openai", baseUrl: "https://api.cerebras.ai/v1", auth: "bearer",
441
446
  maxTokensParam: "max_completion_tokens", reasoningEffort: true },
@@ -18,6 +18,7 @@
18
18
  // The runner image bundles @foldrun/core itself, so the driver runs the same
19
19
  // executeStep the server would — one implementation, two homes.
20
20
 
21
+ import type { PeerInfo } from "./operator.ts";
21
22
  import { isHookRecord, type HookRecord } from "./hooks.ts";
22
23
  import fs from "node:fs";
23
24
  import os from "node:os";
@@ -25,9 +26,9 @@ import path from "node:path";
25
26
  import crypto from "node:crypto";
26
27
  import { spawn, spawnSync } from "node:child_process";
27
28
  import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
28
- import { isPlatformPath, type ApiSpec, type Effort } from "./store.ts";
29
+ import { CAPTURE_CAP, isPlatformPath, type ApiSpec, type Effort, type ModelCall } from "./store.ts";
29
30
  import { divertedSummary, isDivertedPath, overlayTestWrites } from "./test-mode.ts";
30
- import type { EventExtra, ExecUsage } from "./step-exec.ts";
31
+ import type { CallIo, EventExtra, ExecUsage } from "./step-exec.ts";
31
32
  import type { TokenPrice } from "./catalog.ts";
32
33
  import { isFileValue, fileContent } from "./secrets.ts";
33
34
  import { safeTenantSegment, systemInstallSh, SYSTEM_FROM_INPUT_SH, INSTALL_SOCKET, type RuntimeSpec } from "./runtime.ts";
@@ -72,11 +73,16 @@ export interface ContainerStepInput {
72
73
  budgetNote?: string;
73
74
  price?: TokenPrice | null;
74
75
  verify?: string;
76
+ /** The model `verify: judge:` grades with (the workspace's `judge:`). */
77
+ judgeModel?: string;
78
+ judgeEffort?: string;
75
79
  /** `output: json` — checked inside, where the reply is. */
76
80
  output?: "json";
77
81
  /** `schema:` and `max_turns:`, resolved host-side — see ExecOptions. */
78
82
  schema?: Record<string, unknown> | boolean;
79
83
  maxTurns?: number;
84
+ /** `capture:` resolved host-side (FOL-54 step 4) — see ExecOptions. */
85
+ capture?: "metadata" | "full";
80
86
  /** `limits:` resolved host-side, and the SDK-name → foldrun-name map the
81
87
  * count reads (limits.ts) — enforced in the pod by the same hook. */
82
88
  limits?: Record<string, number>;
@@ -93,6 +99,10 @@ export interface ContainerStepInput {
93
99
  desks?: RunDigest[];
94
100
  /** tools: [ask] — ask_person, and how long it may wait for an answer. */
95
101
  ask?: { timeoutSec: number };
102
+ /** tools: [peers] — this step's agent and the other steps of its
103
+ * parallel group, gathered host-side at launch. The driver rebuilds the
104
+ * colleagues' tools in here over the lease's /peers door. */
105
+ peers?: { self: string; siblings: PeerInfo[] };
96
106
  /** A Chat-Completions provider: the driver starts the runtime's translator
97
107
  * on loopback and points the SDK at it. Null or absent: the env's
98
108
  * ANTHROPIC_BASE_URL is spoken to directly. */
@@ -549,7 +559,8 @@ export function runtimeCacheMount(
549
559
 
550
560
  // ---------- the runner image ----------
551
561
 
552
- const DRIVER = `// The runner container's entrypoint. Reads one step, executes it with the
562
+ /** The runner container's entrypoint, as JS text: written into the image as driver.mjs, so it must be plain JS — a TypeScript cast here broke every step on v2026.10.21.5 (tests/run-container.test.ts checks it). */
563
+ export const DRIVER = `// The runner container's entrypoint. Reads one step, executes it with the
553
564
  // same core the server uses, speaks JSONL on stdout, exits.
554
565
  import fs from "node:fs";
555
566
  const input = JSON.parse(fs.readFileSync("/opt/runner/job/input.json", "utf8"));
@@ -614,7 +625,7 @@ try {
614
625
  const { buildScriptTools } = await import("@foldrun/core/script-tools");
615
626
  const { buildConsultTools } = await import("@foldrun/core/agent-tools");
616
627
  const { buildSearchTools, buildHistoryTools, buildDeskTools } = await import("@foldrun/core/context-tools");
617
- const { httpChannel, buildAskTool, inboxReader } = await import("@foldrun/core/operator");
628
+ const { httpChannel, buildAskTool, buildPeerTools, inboxReader } = await import("@foldrun/core/operator");
618
629
  const { startTranslator } = await import("@foldrun/core/translator");
619
630
  const { prepareRuntime } = await import("@foldrun/core/runtime");
620
631
  const { materializeFileSecrets } = await import("@foldrun/core/secret-files");
@@ -678,11 +689,18 @@ try {
678
689
  // A long result is kept in a file only when the agent can read it back.
679
690
  // (tool-output.ts canReadBack, inlined: the driver imports core by subpath.)
680
691
  const keepIn = { dir: agentDir, readable: () => (input.allowed ?? []).some((t) => t === "Read" || t === "Grep") };
681
- const api = buildApiTools("", input.apis, undefined, { env, missing: [] }, keepIn);
692
+ // A declared secret named like a model variable crosses as
693
+ // FOLDRUN_DECLARED_<NAME> (runner.ts, FOL-19): the model keeps its own
694
+ // settings, and the agent's API and script tools and verify: get the value
695
+ // back under the name it declared. Built here, after the runtime and any
696
+ // mid-step install have added their paths to env.
697
+ const toolEnv = { ...env };
698
+ for (const [k, v] of Object.entries(env)) if (k.startsWith("FOLDRUN_DECLARED_")) toolEnv[k.slice("FOLDRUN_DECLARED_".length)] = v;
699
+ const api = buildApiTools("", input.apis, undefined, { env: toolEnv, missing: [] }, keepIn);
682
700
  const script = buildScriptTools(
683
701
  agentDir,
684
702
  input.scripts,
685
- { ...env, ...runtime.env },
703
+ toolEnv,
686
704
  "/library/scripts",
687
705
  runtime.interpreters,
688
706
  null,
@@ -705,6 +723,7 @@ try {
705
723
  // ask_person when tools: [ask], and the run's inbox after every tool call.
706
724
  const channel = httpChannel(env.FOLDRUN_EGRESS);
707
725
  const ask = input.ask ? buildAskTool({ channel, timeoutSec: input.ask.timeoutSec, emit }) : { server: null };
726
+ const peers = input.peers ? buildPeerTools({ channel, self: input.peers.self, siblings: input.peers.siblings, emit }) : { server: null };
708
727
  const inbox = channel ? inboxReader(channel, emit) : undefined;
709
728
 
710
729
  mark("tools");
@@ -726,6 +745,7 @@ try {
726
745
  ...(history.server ? { foldrun_history: history.server } : {}),
727
746
  ...(desks.server ? { foldrun_desks: desks.server } : {}),
728
747
  ...(ask.server ? { foldrun_ask: ask.server } : {}),
748
+ ...(peers.server ? { foldrun_peers: peers.server } : {}),
729
749
  ...(install.server ? { foldrun_install: install.server } : {}),
730
750
  ...input.mcpServers,
731
751
  },
@@ -737,10 +757,13 @@ try {
737
757
  budgetNote: input.budgetNote,
738
758
  price: input.price,
739
759
  verify: input.verify,
740
- verifyEnv: env,
760
+ verifyEnv: toolEnv,
761
+ ...(typeof input.judgeModel === "string" ? { judgeModel: input.judgeModel } : {}),
762
+ ...(typeof input.judgeEffort === "string" ? { judgeEffort: input.judgeEffort } : {}),
741
763
  output: input.output,
742
764
  schema: input.schema,
743
765
  maxTurns: input.maxTurns,
766
+ ...(input.capture === "metadata" || input.capture === "full" ? { capture: input.capture } : {}),
744
767
  ...(input.limits && Object.keys(input.limits).length ? { limits: input.limits, toolOwners: input.toolOwners ?? {} } : {}),
745
768
  ...(input.hookNames?.length ? { hookNames: input.hookNames, hookBase: input.hookBase ?? {} } : {}),
746
769
  // Slim browsing: the platform put this step on the slim image to browse
@@ -1190,9 +1213,47 @@ export function buildFailureSummary(log: string, opts: { tail?: number; maxLines
1190
1213
 
1191
1214
  // ---------- running one step ----------
1192
1215
 
1216
+ const num = (v: unknown) => (typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0);
1217
+
1218
+ /** A tool call's content from the sandbox, as far as it is well-formed and
1219
+ * within the cap; anything else is dropped (FOL-54 step 4). */
1220
+ function callIoOf(v: unknown): CallIo | null {
1221
+ if (!v || typeof v !== "object") return null;
1222
+ const o = v as Record<string, unknown>;
1223
+ // The cap plus the slack the runner masks inside before it cuts.
1224
+ const text = (x: unknown) => (typeof x === "string" ? x.slice(0, CAPTURE_CAP + 4096) : undefined);
1225
+ const io: CallIo = {
1226
+ ...(text(o.input) !== undefined ? { input: text(o.input) } : {}),
1227
+ ...(text(o.output) !== undefined ? { output: text(o.output) } : {}),
1228
+ ...(typeof o.inputChars === "number" ? { inputChars: num(o.inputChars) } : {}),
1229
+ ...(typeof o.outputChars === "number" ? { outputChars: num(o.outputChars) } : {}),
1230
+ ...(o.truncated === true ? { truncated: true } : {}),
1231
+ };
1232
+ return io.input !== undefined || io.output !== undefined ? io : null;
1233
+ }
1234
+
1235
+ /** One model call from the sandbox, well-formed or dropped. */
1236
+ function modelCallOf(v: unknown): ModelCall | null {
1237
+ if (!v || typeof v !== "object") return null;
1238
+ const o = v as Record<string, unknown>;
1239
+ const t = (o.tokens ?? {}) as Record<string, unknown>;
1240
+ if (typeof o.id !== "string" || !o.id || o.id.length > 128 || typeof o.at !== "string" || Number.isNaN(Date.parse(o.at))) return null;
1241
+ return {
1242
+ id: o.id,
1243
+ n: Math.max(1, Math.floor(num(o.n))),
1244
+ at: o.at,
1245
+ ms: num(o.ms),
1246
+ ...(typeof o.model === "string" ? { model: o.model.slice(0, 128) } : {}),
1247
+ ...(typeof o.stop === "string" ? { stop: o.stop.slice(0, 64) } : {}),
1248
+ tokens: { input: num(t.input), output: num(t.output), cacheRead: num(t.cacheRead), cacheWrite: num(t.cacheWrite) },
1249
+ tools: Math.floor(num(o.tools)),
1250
+ ...(typeof o.subagent === "string" && /^[a-z0-9-]{1,64}$/.test(o.subagent) ? { subagent: o.subagent } : {}),
1251
+ };
1252
+ }
1253
+
1193
1254
  export function parseDriverLine(
1194
1255
  line: string,
1195
- ): { e: "event"; type: "text" | "tool" | "info" | "error"; text: string; call?: string; ms?: number; err?: boolean; bytes?: number; operator?: OperatorEvent; subagent?: string; check?: boolean; hook?: HookRecord } | { e: "done" } & ContainerStepOutcome | null {
1256
+ ): { e: "event"; type: "text" | "tool" | "info" | "error"; text: string; call?: string; ms?: number; err?: boolean; bytes?: number; operator?: OperatorEvent; subagent?: string; check?: boolean; hook?: HookRecord; io?: CallIo; turn?: ModelCall; inCall?: string; responding?: true } | { e: "done" } & ContainerStepOutcome | null {
1196
1257
  const trimmed = line.trim();
1197
1258
  if (!trimmed.startsWith("{")) return null;
1198
1259
  try {
@@ -1210,6 +1271,10 @@ export function parseDriverLine(
1210
1271
  ...(typeof parsed.subagent === "string" && /^[a-z0-9-]{1,64}$/.test(parsed.subagent) ? { subagent: parsed.subagent } : {}),
1211
1272
  ...(parsed.check === true ? { check: true } : {}),
1212
1273
  ...(isHookRecord(parsed.hook) ? { hook: parsed.hook } : {}),
1274
+ ...(callIoOf(parsed.io) ? { io: callIoOf(parsed.io)! } : {}),
1275
+ ...(typeof parsed.inCall === "string" && parsed.inCall.length <= 128 ? { inCall: parsed.inCall } : {}),
1276
+ ...(modelCallOf(parsed.turn) ? { turn: modelCallOf(parsed.turn)! } : {}),
1277
+ ...(parsed.responding === true ? { responding: true as const } : {}),
1213
1278
  };
1214
1279
  }
1215
1280
  if (parsed.e === "done") {
@@ -1299,6 +1364,10 @@ export interface RunInContainerArgs {
1299
1364
  * without it, "stop" could only mean "stop after this step", and a
1300
1365
  * browser step has fifteen minutes left to spend. */
1301
1366
  runId?: string;
1367
+ /** Whether a person has stopped the run, read from the record. A step that
1368
+ * waits for room before its sandbox exists asks this, since there is no
1369
+ * sandbox yet for the stop to destroy. */
1370
+ stopRequested?: () => boolean;
1302
1371
  /** The reservation class — which limits this step's sandbox holds. */
1303
1372
  size?: "small" | "large" | "heavy";
1304
1373
  /** Re-attach to a sandbox a previous driver started, instead of creating