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
|
@@ -10,7 +10,7 @@ import { REPO_SLUG_PATTERN } from "./parse/values.js";
|
|
|
10
10
|
import { declaredTargets } from "./plan-participants.js";
|
|
11
11
|
import { actorResolvesToComputerUse, actorResolvesToScriptedBrowser, actorResolvesToTerminal, computerUseParticipantCount, registeredComputerUseActors, registeredScriptedBrowserActors, registeredTerminalActors, isComputerUseComposition, } from "./routing.js";
|
|
12
12
|
import { cloneTargetValidationReason, computerUseValidationReason, sharedWorldValidationReason, } from "./validation.js";
|
|
13
|
-
import {
|
|
13
|
+
import { actorOf, participantList, declaredParticipantCount, surfaceCount, modeOf, scenarioRefOf, declaresSharedWorld, } from "./study-fields.js";
|
|
14
14
|
/** The first composition rule a config breaks, in the parser's order, or null. */
|
|
15
15
|
export function compositionReason(config) {
|
|
16
16
|
return (thisRepoValidationReason(config) ??
|
|
@@ -25,7 +25,7 @@ export function compositionReason(config) {
|
|
|
25
25
|
(isComputerUseComposition(config) ? computerUseValidationReason(config) : null) ??
|
|
26
26
|
// Whenever shared world is declared, not only when it routes, so a half-declared shared world
|
|
27
27
|
// fails with a precise reason instead of running as independent participants.
|
|
28
|
-
(config
|
|
28
|
+
(declaresSharedWorld(config) ? sharedWorldValidationReason(config) : null) ??
|
|
29
29
|
desktopCliValidationReason(config) ??
|
|
30
30
|
terminalValidationReason(config) ??
|
|
31
31
|
cloneActorValidationReason(config));
|
|
@@ -39,7 +39,7 @@ function thisRepoValidationReason(config) {
|
|
|
39
39
|
if (config.execution?.target) {
|
|
40
40
|
return "`execution.target` applies only to clone/app-url/local-app subjects; this-repo studies run locally.";
|
|
41
41
|
}
|
|
42
|
-
if (config
|
|
42
|
+
if (modeOf(config) === "live") {
|
|
43
43
|
return THIS_REPO_DRY_RUN_ONLY;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -51,21 +51,21 @@ function thisRepoValidationReason(config) {
|
|
|
51
51
|
// desktop), and no public-target policy (it is always loopback; the loopback shape was already
|
|
52
52
|
// enforced in parseSubject). The actual "no inProcess executor supplied" case is inherently an
|
|
53
53
|
// engine-time decision (the parser cannot know whether a library caller will pass one), so
|
|
54
|
-
//
|
|
54
|
+
// planComputerUseStudy refuses it with HUMANISH_COMPUTER_USE_LOCAL_APP_NO_EXECUTOR.
|
|
55
55
|
function localAppValidationReason(config) {
|
|
56
56
|
if (config.subject.source === "local-app") {
|
|
57
|
-
const type = config
|
|
57
|
+
const type = actorOf(config)?.type ?? "";
|
|
58
58
|
if (config.execution?.target !== undefined && config.execution.target !== "local") {
|
|
59
59
|
return "A local-app subject drives a local dev server in this process, with no E2B desktop. Set `execution.target: local` or omit it. To run on a hosted desktop, use an app-url subject with `execution.target: e2b-desktop`.";
|
|
60
60
|
}
|
|
61
61
|
if (!actorResolvesToComputerUse(type)) {
|
|
62
|
-
return `
|
|
62
|
+
return `actor.type must be a registered computer-use actor for local-app subjects (one of: ${registeredComputerUseActors().join(", ")}); the caller's custom executor runs the computer-use loop. Got "${type}".`;
|
|
63
63
|
}
|
|
64
64
|
if (computerUseParticipantCount(config) > 1) {
|
|
65
|
-
return "Fan-out to more than one participant is not supported on the in-process/local-app route: fan-out provisions one independent E2B desktop per participant, which the in-process route deliberately skips. Set
|
|
65
|
+
return "Fan-out to more than one participant is not supported on the in-process/local-app route: fan-out provisions one independent E2B desktop per participant, which the in-process route deliberately skips. Set participants to 1 and drop participants (use an app-url or clone subject on execution.target: e2b-desktop for fan-out).";
|
|
66
66
|
}
|
|
67
|
-
if (
|
|
68
|
-
return "`
|
|
67
|
+
if (participantList(config) !== undefined) {
|
|
68
|
+
return "`participants` (fan-out roster) is not supported on the in-process/local-app route: it provisions one E2B desktop per participant, which this route skips. Use an app-url or clone subject with execution.target: e2b-desktop.";
|
|
69
69
|
}
|
|
70
70
|
if (config.policies?.allowPublicTargets === true) {
|
|
71
71
|
return "`policies.allowPublicTargets` does not apply to a local-app subject: it is always a loopback dev server, so there is no public target to allow. Remove the setting.";
|
|
@@ -78,18 +78,18 @@ function localAppValidationReason(config) {
|
|
|
78
78
|
// app; a computer-use actor drives a hosted desktop browser. Fail closed on mis-configs.
|
|
79
79
|
function appUrlValidationReason(config) {
|
|
80
80
|
if (config.subject.source === "app-url") {
|
|
81
|
-
const type = config
|
|
81
|
+
const type = actorOf(config)?.type ?? "";
|
|
82
82
|
if (actorResolvesToScriptedBrowser(type)) {
|
|
83
83
|
// Scripted-browser route (all fail-closed, so claims match mechanism: a field that cannot act on
|
|
84
84
|
// this route is rejected, never silently ignored).
|
|
85
85
|
if (config.execution?.target !== undefined && config.execution.target !== "local") {
|
|
86
86
|
return "A scripted-browser actor on an app-url subject runs on this machine. Set `execution.target: local` or omit it. To run scripted steps on a hosted desktop, use a clone subject.";
|
|
87
87
|
}
|
|
88
|
-
if (!config
|
|
89
|
-
return "A scripted-browser study needs `scenario
|
|
88
|
+
if (!scenarioRefOf(config)) {
|
|
89
|
+
return "A scripted-browser study needs `scenario`: the actor runs the browser steps in that scenario file.";
|
|
90
90
|
}
|
|
91
|
-
if ((config
|
|
92
|
-
return "A scripted-browser study takes `
|
|
91
|
+
if ((surfaceCount(config) ?? 1) > 2) {
|
|
92
|
+
return "A scripted-browser study takes `surfaces` 1 (desktop) or 2 (desktop and mobile). Higher counts are not supported yet.";
|
|
93
93
|
}
|
|
94
94
|
if (config.policies?.redactScreenshots === true) {
|
|
95
95
|
return "`policies.redactScreenshots: true` is not supported on the scripted-browser route yet, so its screenshots would be stored unredacted in .humanish/. Remove the setting, or use a computer-use actor, which blurs screenshots as it takes them.";
|
|
@@ -106,7 +106,7 @@ function appUrlValidationReason(config) {
|
|
|
106
106
|
return "app-url computer-use subjects require `execution.target: local` or `e2b-desktop`.";
|
|
107
107
|
}
|
|
108
108
|
if (!actorResolvesToComputerUse(type)) {
|
|
109
|
-
return `
|
|
109
|
+
return `actor.type must be a registered computer-use actor for app-url × e2b-desktop studies (one of: ${registeredComputerUseActors().join(", ")}); for local scripted execution use a registered scripted-browser actor (${registeredScriptedBrowserActors().join(", ")}). Got "${type}".`;
|
|
110
110
|
}
|
|
111
111
|
// Multi-participant fan-out is consumed on this route (`per-lane-worlds`; the shared cua
|
|
112
112
|
// cross-validation below enforces `lanes`/`count` XOR rules, the 16 cap, and the
|
|
@@ -116,7 +116,7 @@ function appUrlValidationReason(config) {
|
|
|
116
116
|
const targets = [config.subject.appUrl ?? "", ...declaredTargets(config)];
|
|
117
117
|
const unsafeTarget = targets.find((target) => !config.policies?.allowPublicTargets && !isLoopbackUrl(target));
|
|
118
118
|
if (unsafeTarget !== undefined) {
|
|
119
|
-
return "`subject.appUrl` and `
|
|
119
|
+
return "`subject.appUrl` and `participants[].target` must be loopback URLs (127.0.0.1 or localhost). To drive a deployed or preview URL you own, set `policies.allowPublicTargets: true`.";
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -125,7 +125,7 @@ function appUrlValidationReason(config) {
|
|
|
125
125
|
// Scripted-browser actors on any other subject: only a provisioned clone on e2b-desktop.
|
|
126
126
|
function scriptedBrowserValidationReason(config) {
|
|
127
127
|
if (config.subject.source !== "app-url" &&
|
|
128
|
-
actorResolvesToScriptedBrowser(config
|
|
128
|
+
actorResolvesToScriptedBrowser(actorOf(config)?.type)) {
|
|
129
129
|
if (config.subject.source !== "clone") {
|
|
130
130
|
return "scripted-browser actors require `subject.source: app-url` (a running app at a loopback URL) or `subject.source: clone` with `execution.target: e2b-desktop` (a provisioned synthetic subject).";
|
|
131
131
|
}
|
|
@@ -148,14 +148,14 @@ function scriptedBrowserValidationReason(config) {
|
|
|
148
148
|
if (config.subject.clone?.fanout !== undefined || config.subject.clone?.keep === true) {
|
|
149
149
|
return "A clone scripted-browser study does not support `subject.clone.fanout` or `subject.clone.keep` yet: its subject is always one sandbox, removed after the run.";
|
|
150
150
|
}
|
|
151
|
-
if (!config
|
|
152
|
-
return "A scripted-browser study needs `scenario
|
|
151
|
+
if (!scenarioRefOf(config)) {
|
|
152
|
+
return "A scripted-browser study needs `scenario`: the actor runs the browser steps in that scenario file.";
|
|
153
153
|
}
|
|
154
|
-
if ((config
|
|
155
|
-
return "A scripted-browser study takes `
|
|
154
|
+
if ((surfaceCount(config) ?? 1) > 2) {
|
|
155
|
+
return "A scripted-browser study takes `surfaces` 1 (desktop) or 2 (desktop and mobile). Higher counts are not supported yet.";
|
|
156
156
|
}
|
|
157
|
-
if (
|
|
158
|
-
return "`
|
|
157
|
+
if (participantList(config) !== undefined) {
|
|
158
|
+
return "`participants` is not supported on the scripted-browser route yet. Use `surfaces` to choose the desktop and mobile surfaces.";
|
|
159
159
|
}
|
|
160
160
|
if (config.policies?.redactScreenshots === true) {
|
|
161
161
|
return "`policies.redactScreenshots: true` is not supported on the scripted-browser route yet, so its screenshots would be stored unredacted in .humanish/. Remove the setting, or use a computer-use actor, which blurs screenshots as it takes them.";
|
|
@@ -183,7 +183,7 @@ function scriptedBrowserValidationReason(config) {
|
|
|
183
183
|
function cloneComputerUseValidationReason(config) {
|
|
184
184
|
if (config.subject.source === "clone" &&
|
|
185
185
|
config.execution?.target === "e2b-desktop" &&
|
|
186
|
-
actorResolvesToComputerUse(config
|
|
186
|
+
actorResolvesToComputerUse(actorOf(config)?.type)) {
|
|
187
187
|
if (!config.subject.serve) {
|
|
188
188
|
return "A clone subject on the computer-use route needs `subject.serve` (start and url): humanish starts the app in the sandbox before the participant opens it.";
|
|
189
189
|
}
|
|
@@ -210,8 +210,8 @@ function localTreeValidationReason(config) {
|
|
|
210
210
|
if (config.execution?.target !== "e2b-desktop") {
|
|
211
211
|
return "local-tree subjects require `execution.target: e2b-desktop`: the packed working tree is provisioned and served inside a hosted desktop sandbox; there is no local route for a local-tree subject.";
|
|
212
212
|
}
|
|
213
|
-
if (!actorResolvesToComputerUse(config
|
|
214
|
-
return `
|
|
213
|
+
if (!actorResolvesToComputerUse(actorOf(config)?.type)) {
|
|
214
|
+
return `actor.type must be a registered computer-use actor for local-tree subjects (one of: ${registeredComputerUseActors().join(", ")}); the actor drives the hosted desktop that serves the packed working tree. Got "${actorOf(config)?.type ?? ""}".`;
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
return null;
|
|
@@ -238,7 +238,7 @@ function desktopCliValidationReason(config) {
|
|
|
238
238
|
if (config.execution?.target !== undefined && config.execution.target !== "e2b-desktop") {
|
|
239
239
|
return "A desktop-cli subject runs on a hosted desktop. Set `execution.target: e2b-desktop` or omit it.";
|
|
240
240
|
}
|
|
241
|
-
if (!actorResolvesToComputerUse(config
|
|
241
|
+
if (!actorResolvesToComputerUse(actorOf(config)?.type ?? "")) {
|
|
242
242
|
return "desktop-cli subjects need a registered computer-use actor: the participant reads the screen and types, which is what makes an interactive surface studiable at all.";
|
|
243
243
|
}
|
|
244
244
|
const install = config.subject.product.install;
|
|
@@ -255,15 +255,15 @@ function desktopCliValidationReason(config) {
|
|
|
255
255
|
// fan-out lands.
|
|
256
256
|
function terminalValidationReason(config) {
|
|
257
257
|
if (config.subject.source === "terminal-product") {
|
|
258
|
-
const type = config
|
|
258
|
+
const type = actorOf(config)?.type ?? "";
|
|
259
259
|
if (config.execution?.target !== undefined && config.execution.target !== "e2b-terminal") {
|
|
260
260
|
return "A terminal-product subject runs its agent in an E2B shell. Set `execution.target: e2b-terminal` or omit it; `local` and `e2b-desktop` are not supported.";
|
|
261
261
|
}
|
|
262
262
|
if (!actorResolvesToTerminal(type)) {
|
|
263
|
-
return `
|
|
263
|
+
return `actor.type must be a registered terminal actor for terminal-product subjects (one of: ${registeredTerminalActors().join(", ")}). Got "${type}".`;
|
|
264
264
|
}
|
|
265
|
-
if ((config
|
|
266
|
-
return "Terminal fan-out to more than one participant is not supported yet; set
|
|
265
|
+
if ((declaredParticipantCount(config) ?? 1) > 1) {
|
|
266
|
+
return "Terminal fan-out to more than one participant is not supported yet; set participants to 1.";
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
else if (config.execution?.target === "e2b-terminal") {
|
|
@@ -271,7 +271,7 @@ function terminalValidationReason(config) {
|
|
|
271
271
|
// mis-config: reject, never silently mishandle (mirrors app-url's e2b-desktop pairing rule).
|
|
272
272
|
return "`execution.target: e2b-terminal` requires `subject.source: terminal-product` with a registered terminal actor.";
|
|
273
273
|
}
|
|
274
|
-
else if (actorResolvesToTerminal(config
|
|
274
|
+
else if (actorResolvesToTerminal(actorOf(config)?.type)) {
|
|
275
275
|
// A registered terminal actor on a non-terminal-product subject: rejected, never ignored (the
|
|
276
276
|
// terminal agent only studies a declared terminal-product from public surfaces).
|
|
277
277
|
return "terminal actors require `subject.source: terminal-product` (a CLI/product the agent studies from public surfaces); other subjects are not supported on this route.";
|
|
@@ -283,7 +283,7 @@ function terminalValidationReason(config) {
|
|
|
283
283
|
// Terminal actors were already refused above with their own message.
|
|
284
284
|
function cloneActorValidationReason(config) {
|
|
285
285
|
if (config.subject.source === "clone") {
|
|
286
|
-
const type = config
|
|
286
|
+
const type = actorOf(config)?.type ?? "";
|
|
287
287
|
if (!actorResolvesToComputerUse(type) && !actorResolvesToScriptedBrowser(type)) {
|
|
288
288
|
return `clone subjects need a registered computer-use actor (one of: ${registeredComputerUseActors().join(", ")}) or scripted-browser actor (one of: ${registeredScriptedBrowserActors().join(", ")}); humanish clones and serves the app for that participant to drive. Got "${type}".`;
|
|
289
289
|
}
|
package/dist/study/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StudyParseResult } from "./types.js";
|
|
1
|
+
import { type StudyConfig, type StudyParseResult } from "./types.js";
|
|
2
2
|
/** The message for a humanish.lab.v2 document, which the package and the CLI no longer run. */
|
|
3
3
|
export declare const V2_UNSUPPORTED_MESSAGE = "This is a humanish.lab.v2 study, which humanish no longer reads. Convert its file with humanish migrate <path>.";
|
|
4
4
|
/**
|
|
@@ -10,6 +10,13 @@ export declare function parseStudy(raw: unknown): StudyParseResult;
|
|
|
10
10
|
* Validate a parsed YAML object, humanish.study.v3 or humanish.lab.v2, into a StudyConfig. Pure:
|
|
11
11
|
* the caller owns file IO. Structural validation only. Fields the engine does not yet consume are
|
|
12
12
|
* accepted but reported in `warnings` so `study show` never silently swallows a setting that does
|
|
13
|
-
* nothing.
|
|
13
|
+
* nothing. The v2 parser's tests call it directly; migrate reads v2 files through migrate/v2.ts.
|
|
14
14
|
*/
|
|
15
15
|
export declare function parseStudyDocument(raw: unknown): StudyParseResult;
|
|
16
|
+
/**
|
|
17
|
+
* The checks that read more than one section of a parsed config, and the defaults they fill:
|
|
18
|
+
* shared-world `execution.concurrency`, `comms.email.recipients` and the local browser's defaults.
|
|
19
|
+
* parseV2 runs them once every section has parsed. parse/study.ts runs them on its v2 view of a v3
|
|
20
|
+
* study.
|
|
21
|
+
*/
|
|
22
|
+
export declare function checkStudyConfig(config: StudyConfig): StudyParseResult;
|
package/dist/study/config.js
CHANGED
|
@@ -63,7 +63,7 @@ import { invalid, optionalStr, str } from "./parse/values.js";
|
|
|
63
63
|
import { isComputerUseComposition, isSharedWorldComposition, routeOf } from "./routing.js";
|
|
64
64
|
import { declaredParticipantIds } from "./plan-participants.js";
|
|
65
65
|
import { ID_PATTERN, V2_SCHEMA, STUDY_SCHEMA, } from "./types.js";
|
|
66
|
-
import { automaticAnalysisRouteReason, desktopMediaValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, scenarioCapsValidationReason, taskProtocolValidationReason, } from "./validation.js";
|
|
66
|
+
import { automaticAnalysisRouteReason, desktopMediaValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, scenarioCapsValidationReason, smtpValidationReason, taskProtocolValidationReason, } from "./validation.js";
|
|
67
67
|
import { forwardDeclaredWarnings, inertFieldLabels } from "./warnings.js";
|
|
68
68
|
import { plural } from "../run/text.js";
|
|
69
69
|
/** The message for a humanish.lab.v2 document, which the package and the CLI no longer run. */
|
|
@@ -84,7 +84,7 @@ export function parseStudy(raw) {
|
|
|
84
84
|
* Validate a parsed YAML object, humanish.study.v3 or humanish.lab.v2, into a StudyConfig. Pure:
|
|
85
85
|
* the caller owns file IO. Structural validation only. Fields the engine does not yet consume are
|
|
86
86
|
* accepted but reported in `warnings` so `study show` never silently swallows a setting that does
|
|
87
|
-
* nothing.
|
|
87
|
+
* nothing. The v2 parser's tests call it directly; migrate reads v2 files through migrate/v2.ts.
|
|
88
88
|
*/
|
|
89
89
|
export function parseStudyDocument(raw) {
|
|
90
90
|
if (!isRecord(raw)) {
|
|
@@ -190,9 +190,18 @@ function parseV2(raw) {
|
|
|
190
190
|
return commsResult;
|
|
191
191
|
if (commsResult.value)
|
|
192
192
|
config.comms = commsResult.value;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
return checkStudyConfig(config);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The checks that read more than one section of a parsed config, and the defaults they fill:
|
|
197
|
+
* shared-world `execution.concurrency`, `comms.email.recipients` and the local browser's defaults.
|
|
198
|
+
* parseV2 runs them once every section has parsed. parse/study.ts runs them on its v2 view of a v3
|
|
199
|
+
* study.
|
|
200
|
+
*/
|
|
201
|
+
export function checkStudyConfig(config) {
|
|
202
|
+
const smtpReason = smtpValidationReason(config);
|
|
203
|
+
if (smtpReason)
|
|
204
|
+
return invalid(smtpReason);
|
|
196
205
|
const mediaReason = desktopMediaValidationReason(config);
|
|
197
206
|
if (mediaReason)
|
|
198
207
|
return invalid(mediaReason);
|
package/dist/study/doctor.js
CHANGED
|
@@ -9,6 +9,7 @@ import { externalCatchHealthy } from "../comms/sandbox-catch.js";
|
|
|
9
9
|
import { receivingRequiredKey } from "../comms/setup.js";
|
|
10
10
|
import { classifyCodexInstallation, codexInstallAdvice, codexVersionRecovery, } from "../actors/codex/codex-admission.js";
|
|
11
11
|
import { cli } from "../cli/invocation.js";
|
|
12
|
+
import { actorOf } from "./study-fields.js";
|
|
12
13
|
/** A ready row's message, with the schema values the launch recorded. */
|
|
13
14
|
function readyMessage(message, readiness) {
|
|
14
15
|
const additions = protocolAdditionsWarning(readiness.cliVersion, readiness.protocolAdditions);
|
|
@@ -151,7 +152,7 @@ export async function studySetupChecks(args) {
|
|
|
151
152
|
{
|
|
152
153
|
name: "study route",
|
|
153
154
|
ok: true,
|
|
154
|
-
message: `${config.id}: ${config
|
|
155
|
+
message: `${config.id}: ${actorOf(config)?.type ?? "synthetic"} / ${route} / ${dryRun ? "dry-run (no live participant)" : "live"}`,
|
|
155
156
|
},
|
|
156
157
|
];
|
|
157
158
|
if (dryRun)
|
|
@@ -246,16 +247,16 @@ async function participantChecks(config, route, keys, local, args, checkAccount)
|
|
|
246
247
|
];
|
|
247
248
|
}
|
|
248
249
|
if ((route !== "computer-use" && route !== "shared-world") ||
|
|
249
|
-
config
|
|
250
|
+
actorOf(config)?.type !== "local-agent")
|
|
250
251
|
return [];
|
|
251
|
-
const choice = config
|
|
252
|
+
const choice = actorOf(config)?.localAgent ?? "codex";
|
|
252
253
|
const agent = args.agents.find((entry) => entry.id === choice);
|
|
253
254
|
if (local)
|
|
254
255
|
return [await localCodexParticipantCheck({ env: args.env, readiness: checkAccount })];
|
|
255
256
|
// A signed-in hosted Codex gets the operator handshake; its sign-in status alone does not show
|
|
256
257
|
// that the operator's config, release and model admit a launch.
|
|
257
258
|
if (choice === "codex" && agent?.authStatus === "authenticated")
|
|
258
|
-
return [await hostedCodexParticipantCheck(args, config
|
|
259
|
+
return [await hostedCodexParticipantCheck(args, actorOf(config))];
|
|
259
260
|
const outdated = agent === undefined ? undefined : localAgentVersionRefusal(agent);
|
|
260
261
|
return [
|
|
261
262
|
{
|
|
@@ -24,5 +24,5 @@ export declare const humanishScripts: Record<string, string>;
|
|
|
24
24
|
* a signed-in Codex and no provider key, the study runs with the operator's own agent and needs
|
|
25
25
|
* only E2B. Writing the other one there would hand someone homework instead of a first run.
|
|
26
26
|
*/
|
|
27
|
-
export declare function starterFilesFor(actor: StarterActor, localBrowser?: LocalBrowserStarter, localAgent?: LocalAgentId): StarterFile[];
|
|
27
|
+
export declare function starterFilesFor(actor: StarterActor, localBrowser?: LocalBrowserStarter, localAgent?: LocalAgentId, command?: string): StarterFile[];
|
|
28
28
|
export {};
|
|
@@ -34,6 +34,44 @@ defaults:
|
|
|
34
34
|
`,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The `humanish/README.md` init writes. Its commands start with `command`, the invocation init
|
|
39
|
+
* chose for this install (src/cli/invocation.ts), as in the section init writes to `AGENTS.md`.
|
|
40
|
+
*/
|
|
41
|
+
function readmeFile(command) {
|
|
42
|
+
return {
|
|
43
|
+
path: "humanish/README.md",
|
|
44
|
+
plane: "source",
|
|
45
|
+
contents: `# humanish
|
|
46
|
+
|
|
47
|
+
This directory holds the committed definitions of this app's humanish studies: study files,
|
|
48
|
+
personas and scenarios.
|
|
49
|
+
|
|
50
|
+
Keep this directory public-safe:
|
|
51
|
+
|
|
52
|
+
- synthetic personas only;
|
|
53
|
+
- synthetic fixtures only;
|
|
54
|
+
- env var names only, never values;
|
|
55
|
+
- no PII, PHI, secrets, raw private transcripts, private screenshots, customer data, or patient data.
|
|
56
|
+
|
|
57
|
+
Generated run bundles, screenshots, traces, logs, and local overrides belong in ignored \`.humanish/\`.
|
|
58
|
+
|
|
59
|
+
Studies:
|
|
60
|
+
|
|
61
|
+
- committed reusable studies live in humanish/studies/*.yaml;
|
|
62
|
+
- private or machine-local studies live in ignored .humanish/studies/*.yaml or
|
|
63
|
+
.humanish/local/studies/*.yaml;
|
|
64
|
+
- run a study with \`${command} run <study>\`, or \`${command} watch <study>\` to stay attached;
|
|
65
|
+
- files under humanish/labs/ move there with \`${command} migrate\`.
|
|
66
|
+
|
|
67
|
+
Format standard:
|
|
68
|
+
|
|
69
|
+
- human-authored humanish source uses .yaml;
|
|
70
|
+
- generated artifacts, synthetic fixtures, and event streams use .json or .ndjson;
|
|
71
|
+
- .yml is reserved for outside ecosystem files such as GitHub Actions, not humanish source.
|
|
72
|
+
`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
37
75
|
/**
|
|
38
76
|
* The flagship live study. A ChatGPT-account Codex participant has no API-dollar price, so preflight
|
|
39
77
|
* refuses a dollar cap on it (HUMANISH_COMPUTER_USE_UNPRICED_CAP); that variant is bounded by the
|
|
@@ -113,38 +151,7 @@ defaults:
|
|
|
113
151
|
};
|
|
114
152
|
}
|
|
115
153
|
export const starterFiles = [
|
|
116
|
-
|
|
117
|
-
path: "humanish/README.md",
|
|
118
|
-
plane: "source",
|
|
119
|
-
contents: `# humanish
|
|
120
|
-
|
|
121
|
-
This directory holds the committed definitions of this app's humanish studies: study files,
|
|
122
|
-
personas and scenarios.
|
|
123
|
-
|
|
124
|
-
Keep this directory public-safe:
|
|
125
|
-
|
|
126
|
-
- synthetic personas only;
|
|
127
|
-
- synthetic fixtures only;
|
|
128
|
-
- env var names only, never values;
|
|
129
|
-
- no PII, PHI, secrets, raw private transcripts, private screenshots, customer data, or patient data.
|
|
130
|
-
|
|
131
|
-
Generated run bundles, screenshots, traces, logs, and local overrides belong in ignored \`.humanish/\`.
|
|
132
|
-
|
|
133
|
-
Studies:
|
|
134
|
-
|
|
135
|
-
- committed reusable studies live in humanish/studies/*.yaml;
|
|
136
|
-
- private or machine-local studies live in ignored .humanish/studies/*.yaml or
|
|
137
|
-
.humanish/local/studies/*.yaml;
|
|
138
|
-
- run a study with \`humanish run <study>\`, or \`humanish watch <study>\` to stay attached;
|
|
139
|
-
- files under humanish/labs/ move there with \`humanish migrate\`.
|
|
140
|
-
|
|
141
|
-
Format standard:
|
|
142
|
-
|
|
143
|
-
- human-authored humanish source uses .yaml;
|
|
144
|
-
- generated artifacts, synthetic fixtures, and event streams use .json or .ndjson;
|
|
145
|
-
- .yml is reserved for outside ecosystem files such as GitHub Actions, not humanish source.
|
|
146
|
-
`,
|
|
147
|
-
},
|
|
154
|
+
readmeFile("humanish"),
|
|
148
155
|
{
|
|
149
156
|
path: "humanish/personas/synthetic-new-user.yaml",
|
|
150
157
|
plane: "source",
|
|
@@ -317,10 +324,12 @@ export const humanishScripts = {
|
|
|
317
324
|
* a signed-in Codex and no provider key, the study runs with the operator's own agent and needs
|
|
318
325
|
* only E2B. Writing the other one there would hand someone homework instead of a first run.
|
|
319
326
|
*/
|
|
320
|
-
export function starterFilesFor(actor, localBrowser = DEFAULT_LOCAL_BROWSER_STARTER, localAgent) {
|
|
327
|
+
export function starterFilesFor(actor, localBrowser = DEFAULT_LOCAL_BROWSER_STARTER, localAgent, command = "humanish") {
|
|
321
328
|
return starterFiles.map((file) => file.path === "humanish/studies/local-browser.yaml"
|
|
322
329
|
? localBrowserStudy(localBrowser)
|
|
323
330
|
: file.path === "humanish/studies/try-live.yaml"
|
|
324
331
|
? tryLiveStudy(actor, localAgent)
|
|
325
|
-
: file
|
|
332
|
+
: file.path === "humanish/README.md"
|
|
333
|
+
? readmeFile(command)
|
|
334
|
+
: file);
|
|
326
335
|
}
|
package/dist/study/init.js
CHANGED
|
@@ -47,7 +47,7 @@ export async function runInit(options) {
|
|
|
47
47
|
const machine = await firstRunEnvironment(options.env ?? process.env, requestedCwd);
|
|
48
48
|
await planAgentsFile(preparedProjectRoot, cwd, plan, humanishCommand());
|
|
49
49
|
const starterActor = starterActorFor(machine);
|
|
50
|
-
await planStarterFiles(preparedProjectRoot, cwd, starterFilesFor(starterActor, localBrowser.value, starterLocalAgentFor(machine)), options.localBrowser !== undefined, plan);
|
|
50
|
+
await planStarterFiles(preparedProjectRoot, cwd, starterFilesFor(starterActor, localBrowser.value, starterLocalAgentFor(machine), machine.humanish), options.localBrowser !== undefined, plan);
|
|
51
51
|
await planRuntimeDirectories(preparedProjectRoot, cwd, plan);
|
|
52
52
|
const gitignorePlan = await planGitignore(preparedProjectRoot, cwd);
|
|
53
53
|
plan.changes.push(gitignorePlan.change);
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
// Convert a humanish.lab.v2 study file to humanish.study.v3. It edits the parsed YAML document in
|
|
2
2
|
// place and moves each pair node, so the comments on a moved key move with it. The result lists
|
|
3
3
|
// every key it moved and every key it dropped. It refuses a file whose v3 form would parse or plan
|
|
4
|
-
// differently from the v2 source with the dropped keys removed.
|
|
4
|
+
// differently from the v2 source with the dropped keys removed. v2.ts reads the v2 source.
|
|
5
5
|
import { isAlias, isMap, isScalar, isSeq, LineCounter, Pair, parseDocument, Scalar, visit, YAMLMap, YAMLSeq, } from "yaml";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { posInt } from "
|
|
9
|
-
import { planStudy } from "
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { deleteNodeFieldPath, deletePlainFieldPath, pairIndex, readFieldPath, } from "./field-paths.js";
|
|
6
|
+
import { parseStudy } from "../config.js";
|
|
7
|
+
import { parseStudyV3 } from "../parse/study.js";
|
|
8
|
+
import { posInt } from "../parse/values.js";
|
|
9
|
+
import { planStudy } from "../plan.js";
|
|
10
|
+
import { V2_SCHEMA, STUDY_SCHEMA } from "../types.js";
|
|
11
|
+
import { deleteNodeFieldPath, deletePlainFieldPath, pairIndex, readFieldPath, } from "../field-paths.js";
|
|
12
|
+
import { checkV2, droppedPaths, v2ShapeReason, v2ToV3Raw } from "./v2.js";
|
|
14
13
|
function isPlainRecord(value) {
|
|
15
14
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
15
|
}
|
|
@@ -36,10 +35,19 @@ export function convertStudyText(text, cwd) {
|
|
|
36
35
|
return refuse("it is not a YAML mapping.");
|
|
37
36
|
if (raw.schema !== V2_SCHEMA)
|
|
38
37
|
return refuse(`its schema is not ${V2_SCHEMA}.`);
|
|
39
|
-
|
|
38
|
+
// The v2 parser's checks: the sections and the v2-only budget rule, then the checks the formats
|
|
39
|
+
// share on the v3 form of the file, then the rules on v2 shapes the v3 form cannot hold.
|
|
40
|
+
const source = checkV2(raw);
|
|
40
41
|
if (!source.ok)
|
|
41
|
-
return refuse(`it does not parse: ${source.
|
|
42
|
-
const
|
|
42
|
+
return refuse(`it does not parse: ${source.message}`);
|
|
43
|
+
const study = source.value;
|
|
44
|
+
const { route } = study;
|
|
45
|
+
const view = parseStudyV3(v2ToV3Raw(raw, route), { inert: "report" });
|
|
46
|
+
if (!view.ok)
|
|
47
|
+
return refuse(`it does not parse: ${view.error.message}`);
|
|
48
|
+
const shape = v2ShapeReason(study);
|
|
49
|
+
if (shape)
|
|
50
|
+
return refuse(`it does not parse: ${shape}`);
|
|
43
51
|
const actorRaw = Array.isArray(raw.actors) ? raw.actors[0] : undefined;
|
|
44
52
|
if (route === "terminal" && isPlainRecord(actorRaw)) {
|
|
45
53
|
for (const key of ["count", "laneFocus"]) {
|
|
@@ -50,7 +58,7 @@ export function convertStudyText(text, cwd) {
|
|
|
50
58
|
}
|
|
51
59
|
const root = doc.contents;
|
|
52
60
|
const dropped = [];
|
|
53
|
-
for (const path of droppedPaths(
|
|
61
|
+
for (const path of droppedPaths(study, view.inert ?? [])) {
|
|
54
62
|
const value = readFieldPath(raw, path);
|
|
55
63
|
const pairs = deleteNodeFieldPath(root, path);
|
|
56
64
|
if (value === undefined || pairs.length === 0)
|
|
@@ -60,8 +68,7 @@ export function convertStudyText(text, cwd) {
|
|
|
60
68
|
}
|
|
61
69
|
const moved = [];
|
|
62
70
|
// v2 reads some laneFocus values as no focus at all ({}, a valueless key, valueless fields).
|
|
63
|
-
const
|
|
64
|
-
const converted = convertDocument(root, route, focused, moved);
|
|
71
|
+
const converted = convertDocument(root, route, study.focus !== undefined, moved);
|
|
65
72
|
if (!converted.ok)
|
|
66
73
|
return converted;
|
|
67
74
|
const out = doc.toString({ lineWidth: foldWidth(text), flowCollectionPadding: false });
|
|
@@ -69,7 +76,7 @@ export function convertStudyText(text, cwd) {
|
|
|
69
76
|
const lost = lostComment(text, out, dropped);
|
|
70
77
|
if (lost !== undefined)
|
|
71
78
|
return refuse(`it would lose the comment "${lost}".`);
|
|
72
|
-
const check = samePlans(raw, dropped.map((key) => key.path), out, cwd);
|
|
79
|
+
const check = samePlans(raw, route, dropped.map((key) => key.path), out, cwd);
|
|
73
80
|
if (!check.ok)
|
|
74
81
|
return check;
|
|
75
82
|
return { ok: true, conversion: { text: out, route, moved, dropped } };
|
|
@@ -105,16 +112,6 @@ function lostComment(source, output, dropped) {
|
|
|
105
112
|
}
|
|
106
113
|
return undefined;
|
|
107
114
|
}
|
|
108
|
-
// The inert-field table's paths for the route, plus terminal `execution.timeoutMs`, which no
|
|
109
|
-
// terminal code reads. Parents come before their children, so a child under a dropped parent is
|
|
110
|
-
// reported with the parent.
|
|
111
|
-
function droppedPaths(config, route, raw) {
|
|
112
|
-
const paths = inertFieldPaths(config);
|
|
113
|
-
if (route === "terminal" && readFieldPath(raw, "execution.timeoutMs") !== undefined)
|
|
114
|
-
paths.push("execution.timeoutMs");
|
|
115
|
-
const depth = (path) => path.split(".").length;
|
|
116
|
-
return [...new Set(paths)].sort((left, right) => depth(left) - depth(right));
|
|
117
|
-
}
|
|
118
115
|
// Folded text is folded again on output. Most of a file's long lines end near the width its author
|
|
119
116
|
// folded at, so the 90th percentile of their lengths stands for it, between 80 and 120.
|
|
120
117
|
function foldWidth(text) {
|
|
@@ -406,19 +403,20 @@ function numericCount(pair) {
|
|
|
406
403
|
function plain(value) {
|
|
407
404
|
return JSON.parse(JSON.stringify(value));
|
|
408
405
|
}
|
|
409
|
-
|
|
406
|
+
// The YAML rewrite against v2ToV3Raw of the source without the dropped keys: two rewrites written
|
|
407
|
+
// separately, which must parse to the same study and plan the same runs.
|
|
408
|
+
function samePlans(raw, route, dropped, text, cwd) {
|
|
410
409
|
const projected = structuredClone(raw);
|
|
411
410
|
for (const path of dropped)
|
|
412
411
|
deletePlainFieldPath(projected, path);
|
|
413
|
-
const before =
|
|
412
|
+
const before = parseStudy(v2ToV3Raw(projected, route));
|
|
414
413
|
if (!before.ok) {
|
|
415
414
|
return refuse(`it does not parse once the unread keys are dropped: ${before.error.message}`);
|
|
416
415
|
}
|
|
417
|
-
const after =
|
|
416
|
+
const after = parseStudy(parseDocument(text).toJS());
|
|
418
417
|
if (!after.ok)
|
|
419
418
|
return refuse(`its v3 form does not parse: ${after.error.message}`);
|
|
420
|
-
if (JSON.stringify(plain(
|
|
421
|
-
JSON.stringify(plain(before.config))) {
|
|
419
|
+
if (JSON.stringify(plain(after.config)) !== JSON.stringify(plain(before.config))) {
|
|
422
420
|
return refuse("its v3 form parses to a different study.");
|
|
423
421
|
}
|
|
424
422
|
for (const dryRun of [true, false]) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StudyRoute } from "
|
|
1
|
+
import type { StudyRoute } from "../routing.js";
|
|
2
2
|
import { type DroppedKey, type MovedKey } from "./convert.js";
|
|
3
3
|
/** The schema of `humanish migrate --json`. */
|
|
4
4
|
declare const MIGRATE_SCHEMA = "humanish.migrate-result.v1";
|
|
@@ -14,9 +14,9 @@ import { randomUUID } from "node:crypto";
|
|
|
14
14
|
import { link, lstat, readdir, readFile, realpath, rename, rmdir } from "node:fs/promises";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { parseDocument } from "yaml";
|
|
17
|
-
import { isNodeError } from "
|
|
17
|
+
import { isNodeError } from "../../run/type-guards.js";
|
|
18
18
|
import { convertStudyText, isStudyV3 } from "./convert.js";
|
|
19
|
-
import { otherStudyFiles, STUDY_DIRECTORIES, studyFileStem } from "
|
|
19
|
+
import { otherStudyFiles, STUDY_DIRECTORIES, studyFileStem } from "../files.js";
|
|
20
20
|
import { assertDirectory, bindDirectory, exists, fileIdentity, NO_HARD_LINKS, removeChecked, removeOwned, restoreName, sameFile, sha256, writeOwned, } from "./owned-files.js";
|
|
21
21
|
/** The schema of `humanish migrate --json`. */
|
|
22
22
|
const MIGRATE_SCHEMA = "humanish.migrate-result.v1";
|
|
@@ -6,7 +6,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
6
6
|
import { constants } from "node:fs";
|
|
7
7
|
import { lstat, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import { isNodeError } from "
|
|
9
|
+
import { isNodeError } from "../../run/type-guards.js";
|
|
10
10
|
export function sha256(bytes) {
|
|
11
11
|
return createHash("sha256").update(bytes).digest("hex");
|
|
12
12
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type StudyRoute } from "../routing.js";
|
|
2
|
+
import { type StudyConfig, type StudyParticipantEntry, type StudyScenarioCaps } from "../types.js";
|
|
3
|
+
type Raw = Record<string, unknown>;
|
|
4
|
+
type Read<T> = {
|
|
5
|
+
ok: true;
|
|
6
|
+
value: T;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
/** `laneFocus`: the steer each of a counted group of participants gets. */
|
|
12
|
+
interface V2Focus {
|
|
13
|
+
id?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
instruction?: string;
|
|
16
|
+
}
|
|
17
|
+
/** The `scenario` object. */
|
|
18
|
+
interface V2Scenario {
|
|
19
|
+
ref?: string;
|
|
20
|
+
mode?: "dry-run" | "live";
|
|
21
|
+
inline?: Raw;
|
|
22
|
+
caps?: StudyScenarioCaps;
|
|
23
|
+
}
|
|
24
|
+
/** A v2 file that checkV2 accepted, as the v2 parser reads it. */
|
|
25
|
+
export interface V2Study {
|
|
26
|
+
readonly route: StudyRoute;
|
|
27
|
+
/**
|
|
28
|
+
* The sections that decide the route and that the v2 parser's first cross-section checks read:
|
|
29
|
+
* subject, the actor without its participant keys, execution, policies and comms.
|
|
30
|
+
*/
|
|
31
|
+
readonly composition: StudyConfig;
|
|
32
|
+
readonly count: number | undefined;
|
|
33
|
+
/** `actors[0].lanes`, or the participants `actors[0].roster` expands into. */
|
|
34
|
+
readonly entries: readonly StudyParticipantEntry[] | undefined;
|
|
35
|
+
readonly focus: V2Focus | undefined;
|
|
36
|
+
readonly executionCaps: StudyScenarioCaps | undefined;
|
|
37
|
+
readonly scenario: V2Scenario | undefined;
|
|
38
|
+
readonly personas: boolean;
|
|
39
|
+
readonly terminalTimeout: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check a humanish.lab.v2 file the way the v2 parser does, up to the checks that read more than
|
|
43
|
+
* one section, and then the budget rule those checks apply first. Each refusal is that parser's
|
|
44
|
+
* message.
|
|
45
|
+
*/
|
|
46
|
+
export declare function checkV2(raw: Raw): Read<V2Study>;
|
|
47
|
+
/**
|
|
48
|
+
* The refusals the v2 parser gives for shapes a v3 file cannot express, once the checks both
|
|
49
|
+
* formats share have passed on the study without them. A file that also breaks a shared check
|
|
50
|
+
* gets that check's message.
|
|
51
|
+
*/
|
|
52
|
+
export declare function v2ShapeReason(study: V2Study): string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* A v2 file as a plain humanish.study.v3 record, for parseStudyV3 and parseStudy. It reads each key
|
|
55
|
+
* it moves the way the v2 parser reads it (`count` as a positive integer, `laneFocus` through its
|
|
56
|
+
* string fields, `roster` groups expanded, `scenario.ref` and `scenario.mode` as strings), and
|
|
57
|
+
* leaves out every key no v3 file can hold, so it parses whenever the v2 parser would. A caps
|
|
58
|
+
* block keeps the keys its route reads. A shared world keeps a count or no list, which only
|
|
59
|
+
* parseStudyV3's report mode takes.
|
|
60
|
+
*/
|
|
61
|
+
export declare function v2ToV3Raw(raw: Raw, route: StudyRoute): Raw;
|
|
62
|
+
/**
|
|
63
|
+
* The keys migrate drops: the v2-only rows above and `inert`, the fields parseStudyV3 reports the
|
|
64
|
+
* route does not read, then terminal `execution.timeoutMs`, which no terminal code reads. Parents
|
|
65
|
+
* come before their children, so a child under a dropped parent is reported with the parent.
|
|
66
|
+
*/
|
|
67
|
+
export declare function droppedPaths(study: V2Study, inert: readonly string[]): string[];
|
|
68
|
+
export {};
|