humanish 0.110.4 → 0.111.1
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 +31 -0
- package/CONTEXT.md +2 -2
- package/dist/actors/codex/restricted-participant-policy.d.ts +2 -0
- package/dist/actors/codex/restricted-participant-policy.js +30 -2
- package/dist/actors/codex/restricted-participant.js +1 -1
- package/dist/actors/computer-use/executor-diagnostic.d.ts +15 -0
- package/dist/actors/computer-use/executor-diagnostic.js +105 -0
- package/dist/actors/computer-use/executor-error.d.ts +18 -3
- package/dist/actors/computer-use/executor-error.js +30 -4
- package/dist/actors/computer-use/loop/actions.d.ts +6 -3
- package/dist/actors/computer-use/loop/actions.js +27 -7
- package/dist/actors/computer-use/loop/ending.js +9 -1
- package/dist/actors/computer-use/loop/types.d.ts +14 -0
- package/dist/actors/computer-use/loop.d.ts +1 -1
- package/dist/actors/computer-use/loop.js +1 -1
- package/dist/actors/computer-use/openai-context.d.ts +8 -2
- package/dist/actors/computer-use/openai-context.js +91 -26
- package/dist/actors/computer-use/openai-provider.js +55 -12
- package/dist/actors/computer-use/openai-requests.d.ts +2 -1
- package/dist/actors/computer-use/openai-requests.js +4 -1
- package/dist/actors/contract.d.ts +7 -1
- 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/browser-control/client.js +5 -5
- package/dist/browser-control/protocol.d.ts +30 -1
- package/dist/browser-control/protocol.js +19 -2
- package/dist/browser-control/transport.d.ts +3 -2
- package/dist/browser-control/transport.js +12 -7
- package/dist/cli/commands/study-route-computer-use.js +2 -2
- package/dist/cli/commands/study-route-shared-world.js +2 -2
- package/dist/cli/commands/study.js +1 -4
- package/dist/cli/doctor.js +11 -0
- package/dist/evidence/encoded-text.d.ts +3 -3
- package/dist/evidence/encoded-text.js +10 -5
- package/dist/evidence/redaction.d.ts +2 -2
- package/dist/evidence/redaction.js +18 -7
- package/dist/guest/browser-tools.js +5 -1
- package/dist/guest/chromium-text.js +42 -18
- package/dist/guest/desktop-executor.js +5 -1
- package/dist/keys/key-resolution.d.ts +3 -0
- package/dist/keys/key-resolution.js +15 -2
- package/dist/routes/computer-use/bundle-parts.d.ts +4 -0
- package/dist/routes/computer-use/bundle-parts.js +7 -2
- package/dist/routes/computer-use/fanout-bundle.js +4 -2
- package/dist/routes/computer-use/local-vm.js +1 -2
- package/dist/routes/computer-use/participant-model.js +7 -2
- package/dist/routes/computer-use/plan.js +7 -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 +2 -3
- package/dist/routes/computer-use/setup.js +2 -2
- package/dist/routes/computer-use/types.d.ts +4 -4
- package/dist/routes/preview.js +1 -0
- package/dist/routes/scripted/plan.js +4 -5
- package/dist/routes/scripted/route.js +3 -3
- package/dist/routes/scripted/scenario.d.ts +1 -1
- package/dist/routes/scripted/scenario.js +3 -3
- 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 +1 -1
- package/dist/routes/shared-world/plan.js +6 -8
- package/dist/routes/shared-world/route.js +2 -2
- package/dist/routes/shared-world/setup.js +1 -0
- package/dist/routes/shared-world/types.d.ts +1 -1
- package/dist/routes/terminal/bundle.d.ts +3 -3
- package/dist/routes/terminal/dry-run.js +1 -0
- package/dist/routes/terminal/ledger.d.ts +2 -2
- package/dist/routes/terminal/lifetime.js +1 -1
- package/dist/routes/terminal/live-finish.js +25 -6
- package/dist/routes/terminal/live-sandbox.js +5 -1
- package/dist/routes/terminal/plan.js +3 -4
- package/dist/routes/terminal/recorder.d.ts +6 -0
- package/dist/routes/terminal/recorder.js +61 -6
- package/dist/routes/terminal/route.js +2 -3
- package/dist/routes/terminal/session.js +2 -1
- package/dist/routes/terminal/token-usage.d.ts +3 -0
- package/dist/routes/terminal/token-usage.js +5 -0
- package/dist/routes/terminal/types.d.ts +3 -2
- package/dist/routes/terminal/types.js +2 -1
- package/dist/run/dry-run.js +1 -0
- package/dist/run/results.d.ts +3 -1
- package/dist/run/run.d.ts +2 -0
- package/dist/run/run.js +16 -2
- package/dist/run/terminal-contract.d.ts +10 -0
- package/dist/run/terminal-contract.js +27 -9
- 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 +36 -27
- package/dist/study/config.d.ts +29 -13
- package/dist/study/config.js +174 -157
- package/dist/study/discover.d.ts +1 -1
- package/dist/study/discover.js +1 -1
- package/dist/study/doctor.js +4 -5
- package/dist/study/keys.d.ts +250 -2
- package/dist/study/keys.js +32 -62
- package/dist/study/migrate/convert.js +3 -4
- package/dist/study/migrate/v2.d.ts +22 -7
- package/dist/study/migrate/v2.js +173 -23
- package/dist/study/parse/actors.d.ts +11 -32
- package/dist/study/parse/actors.js +11 -156
- package/dist/study/parse/comms.js +6 -2
- package/dist/study/parse/execution.d.ts +9 -4
- package/dist/study/parse/execution.js +4 -39
- package/dist/study/parse/{study-v3.d.ts → front.d.ts} +6 -17
- package/dist/study/parse/{study-v3.js → front.js} +24 -112
- package/dist/study/parse/subject.d.ts +2 -0
- package/dist/study/parse/subject.js +15 -18
- package/dist/study/parse/url-credentials.d.ts +3 -3
- package/dist/study/parse/url-credentials.js +155 -13
- package/dist/study/persona-resolve.js +7 -9
- package/dist/study/plan-base.d.ts +1 -1
- package/dist/study/plan-base.js +5 -7
- package/dist/study/plan-participants.d.ts +2 -2
- package/dist/study/plan-participants.js +3 -3
- package/dist/study/plan-types.d.ts +7 -5
- package/dist/study/plan.js +12 -6
- package/dist/study/preflight-probes.js +2 -2
- package/dist/study/preflight.d.ts +1 -1
- package/dist/study/preflight.js +1 -1
- package/dist/study/routing.d.ts +5 -5
- package/dist/study/routing.js +16 -16
- package/dist/study/run-study-options.d.ts +1 -1
- package/dist/study/run-study-options.js +2 -2
- package/dist/study/study-deps.d.ts +1 -1
- package/dist/study/study-fields.d.ts +4 -25
- package/dist/study/study-fields.js +15 -44
- package/dist/study/summary.js +13 -8
- package/dist/study/types.d.ts +51 -128
- package/dist/study/types.js +0 -1
- package/dist/study/url-credentials.js +3 -4
- package/dist/study/validation.d.ts +2 -10
- package/dist/study/validation.js +23 -53
- package/dist/study/warnings.d.ts +11 -3
- package/dist/study/warnings.js +56 -110
- package/dist/substrates/local/runtime-config.js +11 -13
- package/docs/architecture/browser-control.md +12 -2
- package/docs/architecture/guest-desktop.md +7 -1
- package/docs/architecture/terminal-product-route.md +3 -2
- package/docs/contracts/schemas.md +39 -9
- package/package.json +1 -1
- package/dist/study/parse/study.d.ts +0 -38
- package/dist/study/parse/study.js +0 -225
package/dist/study/types.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ 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
|
-
export declare const V2_SCHEMA = "humanish.lab.v2";
|
|
7
6
|
/** The study format: it declares its route, one actor, its participants and one caps block. */
|
|
8
7
|
export declare const STUDY_SCHEMA = "humanish.study.v3";
|
|
9
8
|
export declare const ID_PATTERN: RegExp;
|
|
@@ -18,16 +17,6 @@ export declare const ID_PATTERN: RegExp;
|
|
|
18
17
|
* error, never a desktop attempt. See docs/architecture/state-driven-executor.md.
|
|
19
18
|
*/
|
|
20
19
|
type StudySubjectSource = "this-repo" | "clone" | "app-url" | "local-app" | "terminal-product" | "desktop-cli" | "local-tree";
|
|
21
|
-
/**
|
|
22
|
-
* How a subject's world relates across participants. `per-lane-worlds` (the default; absent ==
|
|
23
|
-
* this) is the only fan-out topology the computer-use route ships: N participants, N independent
|
|
24
|
-
* worlds, isolation + per-participant attribution. `shared-world` is the declared override: one
|
|
25
|
-
* mutable service plane that N participants use at the same time, so their actions interact through
|
|
26
|
-
* shared state. Consumed only on the shared-world routes (a provisioned clone plane, or an
|
|
27
|
-
* external-public app-url plane) with a computer-use actor; inert/warned everywhere else
|
|
28
|
-
* (claims match mechanism).
|
|
29
|
-
*/
|
|
30
|
-
type StudySubjectTopology = "per-lane-worlds" | "shared-world";
|
|
31
20
|
export interface StudySubjectClone {
|
|
32
21
|
/** git clone depth; 1 (shallow) by default. Consumed on the computer-use clone route. */
|
|
33
22
|
depth?: number;
|
|
@@ -189,26 +178,18 @@ export interface StudySubjectProduct {
|
|
|
189
178
|
}
|
|
190
179
|
export interface StudySubject {
|
|
191
180
|
source: StudySubjectSource;
|
|
192
|
-
/**
|
|
193
|
-
* World topology across participants. Absent == `per-lane-worlds` (the isolation default; every
|
|
194
|
-
* existing study is byte-stable). `shared-world` is the declared override: one mutable service
|
|
195
|
-
* plane, N participants at once, each in its role. Consumed only on the shared-world routes (a
|
|
196
|
-
* provisioned clone or an external-public app-url plane, a computer-use actor, and a roster of ≥2
|
|
197
|
-
* participants); inert/warned elsewhere.
|
|
198
|
-
*/
|
|
199
|
-
topology?: StudySubjectTopology;
|
|
200
181
|
/**
|
|
201
182
|
* Concurrent shared-world route only: the author's required attestation that the
|
|
202
183
|
* subject behind the internet-reachable `getHost` URL is synthetic seeded data. The concurrent
|
|
203
184
|
* route exposes the subject on a tokenless public URL for the run's duration, so real/external
|
|
204
185
|
* data must never sit behind it. This is author-trust + a provenance gate (verify also requires
|
|
205
186
|
* `subject.state.provenance == "seeded"`). It does not guarantee the data is synthetic. Required when
|
|
206
|
-
* `
|
|
187
|
+
* `route: shared-world` + `execution.concurrency > 1`; inert/warned elsewhere.
|
|
207
188
|
*/
|
|
208
189
|
exposure?: "synthetic";
|
|
209
190
|
/**
|
|
210
191
|
* External-public shared-world route only: the author's required ownership
|
|
211
|
-
* attestation when a real public deployment (`source: app-url` + `
|
|
192
|
+
* attestation when a real public deployment (`source: app-url` + `route: shared-world` +
|
|
212
193
|
* `concurrency > 1` + `policies.allowPublicTargets: true`) is used directly as the shared plane.
|
|
213
194
|
* The harness neither provisions nor exposes this target (no getHost, no clone, no seed), so it
|
|
214
195
|
* cannot attest the data is synthetic; instead the operator must attest they own/operate it.
|
|
@@ -276,12 +257,6 @@ export interface StudySubject {
|
|
|
276
257
|
*/
|
|
277
258
|
localTree?: StudySubjectLocalTree;
|
|
278
259
|
}
|
|
279
|
-
export interface StudyParticipantFocus {
|
|
280
|
-
id?: string;
|
|
281
|
-
label?: string;
|
|
282
|
-
/** Per-participant steer appended to the actor's mission. Consumed on the app-url route. */
|
|
283
|
-
instruction?: string;
|
|
284
|
-
}
|
|
285
260
|
/**
|
|
286
261
|
* One participant in a differentiated fan-out on the computer-use E2B route (`per-lane-worlds`).
|
|
287
262
|
* Each entry becomes an independent E2B desktop sandbox with its own persona/device/starting-steer. All
|
|
@@ -294,7 +269,7 @@ export interface StudyParticipantEntry {
|
|
|
294
269
|
id?: string;
|
|
295
270
|
/**
|
|
296
271
|
* App-defined actor type label for grouping simulated users ("operator", "viewer",
|
|
297
|
-
* "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`);
|
|
298
273
|
* it is adapter-owned taxonomy for roster/readback.
|
|
299
274
|
*/
|
|
300
275
|
actorType?: string;
|
|
@@ -302,7 +277,7 @@ export interface StudyParticipantEntry {
|
|
|
302
277
|
surface?: string;
|
|
303
278
|
/** App-defined correlation id tying participants to one shared case/account/work item. */
|
|
304
279
|
caseGroup?: string;
|
|
305
|
-
/** 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. */
|
|
306
281
|
persona?: string;
|
|
307
282
|
/** Named hosted-screen preset for this participant. XOR raw execution.desktop.resolution. */
|
|
308
283
|
device?: string;
|
|
@@ -345,17 +320,6 @@ export interface StudyParticipantEntry {
|
|
|
345
320
|
*/
|
|
346
321
|
host?: boolean;
|
|
347
322
|
}
|
|
348
|
-
/**
|
|
349
|
-
* Compact authoring sugar for repeated participant groups. The parser expands each group into concrete
|
|
350
|
-
* `lanes[]` with deterministic ids (`<group.id>-01`, `<group.id>-02`, ...). The runtime never
|
|
351
|
-
* consumes this shape directly; it always sees ordinary `LabActorLane` entries.
|
|
352
|
-
*/
|
|
353
|
-
export interface StudyActorRosterGroup extends Omit<StudyParticipantEntry, "id"> {
|
|
354
|
-
/** Public-safe group id; prefixes generated participant ids. */
|
|
355
|
-
id: string;
|
|
356
|
-
/** Number of participants to generate for this group. */
|
|
357
|
-
count: number;
|
|
358
|
-
}
|
|
359
323
|
export interface StudyActor {
|
|
360
324
|
/**
|
|
361
325
|
* The actor label. On computer-use (including shared-world), scripted-browser, and
|
|
@@ -365,18 +329,8 @@ export interface StudyActor {
|
|
|
365
329
|
* the descriptor for dispatch and capability enforcement.
|
|
366
330
|
*/
|
|
367
331
|
type: string;
|
|
368
|
-
/** Participant count, route-specific (see the scope header in config.ts): synthetic simCount; scripted
|
|
369
|
-
* surface roster {1 = desktop, 2 = desktop + mobile, default 1}; computer-use E2B route the
|
|
370
|
-
* homogeneous fan-out participant count (cap 16). XOR `lanes`. */
|
|
371
|
-
count?: number;
|
|
372
|
-
/** Computer-use E2B route: a differentiated fan-out roster (`per-lane-worlds`). XOR `count`,
|
|
373
|
-
* `roster`, and `laneFocus`. Cap 16 participants. Consumed only on the cua E2B route
|
|
374
|
-
* (inert/warned elsewhere). */
|
|
375
|
-
lanes?: StudyParticipantEntry[];
|
|
376
332
|
/** Persona id/label threaded into the actor prompt. Consumed on the app-url route. */
|
|
377
333
|
persona?: string;
|
|
378
|
-
/** Consumed on the app-url route (laneFocus.instruction appended to the mission). XOR `lanes`. */
|
|
379
|
-
laneFocus?: StudyParticipantFocus;
|
|
380
334
|
/** Free-form mission threaded into the actor prompt. Consumed on the app-url route. A mission on
|
|
381
335
|
* its own is a complete, valid study; `tasks` is additive, never required. */
|
|
382
336
|
mission?: string;
|
|
@@ -404,7 +358,7 @@ export interface StudyActor {
|
|
|
404
358
|
*/
|
|
405
359
|
localAgent?: "codex" | "claude";
|
|
406
360
|
/**
|
|
407
|
-
* 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.
|
|
408
362
|
*
|
|
409
363
|
* Absent means the provider's default, and absence is recorded as absence: a run that did not
|
|
410
364
|
* declare an effort does not claim one. Support is model-dependent (see src/actors/reasoning-effort.ts),
|
|
@@ -419,12 +373,12 @@ export interface StudyActor {
|
|
|
419
373
|
*/
|
|
420
374
|
reasoningEffort?: ReasoningEffort;
|
|
421
375
|
/**
|
|
422
|
-
* 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
|
|
423
377
|
* overrides this value.
|
|
424
378
|
*/
|
|
425
379
|
stopWhen?: StopWhen;
|
|
426
380
|
/**
|
|
427
|
-
* 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
|
|
428
382
|
* it. `when` is a stopWhen-shaped condition (absent: the window opens after the first
|
|
429
383
|
* observation); `ms` is the hold, `everyMs` the frame cadence (default 10 s), `then` whether
|
|
430
384
|
* the participant continues afterwards (default) or the session ends. The harness takes no
|
|
@@ -545,20 +499,6 @@ export interface StudyExecution {
|
|
|
545
499
|
*/
|
|
546
500
|
concurrency?: number;
|
|
547
501
|
desktop?: StudyExecutionDesktop;
|
|
548
|
-
/**
|
|
549
|
-
* Blast-radius budget for each computer-use participant. Consumed on the computer-use route:
|
|
550
|
-
* `caps.maxUsd`, when set, is a fail-closed abort: the session stops the moment its running
|
|
551
|
-
* estimated spend crosses it (the runaway-retry guard), and a cap on a model src/run/pricing.ts
|
|
552
|
-
* cannot price is refused at preflight rather than run uncapped. It is a per-participant cap:
|
|
553
|
-
* enforced inside each participant's loop, so an N-participant fan-out can spend up to N × maxUsd
|
|
554
|
-
* before any participant aborts (the run warns with the true ~N × cap ceiling).
|
|
555
|
-
* `caps.maxTotalUsd` is the shared study budget: one ledger across every participant, the knob a
|
|
556
|
-
* researcher actually reasons with. Absent = uncapped (the historical CUA behavior); maxUsd: 0
|
|
557
|
-
* still permits a request before reported usage trips it. Inert (warned) on non-computer-use
|
|
558
|
-
* routes. Reuses the same StudyScenarioCaps shape as the terminal route's `scenario.caps` (not a
|
|
559
|
-
* fork).
|
|
560
|
-
*/
|
|
561
|
-
caps?: StudyScenarioCaps;
|
|
562
502
|
/** `terminal-product` route: the terminal transport + stdin posture. Consumed on that route. */
|
|
563
503
|
terminal?: StudyExecutionTerminal;
|
|
564
504
|
/** `terminal-product` route: runtime key placement, defaulting to openai-egress, an external
|
|
@@ -571,6 +511,7 @@ export interface StudyExecution {
|
|
|
571
511
|
};
|
|
572
512
|
/**
|
|
573
513
|
* `terminal-product` route: outbound routing allowlist passed to E2B with a deny-all fallback.
|
|
514
|
+
* Every other route leaves egress open and ignores it with a warning; 0.112.0 refuses it there.
|
|
574
515
|
* Domain filtering is a routing control, not strict destination isolation on shared hosting.
|
|
575
516
|
* It does not constrain spending through an allowed runtime provider, including when
|
|
576
517
|
* openai-egress keeps the raw runtime key outside the sandbox.
|
|
@@ -584,48 +525,45 @@ export interface StudyExecution {
|
|
|
584
525
|
*/
|
|
585
526
|
egressAllow?: string[];
|
|
586
527
|
}
|
|
587
|
-
type
|
|
528
|
+
type StudyMode = "dry-run" | "live";
|
|
588
529
|
/**
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
530
|
+
* A study's spend, job and time caps, the top-level `caps` block. Each route reads its own keys, and
|
|
531
|
+
* parseStudy refuses a key the route does not read. All values are non-negative numbers.
|
|
532
|
+
*
|
|
533
|
+
* Computer use and shared world read `maxUsd` and `maxTotalUsd`. `maxUsd`, when set, is a
|
|
534
|
+
* fail-closed abort for each participant: the session stops the moment its running estimated spend
|
|
535
|
+
* crosses it (the runaway-retry guard), and a cap on a model src/run/pricing.ts cannot price is
|
|
536
|
+
* refused at preflight rather than run uncapped. It is enforced inside each participant's loop, so
|
|
537
|
+
* an N-participant fan-out can spend up to N × maxUsd before any participant aborts (the run warns
|
|
538
|
+
* with the true ~N × cap ceiling). Absent = uncapped; maxUsd: 0 still permits a request before
|
|
539
|
+
* reported usage trips it.
|
|
540
|
+
*
|
|
541
|
+
* The terminal route passes a live key to an in-sandbox command, and per the safety contract the
|
|
542
|
+
* live key is never exercised without a fail-closed cap in force. It reads `maxUsd`, `maxJobs` and
|
|
543
|
+
* `maxMinutes` (0 is the no-spend default). Live runs require maxUsd and a positive maxMinutes;
|
|
544
|
+
* maxUsd/maxJobs are checked after the session against known ledger signals and maxMinutes is
|
|
545
|
+
* enforced as the command wall clock. Codex tokens are unpriced, so a live run refuses a positive
|
|
546
|
+
* maxUsd unless a costProbe measures spend (HUMANISH_TERMINAL_UNPRICED_CAP).
|
|
595
547
|
*/
|
|
596
|
-
export interface
|
|
548
|
+
export interface StudyCaps {
|
|
597
549
|
/** Max USD the run may spend (provider + product). 0 = no-spend. */
|
|
598
550
|
maxUsd?: number;
|
|
599
551
|
/**
|
|
600
552
|
* study-level model-spend budget, the number a researcher actually reasons with: "this
|
|
601
553
|
* study is N participants, roughly $X", decided once, up front, where recruiting decisions are
|
|
602
|
-
* made.
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
* be set. A positive `scenario.caps.maxTotalUsd` on a computer-use study is a parse error; on the
|
|
609
|
-
* terminal route it is inert (warned), since the single agent's maxUsd already caps the run.
|
|
554
|
+
* made. Computer use and shared world only: every participant's running estimated model spend
|
|
555
|
+
* feeds one shared ledger, and the moment the run total crosses this, each participant stops at
|
|
556
|
+
* its next turn with `budget_reached` (status `incomplete`: the participant ran out of budget;
|
|
557
|
+
* never `gave_up`, because a study-level stop is not the participant's doing). Estimated model
|
|
558
|
+
* spend only; desktop-minutes ride the cost summary but not this ledger. Independent of the
|
|
559
|
+
* per-participant `maxUsd` backstop; either, both, or neither may be set.
|
|
610
560
|
*/
|
|
611
561
|
maxTotalUsd?: number;
|
|
612
|
-
/**
|
|
562
|
+
/** Terminal: max billable product jobs the agent may trigger. 0 = none. */
|
|
613
563
|
maxJobs?: number;
|
|
614
|
-
/**
|
|
564
|
+
/** Terminal: max wall-clock minutes for the agent session. */
|
|
615
565
|
maxMinutes?: number;
|
|
616
566
|
}
|
|
617
|
-
export interface StudyScenario {
|
|
618
|
-
/** Reference a committed scenario by id (humanish/scenarios/<ref>.yaml) or path. Consumed
|
|
619
|
-
* (and required) on the scripted-browser route; forward-declared elsewhere. */
|
|
620
|
-
ref?: string;
|
|
621
|
-
/** Or inline the scenario body. Forward-declared: no route reads it yet, so a set value warns. */
|
|
622
|
-
inline?: Record<string, unknown>;
|
|
623
|
-
/** dry-run = a synthetic bundle with no provider spend; live = real run. Consumed. */
|
|
624
|
-
mode?: StudyScenarioMode;
|
|
625
|
-
/** Spend/job/time caps. Consumed (recorded in the bundle) on the terminal-product route;
|
|
626
|
-
* inert (warned) elsewhere. */
|
|
627
|
-
caps?: StudyScenarioCaps;
|
|
628
|
-
}
|
|
629
567
|
export interface StudyPolicies {
|
|
630
568
|
/**
|
|
631
569
|
* Redact target repo labels in durable artifacts. Consumed on the computer-use,
|
|
@@ -722,7 +660,7 @@ export interface StudyCommsReceivingEmail {
|
|
|
722
660
|
interface StudyCommsCaptureEmail {
|
|
723
661
|
connection?: never;
|
|
724
662
|
allowedOrigins?: never;
|
|
725
|
-
/** Which implementation backs the inbox (a backend discriminator, distinct from `
|
|
663
|
+
/** Which implementation backs the inbox (a backend discriminator, distinct from `mode`):
|
|
726
664
|
* `fake` (default) is an in-harness in-memory inbox in the Fowler test-double sense: an in-sandbox
|
|
727
665
|
* catch captures the app's sends. Provider-backed receiving uses the separate
|
|
728
666
|
* StudyCommsReceivingEmail configuration selected by a saved connection. */
|
|
@@ -789,45 +727,28 @@ export interface StudyCommsRecipient {
|
|
|
789
727
|
* for the persona surface's default address (not drain-matched). */
|
|
790
728
|
address?: string;
|
|
791
729
|
}
|
|
792
|
-
export interface StudyConfig {
|
|
793
|
-
/** The format the file was written in. Both parse into this one shape. */
|
|
794
|
-
schema: typeof V2_SCHEMA | typeof STUDY_SCHEMA;
|
|
795
|
-
id: string;
|
|
796
|
-
title?: string;
|
|
797
|
-
description?: string;
|
|
798
|
-
subject: StudySubject;
|
|
799
|
-
actors: StudyActor[];
|
|
800
|
-
execution?: StudyExecution;
|
|
801
|
-
/** Forward-declared: no route reads it yet, so a set value warns. */
|
|
802
|
-
personas?: Record<string, unknown>[];
|
|
803
|
-
scenario?: StudyScenario;
|
|
804
|
-
policies?: StudyPolicies;
|
|
805
|
-
review?: StudyReview;
|
|
806
|
-
defaults?: StudyDefaults;
|
|
807
|
-
comms?: StudyComms;
|
|
808
|
-
}
|
|
809
730
|
/**
|
|
810
|
-
* A humanish.study.v3
|
|
811
|
-
* participants and one caps block.
|
|
812
|
-
* StudyConfig above.
|
|
731
|
+
* A humanish.study.v3 study as parseStudy returns it, with the keys the file has: the declared
|
|
732
|
+
* route, one actor, its participants and one caps block.
|
|
813
733
|
*/
|
|
814
|
-
export interface
|
|
734
|
+
export interface StudyConfig {
|
|
815
735
|
schema: typeof STUDY_SCHEMA;
|
|
816
736
|
id: string;
|
|
817
737
|
title?: string;
|
|
818
738
|
description?: string;
|
|
739
|
+
/** The route the study declares. parseStudy refuses one its subject and actor do not take. */
|
|
819
740
|
route: StudyRoute;
|
|
820
741
|
/** Absent: a dry run. */
|
|
821
|
-
mode?:
|
|
822
|
-
subject:
|
|
823
|
-
actor:
|
|
742
|
+
mode?: StudyMode;
|
|
743
|
+
subject: StudySubject;
|
|
744
|
+
actor: StudyActor;
|
|
824
745
|
/** Preview, computer-use and shared-world, in the form the file used. */
|
|
825
746
|
participants?: StudyParticipants;
|
|
826
747
|
/** Scripted only. */
|
|
827
748
|
surfaces?: StudySurfaces;
|
|
828
749
|
/** The caps keys the route reads. */
|
|
829
|
-
caps?:
|
|
830
|
-
execution?:
|
|
750
|
+
caps?: StudyCaps;
|
|
751
|
+
execution?: StudyExecution;
|
|
831
752
|
/** Scripted: a committed scenario id or path. */
|
|
832
753
|
scenario?: string;
|
|
833
754
|
policies?: StudyPolicies;
|
|
@@ -836,15 +757,17 @@ export interface StudyV3 {
|
|
|
836
757
|
comms?: StudyComms;
|
|
837
758
|
}
|
|
838
759
|
/**
|
|
839
|
-
* A count, a count with one instruction for every participant, or a list.
|
|
840
|
-
* `count`
|
|
760
|
+
* A count, a count with one instruction for every participant, or a list. parseStudy expands a list
|
|
761
|
+
* entry with a `count` into its participants, `<id>-01` to `<id>-NN`.
|
|
841
762
|
*/
|
|
842
763
|
type StudyParticipants = number | StudyParticipantGroup | StudyParticipantEntry[];
|
|
843
|
-
|
|
764
|
+
/** `participants` as identical participants that share one instruction. */
|
|
765
|
+
export interface StudyParticipantGroup {
|
|
844
766
|
count?: number;
|
|
845
767
|
instruction?: string;
|
|
846
768
|
}
|
|
847
|
-
|
|
769
|
+
/** The scripted surfaces: desktop, or desktop and mobile. */
|
|
770
|
+
export type StudySurfaces = readonly ["desktop"] | readonly ["desktop", "mobile"];
|
|
848
771
|
interface StudyParseSuccess {
|
|
849
772
|
ok: true;
|
|
850
773
|
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
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// Every URL a study config declares, checked for a credential. Route planners call it, since a
|
|
2
2
|
// library caller's config skips the parser that refuses these first.
|
|
3
|
-
import { rosterOf } from "./parse/actors.js";
|
|
4
3
|
import { entryCredentialReason, urlCredentialReason } from "./parse/url-credentials.js";
|
|
5
|
-
import {
|
|
4
|
+
import { participantList } from "./study-fields.js";
|
|
6
5
|
/**
|
|
7
6
|
* The first URL in `config` that carries a credential, with why: the subject's app, serve and
|
|
8
7
|
* public surface URLs, and each participant's target and entry.
|
|
@@ -17,14 +16,14 @@ export function studyUrlCredentialReason(config) {
|
|
|
17
16
|
? undefined
|
|
18
17
|
: urlCredentialReason("subject.serve.url", subject.serve.url),
|
|
19
18
|
...(subject.product?.publicSurfaces ?? []).map((surface) => () => urlCredentialReason("subject.product.publicSurfaces", surface)),
|
|
20
|
-
...
|
|
19
|
+
...(participantList(config) ?? []).flatMap((participant, index) => [
|
|
21
20
|
() => participant.target === undefined
|
|
22
21
|
? undefined
|
|
23
22
|
: urlCredentialReason(`participants[${index}].target`, participant.target),
|
|
24
23
|
() => participant.entry === undefined
|
|
25
24
|
? undefined
|
|
26
25
|
: entryCredentialReason(`participants[${index}].entry`, participant.entry),
|
|
27
|
-
])
|
|
26
|
+
]),
|
|
28
27
|
];
|
|
29
28
|
for (const reason of reasons) {
|
|
30
29
|
const found = reason();
|
|
@@ -7,7 +7,7 @@ import type { StudyConfig } from "./types.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function computerUseValidationReason(config: StudyConfig): string | null;
|
|
9
9
|
/**
|
|
10
|
-
* Cross-validate a `
|
|
10
|
+
* Cross-validate a `route: shared-world` declaration. Returns the failure message, or
|
|
11
11
|
* null when the one shared-world route can run it: the external-public checks for an app-url
|
|
12
12
|
* subject, the provisioned checks otherwise. Enforced at parse and again by the route, since
|
|
13
13
|
* runStudyWith takes a config that skipped the parser.
|
|
@@ -34,14 +34,6 @@ export declare function taskProtocolValidationReason(config: StudyConfig, suppor
|
|
|
34
34
|
* at parse and again on the computer-use and scripted-browser routes for library callers.
|
|
35
35
|
*/
|
|
36
36
|
export declare function cloneTargetValidationReason(config: StudyConfig): string | null;
|
|
37
|
-
/**
|
|
38
|
-
* Refuse a positive `scenario.caps.maxUsd` or `maxTotalUsd` on a computer-use study.
|
|
39
|
-
* `scenario.caps` belongs to the terminal route; the computer-use route stops on `execution.caps`,
|
|
40
|
-
* so a dollar figure here would read as a cap while the study ran uncapped. Zero spends nothing
|
|
41
|
-
* either way and stays a warning. Enforced at parse and again on the computer-use routes for
|
|
42
|
-
* library callers.
|
|
43
|
-
*/
|
|
44
|
-
export declare function scenarioCapsValidationReason(config: StudyConfig): string | null;
|
|
45
37
|
/** Refuse a claimed output bound when the route cannot pass it to the first-party provider. */
|
|
46
38
|
export declare function outputTokenLimitValidationReason(config: StudyConfig): string | null;
|
|
47
39
|
/**
|
|
@@ -60,7 +52,7 @@ export declare function concurrentSharedWorldValidationReason(config: StudyConfi
|
|
|
60
52
|
* it rejects every provisioned-subject field (serve/state.seed/state.checkpoint/exposure/clone/repos
|
|
61
53
|
* are inert with no sandbox, so they fail closed), and requires a
|
|
62
54
|
* non-loopback appUrl + allowPublicTargets + the operator-ownership attestation subject.publicTarget +
|
|
63
|
-
* concurrency >= 2 +
|
|
55
|
+
* concurrency >= 2 + a `participants` list of ≥2 with exactly one host participant. The getHost synthetic
|
|
64
56
|
* gate is deliberately unreachable here (there is no internet-reachable harness-owned URL to attest).
|
|
65
57
|
* Enforced at parse and re-enforced in the engine (runStudyWith takes a config that skipped the parser).
|
|
66
58
|
*/
|
package/dist/study/validation.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { isMaxOutputTokens } from "../actors/output-token-limit.js";
|
|
2
|
-
import { PARTICIPANT_ID_MAX_CHARS, PARTICIPANT_ID_PATTERN
|
|
2
|
+
import { PARTICIPANT_ID_MAX_CHARS, PARTICIPANT_ID_PATTERN } from "./parse/actors.js";
|
|
3
3
|
import { isHttpUrl, isLoopbackUrl } from "./parse/subject.js";
|
|
4
4
|
import { declaredTargets } from "./plan-participants.js";
|
|
5
5
|
import { actorResolvesToComputerUse, computerUseParticipantCount, MAX_COMPUTER_USE_PARTICIPANTS, registeredComputerUseActors, resolveEntryUrl, isComputerUseComposition, isScriptedBrowserComposition, isSharedWorldComposition, isTerminalProductComposition, } from "./routing.js";
|
|
6
|
-
import {
|
|
6
|
+
import { participantList } from "./study-fields.js";
|
|
7
7
|
/**
|
|
8
8
|
* Cross-validate the computer-use fan-out declaration (`per-lane-worlds`). Returns the failure
|
|
9
9
|
* message, or null when valid. The parser enforces it, and runStudyWith checks it again for a config
|
|
@@ -11,25 +11,18 @@ import { actorOf, actorsOf, participantList, declaredParticipantCount, declaresS
|
|
|
11
11
|
* validity, unique ids) first, then the route-scoped XOR, cap and policy checks.
|
|
12
12
|
*/
|
|
13
13
|
export function computerUseValidationReason(config) {
|
|
14
|
-
const actor = actorOf(config);
|
|
15
14
|
const roster = participantList(config);
|
|
16
15
|
const structuralReason = rosterStructuralValidationReason(config);
|
|
17
16
|
if (structuralReason) {
|
|
18
17
|
return structuralReason;
|
|
19
18
|
}
|
|
20
19
|
// clone.fanout is a declared behavior change: rejected on the computer-use route (was
|
|
21
|
-
// inert-warned). Fan-out is declared
|
|
22
|
-
//
|
|
20
|
+
// inert-warned). Fan-out is declared with `participants`; subject.clone.fanout never applied
|
|
21
|
+
// here.
|
|
23
22
|
if (config.subject.clone?.fanout !== undefined) {
|
|
24
23
|
return "`subject.clone.fanout` is not used on the computer-use route: declare fan-out with participants (homogeneous) or participants (a roster of participants). (No current route reads clone.fanout.)";
|
|
25
24
|
}
|
|
26
25
|
if (roster !== undefined) {
|
|
27
|
-
if (declaredParticipantCount(config) !== undefined) {
|
|
28
|
-
return "Set either `actors[0].count` (identical participants) or `actors[0].lanes` (a roster of distinct participants), not both.";
|
|
29
|
-
}
|
|
30
|
-
if (focusOf(actor) !== undefined) {
|
|
31
|
-
return "actors[0].laneFocus and actors[0].lanes are mutually exclusive: each roster entry's `instruction` is the fan-out steer; laneFocus is the steer for a single participant.";
|
|
32
|
-
}
|
|
33
26
|
if (config.execution?.desktop?.resolution !== undefined &&
|
|
34
27
|
roster.some((entry) => entry.device !== undefined)) {
|
|
35
28
|
return "participants[].device and a raw execution.desktop.resolution are mutually exclusive: a per-participant device preset and a single hand-set resolution cannot both govern participant geometry.";
|
|
@@ -54,13 +47,13 @@ export function computerUseValidationReason(config) {
|
|
|
54
47
|
// Public targets fan out into N independent worlds driving the same public app, which is an
|
|
55
48
|
// ambiguous shared-world-ish shape, not a per-participant target swarm. Permit N>1 public runs only
|
|
56
49
|
// when every roster entry declares its own target, making the adapter-owned topology explicit. But when
|
|
57
|
-
// `
|
|
50
|
+
// `route: shared-world` is also declared, N participants against one public target is the
|
|
58
51
|
// external-public shared-world topology, so route it there (a real public deployment
|
|
59
52
|
// as the shared plane) instead of refusing; externalPublicSharedWorldValidationReason then applies.
|
|
60
53
|
if (participantCount > 1 &&
|
|
61
54
|
config.policies?.allowPublicTargets === true &&
|
|
62
55
|
declaredTargets(config).length === 0 &&
|
|
63
|
-
|
|
56
|
+
config.route !== "shared-world") {
|
|
64
57
|
return "`policies.allowPublicTargets` with more than one participant sends them all to one public app, which is a shared world. Set `route: shared-world` to run them together there, give each `participants[]` entry its own `target`, or run one participant.";
|
|
65
58
|
}
|
|
66
59
|
return null;
|
|
@@ -98,7 +91,7 @@ function rosterStructuralValidationReason(config) {
|
|
|
98
91
|
return null;
|
|
99
92
|
}
|
|
100
93
|
/**
|
|
101
|
-
* Cross-validate a `
|
|
94
|
+
* Cross-validate a `route: shared-world` declaration. Returns the failure message, or
|
|
102
95
|
* null when the one shared-world route can run it: the external-public checks for an app-url
|
|
103
96
|
* subject, the provisioned checks otherwise. Enforced at parse and again by the route, since
|
|
104
97
|
* runStudyWith takes a config that skipped the parser.
|
|
@@ -110,8 +103,8 @@ export function sharedWorldValidationReason(config) {
|
|
|
110
103
|
}
|
|
111
104
|
/**
|
|
112
105
|
* The structural checks every provisioned shared world shares. It requires: a clone or local-tree source + e2b-desktop
|
|
113
|
-
* target + a computer-use actor + a `subject.serve` block +
|
|
114
|
-
*
|
|
106
|
+
* target + a computer-use actor + a `subject.serve` block + a `participants` list of ≥2 (the list
|
|
107
|
+
* is the role roster; there is no separate roles[] field), and every role `entry` must resolve
|
|
115
108
|
* same-origin (loopback) with serve.url. Fail-closed: a half-declared shared-world is rejected,
|
|
116
109
|
* never silently downgraded.
|
|
117
110
|
*/
|
|
@@ -126,7 +119,7 @@ function provisionedSharedWorldStructureReason(config) {
|
|
|
126
119
|
if (config.execution?.target !== "e2b-desktop") {
|
|
127
120
|
return "`route: shared-world` requires `execution.target: e2b-desktop`: the role participants drive hosted desktop browsers against one in-sandbox app.";
|
|
128
121
|
}
|
|
129
|
-
if (!actorResolvesToComputerUse(
|
|
122
|
+
if (!actorResolvesToComputerUse(config.actor?.type)) {
|
|
130
123
|
return `\`route: shared-world\` requires a registered computer-use actor (one of: ${registeredComputerUseActors().join(", ")}); each role participant runs a computer-use session.`;
|
|
131
124
|
}
|
|
132
125
|
const serve = config.subject.serve;
|
|
@@ -149,7 +142,7 @@ function provisionedSharedWorldStructureReason(config) {
|
|
|
149
142
|
}
|
|
150
143
|
/** SMTP capture runs only where each participant has its own world, so a shared world refuses it. */
|
|
151
144
|
export function smtpValidationReason(config) {
|
|
152
|
-
return config.comms?.email?.smtp &&
|
|
145
|
+
return config.comms?.email?.smtp && config.route === "shared-world"
|
|
153
146
|
? "SMTP capture is not supported yet for shared-world studies. Use the default per-lane-worlds topology for SMTP, or configure supported HTTP email capture for concurrent shared-world studies."
|
|
154
147
|
: undefined;
|
|
155
148
|
}
|
|
@@ -160,7 +153,7 @@ export function smtpValidationReason(config) {
|
|
|
160
153
|
*/
|
|
161
154
|
export function desktopMediaValidationReason(config, supportsMedia = isComputerUseComposition(config)) {
|
|
162
155
|
if (config.execution?.desktop?.recording !== undefined &&
|
|
163
|
-
(!supportsMedia ||
|
|
156
|
+
(!supportsMedia || config.route === "shared-world" || config.subject.source === "local-app")) {
|
|
164
157
|
return "execution.desktop.recording is supported only for computer-use participants on independent desktops. Remove the declaration or select a supported route.";
|
|
165
158
|
}
|
|
166
159
|
const media = config.execution?.desktop?.media;
|
|
@@ -169,7 +162,7 @@ export function desktopMediaValidationReason(config, supportsMedia = isComputerU
|
|
|
169
162
|
if (media.microphone !== undefined && media.microphone.source !== "speech") {
|
|
170
163
|
return "execution.desktop.media.microphone.source must be speech. Microphone source-file injection is unsupported.";
|
|
171
164
|
}
|
|
172
|
-
if (
|
|
165
|
+
if (config.route === "shared-world") {
|
|
173
166
|
return "execution.desktop.media is unsupported on shared-world routes; declared capture devices would not be provisioned. Use independent computer-use browser participants or remove the declaration.";
|
|
174
167
|
}
|
|
175
168
|
if (!supportsMedia ||
|
|
@@ -181,8 +174,10 @@ export function desktopMediaValidationReason(config, supportsMedia = isComputerU
|
|
|
181
174
|
return "execution.desktop.media requires Chrome or Chromium; Firefox cannot receive the declared synthetic capture device. Set execution.desktop.browser: chrome or chromium.";
|
|
182
175
|
}
|
|
183
176
|
if (media.microphone !== undefined) {
|
|
184
|
-
|
|
185
|
-
|
|
177
|
+
const actor = config.actor;
|
|
178
|
+
if (actor !== undefined &&
|
|
179
|
+
(actor.type !== "local-agent" ||
|
|
180
|
+
(actor.localAgent !== undefined && actor.localAgent !== "codex"))) {
|
|
186
181
|
return "Participant speech currently requires local-agent with Codex, on a local or hosted desktop.";
|
|
187
182
|
}
|
|
188
183
|
if (config.execution?.target !== "local" && media.camera !== undefined) {
|
|
@@ -199,7 +194,7 @@ export function receivingEmailValidationReason(config) {
|
|
|
199
194
|
!["app-url", "clone", "local-tree"].includes(config.subject.source)) {
|
|
200
195
|
return "Real email receiving requires a hosted computer-use browser study with an app-url, clone, or local-tree subject. Scripted, terminal, desktop-cli and local-app routes are unsupported.";
|
|
201
196
|
}
|
|
202
|
-
if (
|
|
197
|
+
if (config.actor?.type === "local-agent") {
|
|
203
198
|
return "Real email receiving is unavailable for local-agent: its host process does not isolate the inbox management credential. Use a hosted first-party computer-use actor.";
|
|
204
199
|
}
|
|
205
200
|
return undefined;
|
|
@@ -207,15 +202,8 @@ export function receivingEmailValidationReason(config) {
|
|
|
207
202
|
/** Refuse task declarations that the selected execution path would discard.
|
|
208
203
|
* Direct runners pass their actual support rather than trusting the config's dispatch shape. */
|
|
209
204
|
export function taskProtocolValidationReason(config, supportsTasks = isComputerUseComposition(config) && !isSharedWorldComposition(config)) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
continue;
|
|
213
|
-
if (index > 0) {
|
|
214
|
-
return `actors[${index}].tasks is unsupported: current runners consume only actors[0]. Use the first actor's computer-use participants for a task protocol.`;
|
|
215
|
-
}
|
|
216
|
-
if (!supportsTasks) {
|
|
217
|
-
return "actor.tasks is unsupported on this execution path. Task protocols require the computer-use route, with one world per participant; shared-world, terminal-product, scripted-browser and synthetic routes do not consume them. Remove tasks only if a mission-only study is intended.";
|
|
218
|
-
}
|
|
205
|
+
if (config.actor?.tasks !== undefined && !supportsTasks) {
|
|
206
|
+
return "actor.tasks is unsupported on this execution path. Task protocols require the computer-use route, with one world per participant; shared-world, terminal-product, scripted-browser and synthetic routes do not consume them. Remove tasks only if a mission-only study is intended.";
|
|
219
207
|
}
|
|
220
208
|
return null;
|
|
221
209
|
}
|
|
@@ -233,27 +221,9 @@ export function cloneTargetValidationReason(config) {
|
|
|
233
221
|
const got = target === undefined ? "it is absent" : `got "${target}"`;
|
|
234
222
|
return `clone subjects require \`execution.target: e2b-desktop\` (${got}): humanish clones and serves the repo inside a hosted desktop sandbox. \`execution.target: local\` applies to app-url and local-app subjects.`;
|
|
235
223
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Refuse a positive `scenario.caps.maxUsd` or `maxTotalUsd` on a computer-use study.
|
|
238
|
-
* `scenario.caps` belongs to the terminal route; the computer-use route stops on `execution.caps`,
|
|
239
|
-
* so a dollar figure here would read as a cap while the study ran uncapped. Zero spends nothing
|
|
240
|
-
* either way and stays a warning. Enforced at parse and again on the computer-use routes for
|
|
241
|
-
* library callers.
|
|
242
|
-
*/
|
|
243
|
-
export function scenarioCapsValidationReason(config) {
|
|
244
|
-
if (!isComputerUseComposition(config))
|
|
245
|
-
return null;
|
|
246
|
-
for (const key of ["maxUsd", "maxTotalUsd"]) {
|
|
247
|
-
const value = config.scenario?.caps?.[key];
|
|
248
|
-
if (value === undefined || value <= 0)
|
|
249
|
-
continue;
|
|
250
|
-
return `scenario.caps.${key} (${value}) does not cap a computer-use study: this route stops on execution.caps.${key}. Move the value to execution.caps.${key}; scenario.caps applies only to terminal-product studies.`;
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
224
|
/** Refuse a claimed output bound when the route cannot pass it to the first-party provider. */
|
|
255
225
|
export function outputTokenLimitValidationReason(config) {
|
|
256
|
-
const actor =
|
|
226
|
+
const actor = config.actor;
|
|
257
227
|
if (actor?.maxOutputTokens === undefined)
|
|
258
228
|
return null;
|
|
259
229
|
if (!isMaxOutputTokens(actor.maxOutputTokens))
|
|
@@ -317,7 +287,7 @@ export function concurrentSharedWorldValidationReason(config) {
|
|
|
317
287
|
* it rejects every provisioned-subject field (serve/state.seed/state.checkpoint/exposure/clone/repos
|
|
318
288
|
* are inert with no sandbox, so they fail closed), and requires a
|
|
319
289
|
* non-loopback appUrl + allowPublicTargets + the operator-ownership attestation subject.publicTarget +
|
|
320
|
-
* concurrency >= 2 +
|
|
290
|
+
* concurrency >= 2 + a `participants` list of ≥2 with exactly one host participant. The getHost synthetic
|
|
321
291
|
* gate is deliberately unreachable here (there is no internet-reachable harness-owned URL to attest).
|
|
322
292
|
* Enforced at parse and re-enforced in the engine (runStudyWith takes a config that skipped the parser).
|
|
323
293
|
*/
|
|
@@ -332,7 +302,7 @@ export function externalPublicSharedWorldValidationReason(config) {
|
|
|
332
302
|
if (config.execution?.target !== "e2b-desktop") {
|
|
333
303
|
return "the external-public shared-world route requires `execution.target: e2b-desktop`: the role participants drive hosted desktop browsers against the one public deployment.";
|
|
334
304
|
}
|
|
335
|
-
if (!actorResolvesToComputerUse(
|
|
305
|
+
if (!actorResolvesToComputerUse(config.actor?.type)) {
|
|
336
306
|
return `the external-public shared-world route requires a registered computer-use actor (one of: ${registeredComputerUseActors().join(", ")}); each role participant runs a computer-use session.`;
|
|
337
307
|
}
|
|
338
308
|
const roster = participantList(config);
|
package/dist/study/warnings.d.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { StudyConfig } from "./types.js";
|
|
2
2
|
/**
|
|
3
|
-
* The fields a config sets that its route does not read, each as `field` or `field (reason)`.
|
|
4
|
-
*
|
|
3
|
+
* The fields a config sets that its route does not read, each as `field` or `field (reason)`.
|
|
4
|
+
* parseStudy refuses a study for any of them.
|
|
5
5
|
*/
|
|
6
6
|
export declare function inertFieldLabels(config: StudyConfig): string[];
|
|
7
7
|
/**
|
|
8
8
|
* The same fields as `inertFieldLabels`, as paths without their reasons, such as
|
|
9
|
-
* `
|
|
9
|
+
* `participants[].entry` or `execution.timeoutMs`. `humanish migrate` drops each one.
|
|
10
10
|
*/
|
|
11
11
|
export declare function inertFieldPaths(config: StudyConfig): string[];
|
|
12
|
+
/**
|
|
13
|
+
* The warning for `execution.egressAllow` on a route that ignores it, or undefined. Only the
|
|
14
|
+
* terminal route creates its sandbox with the list as an outbound allowlist; on any other route the
|
|
15
|
+
* study author would trust a list that blocks nothing. `humanish study check` and every run print
|
|
16
|
+
* it, and the run records it in its bundle.
|
|
17
|
+
* Removed in 0.112.0: deprecated on 2026-10-05. 0.112.0 refuses the field off the terminal route.
|
|
18
|
+
*/
|
|
19
|
+
export declare function egressAllowIgnoredWarning(config: StudyConfig): string | undefined;
|
|
12
20
|
export declare function forwardDeclaredWarnings(config: StudyConfig): string[];
|