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/plan-base.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// What every route planner shares: the plan fields common to all routes, the frozen residual config,
|
|
2
2
|
// and the requirement and subject helpers.
|
|
3
3
|
import { DEFAULT_OPENAI_CU_MODEL } from "../actors/computer-use/openai-provider.js";
|
|
4
|
-
import { actorOf, capsOf } from "./study-fields.js";
|
|
5
4
|
// A YAML alias can make a parsed record contain itself (an inline persona that names its own
|
|
6
5
|
// anchor), and structuredClone keeps the cycle, so each object is frozen once.
|
|
7
6
|
function deepFreeze(value, frozen = new WeakSet()) {
|
|
@@ -14,11 +13,10 @@ function deepFreeze(value, frozen = new WeakSet()) {
|
|
|
14
13
|
return value;
|
|
15
14
|
}
|
|
16
15
|
function residualOf(config) {
|
|
17
|
-
const { comms, policies,
|
|
16
|
+
const { comms, policies, defaults, review } = config;
|
|
18
17
|
return deepFreeze(structuredClone({
|
|
19
18
|
...(comms === undefined ? {} : { comms }),
|
|
20
19
|
...(policies === undefined ? {} : { policies }),
|
|
21
|
-
...(personas === undefined ? {} : { personas }),
|
|
22
20
|
...(defaults === undefined ? {} : { defaults }),
|
|
23
21
|
...(review === undefined ? {} : { review }),
|
|
24
22
|
...(config.execution?.desktop === undefined && config.execution?.target === undefined
|
|
@@ -44,8 +42,8 @@ function residualOf(config) {
|
|
|
44
42
|
export function isNonEmpty(values) {
|
|
45
43
|
return values.length > 0;
|
|
46
44
|
}
|
|
47
|
-
export function planCaps(config
|
|
48
|
-
const caps =
|
|
45
|
+
export function planCaps(config) {
|
|
46
|
+
const caps = config.caps;
|
|
49
47
|
return {
|
|
50
48
|
...(caps?.maxUsd === undefined ? {} : { maxUsd: caps.maxUsd }),
|
|
51
49
|
...(caps?.maxTotalUsd === undefined ? {} : { maxTotalUsd: caps.maxTotalUsd }),
|
|
@@ -87,7 +85,7 @@ export function desktopRequirements(config, args) {
|
|
|
87
85
|
return requirements;
|
|
88
86
|
}
|
|
89
87
|
export function brainOf(config, callerProvider) {
|
|
90
|
-
const actor =
|
|
88
|
+
const actor = config.actor;
|
|
91
89
|
if (callerProvider)
|
|
92
90
|
return callerBrainOf(config);
|
|
93
91
|
const declared = declaredModelOf(config);
|
|
@@ -100,7 +98,7 @@ export function callerBrainOf(config) {
|
|
|
100
98
|
return { kind: "caller", ...declaredModelOf(config) };
|
|
101
99
|
}
|
|
102
100
|
function declaredModelOf(config) {
|
|
103
|
-
const model =
|
|
101
|
+
const model = config.actor?.model;
|
|
104
102
|
return model === undefined ? {} : { declaredModel: model };
|
|
105
103
|
}
|
|
106
104
|
/** The model a participant's spend is priced at when its provider reports none. */
|
|
@@ -9,7 +9,7 @@ export interface Participant {
|
|
|
9
9
|
readonly id: string;
|
|
10
10
|
/** 0-based position in the roster. Bundle `sim-NNN` and `stream-NNN` ids derive from it. */
|
|
11
11
|
readonly index: number;
|
|
12
|
-
/** `
|
|
12
|
+
/** `participants[i].persona ?? actor.persona`. The route compiles the committed persona file. */
|
|
13
13
|
readonly personaId: string | undefined;
|
|
14
14
|
/** The declared mission and per-participant focus. An absent mission takes the route's default. */
|
|
15
15
|
readonly assignment: {
|
|
@@ -30,7 +30,7 @@ interface DesktopParticipant extends Participant {
|
|
|
30
30
|
readonly preset: DevicePreset;
|
|
31
31
|
readonly resolution: [number, number];
|
|
32
32
|
};
|
|
33
|
-
/** The `
|
|
33
|
+
/** The `participants` entry's value, else actor value, else absent (the provider default is recorded in the trace). */
|
|
34
34
|
readonly limits: {
|
|
35
35
|
readonly stopWhen?: StopWhen;
|
|
36
36
|
readonly dwell?: DwellWindow;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Computer-use and shared-world participants share the desktop fields; each kind adds its own.
|
|
3
3
|
import { resolveParticipantDevice } from "./device-presets.js";
|
|
4
4
|
import { isSharedWorldComposition, participantIdAt } from "./routing.js";
|
|
5
|
-
import {
|
|
5
|
+
import { participantList, declaredParticipantCount, participantInstruction, } from "./study-fields.js";
|
|
6
6
|
function desktopParticipant(config, entry, index, kind, focus) {
|
|
7
|
-
const actor =
|
|
7
|
+
const actor = config.actor;
|
|
8
8
|
const device = resolveParticipantDevice(config, entry?.device);
|
|
9
9
|
const personaId = entry?.persona ?? actor?.persona;
|
|
10
10
|
const mission = actor?.mission;
|
|
@@ -39,7 +39,7 @@ function desktopParticipant(config, entry, index, kind, focus) {
|
|
|
39
39
|
* override (`--count`) applies only when no roster is declared.
|
|
40
40
|
*/
|
|
41
41
|
export function computerUseParticipants(config, countOverride) {
|
|
42
|
-
const actor =
|
|
42
|
+
const actor = config.actor;
|
|
43
43
|
const roster = participantList(config);
|
|
44
44
|
const count = roster
|
|
45
45
|
? roster.length
|
|
@@ -8,7 +8,7 @@ import type { RunStudyProvenance } from "../run/study-provenance.js";
|
|
|
8
8
|
import type { ComputerUseRefusal } from "../routes/computer-use/plan.js";
|
|
9
9
|
import type { SharedWorldRefusal } from "../routes/shared-world/plan.js";
|
|
10
10
|
import type { ComputerUseParticipant, ExternalPublicParticipant, ProvisionedParticipant } from "./plan-participants.js";
|
|
11
|
-
import type { StudyConfig, StudyExecutionTerminal, StudyRuntimeAuth,
|
|
11
|
+
import type { StudyConfig, StudyExecutionTerminal, StudyRuntimeAuth, StudyCaps, StudySubjectProduct, StudySubjectServe, StudySubjectState, StudySubjectStateCheckpoint } from "./types.js";
|
|
12
12
|
export type NonEmpty<T> = readonly [T, ...T[]];
|
|
13
13
|
type AtLeastTwo<T> = readonly [T, T, ...T[]];
|
|
14
14
|
/**
|
|
@@ -48,7 +48,7 @@ export type Requirement = {
|
|
|
48
48
|
* VM study read it before a plan exists. Shared world, terminal and scripted still read some config
|
|
49
49
|
* fields at run time.
|
|
50
50
|
*/
|
|
51
|
-
export type ResidualConfig = Pick<StudyConfig, "comms" | "policies" | "
|
|
51
|
+
export type ResidualConfig = Pick<StudyConfig, "comms" | "policies" | "defaults" | "review"> & {
|
|
52
52
|
readonly execution?: Pick<NonNullable<StudyConfig["execution"]>, "desktop" | "target">;
|
|
53
53
|
readonly subject: Pick<StudyConfig["subject"], "clone" | "localTree" | "repos" | "envValues">;
|
|
54
54
|
};
|
|
@@ -63,6 +63,8 @@ interface PlanBase {
|
|
|
63
63
|
/** The study's declared title, which bundles record. */
|
|
64
64
|
readonly title?: string;
|
|
65
65
|
readonly study?: RunStudyProvenance;
|
|
66
|
+
/** Warnings about the study's own fields, which the run records in its bundle as warn events. */
|
|
67
|
+
readonly warnings?: readonly string[];
|
|
66
68
|
/** A frozen copy owned by the plan. */
|
|
67
69
|
readonly residual: Readonly<ResidualConfig>;
|
|
68
70
|
readonly dryRun: boolean;
|
|
@@ -83,7 +85,7 @@ interface PreviewPlan extends PlanBase {
|
|
|
83
85
|
}
|
|
84
86
|
/**
|
|
85
87
|
* The model driving a desktop participant. `caller` is the library caller's createProvider.
|
|
86
|
-
* `declaredModel` is
|
|
88
|
+
* `declaredModel` is actor.model as the study wrote it, absent when undeclared: the providers
|
|
87
89
|
* take it as written, and spend is priced at it, else the default (pricedModel). An openai
|
|
88
90
|
* brain's `model` is the one its provider runs, with that default applied.
|
|
89
91
|
*/
|
|
@@ -229,10 +231,10 @@ export type TerminalPlan = PlanBase & {
|
|
|
229
231
|
readonly stdin?: NonNullable<StudyExecutionTerminal["stdin"]>;
|
|
230
232
|
} & ({
|
|
231
233
|
readonly dryRun: true;
|
|
232
|
-
readonly caps?:
|
|
234
|
+
readonly caps?: StudyCaps;
|
|
233
235
|
} | {
|
|
234
236
|
readonly dryRun: false;
|
|
235
|
-
readonly caps:
|
|
237
|
+
readonly caps: StudyCaps & {
|
|
236
238
|
readonly maxUsd: number;
|
|
237
239
|
readonly maxMinutes: number;
|
|
238
240
|
};
|
package/dist/study/plan.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The route decision. A study's route follows from its composition (subject.source,
|
|
2
|
-
// execution.target, the
|
|
2
|
+
// execution.target, the actor's registered run kind, `route: shared-world`), never from a
|
|
3
3
|
// declared kind. This is the only function that decides it.
|
|
4
4
|
import { resolveAutomaticAnalysis } from "../analysis/automatic-config.js";
|
|
5
5
|
import { callerDrivingOf, planComputerUseStudy } from "../routes/computer-use/plan.js";
|
|
@@ -11,17 +11,18 @@ import { THIS_REPO_DRY_RUN_ONLY } from "./composition-rules.js";
|
|
|
11
11
|
import { planBase } from "./plan-base.js";
|
|
12
12
|
import { routeOf } from "./routing.js";
|
|
13
13
|
import { automaticAnalysisRouteReason, taskProtocolValidationReason } from "./validation.js";
|
|
14
|
-
import { declaredParticipantCount
|
|
14
|
+
import { declaredParticipantCount } from "./study-fields.js";
|
|
15
|
+
import { egressAllowIgnoredWarning } from "./warnings.js";
|
|
15
16
|
export { routeOf } from "./routing.js";
|
|
16
17
|
/** Resolve dry-run: explicit override wins, else the scenario mode, else the given fallback. */
|
|
17
18
|
export function resolveStudyDryRun(config, override, fallback) {
|
|
18
19
|
if (override !== undefined) {
|
|
19
20
|
return override;
|
|
20
21
|
}
|
|
21
|
-
if (
|
|
22
|
+
if (config.mode === "live") {
|
|
22
23
|
return false;
|
|
23
24
|
}
|
|
24
|
-
if (
|
|
25
|
+
if (config.mode === "dry-run") {
|
|
25
26
|
return true;
|
|
26
27
|
}
|
|
27
28
|
return fallback;
|
|
@@ -73,8 +74,13 @@ export function planStudy(config, options, deps = {}) {
|
|
|
73
74
|
if (!result.ok)
|
|
74
75
|
return result;
|
|
75
76
|
// The manifest the CLI resolved enters the plan here and nowhere else; the routes read
|
|
76
|
-
// plan.study for the run's status record and bundle.
|
|
77
|
-
const
|
|
77
|
+
// plan.study for the run's status record and bundle, and plan.warnings for its bundle events.
|
|
78
|
+
const warning = egressAllowIgnoredWarning(study);
|
|
79
|
+
const plan = {
|
|
80
|
+
...result.plan,
|
|
81
|
+
...(options.study === undefined ? {} : { study: options.study }),
|
|
82
|
+
...(warning === undefined ? {} : { warnings: [warning] }),
|
|
83
|
+
};
|
|
78
84
|
return { ok: true, planned: { plan } };
|
|
79
85
|
}
|
|
80
86
|
function planRoute(route, study, options, input, deps) {
|
|
@@ -25,7 +25,7 @@ export async function runPublicPreviewPreflight(ctx) {
|
|
|
25
25
|
{ name: "route", ok: false, message: routeError },
|
|
26
26
|
]);
|
|
27
27
|
}
|
|
28
|
-
const rosterTargets = ctx.targets.filter((target) => target.kind === "
|
|
28
|
+
const rosterTargets = ctx.targets.filter((target) => target.kind === "participants[].target");
|
|
29
29
|
const publicTargets = rosterTargets.length > 0
|
|
30
30
|
? rosterTargets
|
|
31
31
|
: ctx.targets.filter((target) => target.kind === "subject.appUrl");
|
|
@@ -270,7 +270,7 @@ function targetUrlFor(config, target) {
|
|
|
270
270
|
if (target.kind === "subject.appUrl" && config.subject.appUrl) {
|
|
271
271
|
return config.subject.appUrl;
|
|
272
272
|
}
|
|
273
|
-
if (target.kind === "
|
|
273
|
+
if (target.kind === "participants[].target") {
|
|
274
274
|
const rosterTarget = participantList(config)?.find((entry) => entry.target && digest(entry.target) === target.targetDigest)?.target;
|
|
275
275
|
if (rosterTarget)
|
|
276
276
|
return rosterTarget;
|
|
@@ -12,7 +12,7 @@ export interface StudyPreflightCheck {
|
|
|
12
12
|
}
|
|
13
13
|
export interface StudyPreflightTarget {
|
|
14
14
|
label: string;
|
|
15
|
-
kind: "subject.appUrl" | "
|
|
15
|
+
kind: "subject.appUrl" | "participants[].target" | "subject.serve.url" | "subject.product.publicSurface";
|
|
16
16
|
targetDigest: string;
|
|
17
17
|
originDigest?: string;
|
|
18
18
|
loopback: boolean;
|
package/dist/study/preflight.js
CHANGED
|
@@ -84,7 +84,7 @@ function collectTargets(config) {
|
|
|
84
84
|
}
|
|
85
85
|
for (const [index, entry] of (participantList(config) ?? []).entries()) {
|
|
86
86
|
if (entry.target) {
|
|
87
|
-
targets.push(makeTarget(`
|
|
87
|
+
targets.push(makeTarget(`participants[${index}].target`, "participants[].target", entry.target));
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
if (config.subject.serve?.url) {
|
package/dist/study/routing.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export declare function registeredScriptedBrowserActors(): string[];
|
|
|
9
9
|
export declare function actorResolvesToTerminal(type: string | undefined): boolean;
|
|
10
10
|
export declare function registeredTerminalActors(): string[];
|
|
11
11
|
/**
|
|
12
|
-
* The declared fan-out participant count on the computer-use route: a `
|
|
13
|
-
* a
|
|
12
|
+
* The declared fan-out participant count on the computer-use route: a `participants` list's
|
|
13
|
+
* length, else a participant count, else 1. The single source of truth shared by the parser, the engine,
|
|
14
14
|
* and the pre-flight plan so the participant count is computed the same way everywhere.
|
|
15
15
|
*/
|
|
16
16
|
export declare function computerUseParticipantCount(config: StudyConfig): number;
|
|
@@ -36,15 +36,15 @@ export declare function isComputerUseComposition(config: StudyConfig): boolean;
|
|
|
36
36
|
* `shared-world` topology. Mirror of isComputerUseComposition; the single source of truth shared by
|
|
37
37
|
* routeOf (which checks it before the computer-use route) and the warning logic. Every
|
|
38
38
|
* shared-world study runs its participants at once (`execution.concurrency` >= 2). The same
|
|
39
|
-
* clone/local-tree × e2b-desktop × computer-use composition without `
|
|
40
|
-
* (the computer-use route); the
|
|
39
|
+
* clone/local-tree × e2b-desktop × computer-use composition without `route: shared-world` stays per-lane-worlds
|
|
40
|
+
* (the computer-use route); the route declaration is the override switch.
|
|
41
41
|
*/
|
|
42
42
|
export declare function isSharedWorldComposition(config: StudyConfig): boolean;
|
|
43
43
|
/** The getHost provisioned-subject shared-world shape (clone/local-tree served + exposed in-sandbox). */
|
|
44
44
|
export declare function isProvisionedSharedWorldComposition(config: StudyConfig): boolean;
|
|
45
45
|
/**
|
|
46
46
|
* The external-public shared-world shape: a real public deployment used directly as
|
|
47
|
-
* the shared plane: `source: app-url` + `
|
|
47
|
+
* the shared plane: `source: app-url` + `route: shared-world` + a computer-use actor on
|
|
48
48
|
* e2b-desktop + `policies.allowPublicTargets: true`. No getHost, no clone, no subject sandbox, no
|
|
49
49
|
* seed. The operator-ownership attestation `subject.publicTarget` is required (validated in
|
|
50
50
|
* externalPublicSharedWorldValidationReason, not here; this predicate is the router only, so a
|
package/dist/study/routing.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { actorRegistry } from "../actors/registry.js";
|
|
2
2
|
import { isLoopbackUrl } from "./parse/subject.js";
|
|
3
|
-
import {
|
|
3
|
+
import { participantList, declaredParticipantCount } from "./study-fields.js";
|
|
4
4
|
// Hard cap on computer-use participants. No setting raises it: each participant is a paid desktop,
|
|
5
5
|
// and they all run at once.
|
|
6
6
|
export const MAX_COMPUTER_USE_PARTICIPANTS = 16;
|
|
@@ -46,8 +46,8 @@ export function registeredTerminalActors() {
|
|
|
46
46
|
return registeredActorsOn("terminal");
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* The declared fan-out participant count on the computer-use route: a `
|
|
50
|
-
* a
|
|
49
|
+
* The declared fan-out participant count on the computer-use route: a `participants` list's
|
|
50
|
+
* length, else a participant count, else 1. The single source of truth shared by the parser, the engine,
|
|
51
51
|
* and the pre-flight plan so the participant count is computed the same way everywhere.
|
|
52
52
|
*/
|
|
53
53
|
export function computerUseParticipantCount(config) {
|
|
@@ -74,7 +74,7 @@ export function isComputerUseComposition(config) {
|
|
|
74
74
|
// local-app drives the cua loop in-process (a custom executor + a non-vision provider), so it
|
|
75
75
|
// routes to the computer-use route exactly like an app-url subject with a computer-use actor.
|
|
76
76
|
if (config.subject.source === "app-url" || config.subject.source === "local-app") {
|
|
77
|
-
return actorResolvesToComputerUse(
|
|
77
|
+
return actorResolvesToComputerUse(config.actor?.type);
|
|
78
78
|
}
|
|
79
79
|
// desktop-cli hands the participant a terminal instead of a served page, but it is the
|
|
80
80
|
// same route: same desktop, same actor, same prompt fields. Leaving it out of this predicate told
|
|
@@ -82,13 +82,13 @@ export function isComputerUseComposition(config) {
|
|
|
82
82
|
// person reads a screen.
|
|
83
83
|
if (config.subject.source === "desktop-cli") {
|
|
84
84
|
return ((config.execution?.target === undefined || config.execution.target === "e2b-desktop") &&
|
|
85
|
-
actorResolvesToComputerUse(
|
|
85
|
+
actorResolvesToComputerUse(config.actor?.type));
|
|
86
86
|
}
|
|
87
87
|
// local-tree packs+uploads the working tree, then serves it exactly like a computer-use clone
|
|
88
88
|
// subject: same e2b-desktop + computer-use-actor gate.
|
|
89
89
|
return ((config.subject.source === "clone" || config.subject.source === "local-tree") &&
|
|
90
90
|
config.execution?.target === "e2b-desktop" &&
|
|
91
|
-
actorResolvesToComputerUse(
|
|
91
|
+
actorResolvesToComputerUse(config.actor?.type));
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* True when this config routes to the shared-world route: a clone or local-tree subject
|
|
@@ -96,8 +96,8 @@ export function isComputerUseComposition(config) {
|
|
|
96
96
|
* `shared-world` topology. Mirror of isComputerUseComposition; the single source of truth shared by
|
|
97
97
|
* routeOf (which checks it before the computer-use route) and the warning logic. Every
|
|
98
98
|
* shared-world study runs its participants at once (`execution.concurrency` >= 2). The same
|
|
99
|
-
* clone/local-tree × e2b-desktop × computer-use composition without `
|
|
100
|
-
* (the computer-use route); the
|
|
99
|
+
* clone/local-tree × e2b-desktop × computer-use composition without `route: shared-world` stays per-lane-worlds
|
|
100
|
+
* (the computer-use route); the route declaration is the override switch.
|
|
101
101
|
*/
|
|
102
102
|
export function isSharedWorldComposition(config) {
|
|
103
103
|
return (isProvisionedSharedWorldComposition(config) || isExternalPublicSharedWorldComposition(config));
|
|
@@ -105,13 +105,13 @@ export function isSharedWorldComposition(config) {
|
|
|
105
105
|
/** The getHost provisioned-subject shared-world shape (clone/local-tree served + exposed in-sandbox). */
|
|
106
106
|
export function isProvisionedSharedWorldComposition(config) {
|
|
107
107
|
return ((config.subject.source === "clone" || config.subject.source === "local-tree") &&
|
|
108
|
-
|
|
108
|
+
config.route === "shared-world" &&
|
|
109
109
|
config.execution?.target === "e2b-desktop" &&
|
|
110
|
-
actorResolvesToComputerUse(
|
|
110
|
+
actorResolvesToComputerUse(config.actor?.type));
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
113
|
* The external-public shared-world shape: a real public deployment used directly as
|
|
114
|
-
* the shared plane: `source: app-url` + `
|
|
114
|
+
* the shared plane: `source: app-url` + `route: shared-world` + a computer-use actor on
|
|
115
115
|
* e2b-desktop + `policies.allowPublicTargets: true`. No getHost, no clone, no subject sandbox, no
|
|
116
116
|
* seed. The operator-ownership attestation `subject.publicTarget` is required (validated in
|
|
117
117
|
* externalPublicSharedWorldValidationReason, not here; this predicate is the router only, so a
|
|
@@ -120,9 +120,9 @@ export function isProvisionedSharedWorldComposition(config) {
|
|
|
120
120
|
*/
|
|
121
121
|
export function isExternalPublicSharedWorldComposition(config) {
|
|
122
122
|
return (config.subject.source === "app-url" &&
|
|
123
|
-
|
|
123
|
+
config.route === "shared-world" &&
|
|
124
124
|
config.execution?.target === "e2b-desktop" &&
|
|
125
|
-
actorResolvesToComputerUse(
|
|
125
|
+
actorResolvesToComputerUse(config.actor?.type) &&
|
|
126
126
|
config.policies?.allowPublicTargets === true);
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
@@ -159,12 +159,12 @@ export function isScriptedBrowserComposition(config) {
|
|
|
159
159
|
return (isLocalScriptedBrowserComposition(config) || isProvisionedScriptedBrowserComposition(config));
|
|
160
160
|
}
|
|
161
161
|
function isLocalScriptedBrowserComposition(config) {
|
|
162
|
-
return
|
|
162
|
+
return config.subject.source === "app-url" && actorResolvesToScriptedBrowser(config.actor?.type);
|
|
163
163
|
}
|
|
164
164
|
export function isProvisionedScriptedBrowserComposition(config) {
|
|
165
165
|
return (config.subject.source === "clone" &&
|
|
166
166
|
config.execution?.target === "e2b-desktop" &&
|
|
167
|
-
actorResolvesToScriptedBrowser(
|
|
167
|
+
actorResolvesToScriptedBrowser(config.actor?.type));
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
170
|
* True when this config routes to the terminal-product backend: a terminal-product subject whose
|
|
@@ -173,7 +173,7 @@ export function isProvisionedScriptedBrowserComposition(config) {
|
|
|
173
173
|
* the single source of truth for routeOf and the warning logic.
|
|
174
174
|
*/
|
|
175
175
|
export function isTerminalProductComposition(config) {
|
|
176
|
-
return (config.subject.source === "terminal-product" && actorResolvesToTerminal(
|
|
176
|
+
return (config.subject.source === "terminal-product" && actorResolvesToTerminal(config.actor?.type));
|
|
177
177
|
}
|
|
178
178
|
/**
|
|
179
179
|
* The route a config takes. It never refuses: a config no route can run still gets the route
|
|
@@ -4,7 +4,7 @@ import type { StudyConfig } from "./types.js";
|
|
|
4
4
|
import { type StudyEvent } from "./run-study-events.js";
|
|
5
5
|
type Refusal = {
|
|
6
6
|
ok: false;
|
|
7
|
-
code: "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED";
|
|
7
|
+
code: "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED" | "HUMANISH_STUDY_INVALID";
|
|
8
8
|
message: string;
|
|
9
9
|
};
|
|
10
10
|
type Normalized = {
|
|
@@ -7,7 +7,7 @@ import { isLocalBrowserStudy } from "../substrates/local/runtime-config.js";
|
|
|
7
7
|
import { resolveStudyDryRun } from "./plan.js";
|
|
8
8
|
import { knownSecretValues, studyEventEmitter } from "./run-study-events.js";
|
|
9
9
|
import { studyResultIdentity } from "../run/study-result.js";
|
|
10
|
-
import {
|
|
10
|
+
import { participantList } from "./study-fields.js";
|
|
11
11
|
const unsupported = (option, route, reason) => ({
|
|
12
12
|
ok: false,
|
|
13
13
|
code: "HUMANISH_STUDY_OPTION_UNSUPPORTED",
|
|
@@ -105,7 +105,7 @@ export function normalizeRunStudyOptions(config, route, options) {
|
|
|
105
105
|
export function optionRefusalOutcome(config, route, options, refusal) {
|
|
106
106
|
const cwd = path.resolve(options.cwd);
|
|
107
107
|
const error = { code: refusal.code, message: refusal.message };
|
|
108
|
-
const actor =
|
|
108
|
+
const actor = config.actor?.type ?? "";
|
|
109
109
|
const dryRun = resolveStudyDryRun(config, options.dryRun, true) ?? true;
|
|
110
110
|
const runId = options.runId ?? "not-created";
|
|
111
111
|
const common = { ok: false, cwd, actor, dryRun, runId, warnings: [], error };
|
|
@@ -27,7 +27,7 @@ export interface StudyDeps {
|
|
|
27
27
|
readonly subjectPhaseSink?: (event: SubjectPhaseEvent, participant?: ParticipantRef) => void;
|
|
28
28
|
/**
|
|
29
29
|
* Shared world: runs each participant's computer-use session in place of the actor's own. The
|
|
30
|
-
* planner reads whether it is set: a custom runner cannot enforce
|
|
30
|
+
* planner reads whether it is set: a custom runner cannot enforce actor.maxOutputTokens.
|
|
31
31
|
*/
|
|
32
32
|
readonly runSession?: (options: CuaActorSessionOptions) => Promise<CuaLoopResult>;
|
|
33
33
|
/**
|
|
@@ -1,28 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
/** The actor that runs. A v3 file declares it as `actor`. */
|
|
4
|
-
export declare function actorOf(config: StudyConfig): StudyActor | undefined;
|
|
5
|
-
/**
|
|
6
|
-
* Every declared actor. parseStudy accepts exactly one; a library config may declare more, and the
|
|
7
|
-
* checks that refuse a second actor's settings read them all.
|
|
8
|
-
*/
|
|
9
|
-
export declare function actorsOf(config: StudyConfig): readonly StudyActor[];
|
|
10
|
-
/** The participants listed one by one: a v3 `participants` list. Undefined when none is listed. */
|
|
1
|
+
import type { StudyConfig, StudyParticipantEntry } from "./types.js";
|
|
2
|
+
/** The participants listed one by one: a `participants` list. Undefined when none is listed. */
|
|
11
3
|
export declare function participantList(config: StudyConfig): readonly StudyParticipantEntry[] | undefined;
|
|
12
|
-
/** The declared number of identical participants:
|
|
4
|
+
/** The declared number of identical participants: `participants: <n>` or `participants.count`. */
|
|
13
5
|
export declare function declaredParticipantCount(config: StudyConfig): number | undefined;
|
|
14
|
-
/** The steer each of a counted group of participants gets:
|
|
6
|
+
/** The steer each of a counted group of participants gets: `participants.instruction`. */
|
|
15
7
|
export declare function participantInstruction(config: StudyConfig): string | undefined;
|
|
16
|
-
/** How many scripted surfaces run, 1 for desktop and 2 with mobile. A v3 file lists `surfaces`. */
|
|
17
|
-
export declare function surfaceCount(config: StudyConfig): number | undefined;
|
|
18
|
-
/** The declared run mode. A v3 file declares it as `mode`. */
|
|
19
|
-
export declare function modeOf(config: StudyConfig): StudyScenario["mode"];
|
|
20
|
-
/** The scripted route's scenario id or path. A v3 file declares it as `scenario`. */
|
|
21
|
-
export declare function scenarioRefOf(config: StudyConfig): string | undefined;
|
|
22
|
-
/**
|
|
23
|
-
* The spend caps a route enforces. A v3 file declares them as `caps`. The v2 shape keeps them in
|
|
24
|
-
* `execution.caps` on computer use and shared world, and in `scenario.caps` on terminal.
|
|
25
|
-
*/
|
|
26
|
-
export declare function capsOf(config: StudyConfig, route: Extract<StudyRoute, "computer-use" | "shared-world" | "terminal">): StudyScenarioCaps | undefined;
|
|
27
|
-
/** True when the participants share one app. A v3 file declares `route: shared-world`. */
|
|
28
|
-
export declare function declaresSharedWorld(config: StudyConfig): boolean;
|
|
@@ -1,51 +1,22 @@
|
|
|
1
|
-
// The study's fields, read in one place.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
/** The actor that runs. A v3 file declares it as `actor`. */
|
|
7
|
-
export function actorOf(config) {
|
|
8
|
-
return config.actors[0];
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Every declared actor. parseStudy accepts exactly one; a library config may declare more, and the
|
|
12
|
-
* checks that refuse a second actor's settings read them all.
|
|
13
|
-
*/
|
|
14
|
-
export function actorsOf(config) {
|
|
15
|
-
return config.actors;
|
|
16
|
-
}
|
|
17
|
-
/** The participants listed one by one: a v3 `participants` list. Undefined when none is listed. */
|
|
1
|
+
// The study's fields, read in one place. Participants are the one read that is not a field: a v3
|
|
2
|
+
// study declares them as a count, as `{ count, instruction }` or as a list, and the accessors below
|
|
3
|
+
// read each form. tests/surface/study-config-reads.test.ts refuses a direct read of a humanish.lab.v2
|
|
4
|
+
// field anywhere outside migrate.
|
|
5
|
+
/** The participants listed one by one: a `participants` list. Undefined when none is listed. */
|
|
18
6
|
export function participantList(config) {
|
|
19
|
-
return config.
|
|
7
|
+
return Array.isArray(config.participants) ? config.participants : undefined;
|
|
20
8
|
}
|
|
21
|
-
/** The declared number of identical participants:
|
|
9
|
+
/** The declared number of identical participants: `participants: <n>` or `participants.count`. */
|
|
22
10
|
export function declaredParticipantCount(config) {
|
|
23
|
-
|
|
11
|
+
const { participants } = config;
|
|
12
|
+
if (typeof participants === "number")
|
|
13
|
+
return participants;
|
|
14
|
+
return isGroup(participants) ? participants.count : undefined;
|
|
24
15
|
}
|
|
25
|
-
/** The steer each of a counted group of participants gets:
|
|
16
|
+
/** The steer each of a counted group of participants gets: `participants.instruction`. */
|
|
26
17
|
export function participantInstruction(config) {
|
|
27
|
-
return config.
|
|
28
|
-
}
|
|
29
|
-
/** How many scripted surfaces run, 1 for desktop and 2 with mobile. A v3 file lists `surfaces`. */
|
|
30
|
-
export function surfaceCount(config) {
|
|
31
|
-
return config.actors[0]?.count;
|
|
32
|
-
}
|
|
33
|
-
/** The declared run mode. A v3 file declares it as `mode`. */
|
|
34
|
-
export function modeOf(config) {
|
|
35
|
-
return config.scenario?.mode;
|
|
36
|
-
}
|
|
37
|
-
/** The scripted route's scenario id or path. A v3 file declares it as `scenario`. */
|
|
38
|
-
export function scenarioRefOf(config) {
|
|
39
|
-
return config.scenario?.ref;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* The spend caps a route enforces. A v3 file declares them as `caps`. The v2 shape keeps them in
|
|
43
|
-
* `execution.caps` on computer use and shared world, and in `scenario.caps` on terminal.
|
|
44
|
-
*/
|
|
45
|
-
export function capsOf(config, route) {
|
|
46
|
-
return route === "terminal" ? config.scenario?.caps : config.execution?.caps;
|
|
18
|
+
return isGroup(config.participants) ? config.participants.instruction : undefined;
|
|
47
19
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return config.subject.topology === "shared-world";
|
|
20
|
+
function isGroup(participants) {
|
|
21
|
+
return typeof participants === "object" && participants !== null && !Array.isArray(participants);
|
|
51
22
|
}
|
package/dist/study/summary.js
CHANGED
|
@@ -18,7 +18,7 @@ import { inspectStudyManifest } from "./discover.js";
|
|
|
18
18
|
import { isComputerUseComposition } from "./routing.js";
|
|
19
19
|
import { probeKeySources } from "../keys/key-resolution.js";
|
|
20
20
|
import { receivingRequiredKey } from "../comms/setup.js";
|
|
21
|
-
import {
|
|
21
|
+
import { participantList, declaredParticipantCount } from "./study-fields.js";
|
|
22
22
|
export const STUDY_SUMMARY_SCHEMA = "humanish.study-summary.v1";
|
|
23
23
|
/**
|
|
24
24
|
* The effort every participant will run at, or `"per-lane"` when they differ. A participant that declares nothing
|
|
@@ -26,7 +26,7 @@ export const STUDY_SUMMARY_SCHEMA = "humanish.study-summary.v1";
|
|
|
26
26
|
* reported as the resolved value, exactly as `model` reports its default rather than hiding it.
|
|
27
27
|
*/
|
|
28
28
|
function reasoningEffortOf(config) {
|
|
29
|
-
const fallback =
|
|
29
|
+
const fallback = config.actor?.reasoningEffort ?? DEFAULT_OPENAI_CU_REASONING_EFFORT;
|
|
30
30
|
const roster = participantList(config) ?? [];
|
|
31
31
|
const resolved = new Set(roster.map((entry) => entry.reasoningEffort ?? fallback));
|
|
32
32
|
if (resolved.size > 1)
|
|
@@ -47,14 +47,19 @@ function subjectOf(config) {
|
|
|
47
47
|
}
|
|
48
48
|
/** How many participants, and who; collapsed when they are all the same persona. */
|
|
49
49
|
function participantsOf(config) {
|
|
50
|
-
const actor =
|
|
50
|
+
const actor = config.actor;
|
|
51
51
|
if (actor === undefined)
|
|
52
52
|
return undefined;
|
|
53
53
|
const rosterPersonas = (participantList(config) ?? [])
|
|
54
54
|
.map((entry) => entry.persona)
|
|
55
55
|
.filter((persona) => typeof persona === "string");
|
|
56
56
|
const personas = rosterPersonas.length > 0 ? rosterPersonas : actor.persona === undefined ? [] : [actor.persona];
|
|
57
|
-
|
|
57
|
+
// A scripted study's participants are its surfaces, one replay on each.
|
|
58
|
+
const count = declaredParticipantCount(config) ??
|
|
59
|
+
config.surfaces?.length ??
|
|
60
|
+
participantList(config)?.length ??
|
|
61
|
+
personas.length ??
|
|
62
|
+
1;
|
|
58
63
|
const unique = [...new Set(personas)];
|
|
59
64
|
if (unique.length === 0)
|
|
60
65
|
return `${count} participant${count === 1 ? "" : "s"}`;
|
|
@@ -62,11 +67,11 @@ function participantsOf(config) {
|
|
|
62
67
|
// are named, because which personas are in a study is the study's design.
|
|
63
68
|
return unique.length === 1 ? `${count} × ${unique[0]}` : unique.join(" · ");
|
|
64
69
|
}
|
|
65
|
-
/** The computer-use caps
|
|
70
|
+
/** The computer-use caps. Other routes draw none. */
|
|
66
71
|
function summaryCapsOf(config) {
|
|
67
72
|
if (!isComputerUseComposition(config))
|
|
68
73
|
return {};
|
|
69
|
-
const caps =
|
|
74
|
+
const caps = config.caps;
|
|
70
75
|
return {
|
|
71
76
|
...(typeof caps?.maxUsd === "number" ? { laneUsd: caps.maxUsd } : {}),
|
|
72
77
|
...(typeof caps?.maxTotalUsd === "number" ? { studyUsd: caps.maxTotalUsd } : {}),
|
|
@@ -134,7 +139,7 @@ export async function readStudySummary(cwd, study, options = {}) {
|
|
|
134
139
|
: undefined;
|
|
135
140
|
const participantReadiness = options.checkKeys === true &&
|
|
136
141
|
isLocalBrowserStudy(inspected.config) &&
|
|
137
|
-
|
|
142
|
+
inspected.config.actor?.type === "local-agent"
|
|
138
143
|
? await localCodexParticipantCheck({ env: options.env ?? process.env })
|
|
139
144
|
: undefined;
|
|
140
145
|
return {
|
|
@@ -161,7 +166,7 @@ export async function readStudySummary(cwd, study, options = {}) {
|
|
|
161
166
|
...(typeof config.description === "string" ? { description: config.description.trim() } : {}),
|
|
162
167
|
...(subject === undefined ? {} : { subject }),
|
|
163
168
|
...(participants === undefined ? {} : { participants }),
|
|
164
|
-
model:
|
|
169
|
+
model: inspected.config.actor?.model ?? DEFAULT_OPENAI_CU_MODEL,
|
|
165
170
|
reasoningEffort: reasoningEffortOf(inspected.config),
|
|
166
171
|
caps: summaryCapsOf(inspected.config),
|
|
167
172
|
...(keysReady === undefined ? {} : { keysReady }),
|