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/config.js
CHANGED
|
@@ -1,207 +1,233 @@
|
|
|
1
|
-
// The
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// The study parser: a humanish.study.v3 document into a StudyConfig with the file's own keys.
|
|
2
|
+
// parse/front.ts checks what the document declares before its sections parse (the route, the
|
|
3
|
+
// actor object, the participants or surfaces form the route takes, and the caps keys it reads). The
|
|
4
|
+
// sections parse here, checkStudyConfig runs the checks that read more than one section, and the
|
|
5
|
+
// study must then take the route it declares and set no field that route does not read
|
|
6
|
+
// (warnings.ts). A humanish.lab.v2 document is refused; `humanish migrate` reads it
|
|
7
|
+
// (migrate/v2.ts).
|
|
5
8
|
//
|
|
6
|
-
// Scope (read before trusting field names): the
|
|
7
|
-
// subject.source × execution.target
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// execution.desktop.{browser,resolution,sandboxTimeoutMs}, and (clone)
|
|
17
|
-
// subject.{serve,env,state,clone.depth}.
|
|
18
|
-
// On the scripted-browser route (app-url × local-or-absent, or clone × e2b-desktop, with a
|
|
19
|
-
// registered scripted-browser actor), `actors[0].type` selects the actor too, and the route
|
|
20
|
-
// consumes scenario.ref (required there, because the committed scenario's browser steps are what the
|
|
21
|
-
// actor executes), actors[0].{persona,count}, and execution.timeoutMs. On the provisioned
|
|
22
|
-
// clone slice it also consumes subject.{repos,serve,env,state,exposure,clone.depth} and
|
|
23
|
-
// execution.desktop.template. actors[0].{mission,laneFocus,model} are inert on that route
|
|
24
|
-
// because no model runs, and most execution.desktop.* fields remain forward-declared (device
|
|
25
|
-
// presets belong to the computer-use route; scripted surfaces are the driver's own desktop/mobile
|
|
26
|
-
// viewports where isMobile/DSF genuinely render via playwright emulation).
|
|
27
|
-
// On the other routes those fields remain forward-declared and are not yet consumed:
|
|
28
|
-
// parseStudy emits a warning listing any such field that is set, so `study show` shows
|
|
29
|
-
// the truth.
|
|
9
|
+
// Scope (read before trusting field names): routeOf derives the route from
|
|
10
|
+
// subject.source × execution.target × the actor's run kind, with `route: shared-world` as the
|
|
11
|
+
// declaration that turns a computer-use composition into a shared world. On the computer-use
|
|
12
|
+
// routes (app-url × e2b-desktop or local, and clone, local-tree or desktop-cli on e2b-desktop with a
|
|
13
|
+
// computer-use actor), `actor.type` must resolve to a registered computer-use actor, and that
|
|
14
|
+
// descriptor runs the session. Those routes consume actor.{mission,persona,model,reasoningEffort},
|
|
15
|
+
// `participants`, `caps`, execution.timeoutMs and execution.desktop.*. The scripted route
|
|
16
|
+
// (app-url × local, or clone × e2b-desktop, with a registered scripted-browser actor) consumes
|
|
17
|
+
// `scenario` (required: its browser steps are what the actor runs), actor.persona and `surfaces`.
|
|
18
|
+
// The terminal route consumes subject.product, `caps` and execution.{terminal,runtimeAuth}.
|
|
30
19
|
//
|
|
31
|
-
// NOTE on
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// independent E2B desktop (`per-lane-worlds`, the default topology). `roster[]` is parser sugar for
|
|
41
|
-
// repeated groups and is normalized into `lanes[]` before the engine sees it. `lanes|roster` XOR
|
|
42
|
-
// `count` (declare a differentiated roster or a homogeneous count, never both); `lanes|roster`
|
|
43
|
-
// XOR `actors[0].laneFocus` (each entry's `instruction` is the roster's steer); `lanes[].device` XOR
|
|
44
|
-
// raw `execution.desktop.resolution`. `execution.concurrency` bounds in-flight participants (default:
|
|
45
|
-
// the participant count, all at once; env HUMANISH_CUA_MAX_CONCURRENCY may only lower it, per invariant
|
|
46
|
-
// 3). On every non-computer-use route normalized `lanes` are inert (warned). subject.clone.fanout
|
|
47
|
-
// is rejected on the computer-use route. `lanes[].target` is app-url × computer-use only: an
|
|
48
|
-
// absolute browser URL this participant opens instead of `subject.appUrl`; it is the generic
|
|
49
|
-
// setup-produced-target handoff, not a service topology primitive.
|
|
50
|
-
//
|
|
51
|
-
// There is deliberately no v1 compatibility: v1 had zero real users. Breaking schema changes
|
|
52
|
-
// bump the version.
|
|
20
|
+
// NOTE on `participants`: a count (preview: the simulated count; computer use: N identical
|
|
21
|
+
// participants, each its own desktop, capped at 16), `{ count, instruction }` (identical
|
|
22
|
+
// participants with one steer), or a list of `{ id?, persona?, device?, instruction?, target? ... }`
|
|
23
|
+
// entries, each its own desktop. A list entry with a `count` is a group that expands into
|
|
24
|
+
// `<id>-01` to `<id>-NN`. A shared world takes a list of at least two. `participants[].device`
|
|
25
|
+
// excludes a raw `execution.desktop.resolution`, and `participants[].target` is app-url computer
|
|
26
|
+
// use only: an absolute browser URL this participant opens in place of `subject.appUrl`.
|
|
27
|
+
// `execution.concurrency` bounds in-flight participants (default: all at once; env
|
|
28
|
+
// HUMANISH_CUA_MAX_CONCURRENCY may only lower it).
|
|
53
29
|
import { isLocalBrowserStudy, localBrowserDefaults, localBrowserUnsupportedReason, } from "../substrates/local/runtime-config.js";
|
|
54
30
|
import { isRecord } from "../run/type-guards.js";
|
|
55
31
|
import { compositionReason } from "./composition-rules.js";
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
32
|
+
import { V2_SCHEMA, V2_UNSUPPORTED_MESSAGE, v2FieldMoves } from "./migrate/v2.js";
|
|
33
|
+
import { parseActorFields, parseActorType, parseParticipantEntries } from "./parse/actors.js";
|
|
58
34
|
import { parseComms, recipientParticipantId } from "./parse/comms.js";
|
|
59
|
-
import { parseDefaults, parseExecution,
|
|
60
|
-
import {
|
|
35
|
+
import { parseCaps, parseDefaults, parseExecution, parsePolicies, parseReview, } from "./parse/execution.js";
|
|
36
|
+
import { ACTOR_REQUIRED, readStudyFront, routeNameReason, } from "./parse/front.js";
|
|
61
37
|
import { parseSubject } from "./parse/subject.js";
|
|
62
38
|
import { invalid, optionalStr, str } from "./parse/values.js";
|
|
63
39
|
import { isComputerUseComposition, isSharedWorldComposition, routeOf } from "./routing.js";
|
|
64
40
|
import { declaredParticipantIds } from "./plan-participants.js";
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
41
|
+
import { declaredParticipantCount, participantList } from "./study-fields.js";
|
|
42
|
+
import { ID_PATTERN, STUDY_SCHEMA, } from "./types.js";
|
|
43
|
+
import { automaticAnalysisRouteReason, desktopMediaValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, smtpValidationReason, taskProtocolValidationReason, } from "./validation.js";
|
|
44
|
+
import { forwardDeclaredWarnings, inertFieldLabels, inertFieldPaths } from "./warnings.js";
|
|
68
45
|
import { plural } from "../run/text.js";
|
|
69
|
-
/** The message for a humanish.lab.v2 document, which the package and the CLI no longer run. */
|
|
70
|
-
export const V2_UNSUPPORTED_MESSAGE = "This is a humanish.lab.v2 study, which humanish no longer reads. Convert its file with humanish migrate <path>.";
|
|
71
46
|
/**
|
|
72
47
|
* The package's study parser (src/index.ts) and the one discovery uses: a humanish.study.v3
|
|
73
48
|
* document. It refuses a humanish.lab.v2 one with HUMANISH_STUDY_V2_UNSUPPORTED.
|
|
74
49
|
*/
|
|
75
50
|
export function parseStudy(raw) {
|
|
76
|
-
if (isRecord(raw)
|
|
51
|
+
if (!isRecord(raw))
|
|
52
|
+
return invalid("A study file must be a YAML object.");
|
|
53
|
+
if (raw.schema === V2_SCHEMA)
|
|
77
54
|
return {
|
|
78
55
|
ok: false,
|
|
79
56
|
error: { code: "HUMANISH_STUDY_V2_UNSUPPORTED", message: V2_UNSUPPORTED_MESSAGE },
|
|
80
57
|
};
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Validate a parsed YAML object, humanish.study.v3 or humanish.lab.v2, into a StudyConfig. Pure:
|
|
85
|
-
* the caller owns file IO. Structural validation only. Fields the engine does not yet consume are
|
|
86
|
-
* accepted but reported in `warnings` so `study show` never silently swallows a setting that does
|
|
87
|
-
* nothing. migrate's conversion (convert.ts) calls it directly, since it reads v2 files.
|
|
88
|
-
*/
|
|
89
|
-
export function parseStudyDocument(raw) {
|
|
90
|
-
if (!isRecord(raw)) {
|
|
91
|
-
return invalid("A study file must be a YAML object.");
|
|
92
|
-
}
|
|
93
|
-
if (raw.schema === STUDY_SCHEMA)
|
|
94
|
-
return parseV3(raw);
|
|
95
|
-
if (raw.schema !== V2_SCHEMA) {
|
|
58
|
+
if (raw.schema !== STUDY_SCHEMA) {
|
|
96
59
|
return invalid(`The study schema must be ${STUDY_SCHEMA} or ${V2_SCHEMA}.`);
|
|
97
60
|
}
|
|
98
|
-
return
|
|
61
|
+
return parseStudyV3(raw);
|
|
99
62
|
}
|
|
100
63
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
64
|
+
* Validate a humanish.study.v3 document into a StudyConfig. Pure: the caller owns file IO. Fields the
|
|
65
|
+
* route does not read refuse the study; migrate's report mode lists them in `inert` instead.
|
|
103
66
|
*/
|
|
104
|
-
function
|
|
105
|
-
const
|
|
106
|
-
if (!
|
|
107
|
-
return
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
67
|
+
export function parseStudyV3(raw, options = {}) {
|
|
68
|
+
const front = readStudyFront(raw, options.inert === "report");
|
|
69
|
+
if (!front.ok)
|
|
70
|
+
return front;
|
|
71
|
+
const study = parseSections(raw, front.value);
|
|
72
|
+
if (!study.ok)
|
|
73
|
+
return study;
|
|
74
|
+
const checked = checkStudyConfig(study.value);
|
|
75
|
+
if (!checked.ok)
|
|
76
|
+
return checked;
|
|
77
|
+
const { config, warnings } = checked;
|
|
78
|
+
const { route } = config;
|
|
79
|
+
const mismatch = routeMismatchReason(config);
|
|
80
|
+
if (mismatch)
|
|
81
|
+
return invalid(mismatch);
|
|
82
|
+
if (options.inert === "report") {
|
|
83
|
+
return { ok: true, config, warnings, inert: inertFieldPaths(config) };
|
|
116
84
|
}
|
|
117
|
-
const inert = inertFieldLabels(
|
|
85
|
+
const inert = inertFieldLabels(config);
|
|
118
86
|
if (inert.length > 0) {
|
|
119
|
-
return invalid(
|
|
87
|
+
return invalid(`route: ${route} does not read ${inert.join(", ")}. Remove ${inert.length === 1 ? "it" : "them"}.`);
|
|
120
88
|
}
|
|
121
|
-
return {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
89
|
+
return { ok: true, config, warnings };
|
|
90
|
+
}
|
|
91
|
+
/** Why a config's declared route is not the one its subject and actor take, or undefined. */
|
|
92
|
+
function routeMismatchReason(config) {
|
|
93
|
+
const taken = routeOf(config);
|
|
94
|
+
if (taken === config.route)
|
|
95
|
+
return undefined;
|
|
96
|
+
return `This study declares route: ${config.route}, but its subject (source: ${config.subject.source}) and actor (type: ${config.actor.type}) take the ${taken} route. Change \`route\`, or change the subject or actor.`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Why runStudy refuses a config a library caller passes, or undefined: a humanish.lab.v2 config,
|
|
100
|
+
* one that still sets a StudyConfig field of humanish 0.110, one with no `actor` object, and one
|
|
101
|
+
* whose `route` is not the route its subject and actor take, with parseStudy's message. A planner
|
|
102
|
+
* reads only the v3 fields, so a budget left in `execution.caps` would otherwise run uncapped.
|
|
103
|
+
*/
|
|
104
|
+
export function libraryConfigRefusal(config) {
|
|
105
|
+
const raw = config;
|
|
106
|
+
if (!isRecord(raw))
|
|
107
|
+
return undefined;
|
|
108
|
+
if (raw.schema === V2_SCHEMA)
|
|
109
|
+
return { code: "HUMANISH_STUDY_V2_UNSUPPORTED", message: V2_UNSUPPORTED_MESSAGE };
|
|
110
|
+
const moves = v2FieldMoves(raw);
|
|
111
|
+
if (moves.length > 0)
|
|
112
|
+
return {
|
|
113
|
+
code: "HUMANISH_STUDY_V2_UNSUPPORTED",
|
|
114
|
+
message: `This StudyConfig sets fields that humanish 0.111.0 renamed: ${moves.join("; ")}. Set the humanish.study.v3 fields, or parse the study file with parseStudy. The 0.111.0 release notes list each renamed field.`,
|
|
115
|
+
};
|
|
116
|
+
if (!isRecord(raw.actor))
|
|
117
|
+
return { code: "HUMANISH_STUDY_INVALID", message: ACTOR_REQUIRED };
|
|
118
|
+
const message = routeNameReason(raw.route) ?? routeMismatchReason(config);
|
|
119
|
+
return message === undefined ? undefined : { code: "HUMANISH_STUDY_INVALID", message };
|
|
126
120
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return invalid(unknownKey);
|
|
121
|
+
// Each section in a fixed order, so a file with more than one error always reports the same one
|
|
122
|
+
// first. migrate's checkV2 reads a v2 file's sections in this order too.
|
|
123
|
+
function parseSections(raw, front) {
|
|
131
124
|
const id = str(raw.id);
|
|
132
125
|
if (!id || !ID_PATTERN.test(id)) {
|
|
133
126
|
return invalid("The study id must be a public-safe token starting with a letter or digit (/^[A-Za-z0-9][A-Za-z0-9_.-]*$/).");
|
|
134
127
|
}
|
|
135
|
-
const
|
|
136
|
-
if (!
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
actors: actorsResult.value,
|
|
154
|
-
...(executionResult.value ? { execution: executionResult.value } : {}),
|
|
155
|
-
};
|
|
156
|
-
const personas = parsePersonas(raw.personas);
|
|
157
|
-
if (personas)
|
|
158
|
-
config.personas = personas;
|
|
159
|
-
const scenarioResult = parseScenario(raw.scenario);
|
|
160
|
-
if (!scenarioResult.ok) {
|
|
161
|
-
return scenarioResult;
|
|
162
|
-
}
|
|
163
|
-
if (scenarioResult.value)
|
|
164
|
-
config.scenario = scenarioResult.value;
|
|
128
|
+
const subject = parseSubject(raw.subject);
|
|
129
|
+
if (!subject.ok)
|
|
130
|
+
return subject;
|
|
131
|
+
const type = parseActorType(front.actor, "actor");
|
|
132
|
+
if (!type.ok)
|
|
133
|
+
return type;
|
|
134
|
+
const participants = parseParticipants(raw.participants, front.participants);
|
|
135
|
+
if (!participants.ok)
|
|
136
|
+
return participants;
|
|
137
|
+
const fields = parseActorFields(front.actor, "actor");
|
|
138
|
+
if (!fields.ok)
|
|
139
|
+
return fields;
|
|
140
|
+
const execution = parseExecution(raw.execution);
|
|
141
|
+
if (!execution.ok)
|
|
142
|
+
return execution;
|
|
143
|
+
const caps = parseCaps(raw.caps);
|
|
144
|
+
if (!caps.ok)
|
|
145
|
+
return caps;
|
|
165
146
|
if (isRecord(raw.policies) &&
|
|
166
147
|
raw.policies.mediaPermission !== undefined &&
|
|
167
148
|
raw.policies.mediaPermission !== "prompt" &&
|
|
168
149
|
raw.policies.mediaPermission !== "granted") {
|
|
169
150
|
return invalid("`policies.mediaPermission` must be `prompt` (the participant answers the browser's own dialog) or `granted`.");
|
|
170
151
|
}
|
|
171
|
-
const
|
|
172
|
-
if (!
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (
|
|
178
|
-
return reviewResult;
|
|
179
|
-
if (reviewResult.value)
|
|
180
|
-
config.review = reviewResult.value;
|
|
181
|
-
if (raw.defaults !== undefined && isRecord(raw.defaults) && raw.defaults.open !== undefined) {
|
|
152
|
+
const policies = parsePolicies(raw.policies);
|
|
153
|
+
if (!policies.ok)
|
|
154
|
+
return policies;
|
|
155
|
+
const review = parseReview(raw.review);
|
|
156
|
+
if (!review.ok)
|
|
157
|
+
return review;
|
|
158
|
+
if (isRecord(raw.defaults) && raw.defaults.open !== undefined) {
|
|
182
159
|
if (typeof raw.defaults.open !== "boolean")
|
|
183
160
|
return invalid("`defaults.open` must be true or false.");
|
|
184
161
|
}
|
|
185
162
|
const defaults = parseDefaults(raw.defaults);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
163
|
+
const comms = parseComms(raw.comms);
|
|
164
|
+
if (!comms.ok)
|
|
165
|
+
return comms;
|
|
166
|
+
const mode = raw.mode === "dry-run" || raw.mode === "live" ? raw.mode : undefined;
|
|
167
|
+
const { surfaces } = front;
|
|
168
|
+
const scenario = str(raw.scenario);
|
|
169
|
+
return {
|
|
170
|
+
ok: true,
|
|
171
|
+
value: {
|
|
172
|
+
schema: STUDY_SCHEMA,
|
|
173
|
+
id,
|
|
174
|
+
...optionalStr("title", raw.title),
|
|
175
|
+
...optionalStr("description", raw.description),
|
|
176
|
+
route: front.route,
|
|
177
|
+
...(mode === undefined ? {} : { mode }),
|
|
178
|
+
subject: subject.value,
|
|
179
|
+
actor: { type: type.value, ...fields.value },
|
|
180
|
+
...(participants.value === undefined ? {} : { participants: participants.value }),
|
|
181
|
+
...(surfaces === undefined ? {} : { surfaces }),
|
|
182
|
+
...(caps.value === undefined ? {} : { caps: caps.value }),
|
|
183
|
+
...(execution.value === undefined ? {} : { execution: execution.value }),
|
|
184
|
+
...(scenario === undefined ? {} : { scenario }),
|
|
185
|
+
...(policies.value === undefined ? {} : { policies: policies.value }),
|
|
186
|
+
...(review.value === undefined ? {} : { review: review.value }),
|
|
187
|
+
...(defaults === undefined ? {} : { defaults }),
|
|
188
|
+
...(comms.value === undefined ? {} : { comms: comms.value }),
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// `participants` in the form the file wrote it. readStudyFront has checked the form against the
|
|
193
|
+
// route and expanded each list entry that has a count; the entries parse here, each named by the
|
|
194
|
+
// `participants` entry it came from. An instruction that is not a non-empty string is dropped, as
|
|
195
|
+
// the v2 parser drops it.
|
|
196
|
+
function parseParticipants(raw, read) {
|
|
197
|
+
if (read.entries !== undefined) {
|
|
198
|
+
const { source } = read;
|
|
199
|
+
return parseParticipantEntries(read.entries, {
|
|
200
|
+
list: "participants",
|
|
201
|
+
entry: (index) => `participants[${source[index] ?? index}]`,
|
|
202
|
+
});
|
|
195
203
|
}
|
|
204
|
+
if (typeof raw === "number")
|
|
205
|
+
return { ok: true, value: read.count };
|
|
206
|
+
if (!isRecord(raw))
|
|
207
|
+
return { ok: true, value: undefined };
|
|
208
|
+
const instruction = str(read.instruction);
|
|
209
|
+
return {
|
|
210
|
+
ok: true,
|
|
211
|
+
value: {
|
|
212
|
+
...(read.count === undefined ? {} : { count: read.count }),
|
|
213
|
+
...(instruction === undefined ? {} : { instruction }),
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The checks that read more than one section of a parsed config, and the defaults they fill:
|
|
219
|
+
* shared-world `execution.concurrency`, `comms.email.recipients` and the local browser's defaults.
|
|
220
|
+
*/
|
|
221
|
+
function checkStudyConfig(config) {
|
|
222
|
+
const smtpReason = smtpValidationReason(config);
|
|
223
|
+
if (smtpReason)
|
|
224
|
+
return invalid(smtpReason);
|
|
196
225
|
const mediaReason = desktopMediaValidationReason(config);
|
|
197
226
|
if (mediaReason)
|
|
198
227
|
return invalid(mediaReason);
|
|
199
228
|
const outputLimitReason = outputTokenLimitValidationReason(config);
|
|
200
229
|
if (outputLimitReason)
|
|
201
230
|
return invalid(outputLimitReason);
|
|
202
|
-
const scenarioCapsReason = scenarioCapsValidationReason(config);
|
|
203
|
-
if (scenarioCapsReason)
|
|
204
|
-
return invalid(scenarioCapsReason);
|
|
205
231
|
// All-parallel default: a multi-participant study that does not declare execution.concurrency
|
|
206
232
|
// runs every participant at once; the declared field is a cap the author chose, never a mode.
|
|
207
233
|
// Independent computer-use participants resolve that default from the final participant count
|
|
@@ -209,7 +235,7 @@ function parseV2(raw) {
|
|
|
209
235
|
// world's roster is fixed, so its default is filled here for the envelopes and warnings that read
|
|
210
236
|
// the parsed config.
|
|
211
237
|
{
|
|
212
|
-
const participantCount =
|
|
238
|
+
const participantCount = participantList(config)?.length ?? declaredParticipantCount(config) ?? 1;
|
|
213
239
|
if (participantCount > 1 &&
|
|
214
240
|
config.execution?.concurrency === undefined &&
|
|
215
241
|
isSharedWorldComposition(config)) {
|
package/dist/study/discover.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActorPersonaRef } from "../actors/contract.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { StudyConfig } from "./types.js";
|
|
3
3
|
declare const STUDY_LIST_SCHEMA = "humanish.study-list.v1";
|
|
4
4
|
declare const STUDY_SHOW_SCHEMA = "humanish.study-show.v1";
|
|
5
5
|
type StudyOrigin = "committed" | "ignored" | "explicit";
|
package/dist/study/discover.js
CHANGED
|
@@ -5,7 +5,7 @@ import { lstat, open, readdir, realpath } from "node:fs/promises";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { parse } from "yaml";
|
|
7
7
|
import { parseStudy } from "./config.js";
|
|
8
|
-
import { V2_SCHEMA } from "./
|
|
8
|
+
import { V2_SCHEMA } from "./migrate/v2.js";
|
|
9
9
|
import { assertPreparedSelectedOutputDirectory, assertSafeOutputPathSegment, prepareSelectedOutputDirectory, readContainedRegularFile, } from "../run/contained-output.js";
|
|
10
10
|
import { isNodeError } from "../run/type-guards.js";
|
|
11
11
|
import { studyFileCandidates, STUDY_DIRECTORIES } from "./files.js";
|
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
|
{
|