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.
- package/ARCHITECTURE.md +10 -15
- package/CHANGELOG.md +27 -0
- package/CONTEXT.md +1 -1
- package/dist/actors/computer-use/actor.js +2 -2
- package/dist/actors/computer-use/loop/ending.js +1 -1
- package/dist/actors/registry.js +1 -1
- package/dist/actors/scripted-browser/actor.js +17 -8
- package/dist/actors/scripted-browser/launch-env.d.ts +12 -0
- package/dist/actors/scripted-browser/launch-env.js +50 -0
- package/dist/analysis/automatic-completion.d.ts +2 -1
- package/dist/analysis/automatic-completion.js +15 -2
- package/dist/cli/commands/migrate.js +1 -1
- package/dist/cli/commands/observe.js +3 -2
- package/dist/cli/commands/runs.js +5 -2
- package/dist/cli/commands/study-route-computer-use.js +3 -2
- package/dist/cli/commands/study-route-preview.js +2 -1
- package/dist/cli/commands/study-route-shared-world.js +3 -3
- package/dist/cli/commands/study-run.js +9 -19
- package/dist/cli/commands/study.js +4 -1
- package/dist/cli/findings.d.ts +5 -0
- package/dist/cli/findings.js +21 -0
- package/dist/cli/io.d.ts +2 -1
- package/dist/cli/io.js +3 -2
- package/dist/cli/observer-follow.d.ts +6 -2
- package/dist/cli/observer-follow.js +15 -5
- package/dist/evidence/redaction.js +15 -4
- package/dist/routes/computer-use/live-phase.js +8 -1
- package/dist/routes/computer-use/local-vm.js +2 -1
- package/dist/routes/computer-use/participant-runs.d.ts +6 -9
- package/dist/routes/computer-use/participant-runs.js +16 -36
- package/dist/routes/computer-use/plan.d.ts +2 -2
- package/dist/routes/computer-use/plan.js +7 -6
- package/dist/routes/computer-use/route.d.ts +5 -18
- package/dist/routes/computer-use/route.js +19 -79
- package/dist/routes/computer-use/setup.d.ts +3 -2
- package/dist/routes/computer-use/setup.js +2 -1
- package/dist/routes/computer-use/types.d.ts +6 -10
- package/dist/routes/scripted/plan.js +5 -4
- package/dist/routes/scripted/route.d.ts +4 -17
- package/dist/routes/scripted/route.js +15 -43
- package/dist/routes/scripted/surface-sessions.d.ts +2 -0
- package/dist/routes/scripted/surface-sessions.js +2 -1
- package/dist/routes/scripted/types.d.ts +1 -6
- package/dist/routes/shared-world/participant-specs.js +5 -1
- package/dist/routes/shared-world/plan.js +7 -6
- package/dist/routes/shared-world/route.d.ts +5 -11
- package/dist/routes/shared-world/route.js +27 -66
- package/dist/routes/shared-world/types.d.ts +2 -6
- package/dist/routes/terminal/plan.js +4 -3
- package/dist/routes/terminal/route.d.ts +3 -13
- package/dist/routes/terminal/route.js +21 -60
- package/dist/routes/terminal/session.d.ts +2 -3
- package/dist/routes/terminal/session.js +3 -3
- package/dist/routes/terminal/types.d.ts +2 -7
- package/dist/run/display.d.ts +10 -0
- package/dist/run/display.js +22 -0
- package/dist/run/reclaim.d.ts +4 -2
- package/dist/run/reclaim.js +61 -18
- package/dist/run/route-shell.d.ts +60 -0
- package/dist/run/route-shell.js +51 -0
- package/dist/run/run.d.ts +16 -1
- package/dist/run/run.js +33 -5
- package/dist/run/status.d.ts +8 -0
- package/dist/run/status.js +6 -0
- package/dist/run/stored-runs.js +26 -11
- package/dist/study/composition-rules.js +33 -33
- package/dist/study/config.d.ts +9 -2
- package/dist/study/config.js +14 -5
- package/dist/study/doctor.js +5 -4
- package/dist/study/init-templates.d.ts +1 -1
- package/dist/study/init-templates.js +43 -34
- package/dist/study/init.js +1 -1
- package/dist/study/{convert.d.ts → migrate/convert.d.ts} +1 -1
- package/dist/study/{convert.js → migrate/convert.js} +29 -31
- package/dist/study/{migrate.d.ts → migrate/migrate.d.ts} +1 -1
- package/dist/study/{migrate.js → migrate/migrate.js} +2 -2
- package/dist/study/{owned-files.js → migrate/owned-files.js} +1 -1
- package/dist/study/migrate/v2.d.ts +68 -0
- package/dist/study/migrate/v2.js +480 -0
- package/dist/study/parse/actors.d.ts +46 -1
- package/dist/study/parse/actors.js +116 -69
- package/dist/study/parse/execution.js +2 -2
- package/dist/study/parse/study-v3.d.ts +26 -0
- package/dist/study/parse/study-v3.js +53 -35
- package/dist/study/parse/study.d.ts +38 -0
- package/dist/study/parse/study.js +225 -0
- package/dist/study/parse/subject.js +1 -1
- package/dist/study/persona-resolve.d.ts +2 -4
- package/dist/study/persona-resolve.js +2 -1
- package/dist/study/plan-base.d.ts +1 -1
- package/dist/study/plan-base.js +5 -4
- package/dist/study/plan-participants.js +12 -11
- package/dist/study/plan.js +4 -3
- package/dist/study/preflight-probes.js +2 -2
- package/dist/study/preflight.js +2 -2
- package/dist/study/routing.js +12 -15
- package/dist/study/run-study-options.js +3 -3
- package/dist/study/study-fields.d.ts +28 -0
- package/dist/study/study-fields.js +51 -0
- package/dist/study/summary.js +13 -17
- package/dist/study/types.d.ts +40 -0
- package/dist/study/url-credentials.js +2 -1
- package/dist/study/validation.d.ts +2 -0
- package/dist/study/validation.js +46 -41
- package/dist/study/warnings.js +5 -4
- package/dist/substrates/local/runtime-config.js +9 -9
- package/dist/tui-app.js +3 -3
- package/docs/architecture/actor-contract.md +1 -1
- package/docs/architecture/external-public-shared-world.md +1 -1
- package/docs/architecture/terminal-product-route.md +13 -13
- package/docs/contracts/schemas.md +13 -4
- package/docs/contracts/study-analysis.md +5 -2
- package/docs/product/automatic-analysis.md +1 -1
- package/package.json +1 -1
- /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.
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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
|
|
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
|
-
}):
|
|
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
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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 } =
|
|
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
|
-
...(
|
|
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(
|
|
115
|
+
const resolved = envLoweredConcurrency(plan.concurrency, participantCount, env);
|
|
123
116
|
const concurrency = resolved.bound;
|
|
124
|
-
const { sessionBudgetMs, sandboxMs } =
|
|
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:
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
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?:
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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:
|
|
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
|
|
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:
|
|
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 {
|
|
27
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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">>;
|