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/warnings.js
CHANGED
|
@@ -1,72 +1,54 @@
|
|
|
1
1
|
import { isComputerUseComposition, isScriptedBrowserComposition, isSharedWorldComposition, isTerminalProductComposition, } from "./routing.js";
|
|
2
2
|
import { declaredParticipantIds } from "./plan-participants.js";
|
|
3
3
|
import { addressedRecipients } from "./parse/comms.js";
|
|
4
|
-
import {
|
|
5
|
-
// The computer-use and terminal routes consume mission/persona/model
|
|
6
|
-
//
|
|
7
|
-
// E2B route actors[0].lanes is consumed (the fan-out roster).
|
|
4
|
+
import { actorOf, participantList, declaredParticipantCount, scenarioRefOf, } from "./study-fields.js";
|
|
5
|
+
// The computer-use and terminal routes consume mission/persona/model (they compose the agent prompt
|
|
6
|
+
// and bundle provenance).
|
|
8
7
|
const promptRoute = (routes) => routes.cua || routes.terminal;
|
|
9
|
-
// The scripted-browser route consumes persona and
|
|
10
|
-
//
|
|
8
|
+
// The scripted-browser route consumes persona and surfaces (trace/bundle provenance). The prompt
|
|
9
|
+
// fields can never act there: the scripted actor runs no model.
|
|
11
10
|
const scriptedRoute = (routes) => !promptRoute(routes) && routes.scripted;
|
|
12
11
|
const otherRoute = (routes) => !promptRoute(routes) && !routes.scripted;
|
|
13
|
-
/** Rows for
|
|
12
|
+
/** Rows for the actor and its participants, reported first, in this order. */
|
|
14
13
|
const ACTOR_ROWS = [
|
|
15
14
|
// Shared-world-only fields on the roster: a participant's `entry` is inert anywhere else
|
|
16
15
|
// (claims match mechanism).
|
|
17
16
|
{
|
|
18
|
-
field: "
|
|
19
|
-
reason: "the per-participant loopback entry is a shared-world capability; needs
|
|
20
|
-
applies: (
|
|
17
|
+
field: "participants[].entry",
|
|
18
|
+
reason: "the per-participant loopback entry is a shared-world capability; needs route: shared-world",
|
|
19
|
+
applies: (config, routes) => Boolean(participantList(config)?.some((entry) => entry.entry !== undefined)) &&
|
|
20
|
+
!routes.shared,
|
|
21
21
|
},
|
|
22
22
|
// The host marker acts only on the external-public shared-world route; inert elsewhere.
|
|
23
23
|
{
|
|
24
|
-
field: "
|
|
24
|
+
field: "participants[].host",
|
|
25
25
|
reason: "the designated host-participant marker; needs the external-public shared-world route: app-url × topology shared-world × allowPublicTargets",
|
|
26
|
-
applies: (
|
|
26
|
+
applies: (config, routes) => Boolean(participantList(config)?.some((entry) => entry.host === true)) &&
|
|
27
|
+
!routes.externalPublic,
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
|
-
field: "
|
|
30
|
-
applies: (actor, routes) => promptRoute(routes) && Boolean(focusOf(actor)?.id),
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
field: "laneFocus.label",
|
|
34
|
-
applies: (actor, routes) => promptRoute(routes) && Boolean(focusOf(actor)?.label),
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
field: "lanes",
|
|
38
|
-
reason: "only the computer-use route runs more than one participant",
|
|
39
|
-
applies: (actor, routes) => routes.terminal && Boolean(rosterOf(actor)),
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
field: "mission",
|
|
30
|
+
field: "actor.mission",
|
|
43
31
|
reason: "the scripted-browser actor runs no model",
|
|
44
|
-
applies: (
|
|
32
|
+
applies: (config, routes) => scriptedRoute(routes) && Boolean(actorOf(config)?.mission),
|
|
45
33
|
},
|
|
46
34
|
{
|
|
47
|
-
field: "
|
|
35
|
+
field: "actor.model",
|
|
48
36
|
reason: "the scripted-browser actor runs no model",
|
|
49
|
-
applies: (
|
|
37
|
+
applies: (config, routes) => scriptedRoute(routes) && Boolean(actorOf(config)?.model),
|
|
50
38
|
},
|
|
39
|
+
// On every other route the prompt fields and persona are inert.
|
|
51
40
|
{
|
|
52
|
-
field: "
|
|
53
|
-
|
|
54
|
-
applies: (actor, routes) => scriptedRoute(routes) && Boolean(actor.model),
|
|
41
|
+
field: "actor.mission",
|
|
42
|
+
applies: (config, routes) => otherRoute(routes) && Boolean(actorOf(config)?.mission),
|
|
55
43
|
},
|
|
56
44
|
{
|
|
57
|
-
field: "
|
|
58
|
-
|
|
59
|
-
applies: (actor, routes) => scriptedRoute(routes) && Boolean(rosterOf(actor)),
|
|
45
|
+
field: "actor.persona",
|
|
46
|
+
applies: (config, routes) => otherRoute(routes) && Boolean(actorOf(config)?.persona),
|
|
60
47
|
},
|
|
61
|
-
// On every other route the prompt fields, persona and the `lanes[]` roster are inert.
|
|
62
|
-
{ field: "mission", applies: (actor, routes) => otherRoute(routes) && Boolean(actor.mission) },
|
|
63
48
|
{
|
|
64
|
-
field: "
|
|
65
|
-
applies: (
|
|
49
|
+
field: "actor.model",
|
|
50
|
+
applies: (config, routes) => otherRoute(routes) && Boolean(actorOf(config)?.model),
|
|
66
51
|
},
|
|
67
|
-
{ field: "persona", applies: (actor, routes) => otherRoute(routes) && Boolean(actor.persona) },
|
|
68
|
-
{ field: "model", applies: (actor, routes) => otherRoute(routes) && Boolean(actor.model) },
|
|
69
|
-
{ field: "lanes", applies: (actor, routes) => otherRoute(routes) && Boolean(rosterOf(actor)) },
|
|
70
52
|
];
|
|
71
53
|
const TERMINAL_ONLY = "needs subject.source: terminal-product + a registered terminal actor";
|
|
72
54
|
const RESERVED = "reserved; no route reads it yet";
|
|
@@ -90,17 +72,10 @@ const CONFIG_ROWS = [
|
|
|
90
72
|
field: "subject.state",
|
|
91
73
|
applies: (config, routes) => Boolean(config.subject.state) && !routes.cua && !routes.scripted,
|
|
92
74
|
},
|
|
93
|
-
//
|
|
94
|
-
// (incl. an explicit per-lane-worlds, which the computer-use route already is by mechanism) warns
|
|
95
|
-
// inert.
|
|
96
|
-
{
|
|
97
|
-
field: "subject.topology",
|
|
98
|
-
reason: "drives behavior only on the shared-world route; needs subject.topology: shared-world + clone × e2b-desktop × a computer-use actor + a roster of ≥2 participants",
|
|
99
|
-
applies: (config, routes) => config.subject.topology !== undefined && !routes.shared,
|
|
100
|
-
},
|
|
75
|
+
// The checkpoint acts only on the shared-world route.
|
|
101
76
|
{
|
|
102
77
|
field: "subject.state.checkpoint",
|
|
103
|
-
reason: "the shared-world state-checkpoint probe; needs
|
|
78
|
+
reason: "the shared-world state-checkpoint probe; needs route: shared-world",
|
|
104
79
|
applies: (config, routes) => config.subject.state?.checkpoint !== undefined && !routes.shared,
|
|
105
80
|
},
|
|
106
81
|
// publicTarget (the external-public ownership attestation) acts only on the external-public
|
|
@@ -160,18 +135,10 @@ const CONFIG_ROWS = [
|
|
|
160
135
|
field: "execution.concurrency",
|
|
161
136
|
applies: (config, routes) => config.execution?.concurrency !== undefined && !routes.cua,
|
|
162
137
|
},
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
field: "execution.caps",
|
|
168
|
-
reason: "only the computer-use route enforces it, with a computer-use actor on e2b-desktop",
|
|
169
|
-
applies: (config, routes) => Boolean(config.execution?.caps) && !routes.cua,
|
|
170
|
-
},
|
|
171
|
-
// terminal-product consumes subject.product, scenario.caps, execution.{terminal,runtimeAuth}:
|
|
172
|
-
// dry-run records the contract; live execution enforces caps and command-scoped auth. On every
|
|
173
|
-
// other route they are inert and must warn so a
|
|
174
|
-
// misplaced safety/budget field is never trusted to do something it cannot.
|
|
138
|
+
// parse/front.ts refuses a `caps` key the declared route does not read, so no row lists `caps`.
|
|
139
|
+
// terminal-product consumes subject.product and execution.{terminal,runtimeAuth}: dry-run records
|
|
140
|
+
// the contract; live execution enforces command-scoped auth. On every other route they are inert
|
|
141
|
+
// and must warn so a misplaced safety field is never trusted to do something it cannot.
|
|
175
142
|
{
|
|
176
143
|
field: "subject.product",
|
|
177
144
|
reason: "needs subject.source: terminal-product or desktop-cli with the matching actor",
|
|
@@ -179,18 +146,6 @@ const CONFIG_ROWS = [
|
|
|
179
146
|
!routes.terminal &&
|
|
180
147
|
config.subject.source !== "desktop-cli",
|
|
181
148
|
},
|
|
182
|
-
{
|
|
183
|
-
field: "scenario.caps",
|
|
184
|
-
reason: TERMINAL_ONLY,
|
|
185
|
-
applies: (config, routes) => Boolean(config.scenario?.caps) && !routes.terminal,
|
|
186
|
-
},
|
|
187
|
-
// The study-level budget is a CUA-route capability; the terminal route is a single agent whose
|
|
188
|
-
// maxUsd already caps the whole run, so a maxTotalUsd there would be trusted and unenforced.
|
|
189
|
-
{
|
|
190
|
-
field: "scenario.caps.maxTotalUsd",
|
|
191
|
-
reason: "the study-level budget is a computer-use route capability; the terminal route's maxUsd already caps the whole run",
|
|
192
|
-
applies: (config, routes) => config.scenario?.caps?.maxTotalUsd !== undefined && routes.terminal,
|
|
193
|
-
},
|
|
194
149
|
{
|
|
195
150
|
field: "execution.terminal",
|
|
196
151
|
reason: TERMINAL_ONLY,
|
|
@@ -246,13 +201,12 @@ const CONFIG_ROWS = [
|
|
|
246
201
|
reason: "no current route reads it",
|
|
247
202
|
applies: (config) => config.execution?.desktop?.codexAppServer !== undefined,
|
|
248
203
|
},
|
|
249
|
-
// scenario
|
|
204
|
+
// `scenario` is consumed on the scripted-browser route (required there); forward-declared
|
|
250
205
|
// everywhere else.
|
|
251
206
|
{
|
|
252
|
-
field: "scenario
|
|
253
|
-
applies: (config, routes) => Boolean(config
|
|
207
|
+
field: "scenario",
|
|
208
|
+
applies: (config, routes) => Boolean(scenarioRefOf(config)) && !routes.scripted,
|
|
254
209
|
},
|
|
255
|
-
{ field: "scenario.inline", applies: (config) => Boolean(config.scenario?.inline) },
|
|
256
210
|
// review.{scoring,milestones,vocabulary} stay forward-declared on every route.
|
|
257
211
|
// review.scorer is consumed (loaded + wired, or fail-closed at load) on every scorer-capable
|
|
258
212
|
// route, so it does not warn there; on the scripted-browser route the actor carries no scorer seam,
|
|
@@ -277,7 +231,6 @@ const CONFIG_ROWS = [
|
|
|
277
231
|
reason: "the scripted-browser actor has no adopter-scorer seam; declare it on a terminal / computer-use / shared-world route",
|
|
278
232
|
applies: (config, routes) => Boolean(config.review?.scorer) && routes.scripted,
|
|
279
233
|
},
|
|
280
|
-
{ field: "personas", applies: (config) => Boolean(config.personas) },
|
|
281
234
|
];
|
|
282
235
|
function routesOf(config) {
|
|
283
236
|
const cua = isComputerUseComposition(config);
|
|
@@ -294,44 +247,23 @@ function routesOf(config) {
|
|
|
294
247
|
config.execution?.target === "e2b-desktop",
|
|
295
248
|
};
|
|
296
249
|
}
|
|
297
|
-
function
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
: `${prefix}${row.field} (${row.reason})`;
|
|
250
|
+
function inertRows(config) {
|
|
251
|
+
const routes = routesOf(config);
|
|
252
|
+
return [...ACTOR_ROWS, ...CONFIG_ROWS].filter((row) => row.applies(config, routes));
|
|
301
253
|
}
|
|
302
254
|
/**
|
|
303
|
-
* The fields a config sets that its route does not read, each as `field` or `field (reason)`.
|
|
304
|
-
*
|
|
255
|
+
* The fields a config sets that its route does not read, each as `field` or `field (reason)`.
|
|
256
|
+
* parseStudy refuses a study for any of them.
|
|
305
257
|
*/
|
|
306
258
|
export function inertFieldLabels(config) {
|
|
307
|
-
|
|
308
|
-
const inert = [];
|
|
309
|
-
for (const [index, actor] of config.actors.entries()) {
|
|
310
|
-
for (const row of ACTOR_ROWS)
|
|
311
|
-
if (row.applies(actor, routes))
|
|
312
|
-
inert.push(rowLabel(row, `actors[${index}].`));
|
|
313
|
-
}
|
|
314
|
-
for (const row of CONFIG_ROWS)
|
|
315
|
-
if (row.applies(config, routes))
|
|
316
|
-
inert.push(rowLabel(row));
|
|
317
|
-
return inert;
|
|
259
|
+
return inertRows(config).map((row) => row.reason === undefined ? row.field : `${row.field} (${row.reason})`);
|
|
318
260
|
}
|
|
319
261
|
/**
|
|
320
262
|
* The same fields as `inertFieldLabels`, as paths without their reasons, such as
|
|
321
|
-
* `
|
|
263
|
+
* `participants[].entry` or `execution.timeoutMs`. `humanish migrate` drops each one.
|
|
322
264
|
*/
|
|
323
265
|
export function inertFieldPaths(config) {
|
|
324
|
-
|
|
325
|
-
const paths = [];
|
|
326
|
-
for (const [index, actor] of config.actors.entries()) {
|
|
327
|
-
for (const row of ACTOR_ROWS)
|
|
328
|
-
if (row.applies(actor, routes))
|
|
329
|
-
paths.push(`actors[${index}].${row.field}`);
|
|
330
|
-
}
|
|
331
|
-
for (const row of CONFIG_ROWS)
|
|
332
|
-
if (row.applies(config, routes))
|
|
333
|
-
paths.push(row.field);
|
|
334
|
-
return paths;
|
|
266
|
+
return inertRows(config).map((row) => row.field);
|
|
335
267
|
}
|
|
336
268
|
// Report fields that are present but not yet consumed by the engine, so a user never trusts a
|
|
337
269
|
// setting that silently does nothing.
|
|
@@ -347,7 +279,7 @@ export function forwardDeclaredWarnings(config) {
|
|
|
347
279
|
// and the cap delivers waves of M. Say so up front (inspect + dry-run + run): a green run in
|
|
348
280
|
// waves is otherwise indistinguishable from the all-live run the author meant.
|
|
349
281
|
{
|
|
350
|
-
const participantCount =
|
|
282
|
+
const participantCount = participantList(config)?.length ?? declaredParticipantCount(config) ?? 1;
|
|
351
283
|
const cap = config.execution?.concurrency;
|
|
352
284
|
if (routes.cua && cap !== undefined && participantCount > 1 && cap < participantCount) {
|
|
353
285
|
warnings.push(`execution.concurrency ${cap} caps a ${participantCount}-participant roster: participants run in waves of ${cap}, never all live at once. Remove execution.concurrency (the default runs all ${participantCount} participants simultaneously) or set it to ${participantCount}; declare a lower cap only to bound simultaneous paid desktops.`);
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { actorOf, capsOf, participantList } from "../../study/study-fields.js";
|
|
2
2
|
// The runtime image enforces a 30-minute lifetime; reserve setup/teardown room.
|
|
3
3
|
export const LOCAL_BROWSER_LIFETIME_MS = 30 * 60_000;
|
|
4
4
|
const MAX_SESSION_MS = 20 * 60_000;
|
|
5
5
|
export function isLocalBrowserStudy(config) {
|
|
6
6
|
return (config.subject.source === "app-url" &&
|
|
7
7
|
config.execution?.target === "local" &&
|
|
8
|
-
["local-agent", "openai-computer-use"].includes(config
|
|
8
|
+
["local-agent", "openai-computer-use"].includes(actorOf(config)?.type ?? ""));
|
|
9
9
|
}
|
|
10
10
|
/** Defaults for the explicitly selected local substrate; hosted configurations are untouched. */
|
|
11
11
|
export function localBrowserDefaults(config) {
|
|
12
|
-
|
|
12
|
+
const actor = actorOf(config);
|
|
13
|
+
if (!isLocalBrowserStudy(config) || actor === undefined)
|
|
13
14
|
return config;
|
|
14
15
|
return {
|
|
15
16
|
...config,
|
|
16
|
-
|
|
17
|
+
actor: {
|
|
17
18
|
...actor,
|
|
18
19
|
model: actor.model ?? "gpt-6-astra",
|
|
19
20
|
reasoningEffort: actor.reasoningEffort ?? "low",
|
|
20
|
-
}
|
|
21
|
+
},
|
|
21
22
|
execution: {
|
|
22
23
|
...config.execution,
|
|
23
24
|
timeoutMs: config.execution?.timeoutMs ?? MAX_SESSION_MS,
|
|
@@ -26,19 +27,18 @@ export function localBrowserDefaults(config) {
|
|
|
26
27
|
resolution: config.execution?.desktop?.resolution ?? [960, 720],
|
|
27
28
|
},
|
|
28
29
|
},
|
|
29
|
-
...(config.review?.analysis === undefined &&
|
|
30
|
+
...(config.review?.analysis === undefined && actor.type === "local-agent"
|
|
30
31
|
? { review: { ...config.review, analysis: { provider: "codex" } } }
|
|
31
32
|
: {}),
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
export function localBrowserUnsupportedReason(config) {
|
|
35
|
-
const actor = config
|
|
36
|
+
const actor = actorOf(config);
|
|
36
37
|
const desktop = config.execution?.desktop;
|
|
37
38
|
if ((config.execution?.timeoutMs ?? MAX_SESSION_MS) > MAX_SESSION_MS) {
|
|
38
39
|
return "Local browser sessions currently support at most 20 minutes, within the runtime's 30-minute lifetime.";
|
|
39
40
|
}
|
|
40
|
-
if (
|
|
41
|
-
!actor ||
|
|
41
|
+
if (!actor ||
|
|
42
42
|
(actor.type === "local-agent" && actor.localAgent !== undefined && actor.localAgent !== "codex")) {
|
|
43
43
|
return "Local browser studies support only Codex local-agent or openai-computer-use participants.";
|
|
44
44
|
}
|
|
@@ -46,7 +46,7 @@ export function localBrowserUnsupportedReason(config) {
|
|
|
46
46
|
desktop.resolution[1] !== 720 ||
|
|
47
47
|
desktop.device !== undefined ||
|
|
48
48
|
(desktop.browser !== undefined && !["default", "chromium"].includes(desktop.browser)) ||
|
|
49
|
-
|
|
49
|
+
participantList(config)?.some((entry) => entry.device !== undefined) ||
|
|
50
50
|
desktop.template !== undefined ||
|
|
51
51
|
desktop.sandboxTimeoutMs !== undefined) {
|
|
52
52
|
return "The local browser runtime currently uses a 960×720 Chromium desktop. Omit hosted templates, device presets and sandboxTimeoutMs.";
|
|
@@ -61,17 +61,15 @@ export function localBrowserUnsupportedReason(config) {
|
|
|
61
61
|
if (actor.type === "local-agent" &&
|
|
62
62
|
(actor.model !== "gpt-6-astra" ||
|
|
63
63
|
actor.reasoningEffort !== "low" ||
|
|
64
|
-
|
|
64
|
+
participantList(config)?.some((entry) => entry.reasoningEffort !== undefined && entry.reasoningEffort !== "low") ||
|
|
65
65
|
actor.maxOutputTokens !== undefined ||
|
|
66
|
-
config
|
|
67
|
-
config
|
|
68
|
-
config.scenario?.caps?.maxUsd !== undefined ||
|
|
69
|
-
config.scenario?.caps?.maxTotalUsd !== undefined)) {
|
|
66
|
+
capsOf(config)?.maxUsd !== undefined ||
|
|
67
|
+
capsOf(config)?.maxTotalUsd !== undefined)) {
|
|
70
68
|
return "Local Codex participants currently use gpt-6-astra at low effort. Account dollar/output-token caps are unavailable; use API participants for those controls.";
|
|
71
69
|
}
|
|
72
70
|
for (const target of [
|
|
73
71
|
config.subject.appUrl,
|
|
74
|
-
...(
|
|
72
|
+
...(participantList(config) ?? []).map((entry) => entry.target),
|
|
75
73
|
].filter(Boolean)) {
|
|
76
74
|
let url;
|
|
77
75
|
try {
|