humanish 0.110.2 → 0.110.4

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 (115) hide show
  1. package/ARCHITECTURE.md +10 -15
  2. package/CHANGELOG.md +27 -0
  3. package/CONTEXT.md +1 -1
  4. package/dist/actors/computer-use/actor.js +2 -2
  5. package/dist/actors/computer-use/loop/ending.js +1 -1
  6. package/dist/actors/registry.js +1 -1
  7. package/dist/actors/scripted-browser/actor.js +17 -8
  8. package/dist/actors/scripted-browser/launch-env.d.ts +12 -0
  9. package/dist/actors/scripted-browser/launch-env.js +50 -0
  10. package/dist/analysis/automatic-completion.d.ts +2 -1
  11. package/dist/analysis/automatic-completion.js +15 -2
  12. package/dist/cli/commands/migrate.js +1 -1
  13. package/dist/cli/commands/observe.js +3 -2
  14. package/dist/cli/commands/runs.js +5 -2
  15. package/dist/cli/commands/study-route-computer-use.js +3 -2
  16. package/dist/cli/commands/study-route-preview.js +2 -1
  17. package/dist/cli/commands/study-route-shared-world.js +3 -3
  18. package/dist/cli/commands/study-run.js +9 -19
  19. package/dist/cli/commands/study.js +4 -1
  20. package/dist/cli/findings.d.ts +5 -0
  21. package/dist/cli/findings.js +21 -0
  22. package/dist/cli/io.d.ts +2 -1
  23. package/dist/cli/io.js +3 -2
  24. package/dist/cli/observer-follow.d.ts +6 -2
  25. package/dist/cli/observer-follow.js +15 -5
  26. package/dist/evidence/redaction.js +15 -4
  27. package/dist/routes/computer-use/live-phase.js +8 -1
  28. package/dist/routes/computer-use/local-vm.js +2 -1
  29. package/dist/routes/computer-use/participant-runs.d.ts +6 -9
  30. package/dist/routes/computer-use/participant-runs.js +16 -36
  31. package/dist/routes/computer-use/plan.d.ts +2 -2
  32. package/dist/routes/computer-use/plan.js +7 -6
  33. package/dist/routes/computer-use/route.d.ts +5 -18
  34. package/dist/routes/computer-use/route.js +19 -79
  35. package/dist/routes/computer-use/setup.d.ts +3 -2
  36. package/dist/routes/computer-use/setup.js +2 -1
  37. package/dist/routes/computer-use/types.d.ts +6 -10
  38. package/dist/routes/scripted/plan.js +5 -4
  39. package/dist/routes/scripted/route.d.ts +4 -17
  40. package/dist/routes/scripted/route.js +15 -43
  41. package/dist/routes/scripted/surface-sessions.d.ts +2 -0
  42. package/dist/routes/scripted/surface-sessions.js +2 -1
  43. package/dist/routes/scripted/types.d.ts +1 -6
  44. package/dist/routes/shared-world/participant-specs.js +5 -1
  45. package/dist/routes/shared-world/plan.js +7 -6
  46. package/dist/routes/shared-world/route.d.ts +5 -11
  47. package/dist/routes/shared-world/route.js +27 -66
  48. package/dist/routes/shared-world/types.d.ts +2 -6
  49. package/dist/routes/terminal/plan.js +4 -3
  50. package/dist/routes/terminal/route.d.ts +3 -13
  51. package/dist/routes/terminal/route.js +21 -60
  52. package/dist/routes/terminal/session.d.ts +2 -3
  53. package/dist/routes/terminal/session.js +3 -3
  54. package/dist/routes/terminal/types.d.ts +2 -7
  55. package/dist/run/display.d.ts +10 -0
  56. package/dist/run/display.js +22 -0
  57. package/dist/run/reclaim.d.ts +4 -2
  58. package/dist/run/reclaim.js +61 -18
  59. package/dist/run/route-shell.d.ts +60 -0
  60. package/dist/run/route-shell.js +51 -0
  61. package/dist/run/run.d.ts +16 -1
  62. package/dist/run/run.js +33 -5
  63. package/dist/run/status.d.ts +8 -0
  64. package/dist/run/status.js +6 -0
  65. package/dist/run/stored-runs.js +26 -11
  66. package/dist/study/composition-rules.js +33 -33
  67. package/dist/study/config.d.ts +9 -2
  68. package/dist/study/config.js +14 -5
  69. package/dist/study/doctor.js +5 -4
  70. package/dist/study/init-templates.d.ts +1 -1
  71. package/dist/study/init-templates.js +43 -34
  72. package/dist/study/init.js +1 -1
  73. package/dist/study/{convert.d.ts → migrate/convert.d.ts} +1 -1
  74. package/dist/study/{convert.js → migrate/convert.js} +29 -31
  75. package/dist/study/{migrate.d.ts → migrate/migrate.d.ts} +1 -1
  76. package/dist/study/{migrate.js → migrate/migrate.js} +2 -2
  77. package/dist/study/{owned-files.js → migrate/owned-files.js} +1 -1
  78. package/dist/study/migrate/v2.d.ts +68 -0
  79. package/dist/study/migrate/v2.js +480 -0
  80. package/dist/study/parse/actors.d.ts +46 -1
  81. package/dist/study/parse/actors.js +116 -69
  82. package/dist/study/parse/execution.js +2 -2
  83. package/dist/study/parse/study-v3.d.ts +26 -0
  84. package/dist/study/parse/study-v3.js +53 -35
  85. package/dist/study/parse/study.d.ts +38 -0
  86. package/dist/study/parse/study.js +225 -0
  87. package/dist/study/parse/subject.js +1 -1
  88. package/dist/study/persona-resolve.d.ts +2 -4
  89. package/dist/study/persona-resolve.js +2 -1
  90. package/dist/study/plan-base.d.ts +1 -1
  91. package/dist/study/plan-base.js +5 -4
  92. package/dist/study/plan-participants.js +12 -11
  93. package/dist/study/plan.js +4 -3
  94. package/dist/study/preflight-probes.js +2 -2
  95. package/dist/study/preflight.js +2 -2
  96. package/dist/study/routing.js +12 -15
  97. package/dist/study/run-study-options.js +3 -3
  98. package/dist/study/study-fields.d.ts +28 -0
  99. package/dist/study/study-fields.js +51 -0
  100. package/dist/study/summary.js +13 -17
  101. package/dist/study/types.d.ts +40 -0
  102. package/dist/study/url-credentials.js +2 -1
  103. package/dist/study/validation.d.ts +2 -0
  104. package/dist/study/validation.js +46 -41
  105. package/dist/study/warnings.js +5 -4
  106. package/dist/substrates/local/runtime-config.js +9 -9
  107. package/dist/tui-app.js +3 -3
  108. package/docs/architecture/actor-contract.md +1 -1
  109. package/docs/architecture/external-public-shared-world.md +1 -1
  110. package/docs/architecture/terminal-product-route.md +13 -13
  111. package/docs/contracts/schemas.md +13 -4
  112. package/docs/contracts/study-analysis.md +5 -2
  113. package/docs/product/automatic-analysis.md +1 -1
  114. package/package.json +1 -1
  115. /package/dist/study/{owned-files.d.ts → migrate/owned-files.d.ts} +0 -0
@@ -58,6 +58,16 @@ const CREDENTIAL_PARAMETER = [
58
58
  // Upper-case variable names that hold a credential, as an env file, a shell or a JSON dump writes
59
59
  // them: GITHUB_TOKEN, VERCEL_TOKEN, AWS_SECRET_ACCESS_KEY, DATABASE_PASSWORD.
60
60
  const CREDENTIAL_VARIABLE = String.raw `\b(?:[A-Z][A-Z0-9_]{0,48}_)?(?:TOKEN|SECRET|SECRET_KEY|PASSWORD|PASSWD|API_KEY|APIKEY|ACCESS_KEY|PRIVATE_KEY)`;
61
+ // The public E2B pages its errors link to, which name no sandbox and carry no credential: the
62
+ // dashboard as the SDK's missing-key error names it (https://e2b.dev/dashboard?tab=keys), with no
63
+ // path below /dashboard and no query but one short `tab`, and a docs page as the API's 401 names
64
+ // it (https://docs.e2b.dev/api-key), whose path segments hold only letters and hyphens and which
65
+ // has no query. Neither has user info, and only closing punctuation, ")" included, may follow
66
+ // before the next space or the end of the text.
67
+ const E2B_PUBLIC_PAGE = [
68
+ String.raw `(?:www\.)?e2b\.dev\/dashboard\/?(?:\?tab=[a-z]{1,16})?`,
69
+ String.raw `(?:docs\.e2b\.dev|(?:www\.)?e2b\.dev\/docs)(?:\/[a-z][a-z-]{0,39}){0,8}\/?`,
70
+ ].join("|");
61
71
  // A pattern that matches a credential by its context puts the context in a `keep` group, which
62
72
  // redaction leaves in place: `?token=[REDACTED_SECRET]`. Context goes in a group and not in a
63
73
  // lookbehind, because a pattern that starts with a lookbehind is tried at every position of the
@@ -106,10 +116,11 @@ const SECRET_PATTERNS = [
106
116
  // bounded, so a long run of `TOKEN=` costs linear time.
107
117
  new RegExp(String.raw `(?<keep>${CREDENTIAL_VARIABLE}${QUOTE}[ \t]{0,4}[:=][ \t]{0,4}${QUOTE})${notAPlaceholder(VARIABLE_END)}${NOT_A_NUMBER}(?=[^\s"'\\,;&]{0,255}[0-9])(?=[^\s"'\\,;&]{0,255}[A-Za-z])[^\s"'\\,;&]{16}[^\s"'\\,;&]*`, "g"),
108
118
  // Any URL on an E2B host: a sandbox host names its sandbox, and a stream URL carries its auth
109
- // key. One URL is exempt: E2B's dashboard, as the SDK's missing-key error names it
110
- // (https://e2b.dev/dashboard?tab=keys), with no userinfo, no path below /dashboard, no query but
111
- // one short `tab`, and only closing punctuation before the next space or ")".
112
- /(?!https:\/\/(?:www\.)?e2b\.dev\/dashboard\/?(?:\?tab=[a-z]{1,16})?[.,;:!?'"]*(?:[)\s]|$))https?:\/\/[^/\s]*e2b[^)\s]+/gi,
119
+ // key. The slashes may be JSON-escaped (`https:\/\/`). User info runs to its last `@`, as a URL
120
+ // parser reads it, and may hold any character but a slash or space. The host part stops at a
121
+ // backslash, quote or angle bracket, so a URL followed by an escaped line break and an `E2B_...`
122
+ // variable in JSON text is not read as an E2B host. A public page followed by `)@` is user info.
123
+ new RegExp(String.raw `(?!https:\/\/(?:${E2B_PUBLIC_PAGE})[.,;:!?'")]*(?:\s|$))https?:\\?\/\\?\/(?:[^/\s@]*@)*[^/\s\\"'<>@]*e2b[^)\s]+`, "gi"),
113
124
  /BEGIN (RSA|OPENSSH|PRIVATE) KEY/gi,
114
125
  ];
115
126
  const LOCAL_PATH_PATTERNS = [
@@ -102,7 +102,14 @@ export async function runStudyParticipants(setup, participants) {
102
102
  let failFastReason;
103
103
  try {
104
104
  if (!dryRun)
105
- ({ outcomes, failFastReason } = await runAllCuaParticipants(participantRuns, deps, participantPlan, inProcess));
105
+ ({ outcomes, failFastReason } = await runAllCuaParticipants(participantRuns, {
106
+ ...deps,
107
+ // A session that throws returns no trace; the run still records that it started.
108
+ runSession: (options) => {
109
+ run.participantStarted();
110
+ return deps.runSession(options);
111
+ },
112
+ }, participantPlan, inProcess));
106
113
  }
107
114
  finally {
108
115
  try {
@@ -10,6 +10,7 @@ import { createRestrictedCodexParticipant } from "../../actors/codex/restricted-
10
10
  import { withCloseReport } from "./participant-model.js";
11
11
  import { guestMediaConfigSchema } from "../../guest/media-config.js";
12
12
  import { startLocalCapturedInbox } from "../../substrates/local/captured-inbox.js";
13
+ import { actorOf } from "../../study/study-fields.js";
13
14
  function guestMedia(config) {
14
15
  const declaredMedia = config.execution?.desktop?.media;
15
16
  return declaredMedia === undefined
@@ -164,7 +165,7 @@ export function prepareLocalVmRun(options) {
164
165
  const media = guestMedia(config);
165
166
  // A caller-supplied provider replaces the Codex account participant, so the account is
166
167
  // neither checked nor used.
167
- const account = config.actors[0]?.type === "local-agent" && options.createProvider === undefined;
168
+ const account = actorOf(config)?.type === "local-agent" && options.createProvider === undefined;
168
169
  const state = { sessions: [], participants: [], cleanupUnconfirmed: false };
169
170
  const context = {
170
171
  config,
@@ -3,7 +3,7 @@ import { type StudyConfig } from "../../study/types.js";
3
3
  import { type ResolvedPersona } from "../../study/persona.js";
4
4
  import { type RunRerunLineage } from "../../run/bundle.js";
5
5
  import type { PreparedSelectedOutputDirectory } from "../../run/contained-output.js";
6
- import { type CuaActorStudyErrorCode, type CuaParticipantPlan, type CuaParticipantPlanEntry, type DesktopParticipantRun } from "./types.js";
6
+ import { type CuaActorStudyErrorCode, type CuaParticipantPlan, type CuaParticipantPlanEntry, type DesktopParticipantRun, type ParticipantRunsAndPlan } from "./types.js";
7
7
  export declare function defaultSessionTimeoutMs(config: StudyConfig): number;
8
8
  /** Per-participant sandbox deadline (each one owns its own desktop). Mirrors the
9
9
  * single-participant formula verbatim so N=1 stays byte-stable: explicit sandboxTimeoutMs, else session budget + (clone
@@ -21,17 +21,14 @@ export declare function resolveParticipantSandboxMs(config: StudyConfig): number
21
21
  */
22
22
  export declare function boundedConcurrency(declared: number | undefined, participantCount: number): number;
23
23
  /**
24
- * Pure pre-flight plan resolver (runs in dry-run and live). Returns the participant table, the
25
- * effective concurrency, the wave count, the per-participant session budget, and the worst-case total
26
- * sandbox-minutes, before any sandbox or provider call. The same plan appears in dry-run,
27
- * marked $0 (dryRun: true).
24
+ * Build the participant runs and the public plan from a computer-use plan (pure). The same plan
25
+ * appears in dry-run and live, marked $0 for a dry run. `env` may lower the planned concurrency;
26
+ * `personas` are the compiled committed personas the prompts carry.
28
27
  */
29
- export declare function resolveCuaParticipantPlan(config: StudyConfig, opts?: {
30
- countOverride?: number;
28
+ export declare function participantRunsAndPlan(plan: ComputerUsePlan, opts?: {
31
29
  env?: Record<string, string | undefined>;
32
- dryRun?: boolean;
33
30
  personas?: Map<string, ResolvedPersona>;
34
- }): CuaParticipantPlan;
31
+ }): ParticipantRunsAndPlan;
35
32
  /** Print the participant plan to stderr before any sandbox/provider call (public-safe: ids, devices,
36
33
  * digests, and budgets only; no prompt text, no secrets). */
37
34
  export declare function emitPreflightPlan(participantPlan: CuaParticipantPlan, studyId: string): void;
@@ -4,7 +4,6 @@
4
4
  // compiles the committed personas first and narrows a rerun to its selected participants.
5
5
  import { isLocalBrowserStudy, LOCAL_BROWSER_LIFETIME_MS, } from "../../substrates/local/runtime-config.js";
6
6
  import { DEFAULT_STATE_STEP_TIMEOUT_MS } from "../../subject/state.js";
7
- import { computerUseParticipants, } from "../../study/plan-participants.js";
8
7
  import { resolveParticipant } from "../../run/participant.js";
9
8
  import {} from "../../study/types.js";
10
9
  import { scrubPersonaBrief } from "../../study/persona.js";
@@ -71,23 +70,17 @@ function envLoweredConcurrency(planned, participantCount, env) {
71
70
  }
72
71
  return { bound: planned };
73
72
  }
74
- /** The same inputs from a config, for callers that have no plan. */
75
- function plannedParticipantsOf(config, countOverride) {
76
- const participants = computerUseParticipants(config, countOverride);
77
- return {
78
- participants,
79
- concurrency: boundedConcurrency(config.execution?.concurrency, participants.length),
80
- sessionBudgetMs: config.execution?.timeoutMs ?? defaultSessionTimeoutMs(config),
81
- sandboxMs: resolveParticipantSandboxMs(config),
82
- desktopCli: config.subject.source === "desktop-cli",
83
- };
84
- }
85
- /** Build the participant runs and the public plan from the planned participants (pure). */
86
- function participantRunsAndPlan(planned, opts = {}) {
73
+ /**
74
+ * Build the participant runs and the public plan from a computer-use plan (pure). The same plan
75
+ * appears in dry-run and live, marked $0 for a dry run. `env` may lower the planned concurrency;
76
+ * `personas` are the compiled committed personas the prompts carry.
77
+ */
78
+ export function participantRunsAndPlan(plan, opts = {}) {
87
79
  const env = opts.env ?? {};
88
80
  // Who each participant is (id, persona, focus, device, limits) comes from the plan; this adds
89
81
  // what the route derives from it: the prompt, bundle ids and artifact paths.
90
- const { participants } = planned;
82
+ const { participants } = plan.runner;
83
+ const desktopCli = plan.runner.subject.kind === "desktop-cli";
91
84
  const participantCount = participants.length;
92
85
  const runs = participants.map((participant) => {
93
86
  const mission = participant.assignment.mission ?? DEFAULT_MISSION;
@@ -102,7 +95,7 @@ function participantRunsAndPlan(planned, opts = {}) {
102
95
  ...(resolvedPersona === undefined ? {} : { resolvedPersona }),
103
96
  ...(focus === undefined ? {} : { instruction: focus }),
104
97
  device: { name: device.name, preset: device.preset },
105
- ...(planned.desktopCli ? { surface: "desktop-cli" } : {}),
98
+ ...(desktopCli ? { surface: "desktop-cli" } : {}),
106
99
  });
107
100
  const run = resolveParticipant(participant, {
108
101
  persona: composed.persona,
@@ -119,9 +112,9 @@ function participantRunsAndPlan(planned, opts = {}) {
119
112
  traceArtifactPath: participantCount === 1 ? "actor.json" : `actors/${run.streamId}.json`,
120
113
  };
121
114
  });
122
- const resolved = envLoweredConcurrency(planned.concurrency, participantCount, env);
115
+ const resolved = envLoweredConcurrency(plan.concurrency, participantCount, env);
123
116
  const concurrency = resolved.bound;
124
- const { sessionBudgetMs, sandboxMs } = planned;
117
+ const { sessionBudgetMs, sandboxMs } = plan;
125
118
  const participantPlan = {
126
119
  strategy: CUA_FANOUT_STRATEGY,
127
120
  laneCount: participantCount,
@@ -132,7 +125,7 @@ function participantRunsAndPlan(planned, opts = {}) {
132
125
  waves: Math.ceil(participantCount / concurrency),
133
126
  perLaneSessionBudgetMs: sessionBudgetMs,
134
127
  worstCaseSandboxMinutes: Math.round((participantCount * sandboxMs) / 60_000),
135
- dryRun: opts.dryRun === true,
128
+ dryRun: plan.dryRun,
136
129
  lanes: runs.map((spec) => ({
137
130
  id: spec.planned.id,
138
131
  ...(spec.planned.labels.actorType === undefined
@@ -162,16 +155,6 @@ function participantRunsAndPlan(planned, opts = {}) {
162
155
  };
163
156
  return { runs, participantPlan };
164
157
  }
165
- /**
166
- * Pure pre-flight plan resolver (runs in dry-run and live). Returns the participant table, the
167
- * effective concurrency, the wave count, the per-participant session budget, and the worst-case total
168
- * sandbox-minutes, before any sandbox or provider call. The same plan appears in dry-run,
169
- * marked $0 (dryRun: true).
170
- */
171
- export function resolveCuaParticipantPlan(config, opts = {}) {
172
- const { countOverride, ...rest } = opts;
173
- return participantRunsAndPlan(plannedParticipantsOf(config, countOverride), rest).participantPlan;
174
- }
175
158
  /** Print the participant plan to stderr before any sandbox/provider call (public-safe: ids, devices,
176
159
  * digests, and budgets only; no prompt text, no secrets). */
177
160
  export function emitPreflightPlan(participantPlan, studyId) {
@@ -216,13 +199,10 @@ export async function loadCuaParticipants(args) {
216
199
  // Compile any committed personas before planning, so the plan builder stays pure and each participant's
217
200
  // prompt carries real behavioral directives rather than a bare `Persona: <id>.` label.
218
201
  const personas = await compileParticipantPersonas(args.projectRoot, participants.map((participant) => participant.personaId));
219
- const { runs: participantRuns, participantPlan } = participantRunsAndPlan({
220
- participants,
221
- concurrency: plan.concurrency,
222
- sessionBudgetMs: plan.sessionBudgetMs,
223
- sandboxMs: plan.sandboxMs,
224
- desktopCli: plan.runner.subject.kind === "desktop-cli",
225
- }, { env: args.env, dryRun: plan.dryRun, personas });
202
+ const { runs: participantRuns, participantPlan } = participantRunsAndPlan(plan, {
203
+ env: args.env,
204
+ personas,
205
+ });
226
206
  const rerun = plan.rerun;
227
207
  if (!rerun)
228
208
  return { ok: true, participantRuns, participantPlan };
@@ -1,7 +1,7 @@
1
1
  import { type CuaActorDescriptor } from "../../actors/registry.js";
2
2
  import type { ComputerUsePlan, ComputerUseRunner, RoutePlanResult, RouteRefusal } from "../../study/plan-types.js";
3
3
  import type { StudyConfig, StudySubjectState } from "../../study/types.js";
4
- import { type CuaActorStudyErrorCode, type RunCuaActorStudyOptions } from "./types.js";
4
+ import { type ComputerUseRunInput, type CuaActorStudyErrorCode } from "./types.js";
5
5
  /** The error a computer-use study returns before a run starts. */
6
6
  export interface ComputerUseRefusal extends RouteRefusal<"computer-use", CuaActorStudyErrorCode> {
7
7
  /**
@@ -44,5 +44,5 @@ export declare function planComputerUseStudy(config: StudyConfig, input: {
44
44
  /** Which of the caller's driving homes are set; neither when absent. */
45
45
  readonly driving?: CallerDriving;
46
46
  readonly countOverride?: number;
47
- readonly rerun?: RunCuaActorStudyOptions["rerun"];
47
+ readonly rerun?: ComputerUseRunInput["rerun"];
48
48
  }): ComputerUsePlanResult;
@@ -7,7 +7,7 @@ import { actorRegistry, isCuaActorDescriptor, } from "../../actors/registry.js";
7
7
  import { isHttpUrl, isLoopbackUrl } from "../../study/parse/subject.js";
8
8
  import { studyUrlCredentialReason } from "../../study/url-credentials.js";
9
9
  import { subjectStateInvalidReason } from "../../study/parse/subject-state.js";
10
- import { brainOf, callerBrainOf, capsOf, desktopRequirements, planBase, provisionedSubject, } from "../../study/plan-base.js";
10
+ import { brainOf, callerBrainOf, planCaps, desktopRequirements, planBase, provisionedSubject, } from "../../study/plan-base.js";
11
11
  import { computerUseParticipants, declaredTargets } from "../../study/plan-participants.js";
12
12
  import { MAX_COMPUTER_USE_PARTICIPANTS } from "../../study/routing.js";
13
13
  import { cloneTargetValidationReason, computerUseValidationReason, desktopMediaValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, scenarioCapsValidationReason, taskProtocolValidationReason, } from "../../study/validation.js";
@@ -16,6 +16,7 @@ import { isLocalBrowserStudy } from "../../substrates/local/runtime-config.js";
16
16
  import { boundedConcurrency, defaultSessionTimeoutMs, resolveParticipantSandboxMs, } from "./participant-runs.js";
17
17
  import { MAX_SANDBOX_MS } from "../../substrates/e2b/lifetime.js";
18
18
  import {} from "./types.js";
19
+ import { actorOf, declaresSharedWorld } from "../../study/study-fields.js";
19
20
  /** The registered descriptor for a planned actor id; planComputerUseStudy checked the registry. */
20
21
  export function cuaDescriptorOf(actor) {
21
22
  const descriptor = actorRegistry[actor];
@@ -88,7 +89,7 @@ function unsupportedDeclarationReason(config, hasRunSession, driving, { inProces
88
89
  scenarioCapsValidationReason(config);
89
90
  if (reason)
90
91
  return invalid(reason);
91
- if (config.actors[0]?.maxOutputTokens !== undefined &&
92
+ if (actorOf(config)?.maxOutputTokens !== undefined &&
92
93
  (hasRunSession || driving.createProvider || driving.inProcess))
93
94
  return invalid("maxOutputTokens cannot be enforced by a custom runSession/provider/executor route.");
94
95
  const receivingReason = receivingEmailValidationReason(config);
@@ -220,7 +221,7 @@ export function planComputerUseStudy(config, input) {
220
221
  if (tasksReason)
221
222
  return refuse("before-scope", "HUMANISH_STUDY_TASKS_UNSUPPORTED", tasksReason);
222
223
  // The parser checks the actor too; a library caller skips the parser.
223
- const actorType = config.actors[0]?.type ?? "";
224
+ const actorType = actorOf(config)?.type ?? "";
224
225
  const descriptor = actorRegistry[actorType];
225
226
  if (!descriptor || !isCuaActorDescriptor(descriptor))
226
227
  return refuse("in-scope", "HUMANISH_COMPUTER_USE_ACTOR_UNSUPPORTED", `actor.type "${actorType}" is not a registered computer-use actor.`);
@@ -231,8 +232,8 @@ export function planComputerUseStudy(config, input) {
231
232
  // A shared world runs every participant against one app; this route would run them as separate
232
233
  // participants. It comes after the rules above, so a shared-world config that breaks one of them,
233
234
  // which runStudyWith sends here, still gets that rule's message.
234
- if (config.subject.topology === "shared-world")
235
- return refuse("in-scope", "HUMANISH_COMPUTER_USE_SUBJECT_INVALID", "route: shared-world studies run every participant against one shared app on the shared-world route; this route runs independent participants. Run the study with runStudy or runConcurrentSharedWorld.", actor);
235
+ if (declaresSharedWorld(config))
236
+ return refuse("in-scope", "HUMANISH_COMPUTER_USE_SUBJECT_INVALID", "route: shared-world studies run every participant against one shared app on the shared-world route; this route runs independent participants. The shared-world route takes subject.source clone or local-tree, or app-url with policies.allowPublicTargets: true, on execution.target e2b-desktop.", actor);
236
237
  // The in-process route drives subject.appUrl on this machine and creates no desktop, so it would
237
238
  // skip the subject a clone, local-tree or desktop-cli study declares.
238
239
  const source = config.subject.source;
@@ -292,7 +293,7 @@ export function planComputerUseStudy(config, input) {
292
293
  concurrency: boundedConcurrency(declared, n),
293
294
  sessionBudgetMs: config.execution?.timeoutMs ?? defaultSessionTimeoutMs(config),
294
295
  sandboxMs: resolveParticipantSandboxMs(config),
295
- caps: capsOf(config),
296
+ caps: planCaps(config, "computer-use"),
296
297
  ...(input.rerun === undefined ? {} : { rerun: rerunPlan(input.rerun) }),
297
298
  requirements: base.dryRun || runner.desktop === "in-process"
298
299
  ? []
@@ -1,33 +1,20 @@
1
+ import { type RefusedStudy } from "../../run/route-shell.js";
1
2
  import type { ComputerUsePlan } from "../../study/plan-types.js";
2
3
  import type { AdmittedPlan } from "../../run-study.js";
3
4
  import type { StudyConfig } from "../../study/types.js";
4
5
  import { type ComputerUseRefusal } from "./plan.js";
5
- import { type ComputerUseRunInput, type CuaActorStudyResult, type RunCuaActorStudyOptions } from "./types.js";
6
- /**
7
- * Plans and runs a computer-use study in one call. It is not exported from src/index.ts; tests call
8
- * it. It plans the config with planComputerUseStudy and runs
9
- * the plan with runComputerUsePlan, whose run scope and withTransientCommsSecrets wrapper cover the
10
- * run and its analysis.
11
- */
12
- export declare function runCuaActorStudy(options: RunCuaActorStudyOptions): Promise<CuaActorStudyResult>;
6
+ import { type ComputerUseRunInput, type CuaActorStudyResult } from "./types.js";
13
7
  /**
14
8
  * A refused computer-use study's result, at the refusal's stage: a before-scope refusal has its own
15
9
  * envelope and no analysis record; the others come after the cwd checks, and the participant cap after
16
10
  * the personas are read.
17
11
  */
18
- export declare function computerUseStudyRefusal(options: RunCuaActorStudyOptions, refusal: ComputerUseRefusal): Promise<CuaActorStudyResult>;
12
+ export declare function computerUseStudyRefusal(options: ComputerUseRunInput & RefusedStudy, refusal: ComputerUseRefusal): Promise<CuaActorStudyResult>;
19
13
  /**
20
14
  * runStudyWith's step for a computer-use plan. It runs the plan's local checks (admitCuaRun: the
21
15
  * participants, the preflight plan, the keys, the local agent, subject env and caps, and the
22
16
  * local-tree archive) before any run scope opens, so the CLI can present their refusal before it
23
- * loads a declared scorer, and returns the run that continues from them with that scorer.
17
+ * loads a declared scorer, and returns the run that continues from them with that scorer. The
18
+ * run and its analysis share one scope for the email secrets it registers.
24
19
  */
25
20
  export declare function admitComputerUsePlan(plan: ComputerUsePlan, input: ComputerUseRunInput, config: StudyConfig): Promise<AdmittedPlan<"computer-use">>;
26
- /**
27
- * Run a computer-use plan: its local checks, then the run. The run scope in runAdmittedCuaRun
28
- * finalizes any status record the run opened, on every exit, so a test or library caller does not
29
- * leave the 5 s status cadence writing into a directory something else is deleting (an unrelated
30
- * ENOTEMPTY). Participants, the brain, the subject, the caps and the residual config come from the
31
- * plan. `config` is what the caller's createProvider and inProcess executor receive.
32
- */
33
- export declare function runComputerUsePlan(plan: ComputerUsePlan, input: ComputerUseRunInput, config: StudyConfig): Promise<CuaActorStudyResult>;
@@ -21,39 +21,18 @@
21
21
  // harness errors are redacted at this boundary; the bundle's `stream.actor` carries the
22
22
  // conformant humanish.actor-trace.v1 projection, whose `redaction.screenshots` records the
23
23
  // run's actual mode ("raw" | "blurred" | "n/a"), and every label downstream derives from it.
24
- import { withTransientCommsSecrets } from "../../run/transient-comms-secrets.js";
25
24
  import path from "node:path";
26
- import { completeAutomaticAnalysis } from "../../analysis/automatic-completion.js";
27
- import { runScope } from "../../run/run.js";
28
- import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
25
+ import {} from "../../run/run.js";
26
+ import { admitRoute, completeRefusalAnalysis } from "../../run/route-shell.js";
29
27
  import { browserRouteScorer } from "../../study/adapter-scorer-loader.js";
30
28
  import { withLateScorer } from "../../study/route-inputs.js";
31
- import { callerDrivingOf, planComputerUseStudy } from "./plan.js";
29
+ import {} from "./plan.js";
32
30
  import { finishCuaRun } from "./result.js";
33
31
  import { runStudyParticipants } from "./live-phase.js";
34
32
  import { admitCuaRun, refuseCuaStudy, startCuaRun } from "./setup.js";
35
33
  import {} from "./types.js";
36
34
  import { studyResultIdentity } from "../../run/study-result.js";
37
- /**
38
- * Plans and runs a computer-use study in one call. It is not exported from src/index.ts; tests call
39
- * it. It plans the config with planComputerUseStudy and runs
40
- * the plan with runComputerUsePlan, whose run scope and withTransientCommsSecrets wrapper cover the
41
- * run and its analysis.
42
- */
43
- export async function runCuaActorStudy(options) {
44
- const { config, dryRun, ...input } = options;
45
- // planComputerUseStudy makes every configuration refusal, in the order this route always has.
46
- const planned = planComputerUseStudy(config, {
47
- dryRun,
48
- hasRunSession: input.deps?.runSession !== undefined,
49
- driving: callerDrivingOf(input),
50
- ...(input.countOverride === undefined ? {} : { countOverride: input.countOverride }),
51
- ...(input.rerun === undefined ? {} : { rerun: input.rerun }),
52
- });
53
- if (planned.ok)
54
- return runComputerUsePlan(planned.plan, input, config);
55
- return computerUseStudyRefusal(options, planned.refusal);
56
- }
35
+ import { actorOf } from "../../study/study-fields.js";
57
36
  /**
58
37
  * A refused computer-use study's result, at the refusal's stage: a before-scope refusal has its own
59
38
  * envelope and no analysis record; the others come after the cwd checks, and the participant cap after
@@ -66,7 +45,7 @@ export async function computerUseStudyRefusal(options, refusal) {
66
45
  ...studyResultIdentity("computer-use", config.id),
67
46
  ok: false,
68
47
  cwd: path.resolve(options.cwd),
69
- actor: config.actors[0]?.type ?? "",
48
+ actor: actorOf(config)?.type ?? "",
70
49
  dryRun,
71
50
  runId: options.runId ?? "not-created",
72
51
  appUrl: config.subject.appUrl ?? config.subject.serve?.url ?? "",
@@ -75,66 +54,27 @@ export async function computerUseStudyRefusal(options, refusal) {
75
54
  error: { code: refusal.code, message: refusal.message },
76
55
  };
77
56
  // The other refusals come after the cwd checks, and the participant cap after the personas are read.
78
- const analysis = resolveAutomaticAnalysis(config.review?.analysis);
79
- return completeAutomaticAnalysis(await refuseCuaStudy(options, refusal), undefined, analysis.ok ? analysis.config : undefined, options, { trigger: config.review?.analysis === undefined ? "default" : "explicit" });
57
+ return completeRefusalAnalysis(await refuseCuaStudy(options, refusal), config, options);
80
58
  }
81
59
  /**
82
60
  * runStudyWith's step for a computer-use plan. It runs the plan's local checks (admitCuaRun: the
83
61
  * participants, the preflight plan, the keys, the local agent, subject env and caps, and the
84
62
  * local-tree archive) before any run scope opens, so the CLI can present their refusal before it
85
- * loads a declared scorer, and returns the run that continues from them with that scorer.
86
- */
87
- export async function admitComputerUsePlan(plan, input, config) {
88
- const admission = await admitCuaRun(plan, input, config);
89
- if (!admission.ok)
90
- return {
91
- ok: false,
92
- outcome: cuaOutcome(await completeCuaAnalysis(plan, input, admission.result, undefined)),
93
- };
94
- return {
95
- ok: true,
96
- run: async (scorer) => {
97
- // The scorer joins the input only; the runSession, provider and desktop the checks admitted
98
- // stay the ones the run uses.
99
- const running = withLateScorer(input, scorer, browserRouteScorer);
100
- return cuaOutcome(await runAdmittedCuaRun(plan, running, admission.admitted));
101
- },
102
- };
103
- }
104
- function cuaOutcome(result) {
105
- return { route: "computer-use", result };
106
- }
107
- /**
108
- * Run a computer-use plan: its local checks, then the run. The run scope in runAdmittedCuaRun
109
- * finalizes any status record the run opened, on every exit, so a test or library caller does not
110
- * leave the 5 s status cadence writing into a directory something else is deleting (an unrelated
111
- * ENOTEMPTY). Participants, the brain, the subject, the caps and the residual config come from the
112
- * plan. `config` is what the caller's createProvider and inProcess executor receive.
113
- */
114
- export async function runComputerUsePlan(plan, input, config) {
115
- const admission = await admitCuaRun(plan, input, config);
116
- if (!admission.ok)
117
- return completeCuaAnalysis(plan, input, admission.result, undefined);
118
- return runAdmittedCuaRun(plan, input, admission.admitted);
119
- }
120
- /**
121
- * Runs an admitted plan in its own run scope, then its automatic analysis. The
122
- * withTransientCommsSecrets wrapper scopes any email secret the run registers to this run and its
123
- * analysis.
63
+ * loads a declared scorer, and returns the run that continues from them with that scorer. The
64
+ * run and its analysis share one scope for the email secrets it registers.
124
65
  */
125
- function runAdmittedCuaRun(plan, input, admitted) {
126
- return withTransientCommsSecrets(async () => {
127
- const { result, finished } = await runScope((scope) => runPlanInScope(plan, input, admitted, scope));
128
- return completeCuaAnalysis(plan, input, result, finished);
129
- });
130
- }
131
- function completeCuaAnalysis(plan, input, result, finished) {
66
+ export function admitComputerUsePlan(plan, input, config) {
132
67
  // A local VM study whose cleanup is unconfirmed records a skip instead of analyzing.
133
- const refusal = input.localVm?.analysisRefusal;
134
- return completeAutomaticAnalysis(result, finished, plan.analysis?.config, input, {
135
- ...(plan.analysis === undefined ? {} : { trigger: plan.analysis.trigger }),
136
- preferLargerOutput: plan.analysis?.preferLargerOutput === true,
137
- ...(refusal === undefined ? {} : { refusal }),
68
+ const analysisRefusal = input.localVm?.analysisRefusal;
69
+ return admitRoute({
70
+ route: "computer-use",
71
+ analysis: plan.analysis,
72
+ input,
73
+ admit: () => admitCuaRun(plan, input, config),
74
+ withScorer: (base, scorer) => withLateScorer(base, scorer, browserRouteScorer),
75
+ runInScope: (admitted, running, scope) => runPlanInScope(plan, running, admitted, scope),
76
+ ...(analysisRefusal === undefined ? {} : { analysisRefusal }),
77
+ commsSecrets: true,
138
78
  });
139
79
  }
140
80
  async function runPlanInScope(plan, input, admitted, scope) {
@@ -1,4 +1,5 @@
1
1
  import { type RunScope } from "../../run/run.js";
2
+ import type { RefusedStudy } from "../../run/route-shell.js";
2
3
  import type { CuaActorDescriptor } from "../../actors/registry.js";
3
4
  import type { StudyCommsEmail, StudyCommsExternal, StudyConfig } from "../../study/types.js";
4
5
  import { type RunRerunLineage } from "../../run/bundle.js";
@@ -8,12 +9,12 @@ import type { ComputerUsePlan } from "../../study/plan-types.js";
8
9
  import { trackRuntimeStreams, type LiveTraceFlush } from "./live-flush.js";
9
10
  import { type CuaRunBundleBase } from "./bundle.js";
10
11
  import { projectParticipantSubjects } from "./subject-projection.js";
11
- import { type CuaActorStudyErrorCode, type CuaActorStudyResult, type CuaParticipantDeps, type CuaParticipantPlan, type DesktopParticipantRun, type CuaSubjectProjection, type CuaSubjectProvenanceArg, type ComputerUseRunInput, type RunCuaActorStudyOptions } from "./types.js";
12
+ import { type CuaActorStudyErrorCode, type CuaActorStudyResult, type CuaParticipantDeps, type CuaParticipantPlan, type DesktopParticipantRun, type CuaSubjectProjection, type CuaSubjectProvenanceArg, type ComputerUseRunInput } from "./types.js";
12
13
  /**
13
14
  * The envelope of a refusal the plan left for after the cwd checks. The participant cap and
14
15
  * in-process fan-out refusals come after the committed personas are read, so a persona-file error still wins.
15
16
  */
16
- export declare function refuseCuaStudy(options: RunCuaActorStudyOptions, refusal: ComputerUseRefusal): Promise<CuaActorStudyResult>;
17
+ export declare function refuseCuaStudy(options: ComputerUseRunInput & RefusedStudy, refusal: ComputerUseRefusal): Promise<CuaActorStudyResult>;
17
18
  export type AdmittedCuaRun = Extract<Awaited<ReturnType<typeof admitCuaRun>>, {
18
19
  ok: true;
19
20
  }>["admitted"];
@@ -25,6 +25,7 @@ import { projectParticipantSubjects, subjectProvenanceArg } from "./subject-proj
25
25
  import { participantSubjectEnv, } from "./types.js";
26
26
  import { studyPersonaIds } from "../../study/persona-resolve.js";
27
27
  import { studyResultIdentity } from "../../run/study-result.js";
28
+ import { actorOf } from "../../study/study-fields.js";
28
29
  /** The physical project, bound before any caller hook runs. */
29
30
  async function bindProject(cwd) {
30
31
  // Capture the physical project before reading or invoking any caller hook. A supported
@@ -47,7 +48,7 @@ export async function refuseCuaStudy(options, refusal) {
47
48
  ...studyResultIdentity("computer-use", config.id),
48
49
  ok: false,
49
50
  cwd: projectRoot.physicalPath,
50
- actor: refusal.actor ?? config.actors[0]?.type ?? "",
51
+ actor: refusal.actor ?? actorOf(config)?.type ?? "",
51
52
  appUrl: declaredAppUrl(config),
52
53
  dryRun,
53
54
  runId: options.runId ?? "not-created",
@@ -19,7 +19,7 @@ import { type E2BDesktopModule } from "../../substrates/e2b/sdk.js";
19
19
  import { type DesktopResourceObservation } from "../../substrates/e2b/desktop-resources.js";
20
20
  import { type DetachedTimers } from "../../substrates/detached.js";
21
21
  import type { Brain, ComputerUsePlan, ComputerUseRunner, ResidualConfig } from "../../study/plan-types.js";
22
- import { type StudyCommsEmail, type StudyConfig } from "../../study/types.js";
22
+ import { type StudyCommsEmail } from "../../study/types.js";
23
23
  import { type ObserverResult } from "../../observer/render.js";
24
24
  import { type BundleRun, type RunBundle, type RunRerunLineage, type RunScorerProvenance, type RunSubjectProvenance, type RunSubjectStateStepRecord } from "../../run/bundle.js";
25
25
  import { type RunDesktopGeometry } from "../../run/streams.js";
@@ -31,11 +31,6 @@ export declare const CUA_FANOUT_STRATEGY: "per-lane-worlds";
31
31
  export declare const CUA_MAX_CONCURRENCY_ENV = "HUMANISH_CUA_MAX_CONCURRENCY";
32
32
  export declare const DEFAULT_APP_URL_SESSION_TIMEOUT_MS: number;
33
33
  export declare const MIN_DERIVED_SESSION_TIMEOUT_MS: number;
34
- /**
35
- * What a computer-use run takes besides its plan and config. The count override and rerun go to
36
- * participant building with the config.
37
- */
38
- export type ComputerUseRunInput = Omit<RunCuaActorStudyOptions, "config" | "dryRun">;
39
34
  /**
40
35
  * What runStudyWith's local VM study gives a computer-use run: the desktop each participant runs on, the
41
36
  * reason automatic analysis must not run (the study's cleanup is unconfirmed), and the signal its
@@ -47,13 +42,14 @@ export interface LocalVmInput {
47
42
  readonly analysisRefusal: () => AutomaticAnalysisRefusal | undefined;
48
43
  readonly signal?: AbortSignal;
49
44
  }
50
- export interface RunCuaActorStudyOptions {
45
+ /**
46
+ * What a computer-use run takes besides its plan and config. The count override and rerun go to
47
+ * participant building with the config.
48
+ */
49
+ export interface ComputerUseRunInput {
51
50
  /** Cancels post-run analysis only. */
52
51
  analysisSignal?: AbortSignal;
53
52
  cwd: string;
54
- config: StudyConfig;
55
- /** Resolved upstream (scenario.mode + CLI override); defaults safe (dry-run). */
56
- dryRun: boolean;
57
53
  open?: boolean;
58
54
  runId?: string;
59
55
  /** CLI `--count` override for the homogeneous fan-out participant count (ignored when a `lanes`
@@ -7,6 +7,7 @@ import { browserSurfaces } from "../../actors/scripted-browser/types.js";
7
7
  import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
8
8
  import { isNonEmpty, planBase, provisionedSubject } from "../../study/plan-base.js";
9
9
  import { cloneTargetValidationReason, desktopMediaValidationReason, taskProtocolValidationReason, } from "../../study/validation.js";
10
+ import { actorOf, surfaceCount, scenarioRefOf } from "../../study/study-fields.js";
10
11
  // Default surface roster is 1 (desktop only): the defaults-table single-participant row governs;
11
12
  // `count: 2` is the declared override that adds the mobile surface.
12
13
  const DEFAULT_SURFACE_COUNT = 1;
@@ -42,7 +43,7 @@ export function planScriptedStudy(config, input) {
42
43
  const cloneTargetReason = cloneTargetValidationReason(config);
43
44
  if (cloneTargetReason)
44
45
  return refuse("HUMANISH_SCRIPTED_SUBJECT_UNSAFE", cloneTargetReason);
45
- const actorType = config.actors[0]?.type ?? "";
46
+ const actorType = actorOf(config)?.type ?? "";
46
47
  const descriptor = actorRegistry[actorType];
47
48
  if (!descriptor || !isScriptedBrowserActorDescriptor(descriptor))
48
49
  return refuse("HUMANISH_SCRIPTED_ACTOR_UNSUPPORTED", `actor.type "${actorType}" is not a registered scripted-browser actor.`);
@@ -63,7 +64,7 @@ export function planScriptedStudy(config, input) {
63
64
  subject = { kind: "loopback", appUrl };
64
65
  }
65
66
  // The steps are the actor, so there is no built-in journey to fall back on.
66
- const scenarioRef = config.scenario?.ref;
67
+ const scenarioRef = scenarioRefOf(config);
67
68
  if (!scenarioRef?.trim())
68
69
  return refuse("HUMANISH_SCRIPTED_SCENARIO_INVALID", "A scripted-browser study needs `scenario`: the actor runs the browser steps in that scenario file.", { actor, appUrl: evidenceAppUrlOf(subject) });
69
70
  const requirements = [];
@@ -75,7 +76,7 @@ export function planScriptedStudy(config, input) {
75
76
  // The browser runs on this machine on both subjects; a clone's is pointed at its getHost URL.
76
77
  if (!input.dryRun && input.injectedBrowser !== true)
77
78
  requirements.push({ kind: "host-browser" });
78
- const persona = config.actors[0]?.persona;
79
+ const persona = actorOf(config)?.persona;
79
80
  const timeoutMs = config.execution?.timeoutMs;
80
81
  return {
81
82
  ok: true,
@@ -88,7 +89,7 @@ export function planScriptedStudy(config, input) {
88
89
  actor,
89
90
  subject,
90
91
  scenarioRef,
91
- surfaces: browserSurfaces.slice(0, config.actors[0]?.count ?? DEFAULT_SURFACE_COUNT),
92
+ surfaces: browserSurfaces.slice(0, surfaceCount(config) ?? DEFAULT_SURFACE_COUNT),
92
93
  ...(persona === undefined ? {} : { personaId: persona }),
93
94
  ...(timeoutMs === undefined ? {} : { sessionTimeoutMs: timeoutMs }),
94
95
  requirements,
@@ -1,28 +1,15 @@
1
1
  import type { ScriptedPlan } from "../../study/plan-types.js";
2
2
  import type { AdmittedPlan } from "../../run-study.js";
3
+ import { type RefusedStudy } from "../../run/route-shell.js";
3
4
  import { type ScriptedRefusal } from "./plan.js";
4
- import { type RunScriptedBrowserStudyOptions, type ScriptedBrowserStudyResult, type ScriptedRunInput } from "./types.js";
5
- /**
6
- * The config-taking entry point. It plans, returns a refusal with the envelope the route has always
7
- * returned at that refusal's stage, and otherwise runs the plan.
8
- */
9
- export declare function runScriptedBrowserStudy(options: RunScriptedBrowserStudyOptions): Promise<ScriptedBrowserStudyResult>;
5
+ import { type ScriptedBrowserStudyResult, type ScriptedRunInput } from "./types.js";
10
6
  /**
11
7
  * A refused scripted study's result, at the refusal's stage: a before-scope refusal has its own field
12
8
  * order and no analysis record; the others come after the output directory checks.
13
9
  */
14
- export declare function scriptedStudyRefusal(options: RunScriptedBrowserStudyOptions, refusal: ScriptedRefusal): Promise<ScriptedBrowserStudyResult>;
10
+ export declare function scriptedStudyRefusal(options: ScriptedRunInput & RefusedStudy, refusal: ScriptedRefusal): Promise<ScriptedBrowserStudyResult>;
15
11
  /**
16
12
  * runStudyWith's step for a scripted plan. It takes no scorer, and its local checks (keys, a browser)
17
13
  * run inside the run, so it returns the run.
18
14
  */
19
- export declare function admitScriptedPlan(plan: ScriptedPlan, input: ScriptedRunInput): AdmittedPlan<"scripted">;
20
- /**
21
- * Run a scripted plan. The run scope gives a direct library caller the same status-record lifetime
22
- * the CLI gets: returning from this function finalizes any record the run opened, whichever of its
23
- * fail-closed exits it took. Each route opens its own scope; `runStudyWith` opens none. Without this a
24
- * test or an adopter calling the route directly leaves
25
- * the 5s cadence ticking into a directory something else is deleting, which surfaces as an
26
- * unrelated ENOTEMPTY.
27
- */
28
- export declare function runScriptedPlan(plan: ScriptedPlan, input: ScriptedRunInput): Promise<ScriptedBrowserStudyResult>;
15
+ export declare function admitScriptedPlan(plan: ScriptedPlan, input: ScriptedRunInput): Promise<AdmittedPlan<"scripted">>;