humanish 0.110.3 → 0.111.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/CONTEXT.md +2 -2
- package/dist/actors/local-agent/claude-session.js +1 -1
- package/dist/actors/local-agent/cli.js +1 -1
- package/dist/actors/scripted-browser/actor.d.ts +2 -2
- 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 +7 -2
- package/dist/cli/commands/migrate.js +1 -1
- 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.js +2 -1
- 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-model.js +1 -1
- package/dist/routes/computer-use/plan.js +9 -10
- package/dist/routes/computer-use/preflight.js +1 -1
- package/dist/routes/computer-use/result.js +1 -1
- package/dist/routes/computer-use/route.js +3 -2
- package/dist/routes/computer-use/setup.js +2 -1
- package/dist/routes/computer-use/types.d.ts +4 -4
- package/dist/routes/scripted/plan.js +5 -4
- package/dist/routes/scripted/route.js +6 -2
- package/dist/routes/scripted/scenario.d.ts +1 -1
- package/dist/routes/scripted/scenario.js +3 -3
- 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 +2 -2
- package/dist/routes/shared-world/handoff.js +1 -1
- package/dist/routes/shared-world/participant-specs.js +6 -2
- package/dist/routes/shared-world/plan.js +8 -8
- package/dist/routes/shared-world/route.js +3 -3
- package/dist/routes/shared-world/types.d.ts +1 -1
- package/dist/routes/terminal/bundle.d.ts +3 -3
- package/dist/routes/terminal/ledger.d.ts +2 -2
- package/dist/routes/terminal/lifetime.js +1 -1
- package/dist/routes/terminal/live-finish.js +1 -1
- package/dist/routes/terminal/plan.js +4 -3
- package/dist/routes/terminal/route.js +3 -2
- package/dist/routes/terminal/session.js +1 -0
- package/dist/routes/terminal/types.d.ts +1 -1
- 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/results.d.ts +1 -1
- package/dist/run/run.d.ts +11 -1
- package/dist/run/run.js +24 -5
- package/dist/run/status.d.ts +6 -0
- package/dist/run/status.js +1 -0
- package/dist/run/stored-runs.js +26 -11
- package/dist/run-study.d.ts +5 -3
- package/dist/run-study.js +7 -11
- package/dist/study/adapter-scorer-loader.d.ts +1 -1
- package/dist/study/adapter-scorer-loader.js +3 -3
- package/dist/study/composition-rules.js +49 -39
- package/dist/study/config.d.ts +31 -8
- package/dist/study/config.js +181 -155
- package/dist/study/discover.d.ts +1 -1
- package/dist/study/discover.js +1 -1
- package/dist/study/doctor.js +5 -4
- package/dist/study/keys.d.ts +250 -2
- package/dist/study/keys.js +32 -62
- package/dist/study/{convert.d.ts → migrate/convert.d.ts} +1 -1
- package/dist/study/{convert.js → migrate/convert.js} +28 -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 +78 -0
- package/dist/study/migrate/v2.js +607 -0
- package/dist/study/parse/actors.d.ts +34 -10
- package/dist/study/parse/actors.js +76 -174
- package/dist/study/parse/comms.js +6 -2
- package/dist/study/parse/execution.d.ts +9 -4
- package/dist/study/parse/execution.js +6 -41
- package/dist/study/parse/front.d.ts +36 -0
- package/dist/study/parse/{study-v3.js → front.js} +37 -88
- package/dist/study/parse/subject.d.ts +2 -0
- package/dist/study/parse/subject.js +15 -18
- package/dist/study/persona-resolve.d.ts +2 -4
- package/dist/study/persona-resolve.js +7 -8
- package/dist/study/plan-base.d.ts +1 -1
- package/dist/study/plan-base.js +6 -6
- package/dist/study/plan-participants.d.ts +2 -2
- package/dist/study/plan-participants.js +12 -11
- package/dist/study/plan-types.d.ts +5 -5
- package/dist/study/plan.js +5 -4
- package/dist/study/preflight-probes.js +4 -4
- package/dist/study/preflight.d.ts +1 -1
- package/dist/study/preflight.js +3 -3
- package/dist/study/routing.d.ts +5 -5
- package/dist/study/routing.js +17 -20
- package/dist/study/run-study-options.d.ts +1 -1
- package/dist/study/run-study-options.js +3 -3
- package/dist/study/study-deps.d.ts +1 -1
- package/dist/study/study-fields.d.ts +19 -0
- package/dist/study/study-fields.js +46 -0
- package/dist/study/summary.js +19 -18
- package/dist/study/types.d.ts +66 -104
- package/dist/study/types.js +0 -1
- package/dist/study/url-credentials.js +3 -3
- package/dist/study/validation.d.ts +4 -10
- package/dist/study/validation.js +54 -79
- package/dist/study/warnings.d.ts +3 -3
- package/dist/study/warnings.js +42 -110
- package/dist/substrates/local/runtime-config.js +14 -16
- package/dist/tui-app.js +3 -3
- package/docs/contracts/schemas.md +36 -8
- package/package.json +1 -1
- package/dist/study/parse/study-v3.d.ts +0 -21
- /package/dist/study/{owned-files.d.ts → migrate/owned-files.d.ts} +0 -0
package/dist/study/routing.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { actorRegistry } from "../actors/registry.js";
|
|
2
2
|
import { isLoopbackUrl } from "./parse/subject.js";
|
|
3
|
+
import { actorOf, participantList, declaredParticipantCount, declaresSharedWorld, } from "./study-fields.js";
|
|
3
4
|
// Hard cap on computer-use participants. No setting raises it: each participant is a paid desktop,
|
|
4
5
|
// and they all run at once.
|
|
5
6
|
export const MAX_COMPUTER_USE_PARTICIPANTS = 16;
|
|
@@ -45,16 +46,12 @@ export function registeredTerminalActors() {
|
|
|
45
46
|
return registeredActorsOn("terminal");
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
|
-
* The declared fan-out participant count on the computer-use route: a `
|
|
49
|
-
* a
|
|
49
|
+
* The declared fan-out participant count on the computer-use route: a `participants` list's
|
|
50
|
+
* length, else a participant count, else 1. The single source of truth shared by the parser, the engine,
|
|
50
51
|
* and the pre-flight plan so the participant count is computed the same way everywhere.
|
|
51
52
|
*/
|
|
52
53
|
export function computerUseParticipantCount(config) {
|
|
53
|
-
|
|
54
|
-
if (actor?.lanes !== undefined) {
|
|
55
|
-
return actor.lanes.length;
|
|
56
|
-
}
|
|
57
|
-
return actor?.count ?? 1;
|
|
54
|
+
return participantList(config)?.length ?? declaredParticipantCount(config) ?? 1;
|
|
58
55
|
}
|
|
59
56
|
/**
|
|
60
57
|
* A participant's id: its declared roster id, else `lane-NN` (independent participants) or `role-NN`
|
|
@@ -77,7 +74,7 @@ export function isComputerUseComposition(config) {
|
|
|
77
74
|
// local-app drives the cua loop in-process (a custom executor + a non-vision provider), so it
|
|
78
75
|
// routes to the computer-use route exactly like an app-url subject with a computer-use actor.
|
|
79
76
|
if (config.subject.source === "app-url" || config.subject.source === "local-app") {
|
|
80
|
-
return actorResolvesToComputerUse(config
|
|
77
|
+
return actorResolvesToComputerUse(actorOf(config)?.type);
|
|
81
78
|
}
|
|
82
79
|
// desktop-cli hands the participant a terminal instead of a served page, but it is the
|
|
83
80
|
// same route: same desktop, same actor, same prompt fields. Leaving it out of this predicate told
|
|
@@ -85,13 +82,13 @@ export function isComputerUseComposition(config) {
|
|
|
85
82
|
// person reads a screen.
|
|
86
83
|
if (config.subject.source === "desktop-cli") {
|
|
87
84
|
return ((config.execution?.target === undefined || config.execution.target === "e2b-desktop") &&
|
|
88
|
-
actorResolvesToComputerUse(config
|
|
85
|
+
actorResolvesToComputerUse(actorOf(config)?.type));
|
|
89
86
|
}
|
|
90
87
|
// local-tree packs+uploads the working tree, then serves it exactly like a computer-use clone
|
|
91
88
|
// subject: same e2b-desktop + computer-use-actor gate.
|
|
92
89
|
return ((config.subject.source === "clone" || config.subject.source === "local-tree") &&
|
|
93
90
|
config.execution?.target === "e2b-desktop" &&
|
|
94
|
-
actorResolvesToComputerUse(config
|
|
91
|
+
actorResolvesToComputerUse(actorOf(config)?.type));
|
|
95
92
|
}
|
|
96
93
|
/**
|
|
97
94
|
* True when this config routes to the shared-world route: a clone or local-tree subject
|
|
@@ -99,8 +96,8 @@ export function isComputerUseComposition(config) {
|
|
|
99
96
|
* `shared-world` topology. Mirror of isComputerUseComposition; the single source of truth shared by
|
|
100
97
|
* routeOf (which checks it before the computer-use route) and the warning logic. Every
|
|
101
98
|
* shared-world study runs its participants at once (`execution.concurrency` >= 2). The same
|
|
102
|
-
* clone/local-tree × e2b-desktop × computer-use composition without `
|
|
103
|
-
* (the computer-use route); the
|
|
99
|
+
* clone/local-tree × e2b-desktop × computer-use composition without `route: shared-world` stays per-lane-worlds
|
|
100
|
+
* (the computer-use route); the route declaration is the override switch.
|
|
104
101
|
*/
|
|
105
102
|
export function isSharedWorldComposition(config) {
|
|
106
103
|
return (isProvisionedSharedWorldComposition(config) || isExternalPublicSharedWorldComposition(config));
|
|
@@ -108,13 +105,13 @@ export function isSharedWorldComposition(config) {
|
|
|
108
105
|
/** The getHost provisioned-subject shared-world shape (clone/local-tree served + exposed in-sandbox). */
|
|
109
106
|
export function isProvisionedSharedWorldComposition(config) {
|
|
110
107
|
return ((config.subject.source === "clone" || config.subject.source === "local-tree") &&
|
|
111
|
-
config
|
|
108
|
+
declaresSharedWorld(config) &&
|
|
112
109
|
config.execution?.target === "e2b-desktop" &&
|
|
113
|
-
actorResolvesToComputerUse(config
|
|
110
|
+
actorResolvesToComputerUse(actorOf(config)?.type));
|
|
114
111
|
}
|
|
115
112
|
/**
|
|
116
113
|
* The external-public shared-world shape: a real public deployment used directly as
|
|
117
|
-
* the shared plane: `source: app-url` + `
|
|
114
|
+
* the shared plane: `source: app-url` + `route: shared-world` + a computer-use actor on
|
|
118
115
|
* e2b-desktop + `policies.allowPublicTargets: true`. No getHost, no clone, no subject sandbox, no
|
|
119
116
|
* seed. The operator-ownership attestation `subject.publicTarget` is required (validated in
|
|
120
117
|
* externalPublicSharedWorldValidationReason, not here; this predicate is the router only, so a
|
|
@@ -123,9 +120,9 @@ export function isProvisionedSharedWorldComposition(config) {
|
|
|
123
120
|
*/
|
|
124
121
|
export function isExternalPublicSharedWorldComposition(config) {
|
|
125
122
|
return (config.subject.source === "app-url" &&
|
|
126
|
-
config
|
|
123
|
+
declaresSharedWorld(config) &&
|
|
127
124
|
config.execution?.target === "e2b-desktop" &&
|
|
128
|
-
actorResolvesToComputerUse(config
|
|
125
|
+
actorResolvesToComputerUse(actorOf(config)?.type) &&
|
|
129
126
|
config.policies?.allowPublicTargets === true);
|
|
130
127
|
}
|
|
131
128
|
/**
|
|
@@ -162,12 +159,12 @@ export function isScriptedBrowserComposition(config) {
|
|
|
162
159
|
return (isLocalScriptedBrowserComposition(config) || isProvisionedScriptedBrowserComposition(config));
|
|
163
160
|
}
|
|
164
161
|
function isLocalScriptedBrowserComposition(config) {
|
|
165
|
-
return (config.subject.source === "app-url" && actorResolvesToScriptedBrowser(config
|
|
162
|
+
return (config.subject.source === "app-url" && actorResolvesToScriptedBrowser(actorOf(config)?.type));
|
|
166
163
|
}
|
|
167
164
|
export function isProvisionedScriptedBrowserComposition(config) {
|
|
168
165
|
return (config.subject.source === "clone" &&
|
|
169
166
|
config.execution?.target === "e2b-desktop" &&
|
|
170
|
-
actorResolvesToScriptedBrowser(config
|
|
167
|
+
actorResolvesToScriptedBrowser(actorOf(config)?.type));
|
|
171
168
|
}
|
|
172
169
|
/**
|
|
173
170
|
* True when this config routes to the terminal-product backend: a terminal-product subject whose
|
|
@@ -176,7 +173,7 @@ export function isProvisionedScriptedBrowserComposition(config) {
|
|
|
176
173
|
* the single source of truth for routeOf and the warning logic.
|
|
177
174
|
*/
|
|
178
175
|
export function isTerminalProductComposition(config) {
|
|
179
|
-
return (config.subject.source === "terminal-product" && actorResolvesToTerminal(config
|
|
176
|
+
return (config.subject.source === "terminal-product" && actorResolvesToTerminal(actorOf(config)?.type));
|
|
180
177
|
}
|
|
181
178
|
/**
|
|
182
179
|
* The route a config takes. It never refuses: a config no route can run still gets the route
|
|
@@ -4,7 +4,7 @@ import type { StudyConfig } from "./types.js";
|
|
|
4
4
|
import { type StudyEvent } from "./run-study-events.js";
|
|
5
5
|
type Refusal = {
|
|
6
6
|
ok: false;
|
|
7
|
-
code: "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED";
|
|
7
|
+
code: "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED" | "HUMANISH_STUDY_INVALID";
|
|
8
8
|
message: string;
|
|
9
9
|
};
|
|
10
10
|
type Normalized = {
|
|
@@ -6,8 +6,8 @@ import path from "node:path";
|
|
|
6
6
|
import { isLocalBrowserStudy } from "../substrates/local/runtime-config.js";
|
|
7
7
|
import { resolveStudyDryRun } from "./plan.js";
|
|
8
8
|
import { knownSecretValues, studyEventEmitter } from "./run-study-events.js";
|
|
9
|
-
import { rosterOf } from "./parse/actors.js";
|
|
10
9
|
import { studyResultIdentity } from "../run/study-result.js";
|
|
10
|
+
import { actorOf, participantList } from "./study-fields.js";
|
|
11
11
|
const unsupported = (option, route, reason) => ({
|
|
12
12
|
ok: false,
|
|
13
13
|
code: "HUMANISH_STUDY_OPTION_UNSUPPORTED",
|
|
@@ -105,7 +105,7 @@ export function normalizeRunStudyOptions(config, route, options) {
|
|
|
105
105
|
export function optionRefusalOutcome(config, route, options, refusal) {
|
|
106
106
|
const cwd = path.resolve(options.cwd);
|
|
107
107
|
const error = { code: refusal.code, message: refusal.message };
|
|
108
|
-
const actor = config
|
|
108
|
+
const actor = actorOf(config)?.type ?? "";
|
|
109
109
|
const dryRun = resolveStudyDryRun(config, options.dryRun, true) ?? true;
|
|
110
110
|
const runId = options.runId ?? "not-created";
|
|
111
111
|
const common = { ok: false, cwd, actor, dryRun, runId, warnings: [], error };
|
|
@@ -154,7 +154,7 @@ export function optionRefusalOutcome(config, route, options, refusal) {
|
|
|
154
154
|
},
|
|
155
155
|
};
|
|
156
156
|
case "shared-world": {
|
|
157
|
-
const participantCount =
|
|
157
|
+
const participantCount = participantList(config)?.length ?? 0;
|
|
158
158
|
return {
|
|
159
159
|
route: "shared-world",
|
|
160
160
|
result: {
|
|
@@ -27,7 +27,7 @@ export interface StudyDeps {
|
|
|
27
27
|
readonly subjectPhaseSink?: (event: SubjectPhaseEvent, participant?: ParticipantRef) => void;
|
|
28
28
|
/**
|
|
29
29
|
* Shared world: runs each participant's computer-use session in place of the actor's own. The
|
|
30
|
-
* planner reads whether it is set: a custom runner cannot enforce
|
|
30
|
+
* planner reads whether it is set: a custom runner cannot enforce actor.maxOutputTokens.
|
|
31
31
|
*/
|
|
32
32
|
readonly runSession?: (options: CuaActorSessionOptions) => Promise<CuaLoopResult>;
|
|
33
33
|
/**
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StudyActor, StudyCaps, StudyConfig, StudyParticipantEntry } from "./types.js";
|
|
2
|
+
/** The actor that runs. */
|
|
3
|
+
export declare function actorOf(config: StudyConfig): StudyActor | undefined;
|
|
4
|
+
/** The participants listed one by one: a `participants` list. Undefined when none is listed. */
|
|
5
|
+
export declare function participantList(config: StudyConfig): readonly StudyParticipantEntry[] | undefined;
|
|
6
|
+
/** The declared number of identical participants: `participants: <n>` or `participants.count`. */
|
|
7
|
+
export declare function declaredParticipantCount(config: StudyConfig): number | undefined;
|
|
8
|
+
/** The steer each of a counted group of participants gets: `participants.instruction`. */
|
|
9
|
+
export declare function participantInstruction(config: StudyConfig): string | undefined;
|
|
10
|
+
/** How many scripted surfaces run, 1 for desktop and 2 with mobile. */
|
|
11
|
+
export declare function surfaceCount(config: StudyConfig): number | undefined;
|
|
12
|
+
/** The declared run mode. */
|
|
13
|
+
export declare function modeOf(config: StudyConfig): StudyConfig["mode"];
|
|
14
|
+
/** The scripted route's scenario id or path. */
|
|
15
|
+
export declare function scenarioRefOf(config: StudyConfig): string | undefined;
|
|
16
|
+
/** The spend caps the route enforces. parseStudy refuses a key the route does not read. */
|
|
17
|
+
export declare function capsOf(config: StudyConfig): StudyCaps | undefined;
|
|
18
|
+
/** True when the participants share one app: `route: shared-world`. */
|
|
19
|
+
export declare function declaresSharedWorld(config: StudyConfig): boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// The study's fields, read in one place. Participants are the one read that is not a field: a v3
|
|
2
|
+
// study declares them as a count, as `{ count, instruction }` or as a list, and the accessors below
|
|
3
|
+
// read each form. tests/surface/study-config-reads.test.ts refuses a direct read of a humanish.lab.v2
|
|
4
|
+
// field anywhere outside migrate.
|
|
5
|
+
/** The actor that runs. */
|
|
6
|
+
export function actorOf(config) {
|
|
7
|
+
return config.actor;
|
|
8
|
+
}
|
|
9
|
+
/** The participants listed one by one: a `participants` list. Undefined when none is listed. */
|
|
10
|
+
export function participantList(config) {
|
|
11
|
+
return Array.isArray(config.participants) ? config.participants : undefined;
|
|
12
|
+
}
|
|
13
|
+
/** The declared number of identical participants: `participants: <n>` or `participants.count`. */
|
|
14
|
+
export function declaredParticipantCount(config) {
|
|
15
|
+
const { participants } = config;
|
|
16
|
+
if (typeof participants === "number")
|
|
17
|
+
return participants;
|
|
18
|
+
return isGroup(participants) ? participants.count : undefined;
|
|
19
|
+
}
|
|
20
|
+
/** The steer each of a counted group of participants gets: `participants.instruction`. */
|
|
21
|
+
export function participantInstruction(config) {
|
|
22
|
+
return isGroup(config.participants) ? config.participants.instruction : undefined;
|
|
23
|
+
}
|
|
24
|
+
/** How many scripted surfaces run, 1 for desktop and 2 with mobile. */
|
|
25
|
+
export function surfaceCount(config) {
|
|
26
|
+
return config.surfaces?.length;
|
|
27
|
+
}
|
|
28
|
+
/** The declared run mode. */
|
|
29
|
+
export function modeOf(config) {
|
|
30
|
+
return config.mode;
|
|
31
|
+
}
|
|
32
|
+
/** The scripted route's scenario id or path. */
|
|
33
|
+
export function scenarioRefOf(config) {
|
|
34
|
+
return config.scenario;
|
|
35
|
+
}
|
|
36
|
+
/** The spend caps the route enforces. parseStudy refuses a key the route does not read. */
|
|
37
|
+
export function capsOf(config) {
|
|
38
|
+
return config.caps;
|
|
39
|
+
}
|
|
40
|
+
/** True when the participants share one app: `route: shared-world`. */
|
|
41
|
+
export function declaresSharedWorld(config) {
|
|
42
|
+
return config.route === "shared-world";
|
|
43
|
+
}
|
|
44
|
+
function isGroup(participants) {
|
|
45
|
+
return typeof participants === "object" && participants !== null && !Array.isArray(participants);
|
|
46
|
+
}
|
package/dist/study/summary.js
CHANGED
|
@@ -18,7 +18,7 @@ import { inspectStudyManifest } from "./discover.js";
|
|
|
18
18
|
import { isComputerUseComposition } from "./routing.js";
|
|
19
19
|
import { probeKeySources } from "../keys/key-resolution.js";
|
|
20
20
|
import { receivingRequiredKey } from "../comms/setup.js";
|
|
21
|
-
import {
|
|
21
|
+
import { actorOf, participantList, declaredParticipantCount, capsOf, surfaceCount, } from "./study-fields.js";
|
|
22
22
|
export const STUDY_SUMMARY_SCHEMA = "humanish.study-summary.v1";
|
|
23
23
|
/**
|
|
24
24
|
* The effort every participant will run at, or `"per-lane"` when they differ. A participant that declares nothing
|
|
@@ -26,10 +26,8 @@ export const STUDY_SUMMARY_SCHEMA = "humanish.study-summary.v1";
|
|
|
26
26
|
* reported as the resolved value, exactly as `model` reports its default rather than hiding it.
|
|
27
27
|
*/
|
|
28
28
|
function reasoningEffortOf(config) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const fallback = actor?.reasoningEffort ?? DEFAULT_OPENAI_CU_REASONING_EFFORT;
|
|
32
|
-
const roster = rosterOf(actor) ?? [];
|
|
29
|
+
const fallback = actorOf(config)?.reasoningEffort ?? DEFAULT_OPENAI_CU_REASONING_EFFORT;
|
|
30
|
+
const roster = participantList(config) ?? [];
|
|
33
31
|
const resolved = new Set(roster.map((entry) => entry.reasoningEffort ?? fallback));
|
|
34
32
|
if (resolved.size > 1)
|
|
35
33
|
return "per-lane";
|
|
@@ -49,15 +47,19 @@ function subjectOf(config) {
|
|
|
49
47
|
}
|
|
50
48
|
/** How many participants, and who; collapsed when they are all the same persona. */
|
|
51
49
|
function participantsOf(config) {
|
|
52
|
-
const
|
|
53
|
-
const actor = actors?.[0];
|
|
50
|
+
const actor = actorOf(config);
|
|
54
51
|
if (actor === undefined)
|
|
55
52
|
return undefined;
|
|
56
|
-
const rosterPersonas = (
|
|
53
|
+
const rosterPersonas = (participantList(config) ?? [])
|
|
57
54
|
.map((entry) => entry.persona)
|
|
58
55
|
.filter((persona) => typeof persona === "string");
|
|
59
56
|
const personas = rosterPersonas.length > 0 ? rosterPersonas : actor.persona === undefined ? [] : [actor.persona];
|
|
60
|
-
|
|
57
|
+
// A scripted study's participants are its surfaces, one replay on each.
|
|
58
|
+
const count = declaredParticipantCount(config) ??
|
|
59
|
+
surfaceCount(config) ??
|
|
60
|
+
participantList(config)?.length ??
|
|
61
|
+
personas.length ??
|
|
62
|
+
1;
|
|
61
63
|
const unique = [...new Set(personas)];
|
|
62
64
|
if (unique.length === 0)
|
|
63
65
|
return `${count} participant${count === 1 ? "" : "s"}`;
|
|
@@ -65,11 +67,11 @@ function participantsOf(config) {
|
|
|
65
67
|
// are named, because which personas are in a study is the study's design.
|
|
66
68
|
return unique.length === 1 ? `${count} × ${unique[0]}` : unique.join(" · ");
|
|
67
69
|
}
|
|
68
|
-
/** The computer-use caps
|
|
69
|
-
function
|
|
70
|
+
/** The computer-use caps. Other routes draw none. */
|
|
71
|
+
function summaryCapsOf(config) {
|
|
70
72
|
if (!isComputerUseComposition(config))
|
|
71
73
|
return {};
|
|
72
|
-
const caps = config
|
|
74
|
+
const caps = capsOf(config);
|
|
73
75
|
return {
|
|
74
76
|
...(typeof caps?.maxUsd === "number" ? { laneUsd: caps.maxUsd } : {}),
|
|
75
77
|
...(typeof caps?.maxTotalUsd === "number" ? { studyUsd: caps.maxTotalUsd } : {}),
|
|
@@ -84,7 +86,6 @@ export async function readStudySummary(cwd, study, options = {}) {
|
|
|
84
86
|
if (inspected === null || !inspected.ok || inspected.config === undefined)
|
|
85
87
|
return null;
|
|
86
88
|
const config = inspected.config;
|
|
87
|
-
const actors = config.actors;
|
|
88
89
|
const route = routeOf(inspected.config);
|
|
89
90
|
let keysReady;
|
|
90
91
|
let missingKeys;
|
|
@@ -128,7 +129,7 @@ export async function readStudySummary(cwd, study, options = {}) {
|
|
|
128
129
|
// Computed once: a test-then-use pair reads as though the two calls could differ.
|
|
129
130
|
const analysis = automaticAnalysisBudget(inspected.config.review?.analysis, route);
|
|
130
131
|
const subject = subjectOf(config);
|
|
131
|
-
const participants = participantsOf(config);
|
|
132
|
+
const participants = participantsOf(inspected.config);
|
|
132
133
|
const runtime = options.checkKeys === true && isLocalBrowserStudy(inspected.config)
|
|
133
134
|
? await localRuntimeStatus({
|
|
134
135
|
...(options.env ? { env: options.env } : {}),
|
|
@@ -138,7 +139,7 @@ export async function readStudySummary(cwd, study, options = {}) {
|
|
|
138
139
|
: undefined;
|
|
139
140
|
const participantReadiness = options.checkKeys === true &&
|
|
140
141
|
isLocalBrowserStudy(inspected.config) &&
|
|
141
|
-
inspected.config
|
|
142
|
+
actorOf(inspected.config)?.type === "local-agent"
|
|
142
143
|
? await localCodexParticipantCheck({ env: options.env ?? process.env })
|
|
143
144
|
: undefined;
|
|
144
145
|
return {
|
|
@@ -165,9 +166,9 @@ export async function readStudySummary(cwd, study, options = {}) {
|
|
|
165
166
|
...(typeof config.description === "string" ? { description: config.description.trim() } : {}),
|
|
166
167
|
...(subject === undefined ? {} : { subject }),
|
|
167
168
|
...(participants === undefined ? {} : { participants }),
|
|
168
|
-
model:
|
|
169
|
-
reasoningEffort: reasoningEffortOf(config),
|
|
170
|
-
caps:
|
|
169
|
+
model: actorOf(inspected.config)?.model ?? DEFAULT_OPENAI_CU_MODEL,
|
|
170
|
+
reasoningEffort: reasoningEffortOf(inspected.config),
|
|
171
|
+
caps: summaryCapsOf(inspected.config),
|
|
171
172
|
...(keysReady === undefined ? {} : { keysReady }),
|
|
172
173
|
...(missingKeys === undefined ? {} : { missingKeys }),
|
|
173
174
|
...(planRefusal === undefined ? {} : { planRefusal }),
|
package/dist/study/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type StudyAnalysis } from "../analysis/automatic-config.js";
|
|
|
2
2
|
import type { StudyTask } from "./tasks.js";
|
|
3
3
|
import type { DwellWindow, StopWhen } from "../actors/stop-conditions.js";
|
|
4
4
|
import { type ReasoningEffort } from "../actors/reasoning-effort.js";
|
|
5
|
-
|
|
5
|
+
import type { StudyRoute } from "./routing.js";
|
|
6
6
|
/** The study format: it declares its route, one actor, its participants and one caps block. */
|
|
7
7
|
export declare const STUDY_SCHEMA = "humanish.study.v3";
|
|
8
8
|
export declare const ID_PATTERN: RegExp;
|
|
@@ -17,16 +17,6 @@ export declare const ID_PATTERN: RegExp;
|
|
|
17
17
|
* error, never a desktop attempt. See docs/architecture/state-driven-executor.md.
|
|
18
18
|
*/
|
|
19
19
|
type StudySubjectSource = "this-repo" | "clone" | "app-url" | "local-app" | "terminal-product" | "desktop-cli" | "local-tree";
|
|
20
|
-
/**
|
|
21
|
-
* How a subject's world relates across participants. `per-lane-worlds` (the default; absent ==
|
|
22
|
-
* this) is the only fan-out topology the computer-use route ships: N participants, N independent
|
|
23
|
-
* worlds, isolation + per-participant attribution. `shared-world` is the declared override: one
|
|
24
|
-
* mutable service plane that N participants use at the same time, so their actions interact through
|
|
25
|
-
* shared state. Consumed only on the shared-world routes (a provisioned clone plane, or an
|
|
26
|
-
* external-public app-url plane) with a computer-use actor; inert/warned everywhere else
|
|
27
|
-
* (claims match mechanism).
|
|
28
|
-
*/
|
|
29
|
-
type StudySubjectTopology = "per-lane-worlds" | "shared-world";
|
|
30
20
|
export interface StudySubjectClone {
|
|
31
21
|
/** git clone depth; 1 (shallow) by default. Consumed on the computer-use clone route. */
|
|
32
22
|
depth?: number;
|
|
@@ -188,26 +178,18 @@ export interface StudySubjectProduct {
|
|
|
188
178
|
}
|
|
189
179
|
export interface StudySubject {
|
|
190
180
|
source: StudySubjectSource;
|
|
191
|
-
/**
|
|
192
|
-
* World topology across participants. Absent == `per-lane-worlds` (the isolation default; every
|
|
193
|
-
* existing study is byte-stable). `shared-world` is the declared override: one mutable service
|
|
194
|
-
* plane, N participants at once, each in its role. Consumed only on the shared-world routes (a
|
|
195
|
-
* provisioned clone or an external-public app-url plane, a computer-use actor, and a roster of ≥2
|
|
196
|
-
* participants); inert/warned elsewhere.
|
|
197
|
-
*/
|
|
198
|
-
topology?: StudySubjectTopology;
|
|
199
181
|
/**
|
|
200
182
|
* Concurrent shared-world route only: the author's required attestation that the
|
|
201
183
|
* subject behind the internet-reachable `getHost` URL is synthetic seeded data. The concurrent
|
|
202
184
|
* route exposes the subject on a tokenless public URL for the run's duration, so real/external
|
|
203
185
|
* data must never sit behind it. This is author-trust + a provenance gate (verify also requires
|
|
204
186
|
* `subject.state.provenance == "seeded"`). It does not guarantee the data is synthetic. Required when
|
|
205
|
-
* `
|
|
187
|
+
* `route: shared-world` + `execution.concurrency > 1`; inert/warned elsewhere.
|
|
206
188
|
*/
|
|
207
189
|
exposure?: "synthetic";
|
|
208
190
|
/**
|
|
209
191
|
* External-public shared-world route only: the author's required ownership
|
|
210
|
-
* attestation when a real public deployment (`source: app-url` + `
|
|
192
|
+
* attestation when a real public deployment (`source: app-url` + `route: shared-world` +
|
|
211
193
|
* `concurrency > 1` + `policies.allowPublicTargets: true`) is used directly as the shared plane.
|
|
212
194
|
* The harness neither provisions nor exposes this target (no getHost, no clone, no seed), so it
|
|
213
195
|
* cannot attest the data is synthetic; instead the operator must attest they own/operate it.
|
|
@@ -275,12 +257,6 @@ export interface StudySubject {
|
|
|
275
257
|
*/
|
|
276
258
|
localTree?: StudySubjectLocalTree;
|
|
277
259
|
}
|
|
278
|
-
export interface StudyParticipantFocus {
|
|
279
|
-
id?: string;
|
|
280
|
-
label?: string;
|
|
281
|
-
/** Per-participant steer appended to the actor's mission. Consumed on the app-url route. */
|
|
282
|
-
instruction?: string;
|
|
283
|
-
}
|
|
284
260
|
/**
|
|
285
261
|
* One participant in a differentiated fan-out on the computer-use E2B route (`per-lane-worlds`).
|
|
286
262
|
* Each entry becomes an independent E2B desktop sandbox with its own persona/device/starting-steer. All
|
|
@@ -293,7 +269,7 @@ export interface StudyParticipantEntry {
|
|
|
293
269
|
id?: string;
|
|
294
270
|
/**
|
|
295
271
|
* App-defined actor type label for grouping simulated users ("operator", "viewer",
|
|
296
|
-
* "maintainer", etc.). It is separate from the execution actor dispatch key (`
|
|
272
|
+
* "maintainer", etc.). It is separate from the execution actor dispatch key (`actor.type`);
|
|
297
273
|
* it is adapter-owned taxonomy for roster/readback.
|
|
298
274
|
*/
|
|
299
275
|
actorType?: string;
|
|
@@ -301,7 +277,7 @@ export interface StudyParticipantEntry {
|
|
|
301
277
|
surface?: string;
|
|
302
278
|
/** App-defined correlation id tying participants to one shared case/account/work item. */
|
|
303
279
|
caseGroup?: string;
|
|
304
|
-
/** Persona id/label threaded into this participant's actor prompt. Default:
|
|
280
|
+
/** Persona id/label threaded into this participant's actor prompt. Default: actor.persona. */
|
|
305
281
|
persona?: string;
|
|
306
282
|
/** Named hosted-screen preset for this participant. XOR raw execution.desktop.resolution. */
|
|
307
283
|
device?: string;
|
|
@@ -344,17 +320,6 @@ export interface StudyParticipantEntry {
|
|
|
344
320
|
*/
|
|
345
321
|
host?: boolean;
|
|
346
322
|
}
|
|
347
|
-
/**
|
|
348
|
-
* Compact authoring sugar for repeated participant groups. The parser expands each group into concrete
|
|
349
|
-
* `lanes[]` with deterministic ids (`<group.id>-01`, `<group.id>-02`, ...). The runtime never
|
|
350
|
-
* consumes this shape directly; it always sees ordinary `LabActorLane` entries.
|
|
351
|
-
*/
|
|
352
|
-
export interface StudyActorRosterGroup extends Omit<StudyParticipantEntry, "id"> {
|
|
353
|
-
/** Public-safe group id; prefixes generated participant ids. */
|
|
354
|
-
id: string;
|
|
355
|
-
/** Number of participants to generate for this group. */
|
|
356
|
-
count: number;
|
|
357
|
-
}
|
|
358
323
|
export interface StudyActor {
|
|
359
324
|
/**
|
|
360
325
|
* The actor label. On computer-use (including shared-world), scripted-browser, and
|
|
@@ -364,18 +329,8 @@ export interface StudyActor {
|
|
|
364
329
|
* the descriptor for dispatch and capability enforcement.
|
|
365
330
|
*/
|
|
366
331
|
type: string;
|
|
367
|
-
/** Participant count, route-specific (see the scope header in config.ts): synthetic simCount; scripted
|
|
368
|
-
* surface roster {1 = desktop, 2 = desktop + mobile, default 1}; computer-use E2B route the
|
|
369
|
-
* homogeneous fan-out participant count (cap 16). XOR `lanes`. */
|
|
370
|
-
count?: number;
|
|
371
|
-
/** Computer-use E2B route: a differentiated fan-out roster (`per-lane-worlds`). XOR `count`,
|
|
372
|
-
* `roster`, and `laneFocus`. Cap 16 participants. Consumed only on the cua E2B route
|
|
373
|
-
* (inert/warned elsewhere). */
|
|
374
|
-
lanes?: StudyParticipantEntry[];
|
|
375
332
|
/** Persona id/label threaded into the actor prompt. Consumed on the app-url route. */
|
|
376
333
|
persona?: string;
|
|
377
|
-
/** Consumed on the app-url route (laneFocus.instruction appended to the mission). XOR `lanes`. */
|
|
378
|
-
laneFocus?: StudyParticipantFocus;
|
|
379
334
|
/** Free-form mission threaded into the actor prompt. Consumed on the app-url route. A mission on
|
|
380
335
|
* its own is a complete, valid study; `tasks` is additive, never required. */
|
|
381
336
|
mission?: string;
|
|
@@ -403,7 +358,7 @@ export interface StudyActor {
|
|
|
403
358
|
*/
|
|
404
359
|
localAgent?: "codex" | "claude";
|
|
405
360
|
/**
|
|
406
|
-
* How hard the model is asked to think, per turn. A `
|
|
361
|
+
* How hard the model is asked to think, per turn. A `participants` entry's `reasoningEffort` overrides this.
|
|
407
362
|
*
|
|
408
363
|
* Absent means the provider's default, and absence is recorded as absence: a run that did not
|
|
409
364
|
* declare an effort does not claim one. Support is model-dependent (see src/actors/reasoning-effort.ts),
|
|
@@ -418,12 +373,12 @@ export interface StudyActor {
|
|
|
418
373
|
*/
|
|
419
374
|
reasoningEffort?: ReasoningEffort;
|
|
420
375
|
/**
|
|
421
|
-
* Deterministic completion guard used as the default for CUA participants. A `
|
|
376
|
+
* Deterministic completion guard used as the default for CUA participants. A `participants` entry's stopWhen
|
|
422
377
|
* overrides this value.
|
|
423
378
|
*/
|
|
424
379
|
stopWhen?: StopWhen;
|
|
425
380
|
/**
|
|
426
|
-
* A declared observation window, the default for every participant; a `
|
|
381
|
+
* A declared observation window, the default for every participant; a `participants` entry's dwell overrides
|
|
427
382
|
* it. `when` is a stopWhen-shaped condition (absent: the window opens after the first
|
|
428
383
|
* observation); `ms` is the hold, `everyMs` the frame cadence (default 10 s), `then` whether
|
|
429
384
|
* the participant continues afterwards (default) or the session ends. The harness takes no
|
|
@@ -544,20 +499,6 @@ export interface StudyExecution {
|
|
|
544
499
|
*/
|
|
545
500
|
concurrency?: number;
|
|
546
501
|
desktop?: StudyExecutionDesktop;
|
|
547
|
-
/**
|
|
548
|
-
* Blast-radius budget for each computer-use participant. Consumed on the computer-use route:
|
|
549
|
-
* `caps.maxUsd`, when set, is a fail-closed abort: the session stops the moment its running
|
|
550
|
-
* estimated spend crosses it (the runaway-retry guard), and a cap on a model src/run/pricing.ts
|
|
551
|
-
* cannot price is refused at preflight rather than run uncapped. It is a per-participant cap:
|
|
552
|
-
* enforced inside each participant's loop, so an N-participant fan-out can spend up to N × maxUsd
|
|
553
|
-
* before any participant aborts (the run warns with the true ~N × cap ceiling).
|
|
554
|
-
* `caps.maxTotalUsd` is the shared study budget: one ledger across every participant, the knob a
|
|
555
|
-
* researcher actually reasons with. Absent = uncapped (the historical CUA behavior); maxUsd: 0
|
|
556
|
-
* still permits a request before reported usage trips it. Inert (warned) on non-computer-use
|
|
557
|
-
* routes. Reuses the same StudyScenarioCaps shape as the terminal route's `scenario.caps` (not a
|
|
558
|
-
* fork).
|
|
559
|
-
*/
|
|
560
|
-
caps?: StudyScenarioCaps;
|
|
561
502
|
/** `terminal-product` route: the terminal transport + stdin posture. Consumed on that route. */
|
|
562
503
|
terminal?: StudyExecutionTerminal;
|
|
563
504
|
/** `terminal-product` route: runtime key placement, defaulting to openai-egress, an external
|
|
@@ -583,48 +524,45 @@ export interface StudyExecution {
|
|
|
583
524
|
*/
|
|
584
525
|
egressAllow?: string[];
|
|
585
526
|
}
|
|
586
|
-
type
|
|
527
|
+
type StudyMode = "dry-run" | "live";
|
|
587
528
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
529
|
+
* A study's spend, job and time caps, the top-level `caps` block. Each route reads its own keys, and
|
|
530
|
+
* parseStudy refuses a key the route does not read. All values are non-negative numbers.
|
|
531
|
+
*
|
|
532
|
+
* Computer use and shared world read `maxUsd` and `maxTotalUsd`. `maxUsd`, when set, is a
|
|
533
|
+
* fail-closed abort for each participant: the session stops the moment its running estimated spend
|
|
534
|
+
* crosses it (the runaway-retry guard), and a cap on a model src/run/pricing.ts cannot price is
|
|
535
|
+
* refused at preflight rather than run uncapped. It is enforced inside each participant's loop, so
|
|
536
|
+
* an N-participant fan-out can spend up to N × maxUsd before any participant aborts (the run warns
|
|
537
|
+
* with the true ~N × cap ceiling). Absent = uncapped; maxUsd: 0 still permits a request before
|
|
538
|
+
* reported usage trips it.
|
|
539
|
+
*
|
|
540
|
+
* The terminal route passes a live key to an in-sandbox command, and per the safety contract the
|
|
541
|
+
* live key is never exercised without a fail-closed cap in force. It reads `maxUsd`, `maxJobs` and
|
|
542
|
+
* `maxMinutes` (0 is the no-spend default). Live runs require maxUsd and a positive maxMinutes;
|
|
543
|
+
* maxUsd/maxJobs are checked after the session against known ledger signals and maxMinutes is
|
|
544
|
+
* enforced as the command wall clock. Codex tokens are unpriced, so a live run refuses a positive
|
|
545
|
+
* maxUsd unless a costProbe measures spend (HUMANISH_TERMINAL_UNPRICED_CAP).
|
|
594
546
|
*/
|
|
595
|
-
export interface
|
|
547
|
+
export interface StudyCaps {
|
|
596
548
|
/** Max USD the run may spend (provider + product). 0 = no-spend. */
|
|
597
549
|
maxUsd?: number;
|
|
598
550
|
/**
|
|
599
551
|
* study-level model-spend budget, the number a researcher actually reasons with: "this
|
|
600
552
|
* study is N participants, roughly $X", decided once, up front, where recruiting decisions are
|
|
601
|
-
* made.
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
* be set. A positive `scenario.caps.maxTotalUsd` on a computer-use study is a parse error; on the
|
|
608
|
-
* terminal route it is inert (warned), since the single agent's maxUsd already caps the run.
|
|
553
|
+
* made. Computer use and shared world only: every participant's running estimated model spend
|
|
554
|
+
* feeds one shared ledger, and the moment the run total crosses this, each participant stops at
|
|
555
|
+
* its next turn with `budget_reached` (status `incomplete`: the participant ran out of budget;
|
|
556
|
+
* never `gave_up`, because a study-level stop is not the participant's doing). Estimated model
|
|
557
|
+
* spend only; desktop-minutes ride the cost summary but not this ledger. Independent of the
|
|
558
|
+
* per-participant `maxUsd` backstop; either, both, or neither may be set.
|
|
609
559
|
*/
|
|
610
560
|
maxTotalUsd?: number;
|
|
611
|
-
/**
|
|
561
|
+
/** Terminal: max billable product jobs the agent may trigger. 0 = none. */
|
|
612
562
|
maxJobs?: number;
|
|
613
|
-
/**
|
|
563
|
+
/** Terminal: max wall-clock minutes for the agent session. */
|
|
614
564
|
maxMinutes?: number;
|
|
615
565
|
}
|
|
616
|
-
export interface StudyScenario {
|
|
617
|
-
/** Reference a committed scenario by id (humanish/scenarios/<ref>.yaml) or path. Consumed
|
|
618
|
-
* (and required) on the scripted-browser route; forward-declared elsewhere. */
|
|
619
|
-
ref?: string;
|
|
620
|
-
/** Or inline the scenario body. Forward-declared: no route reads it yet, so a set value warns. */
|
|
621
|
-
inline?: Record<string, unknown>;
|
|
622
|
-
/** dry-run = a synthetic bundle with no provider spend; live = real run. Consumed. */
|
|
623
|
-
mode?: StudyScenarioMode;
|
|
624
|
-
/** Spend/job/time caps. Consumed (recorded in the bundle) on the terminal-product route;
|
|
625
|
-
* inert (warned) elsewhere. */
|
|
626
|
-
caps?: StudyScenarioCaps;
|
|
627
|
-
}
|
|
628
566
|
export interface StudyPolicies {
|
|
629
567
|
/**
|
|
630
568
|
* Redact target repo labels in durable artifacts. Consumed on the computer-use,
|
|
@@ -721,7 +659,7 @@ export interface StudyCommsReceivingEmail {
|
|
|
721
659
|
interface StudyCommsCaptureEmail {
|
|
722
660
|
connection?: never;
|
|
723
661
|
allowedOrigins?: never;
|
|
724
|
-
/** Which implementation backs the inbox (a backend discriminator, distinct from `
|
|
662
|
+
/** Which implementation backs the inbox (a backend discriminator, distinct from `mode`):
|
|
725
663
|
* `fake` (default) is an in-harness in-memory inbox in the Fowler test-double sense: an in-sandbox
|
|
726
664
|
* catch captures the app's sends. Provider-backed receiving uses the separate
|
|
727
665
|
* StudyCommsReceivingEmail configuration selected by a saved connection. */
|
|
@@ -788,23 +726,47 @@ export interface StudyCommsRecipient {
|
|
|
788
726
|
* for the persona surface's default address (not drain-matched). */
|
|
789
727
|
address?: string;
|
|
790
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* A humanish.study.v3 study as parseStudy returns it, with the keys the file has: the declared
|
|
731
|
+
* route, one actor, its participants and one caps block.
|
|
732
|
+
*/
|
|
791
733
|
export interface StudyConfig {
|
|
792
|
-
|
|
793
|
-
schema: typeof V2_SCHEMA | typeof STUDY_SCHEMA;
|
|
734
|
+
schema: typeof STUDY_SCHEMA;
|
|
794
735
|
id: string;
|
|
795
736
|
title?: string;
|
|
796
737
|
description?: string;
|
|
738
|
+
/** The route the study declares. parseStudy refuses one its subject and actor do not take. */
|
|
739
|
+
route: StudyRoute;
|
|
740
|
+
/** Absent: a dry run. */
|
|
741
|
+
mode?: StudyMode;
|
|
797
742
|
subject: StudySubject;
|
|
798
|
-
|
|
743
|
+
actor: StudyActor;
|
|
744
|
+
/** Preview, computer-use and shared-world, in the form the file used. */
|
|
745
|
+
participants?: StudyParticipants;
|
|
746
|
+
/** Scripted only. */
|
|
747
|
+
surfaces?: StudySurfaces;
|
|
748
|
+
/** The caps keys the route reads. */
|
|
749
|
+
caps?: StudyCaps;
|
|
799
750
|
execution?: StudyExecution;
|
|
800
|
-
/**
|
|
801
|
-
|
|
802
|
-
scenario?: StudyScenario;
|
|
751
|
+
/** Scripted: a committed scenario id or path. */
|
|
752
|
+
scenario?: string;
|
|
803
753
|
policies?: StudyPolicies;
|
|
804
754
|
review?: StudyReview;
|
|
805
755
|
defaults?: StudyDefaults;
|
|
806
756
|
comms?: StudyComms;
|
|
807
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* A count, a count with one instruction for every participant, or a list. parseStudy expands a list
|
|
760
|
+
* entry with a `count` into its participants, `<id>-01` to `<id>-NN`.
|
|
761
|
+
*/
|
|
762
|
+
type StudyParticipants = number | StudyParticipantGroup | StudyParticipantEntry[];
|
|
763
|
+
/** `participants` as identical participants that share one instruction. */
|
|
764
|
+
export interface StudyParticipantGroup {
|
|
765
|
+
count?: number;
|
|
766
|
+
instruction?: string;
|
|
767
|
+
}
|
|
768
|
+
/** The scripted surfaces: desktop, or desktop and mobile. */
|
|
769
|
+
export type StudySurfaces = readonly ["desktop"] | readonly ["desktop", "mobile"];
|
|
808
770
|
interface StudyParseSuccess {
|
|
809
771
|
ok: true;
|
|
810
772
|
config: StudyConfig;
|
package/dist/study/types.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {} from "../analysis/automatic-config.js";
|
|
2
2
|
import {} from "../actors/reasoning-effort.js";
|
|
3
|
-
export const V2_SCHEMA = "humanish.lab.v2";
|
|
4
3
|
/** The study format: it declares its route, one actor, its participants and one caps block. */
|
|
5
4
|
export const STUDY_SCHEMA = "humanish.study.v3";
|
|
6
5
|
// Must start alphanumeric so an id never collides with the path-vs-id resolver heuristic
|