humanish 0.125.0 → 0.127.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/dist/actors/computer-use/actor.d.ts +2 -0
- package/dist/actors/computer-use/actor.js +1 -0
- package/dist/actors/computer-use/loop/clock.d.ts +9 -8
- package/dist/actors/computer-use/loop/clock.js +27 -17
- package/dist/actors/computer-use/loop/debrief.d.ts +1 -1
- package/dist/actors/computer-use/loop/debrief.js +15 -5
- package/dist/actors/computer-use/loop/ending.d.ts +10 -5
- package/dist/actors/computer-use/loop/ending.js +12 -7
- package/dist/actors/computer-use/loop/observation.d.ts +6 -0
- package/dist/actors/computer-use/loop/observation.js +19 -1
- package/dist/actors/computer-use/loop/session.d.ts +4 -2
- package/dist/actors/computer-use/loop/session.js +4 -1
- package/dist/actors/computer-use/loop/trace.js +4 -2
- package/dist/actors/computer-use/loop/types.d.ts +22 -8
- package/dist/actors/computer-use/loop.js +5 -5
- package/dist/actors/computer-use/openai-provider.js +1 -1
- package/dist/actors/contract.d.ts +31 -8
- package/dist/actors/contract.js +3 -0
- package/dist/actors/goal-source.d.ts +32 -2
- package/dist/actors/goal-source.js +41 -2
- package/dist/actors/stop-cause.js +1 -0
- package/dist/analysis/evidence-sources.js +2 -2
- package/dist/analysis/types.d.ts +2 -2
- package/dist/analysis/validation.js +2 -1
- package/dist/cli/commands/study.js +8 -0
- package/dist/evidence/redaction.d.ts +1 -0
- package/dist/evidence/redaction.js +1 -0
- package/dist/evidence/trace-page.d.ts +6 -0
- package/dist/evidence/trace-page.js +90 -0
- package/dist/observer/data.js +8 -5
- package/dist/observer/feed-cursors.d.ts +14 -0
- package/dist/observer/feed-cursors.js +61 -0
- package/dist/observer/participant-items.d.ts +7 -0
- package/dist/observer/pinned-files.d.ts +2 -1
- package/dist/observer/pinned-files.js +2 -2
- package/dist/observer/run-routes.d.ts +1 -1
- package/dist/observer/run-routes.js +38 -17
- package/dist/observer-app.html +7 -7
- package/dist/routes/computer-use/bundle.js +4 -0
- package/dist/routes/computer-use/diagnostics.d.ts +1 -1
- package/dist/routes/computer-use/diagnostics.js +3 -3
- package/dist/routes/computer-use/e2b-desktop/desktop.js +2 -1
- package/dist/routes/computer-use/e2b-desktop/prepare.d.ts +6 -0
- package/dist/routes/computer-use/e2b-desktop/prepare.js +47 -1
- package/dist/routes/computer-use/e2b-desktop/start.d.ts +2 -1
- package/dist/routes/computer-use/e2b-desktop/start.js +15 -3
- package/dist/routes/computer-use/e2b-desktop/state.d.ts +3 -1
- package/dist/routes/computer-use/e2b-desktop/state.js +2 -0
- package/dist/routes/computer-use/fanout-bundle.js +3 -0
- package/dist/routes/computer-use/fanout-records.js +5 -1
- package/dist/routes/computer-use/participant-deps.d.ts +1 -1
- package/dist/routes/computer-use/participant-desktop.d.ts +1 -1
- package/dist/routes/computer-use/participant-execution.js +6 -2
- package/dist/routes/computer-use/participant-facts.js +4 -1
- package/dist/routes/computer-use/participant-model.js +16 -6
- package/dist/routes/computer-use/participant-runs.d.ts +10 -10
- package/dist/routes/computer-use/participant-runs.js +54 -84
- package/dist/routes/computer-use/plan.d.ts +2 -0
- package/dist/routes/computer-use/plan.js +65 -17
- package/dist/routes/computer-use/rerun-selection.js +8 -5
- package/dist/routes/computer-use/setup.d.ts +1 -0
- package/dist/routes/computer-use/setup.js +29 -1
- package/dist/routes/computer-use/single-bundle.d.ts +5 -1
- package/dist/routes/computer-use/single-bundle.js +11 -1
- package/dist/routes/computer-use/types.d.ts +18 -13
- package/dist/routes/computer-use/types.js +0 -3
- package/dist/routes/shared-world/bundle-records.js +4 -1
- package/dist/routes/shared-world/external-public.js +1 -1
- package/dist/routes/shared-world/participant-specs.js +3 -2
- package/dist/routes/shared-world/plan.d.ts +2 -0
- package/dist/routes/shared-world/plan.js +8 -3
- package/dist/routes/shared-world/provisioned.js +1 -1
- package/dist/run/bundle.d.ts +11 -1
- package/dist/run/contained-output.d.ts +7 -3
- package/dist/run/contained-output.js +11 -7
- package/dist/run/judge.d.ts +14 -4
- package/dist/run/judge.js +17 -12
- package/dist/run/live-items.d.ts +19 -0
- package/dist/run/live-items.js +28 -8
- package/dist/run/notes.js +1 -1
- package/dist/run/outcomes.d.ts +8 -2
- package/dist/run/outcomes.js +56 -14
- package/dist/run/stream-trace.d.ts +25 -4
- package/dist/run/stream-trace.js +55 -17
- package/dist/run/streams.d.ts +16 -0
- package/dist/study/arrivals.d.ts +105 -34
- package/dist/study/arrivals.js +179 -74
- package/dist/study/bundle-estimate.d.ts +12 -10
- package/dist/study/bundle-estimate.js +138 -72
- package/dist/study/desktop-files.d.ts +37 -0
- package/dist/study/desktop-files.js +174 -0
- package/dist/study/keys.d.ts +8 -0
- package/dist/study/keys.js +3 -0
- package/dist/study/migrate/v2.js +12 -3
- package/dist/study/parse/actors.d.ts +1 -1
- package/dist/study/parse/actors.js +28 -20
- package/dist/study/parse/clock.d.ts +18 -0
- package/dist/study/parse/clock.js +36 -0
- package/dist/study/plan-participants.d.ts +19 -10
- package/dist/study/plan-participants.js +13 -9
- package/dist/study/plan-types.d.ts +6 -1
- package/dist/study/plan.d.ts +2 -1
- package/dist/study/plan.js +28 -15
- package/dist/study/preflight.js +4 -0
- package/dist/study/types.d.ts +35 -5
- package/dist/study/validation.js +3 -0
- package/dist/study/warnings.d.ts +2 -2
- package/dist/study/warnings.js +7 -0
- package/dist/subject/local-tree-archive.d.ts +7 -0
- package/dist/subject/local-tree-archive.js +12 -0
- package/dist/substrates/e2b/desktop-browser.d.ts +4 -1
- package/dist/substrates/e2b/desktop-browser.js +6 -2
- package/dist/substrates/e2b/desktop-clock.d.ts +10 -0
- package/dist/substrates/e2b/desktop-clock.js +29 -0
- package/dist/substrates/e2b/lifetime.d.ts +22 -8
- package/dist/substrates/e2b/lifetime.js +54 -10
- package/dist/substrates/e2b/sdk.d.ts +6 -0
- package/dist/verify/pages.d.ts +7 -0
- package/dist/verify/pages.js +50 -0
- package/dist/verify/verify.js +10 -3
- package/docs/architecture/actor-contract.md +2 -0
- package/docs/architecture/external-public-shared-world.md +4 -1
- package/docs/architecture/observer.md +10 -1
- package/docs/contracts/policy.md +7 -0
- package/docs/contracts/run-bundle.md +31 -1
- package/docs/contracts/schemas.md +93 -11
- package/package.json +2 -2
|
@@ -10,15 +10,14 @@ import { redactText } from "../../evidence/redaction.js";
|
|
|
10
10
|
import {} from "../../run/bundle.js";
|
|
11
11
|
import { participantAssignment } from "../../study/participant-assignment.js";
|
|
12
12
|
import { resolveCommittedPersonas } from "../../study/persona-resolve.js";
|
|
13
|
-
import { CUA_FANOUT_STRATEGY,
|
|
14
|
-
import { sandboxHeadroomMs } from "../../substrates/e2b/lifetime.js";
|
|
15
|
-
import { readPositiveInt } from "../../study/parse/values.js";
|
|
13
|
+
import { CUA_FANOUT_STRATEGY, DEFAULT_APP_URL_SESSION_TIMEOUT_MS, MIN_DERIVED_SESSION_TIMEOUT_MS, } from "./types.js";
|
|
14
|
+
import { CUA_MAX_CONCURRENCY_ENV, sandboxHeadroomMs } from "../../substrates/e2b/lifetime.js";
|
|
16
15
|
import { digestUrl } from "./bundle-parts.js";
|
|
17
16
|
import { composeParticipantInstructions, DEFAULT_MISSION } from "./participant-prompt.js";
|
|
18
17
|
import { resolveCuaRerunSelection } from "./rerun-selection.js";
|
|
19
18
|
import { plural } from "../../run/text.js";
|
|
20
19
|
import { formatDuration } from "../../run/projection.js";
|
|
21
|
-
import { describeArrivals,
|
|
20
|
+
import { describeArrivals, participantEnd, participantSession, } from "../../study/arrivals.js";
|
|
22
21
|
/**
|
|
23
22
|
* The session budget of a study that declares no execution.timeoutMs. On a provisioned route it is
|
|
24
23
|
* what the `ceilingMs` sandbox ceiling leaves after provisioning, seeding and the teardown buffer.
|
|
@@ -45,28 +44,21 @@ export function resolveParticipantSandboxMs(config, ceilingMs) {
|
|
|
45
44
|
timeoutMs +
|
|
46
45
|
sandboxHeadroomMs(provisionedRoute ? { seed: config.subject.state?.seed ?? [] } : undefined));
|
|
47
46
|
}
|
|
48
|
-
/**
|
|
49
|
-
* A participant's own session budget and sandbox deadline: the study's, or with a declared end its
|
|
50
|
-
* window plus room for the closing report (participantSessionMs), with the same setup and
|
|
51
|
-
* teardown headroom the study's sandbox has over its session.
|
|
52
|
-
*/
|
|
53
|
-
function participantWindow(participant, study) {
|
|
54
|
-
const timeoutMs = participantSessionMs(participant, study.sessionBudgetMs);
|
|
55
|
-
return { timeoutMs, sandboxMs: study.sandboxMs - study.sessionBudgetMs + timeoutMs };
|
|
56
|
-
}
|
|
57
47
|
/**
|
|
58
48
|
* The deps one participant runs with once its start comes: its own session budget and sandbox
|
|
59
|
-
* deadline, and with a declared end
|
|
60
|
-
* due, from runOnSchedule).
|
|
49
|
+
* deadline (participantSession), and with a declared end or cap, how its session ends
|
|
50
|
+
* (`scheduledAt` is when it was due, from runOnSchedule).
|
|
61
51
|
*/
|
|
62
52
|
export function scheduledParticipantDeps(participant, deps, scheduledAt) {
|
|
63
|
-
const
|
|
53
|
+
const own = participantSession(participant.schedule, {
|
|
54
|
+
sessionMs: deps.timeoutMs,
|
|
55
|
+
sandboxMs: deps.sandboxMs,
|
|
56
|
+
});
|
|
57
|
+
const end = participantEnd(participant.schedule, scheduledAt);
|
|
64
58
|
return {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}),
|
|
69
|
-
...(end === undefined ? {} : { scheduledEnd: end }),
|
|
59
|
+
timeoutMs: own.sessionMs,
|
|
60
|
+
sandboxMs: own.sandboxMs,
|
|
61
|
+
...(end === undefined ? {} : { end }),
|
|
70
62
|
};
|
|
71
63
|
}
|
|
72
64
|
/**
|
|
@@ -74,33 +66,17 @@ export function scheduledParticipantDeps(participant, deps, scheduledAt) {
|
|
|
74
66
|
* declared participant runs at once, because a throttle nobody asked for silently turns "N actors
|
|
75
67
|
* live" into waves; total session count and spend are the same either way, only wall-clock
|
|
76
68
|
* and simultaneity differ. A declared execution.concurrency is a cap, clamped to [1, participants].
|
|
77
|
-
*
|
|
69
|
+
* participantsAtOnce then lowers it to what the E2B plan and HUMANISH_CUA_MAX_CONCURRENCY allow.
|
|
78
70
|
*/
|
|
79
71
|
export function boundedConcurrency(declared, participantCount) {
|
|
80
72
|
return Math.max(1, declared === undefined ? participantCount : Math.min(Math.max(1, declared), participantCount));
|
|
81
73
|
}
|
|
82
|
-
/**
|
|
83
|
-
* The planned bound, lowered by the env override. The override may only lower it (never raise
|
|
84
|
-
* concurrent paid desktops), and a lowering is reported via envLoweredFrom so the
|
|
85
|
-
* plan never silently disagrees with the manifest.
|
|
86
|
-
*/
|
|
87
|
-
function envLoweredConcurrency(planned, participantCount, env) {
|
|
88
|
-
const envLower = readPositiveInt(env[CUA_MAX_CONCURRENCY_ENV], 0);
|
|
89
|
-
if (envLower > 0 && envLower < planned) {
|
|
90
|
-
return {
|
|
91
|
-
bound: Math.max(1, Math.min(planned, envLower, participantCount)),
|
|
92
|
-
envLoweredFrom: planned,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return { bound: planned };
|
|
96
|
-
}
|
|
97
74
|
/**
|
|
98
75
|
* Build the participant runs and the public plan from a computer-use plan (pure). The same plan
|
|
99
|
-
* appears in dry-run and live, marked $0 for a dry run. `
|
|
100
|
-
*
|
|
76
|
+
* appears in dry-run and live, marked $0 for a dry run. `personas` are the compiled committed
|
|
77
|
+
* personas the prompts carry.
|
|
101
78
|
*/
|
|
102
79
|
export function participantRunsAndPlan(plan, opts = {}) {
|
|
103
|
-
const env = opts.env ?? {};
|
|
104
80
|
// Who each participant is (id, persona, focus, device, limits) comes from the plan; this adds
|
|
105
81
|
// what the route derives from it: the prompt, bundle ids and artifact paths.
|
|
106
82
|
const { participants } = plan.runner;
|
|
@@ -136,58 +112,54 @@ export function participantRunsAndPlan(plan, opts = {}) {
|
|
|
136
112
|
traceArtifactPath: participantCount === 1 ? "actor.json" : `actors/${run.streamId}.json`,
|
|
137
113
|
};
|
|
138
114
|
});
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const schedule = resolved.envLoweredFrom === undefined
|
|
143
|
-
? plan.arrivals
|
|
144
|
-
: planArrivals(participants.map((participant) => participant.startAfterMs), { slots: concurrency, sessionMs: sessionBudgetMs }, participants.map((participant) => participantSessionMs(participant, sessionBudgetMs)));
|
|
145
|
-
const sandboxesMs = participants.reduce((sum, participant) => sum + participantWindow(participant, plan).sandboxMs, 0);
|
|
115
|
+
const { concurrency, sessionBudgetMs, arrivals: schedule, envLoweredConcurrencyFrom } = plan;
|
|
116
|
+
const study = { sessionMs: sessionBudgetMs, sandboxMs: plan.sandboxMs };
|
|
117
|
+
const sandboxesMs = participants.reduce((sum, participant) => sum + participantSession(participant.schedule, study).sandboxMs, 0);
|
|
146
118
|
const participantPlan = {
|
|
147
119
|
strategy: CUA_FANOUT_STRATEGY,
|
|
148
120
|
laneCount: participantCount,
|
|
149
121
|
concurrency,
|
|
150
|
-
...(
|
|
151
|
-
? {}
|
|
152
|
-
: { envLoweredConcurrencyFrom: resolved.envLoweredFrom }),
|
|
122
|
+
...(envLoweredConcurrencyFrom === undefined ? {} : { envLoweredConcurrencyFrom }),
|
|
153
123
|
waves: Math.ceil(participantCount / concurrency),
|
|
154
124
|
perLaneSessionBudgetMs: sessionBudgetMs,
|
|
155
125
|
worstCaseSandboxMinutes: Math.round(sandboxesMs / 60_000),
|
|
156
126
|
...(schedule.declared ? { schedule } : {}),
|
|
157
127
|
dryRun: plan.dryRun,
|
|
158
|
-
lanes: runs.map((spec) => (
|
|
159
|
-
id: spec.planned.id,
|
|
160
|
-
...(spec.planned.labels.actorType === undefined
|
|
161
|
-
? {}
|
|
162
|
-
: { actorType: spec.planned.labels.actorType }),
|
|
163
|
-
...(spec.planned.labels.surface === undefined
|
|
164
|
-
? {}
|
|
165
|
-
: { surface: spec.planned.labels.surface }),
|
|
166
|
-
...(spec.planned.labels.caseGroup === undefined
|
|
167
|
-
? {}
|
|
168
|
-
: { caseGroup: spec.planned.labels.caseGroup }),
|
|
169
|
-
index: spec.planned.index + 1,
|
|
170
|
-
persona: spec.persona.id,
|
|
171
|
-
device: spec.planned.device.name,
|
|
172
|
-
resolution: spec.planned.device.resolution,
|
|
173
|
-
instructionDigest: spec.persona.promptDigest,
|
|
174
|
-
...(spec.planned.limits.reasoningEffort === undefined
|
|
175
|
-
? {}
|
|
176
|
-
: { reasoningEffort: spec.planned.limits.reasoningEffort }),
|
|
177
|
-
...(spec.planned.limits.maxOutputTokens === undefined
|
|
178
|
-
? {}
|
|
179
|
-
: { maxOutputTokens: spec.planned.limits.maxOutputTokens }),
|
|
180
|
-
...(spec.planned.targetUrl === undefined
|
|
181
|
-
? {}
|
|
182
|
-
: { targetDigest: digestUrl(spec.planned.targetUrl) }),
|
|
183
|
-
...(spec.planned.startAfterMs === undefined
|
|
184
|
-
? {}
|
|
185
|
-
: { startAfterMs: spec.planned.startAfterMs }),
|
|
186
|
-
...(spec.planned.endAfterMs === undefined ? {} : { endAfterMs: spec.planned.endAfterMs }),
|
|
187
|
-
})),
|
|
128
|
+
lanes: runs.map((spec) => planEntry(spec)),
|
|
188
129
|
};
|
|
189
130
|
return { runs, participantPlan };
|
|
190
131
|
}
|
|
132
|
+
/** One participant's row in the plan: ids, labels, device, digests and its declared times. */
|
|
133
|
+
function planEntry(spec) {
|
|
134
|
+
const { startAfterMs, end } = spec.planned.schedule;
|
|
135
|
+
return {
|
|
136
|
+
id: spec.planned.id,
|
|
137
|
+
...(spec.planned.labels.actorType === undefined
|
|
138
|
+
? {}
|
|
139
|
+
: { actorType: spec.planned.labels.actorType }),
|
|
140
|
+
...(spec.planned.labels.surface === undefined ? {} : { surface: spec.planned.labels.surface }),
|
|
141
|
+
...(spec.planned.labels.caseGroup === undefined
|
|
142
|
+
? {}
|
|
143
|
+
: { caseGroup: spec.planned.labels.caseGroup }),
|
|
144
|
+
index: spec.planned.index + 1,
|
|
145
|
+
persona: spec.persona.id,
|
|
146
|
+
device: spec.planned.device.name,
|
|
147
|
+
resolution: spec.planned.device.resolution,
|
|
148
|
+
instructionDigest: spec.persona.promptDigest,
|
|
149
|
+
...(spec.planned.limits.reasoningEffort === undefined
|
|
150
|
+
? {}
|
|
151
|
+
: { reasoningEffort: spec.planned.limits.reasoningEffort }),
|
|
152
|
+
...(spec.planned.limits.maxOutputTokens === undefined
|
|
153
|
+
? {}
|
|
154
|
+
: { maxOutputTokens: spec.planned.limits.maxOutputTokens }),
|
|
155
|
+
...(spec.planned.targetUrl === undefined
|
|
156
|
+
? {}
|
|
157
|
+
: { targetDigest: digestUrl(spec.planned.targetUrl) }),
|
|
158
|
+
...(startAfterMs === undefined ? {} : { startAfterMs }),
|
|
159
|
+
...(end?.kind === "scheduled_end" ? { endAfterMs: end.endAfterMs } : {}),
|
|
160
|
+
...(end?.kind === "session_cap" ? { maxSessionMs: end.windowMs } : {}),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
191
163
|
/** Print the participant plan to stderr before any sandbox/provider call (public-safe: ids, devices,
|
|
192
164
|
* digests, and budgets only; no prompt text, no secrets). */
|
|
193
165
|
export function emitPreflightPlan(participantPlan, studyId) {
|
|
@@ -209,6 +181,7 @@ export function formatParticipantPlanEntry(entry) {
|
|
|
209
181
|
entry.reasoningEffort ? `effort=${entry.reasoningEffort}` : undefined,
|
|
210
182
|
entry.startAfterMs === undefined ? undefined : `start=+${formatDuration(entry.startAfterMs)}`,
|
|
211
183
|
entry.endAfterMs === undefined ? undefined : `end=+${formatDuration(entry.endAfterMs)}`,
|
|
184
|
+
entry.maxSessionMs === undefined ? undefined : `cap=${formatDuration(entry.maxSessionMs)}`,
|
|
212
185
|
].filter((part) => part !== undefined);
|
|
213
186
|
return `${entry.id}: persona=${entry.persona}${taxonomy.length > 0 ? ` ${taxonomy.join(" ")}` : ""} device=${entry.device} ${entry.resolution[0]}x${entry.resolution[1]} prompt#${entry.instructionDigest}${entry.targetDigest ? ` target#${entry.targetDigest}` : ""}`;
|
|
214
187
|
}
|
|
@@ -236,10 +209,7 @@ export async function loadCuaParticipants(args) {
|
|
|
236
209
|
// Compile any committed personas before planning, so the plan builder stays pure and each participant's
|
|
237
210
|
// prompt carries real behavioral directives rather than a bare `Persona: <id>.` label.
|
|
238
211
|
const personas = await compileParticipantPersonas(args.projectRoot, participants.map((participant) => participant.personaId));
|
|
239
|
-
const { runs: participantRuns, participantPlan } = participantRunsAndPlan(plan, {
|
|
240
|
-
env: args.env,
|
|
241
|
-
personas,
|
|
242
|
-
});
|
|
212
|
+
const { runs: participantRuns, participantPlan } = participantRunsAndPlan(plan, { personas });
|
|
243
213
|
const rerun = plan.rerun;
|
|
244
214
|
if (!rerun)
|
|
245
215
|
return { ok: true, participantRuns, participantPlan };
|
|
@@ -50,4 +50,6 @@ export declare function planComputerUseStudy(config: StudyConfig, input: {
|
|
|
50
50
|
readonly sandboxCeiling: SandboxCeiling;
|
|
51
51
|
/** How many sandboxes the operator's E2B plan runs at once (concurrentSandboxes). */
|
|
52
52
|
readonly concurrentSandboxes: ConcurrentSandboxes;
|
|
53
|
+
/** HUMANISH_CUA_MAX_CONCURRENCY (concurrencyCap), which may only lower the participants at once. */
|
|
54
|
+
readonly concurrencyCap?: number | undefined;
|
|
53
55
|
}): ComputerUsePlanResult;
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// in its order and with its codes, then the plan the run uses. What stays in the route reads
|
|
3
3
|
// external state: committed personas, a rerun's source run, keys, the local agent, subject env
|
|
4
4
|
// values, spend-cap prices and the comms catch.
|
|
5
|
+
import { DESKTOP_FILES_ROUTE_REASON } from "../../study/desktop-files.js";
|
|
5
6
|
import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
|
|
6
7
|
import { actorRegistry, isCuaActorDescriptor, } from "../../actors/registry.js";
|
|
7
8
|
import { isHttpUrl, isLoopbackUrl } from "../../study/parse/subject.js";
|
|
8
9
|
import { studyUrlCredentialReason } from "../../study/url-credentials.js";
|
|
9
10
|
import { subjectStateInvalidReason } from "../../study/parse/subject-state.js";
|
|
10
11
|
import { brainOf, callerBrainOf, planCaps, desktopRequirements, planBase, provisionedSubject, } from "../../study/plan-base.js";
|
|
11
|
-
import { computerUseParticipants, declaredTargets } from "../../study/plan-participants.js";
|
|
12
|
+
import { computerUseParticipants, declaredTargets, } from "../../study/plan-participants.js";
|
|
12
13
|
import { planStarts, windowDeadlineRefusal } from "../../study/arrivals.js";
|
|
13
14
|
import { computerUseParticipantCount, participantCountReason } from "../../study/routing.js";
|
|
14
15
|
import { cloneTargetValidationReason, computerUseValidationReason, desktopMediaValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, taskProtocolValidationReason, waitLimitValidationReason, } from "../../study/validation.js";
|
|
@@ -197,36 +198,82 @@ function rosterShapeReason(config, ceilingMs) {
|
|
|
197
198
|
* How many of the runner's participants run at once, and when each one starts, with the session
|
|
198
199
|
* budget the `ceilingMs` sandbox ceiling leaves. Hosted desktops share the E2B plan's concurrent
|
|
199
200
|
* sandboxes; the local VM has its own capacity rules and the in-process route runs one
|
|
200
|
-
* participant.
|
|
201
|
+
* participant. HUMANISH_CUA_MAX_CONCURRENCY (`concurrencyCap`) lowers the count on every desktop.
|
|
202
|
+
* `starts` holds the plan's fields for them.
|
|
201
203
|
*/
|
|
202
|
-
function startsOf(config, runner,
|
|
204
|
+
function startsOf(config, runner, limits, ceilingMs) {
|
|
203
205
|
const declared = config.execution?.concurrency;
|
|
204
206
|
const n = runner.participants.length;
|
|
205
|
-
const
|
|
206
|
-
const atOnce =
|
|
207
|
-
|
|
208
|
-
:
|
|
207
|
+
const hosted = runner.desktop === "e2b-desktop";
|
|
208
|
+
const atOnce = participantsAtOnce({
|
|
209
|
+
concurrency: boundedConcurrency(declared, n),
|
|
210
|
+
declared: declared !== undefined,
|
|
211
|
+
participants: n,
|
|
212
|
+
}, {
|
|
213
|
+
...(hosted ? { sandboxes: limits.concurrentSandboxes } : {}),
|
|
214
|
+
cap: limits.concurrencyCap,
|
|
215
|
+
});
|
|
209
216
|
if (!atOnce.ok)
|
|
210
217
|
return atOnce;
|
|
211
218
|
const sessionMs = config.execution?.timeoutMs ?? defaultSessionTimeoutMs(config, ceilingMs);
|
|
212
219
|
// A participant with a declared end gets a sandbox sized from its own window.
|
|
213
|
-
if (
|
|
214
|
-
const
|
|
215
|
-
const window = windowDeadlineRefusal(runner.participants, { sessionMs,
|
|
220
|
+
if (hosted) {
|
|
221
|
+
const sandboxMs = resolveParticipantSandboxMs(config, ceilingMs);
|
|
222
|
+
const window = windowDeadlineRefusal(runner.participants, { sessionMs, sandboxMs }, ceilingMs);
|
|
216
223
|
if (window !== undefined)
|
|
217
224
|
return { ok: false, message: window };
|
|
218
225
|
}
|
|
219
|
-
const
|
|
226
|
+
const { arrivals, warnings } = planStarts(runner.participants, { slots: atOnce.count, sessionMs }, atOnce.lowered);
|
|
227
|
+
const lowered = atOnce.loweredFrom;
|
|
220
228
|
return {
|
|
221
229
|
ok: true,
|
|
222
|
-
|
|
223
|
-
|
|
230
|
+
starts: {
|
|
231
|
+
concurrency: atOnce.count,
|
|
232
|
+
...(lowered === undefined ? {} : { envLoweredConcurrencyFrom: lowered }),
|
|
233
|
+
arrivals,
|
|
234
|
+
sessionBudgetMs: sessionMs,
|
|
235
|
+
},
|
|
236
|
+
warnings,
|
|
224
237
|
};
|
|
225
238
|
}
|
|
226
239
|
/** The plan's rerun: the source run and, when given, the participants to rerun. */
|
|
227
240
|
function rerunPlan({ sourceRunId, participantIds, }) {
|
|
228
241
|
return { sourceRunId, ...(participantIds === undefined ? {} : { participantIds }) };
|
|
229
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Why participants[].files cannot run on this runner, or undefined. The parser refuses them off the
|
|
245
|
+
* hosted desktop; a library caller's config skips the parser, and only the hosted desktop copies
|
|
246
|
+
* files.
|
|
247
|
+
*/
|
|
248
|
+
function desktopFilesRunnerReason(desktop, participants) {
|
|
249
|
+
if (desktop === "e2b-desktop" || participants.every((entry) => entry.files === undefined))
|
|
250
|
+
return undefined;
|
|
251
|
+
return `participants[].files: ${DESKTOP_FILES_ROUTE_REASON}. This study runs on a ${desktop} desktop; remove the files or set execution.target: e2b-desktop.`;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Why a participant's time zone cannot run on this runner, or undefined. The zone also sets the
|
|
255
|
+
* zone its browser runs in, which humanish sets on a hosted desktop's browser launch only: the
|
|
256
|
+
* local runtime's guest browser and an in-process caller's browser keep their own.
|
|
257
|
+
*/
|
|
258
|
+
function timeZoneRunnerReason(desktop, participants) {
|
|
259
|
+
const zoned = participants.find((entry) => entry.limits.timeZone !== undefined);
|
|
260
|
+
if (desktop === "e2b-desktop" || zoned === undefined)
|
|
261
|
+
return undefined;
|
|
262
|
+
const declared = `Participant ${zoned.id} has the time zone ${zoned.limits.timeZone}, which sets the zone its browser runs in, and humanish sets a browser's zone only on a hosted desktop.`;
|
|
263
|
+
return desktop === "local-vm"
|
|
264
|
+
? `${declared} This study runs on the local browser runtime. Remove actor.clock and participants[].clock, or set execution.target: e2b-desktop.`
|
|
265
|
+
: `${declared} This study drives your own browser through RunStudyOptions.inProcess. Remove actor.clock and participants[].clock, and set the zone in that browser yourself, or run on a hosted desktop.`;
|
|
266
|
+
}
|
|
267
|
+
/** What the participants declare that this runner cannot do, with its code, or undefined. */
|
|
268
|
+
function runnerRefusal(desktop, participants) {
|
|
269
|
+
const files = desktopFilesRunnerReason(desktop, participants);
|
|
270
|
+
if (files)
|
|
271
|
+
return { code: "HUMANISH_COMPUTER_USE_DESKTOP_FILES_INVALID", message: files };
|
|
272
|
+
const timeZone = timeZoneRunnerReason(desktop, participants);
|
|
273
|
+
if (timeZone)
|
|
274
|
+
return { code: "HUMANISH_COMPUTER_USE_TIME_ZONE_UNSUPPORTED", message: timeZone };
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
230
277
|
/**
|
|
231
278
|
* Plan a computer-use study. It is called for any config handed to the computer-use runner, not only
|
|
232
279
|
* one routeOf sends here, so a config for another route gets this route's refusal.
|
|
@@ -311,7 +358,10 @@ export function planComputerUseStudy(config, input) {
|
|
|
311
358
|
: isLocalBrowserStudy(config)
|
|
312
359
|
? { desktop: "local-vm", brain, participants: [first, ...rest], subject: appUrlSubject }
|
|
313
360
|
: { desktop: "e2b-desktop", brain, participants: [first, ...rest], subject: hosted };
|
|
314
|
-
const
|
|
361
|
+
const unsupported = runnerRefusal(runner.desktop, participants);
|
|
362
|
+
if (unsupported)
|
|
363
|
+
return refuse("in-scope", unsupported.code, unsupported.message, actor);
|
|
364
|
+
const starts = startsOf(config, runner, input, ceiling.ms);
|
|
315
365
|
if (!starts.ok)
|
|
316
366
|
return refuse("after-personas", "HUMANISH_COMPUTER_USE_FANOUT_INVALID", starts.message, actor);
|
|
317
367
|
const provisioned = source === "clone" || source === "local-tree";
|
|
@@ -324,9 +374,7 @@ export function planComputerUseStudy(config, input) {
|
|
|
324
374
|
route: "computer-use",
|
|
325
375
|
actor,
|
|
326
376
|
runner,
|
|
327
|
-
|
|
328
|
-
arrivals: starts.arrivals,
|
|
329
|
-
sessionBudgetMs: starts.arrivals.sessionMs,
|
|
377
|
+
...starts.starts,
|
|
330
378
|
sandboxMs: resolveParticipantSandboxMs(config, ceiling.ms),
|
|
331
379
|
caps: planCaps(config),
|
|
332
380
|
...(input.rerun === undefined ? {} : { rerun: rerunPlan(input.rerun) }),
|
|
@@ -7,6 +7,7 @@ import {} from "../../run/streams.js";
|
|
|
7
7
|
import { plural } from "../../run/text.js";
|
|
8
8
|
import { access } from "node:fs/promises";
|
|
9
9
|
import { cli } from "../../cli/invocation.js";
|
|
10
|
+
import { startingWithTheRun } from "../../study/arrivals.js";
|
|
10
11
|
export async function resolveCuaRerunSelection(args) {
|
|
11
12
|
const { studyId, sourceRunId } = args;
|
|
12
13
|
// Every refusal ends with the command that runs the whole study, which is what a user who
|
|
@@ -59,7 +60,7 @@ export async function resolveCuaRerunSelection(args) {
|
|
|
59
60
|
}
|
|
60
61
|
// A rerun runs the selected participants again now, all together: the study's start offsets
|
|
61
62
|
// placed them in the first run's day, and that day is over.
|
|
62
|
-
const selectedSpecs = selectedIds.map((id) =>
|
|
63
|
+
const selectedSpecs = selectedIds.map((id) => startingNow(specsById.get(id)));
|
|
63
64
|
const selectedPlanIds = new Set(selectedIds);
|
|
64
65
|
const selectedPlanEntries = args.participantPlan.lanes
|
|
65
66
|
.filter((entry) => selectedPlanIds.has(entry.id))
|
|
@@ -86,11 +87,13 @@ export async function resolveCuaRerunSelection(args) {
|
|
|
86
87
|
},
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
|
-
function
|
|
90
|
-
if (spec.planned.startAfterMs === undefined)
|
|
90
|
+
function startingNow(spec) {
|
|
91
|
+
if (spec.planned.schedule.startAfterMs === undefined)
|
|
91
92
|
return spec;
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
return {
|
|
94
|
+
...spec,
|
|
95
|
+
planned: { ...spec.planned, schedule: startingWithTheRun(spec.planned.schedule) },
|
|
96
|
+
};
|
|
94
97
|
}
|
|
95
98
|
function uniqueIds(values) {
|
|
96
99
|
const seen = new Set();
|
|
@@ -115,6 +115,7 @@ export declare function admitCuaRun(plan: ComputerUsePlan, input: ComputerUseRun
|
|
|
115
115
|
publicRepo: string | undefined;
|
|
116
116
|
localTreeArchive: LocalTreeArchive | undefined;
|
|
117
117
|
localTreeArchiveBuffer: ArrayBuffer | undefined;
|
|
118
|
+
desktopFiles: ReadonlyMap<string, readonly import("../../study/desktop-files.js").DesktopFile[]>;
|
|
118
119
|
};
|
|
119
120
|
}>;
|
|
120
121
|
/** Starts the run and builds what the participants and the finish read: the deps and the bundle base. */
|
|
@@ -24,6 +24,7 @@ import { projectParticipantSubjects } from "./subject-projection.js";
|
|
|
24
24
|
import { participantSubjectEnv, } from "./types.js";
|
|
25
25
|
import { capacityShortfall } from "../../substrates/local/capacity.js";
|
|
26
26
|
import { studyPersonaIds } from "../../study/persona-resolve.js";
|
|
27
|
+
import { readDesktopFiles } from "../../study/desktop-files.js";
|
|
27
28
|
import { refusedResult } from "../../run/study-result.js";
|
|
28
29
|
/** The physical project, bound before any caller hook runs. */
|
|
29
30
|
async function bindProject(cwd) {
|
|
@@ -107,7 +108,7 @@ export async function admitCuaRun(plan, input, config) {
|
|
|
107
108
|
? comms?.email?.external
|
|
108
109
|
: undefined;
|
|
109
110
|
const externalCommsEmail = externalCommsConfig ? comms?.email : undefined;
|
|
110
|
-
const participants = await loadCuaParticipants({ plan, cwd, projectRoot
|
|
111
|
+
const participants = await loadCuaParticipants({ plan, cwd, projectRoot });
|
|
111
112
|
if (!participants.ok) {
|
|
112
113
|
return refuse(participants.code, participants.message, descriptor.id);
|
|
113
114
|
}
|
|
@@ -132,6 +133,9 @@ export async function admitCuaRun(plan, input, config) {
|
|
|
132
133
|
...subjectEnvNames.map((name) => env[name] ?? ""),
|
|
133
134
|
]);
|
|
134
135
|
sanitizeParticipantRuns(participantRuns, secrets.scrub);
|
|
136
|
+
const desktopFiles = await readStudyDesktopFiles(input.cwd, plan, participantRuns, secrets.scrub);
|
|
137
|
+
if (!desktopFiles.ok)
|
|
138
|
+
return refuse("HUMANISH_COMPUTER_USE_DESKTOP_FILES_INVALID", desktopFiles.message, descriptor.id);
|
|
135
139
|
const redactRepoLabel = plan.residual.policies?.redactRepos ?? subjectEnvNames.includes("GITHUB_TOKEN");
|
|
136
140
|
const publicRepo = subject.kind === "clone" ? (redactRepoLabel ? "repo-01" : subject.repo) : undefined;
|
|
137
141
|
// Key-gating is route-aware: the in-process route uses the caller's own model + executor, and
|
|
@@ -195,9 +199,32 @@ export async function admitCuaRun(plan, input, config) {
|
|
|
195
199
|
publicRepo,
|
|
196
200
|
localTreeArchive,
|
|
197
201
|
localTreeArchiveBuffer,
|
|
202
|
+
desktopFiles: desktopFiles.files,
|
|
198
203
|
},
|
|
199
204
|
};
|
|
200
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Each participant's files, read on dry runs too so a bad file is named before a live run. Paths
|
|
208
|
+
* are relative to the folder that holds the study file; a library caller's config comes from no
|
|
209
|
+
* file, so its paths are relative to the project. An `as` that holds a value `scrub` removes, such
|
|
210
|
+
* as a key from the environment, is refused: the run records `as`, and redacting it would record a
|
|
211
|
+
* different path.
|
|
212
|
+
*/
|
|
213
|
+
async function readStudyDesktopFiles(cwd, plan, participantRuns, scrub) {
|
|
214
|
+
const folder = plan.study?.path === undefined
|
|
215
|
+
? path.resolve(cwd)
|
|
216
|
+
: path.dirname(path.resolve(cwd, plan.study.path));
|
|
217
|
+
const read = await readDesktopFiles(folder, participantRuns.map((run) => run.planned));
|
|
218
|
+
if (!read.ok)
|
|
219
|
+
return read;
|
|
220
|
+
const named = [...read.files].find(([, files]) => files.some((file) => scrub(file.as) !== file.as));
|
|
221
|
+
return named === undefined
|
|
222
|
+
? read
|
|
223
|
+
: {
|
|
224
|
+
ok: false,
|
|
225
|
+
message: `Participant ${named[0]}'s files name an as path that holds a key or another value from this run's environment, which the run would record. Rename the file.`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
201
228
|
/**
|
|
202
229
|
* Whether a local study's desktops at once fit the local runtime. Read before any desktop: a
|
|
203
230
|
* desktop past the VM's memory is killed mid-session, after its participant has spent.
|
|
@@ -317,6 +344,7 @@ function cuaParticipantDeps(plan, input, admitted, run) {
|
|
|
317
344
|
createDesktop: input.localVm?.desktop,
|
|
318
345
|
...callerDriving(input, config),
|
|
319
346
|
localTreeArchiveBuffer: admitted.localTreeArchiveBuffer,
|
|
347
|
+
desktopFiles: admitted.desktopFiles.size === 0 ? undefined : admitted.desktopFiles,
|
|
320
348
|
externalComms: externalCommsConfig === undefined || externalCommsEmail === undefined
|
|
321
349
|
? undefined
|
|
322
350
|
: { email: externalCommsEmail, inboxUrl: externalInboxUrl(externalCommsConfig) },
|
|
@@ -4,7 +4,7 @@ import type { DesktopBrowserEvidence } from "../../substrates/e2b/desktop-browse
|
|
|
4
4
|
import type { ActorPersonaRef } from "../../actors/contract.js";
|
|
5
5
|
import type { CuaLoopResult } from "../../actors/computer-use/loop.js";
|
|
6
6
|
import { type BundleRun, type RunBundle, type RunFeedbackCandidate, type RunProviderResource, type RunSubjectProvenance } from "../../run/bundle.js";
|
|
7
|
-
import { type RunDesktopGeometry, type RunStream } from "../../run/streams.js";
|
|
7
|
+
import { type RunDesktopFile, type RunDesktopGeometry, type RunStream } from "../../run/streams.js";
|
|
8
8
|
import { type Verdict } from "../../run/judge.js";
|
|
9
9
|
import { type DesktopUsage } from "../../run/cost-summary.js";
|
|
10
10
|
import type { HostSuspensionReading } from "../../run/host-suspension.js";
|
|
@@ -41,6 +41,10 @@ export declare function buildSingleParticipantBundle(args: {
|
|
|
41
41
|
/** Runtime screen/window/viewport evidence. `viewport` inside this object must be measured. */
|
|
42
42
|
desktopGeometry?: RunDesktopGeometry;
|
|
43
43
|
recording?: RunDesktopRecording;
|
|
44
|
+
/** participants[].files confirmed on the participant's desktop. */
|
|
45
|
+
desktopFiles?: RunDesktopFile[];
|
|
46
|
+
/** The participant's time zone, which its turns and its hosted browser used. */
|
|
47
|
+
timeZone?: string;
|
|
44
48
|
/** Device-preset touch metadata echoed on the measured stream viewport (a prompt signal on
|
|
45
49
|
* this route, never a rendered claim); the measured width/height/DPR stay authoritative. */
|
|
46
50
|
isMobile?: boolean;
|
|
@@ -5,6 +5,7 @@ import {} from "../../run/streams.js";
|
|
|
5
5
|
import { aggregateTaskFunnels, formatParticipantOutcomes, formatRunTaskFunnel, participantSummary, withCuaReviewProvenance, } from "../../run/outcomes.js";
|
|
6
6
|
import { participantCaption } from "../../run/participant-caption.js";
|
|
7
7
|
import { actorEnding } from "../../actors/stop-cause.js";
|
|
8
|
+
import { cuaGoalSource, ranUntilClockEnd } from "../../actors/goal-source.js";
|
|
8
9
|
import { describeSubjectState, phaseEventIdSuffix, publicSafeAppUrlLabel, subjectProvenanceMessage, } from "./bundle-parts.js";
|
|
9
10
|
import { participantFeedbackCandidates } from "./participant-feedback.js";
|
|
10
11
|
import { judgeParticipantRecords } from "../../run/judge.js";
|
|
@@ -46,6 +47,7 @@ function runCost(args) {
|
|
|
46
47
|
}
|
|
47
48
|
/** The participant's status, reason, last frame, geometry and screenshot mode, shared by its records. */
|
|
48
49
|
function participantView(args, publicAppUrl) {
|
|
50
|
+
const ranUntil = args.session === undefined ? undefined : ranUntilClockEnd(args.session.trace);
|
|
49
51
|
const judgment = judgeParticipantRecords([
|
|
50
52
|
{
|
|
51
53
|
...(args.session === undefined
|
|
@@ -55,6 +57,7 @@ function participantView(args, publicAppUrl) {
|
|
|
55
57
|
completionReason: args.session.completionReason,
|
|
56
58
|
reason: args.session.reason,
|
|
57
59
|
}),
|
|
60
|
+
...(ranUntil === undefined ? {} : { ranUntil }),
|
|
58
61
|
...(args.sessionError === undefined ? {} : { sessionError: args.sessionError }),
|
|
59
62
|
inProgress: args.inProgress === true,
|
|
60
63
|
skipped: false,
|
|
@@ -151,6 +154,8 @@ function singleStream(args, view) {
|
|
|
151
154
|
},
|
|
152
155
|
}),
|
|
153
156
|
...(desktopGeometry === undefined ? {} : { desktopGeometry }),
|
|
157
|
+
...(args.desktopFiles === undefined ? {} : { desktopFiles: args.desktopFiles }),
|
|
158
|
+
...(args.timeZone === undefined ? {} : { timeZone: args.timeZone }),
|
|
154
159
|
...(args.recording === undefined ? {} : { recording: args.recording }),
|
|
155
160
|
ui: {
|
|
156
161
|
route: publicAppUrl,
|
|
@@ -296,8 +301,13 @@ function singleSummary(args, view, facts) {
|
|
|
296
301
|
: `1 participant was planned, but the run failed before their session finished: ${view.reason}`;
|
|
297
302
|
}
|
|
298
303
|
const ending = actorEnding(args.session.trace);
|
|
304
|
+
const goalSource = cuaGoalSource(args.session.trace);
|
|
299
305
|
const outcomes = formatParticipantOutcomes(view.tally, [
|
|
300
|
-
{
|
|
306
|
+
{
|
|
307
|
+
status: args.session.status,
|
|
308
|
+
...(ending === undefined ? {} : { label: ending.label }),
|
|
309
|
+
...(goalSource === undefined ? {} : { goalSource }),
|
|
310
|
+
},
|
|
301
311
|
]);
|
|
302
312
|
return [
|
|
303
313
|
`1 participant took part: ${outcomes}.`,
|
|
@@ -13,7 +13,7 @@ import { type CuaDiagnostics } from "./diagnostics.js";
|
|
|
13
13
|
import type { ActorCompletionReason, ActorStatus, ActorStopCause, ActorTokenUsage, ActorTraceItem } from "../../actors/contract.js";
|
|
14
14
|
import { type CuaActorDescriptor } from "../../actors/registry.js";
|
|
15
15
|
import type { StudyDeps } from "../../study/study-deps.js";
|
|
16
|
-
import type { ArrivalPlan, ParticipantArrival } from "../../study/arrivals.js";
|
|
16
|
+
import type { ArrivalPlan, ParticipantArrival, ParticipantEnd } from "../../study/arrivals.js";
|
|
17
17
|
import type { StudyEvent, ParticipantRef } from "../../study/run-study-events.js";
|
|
18
18
|
import type { InProcessDriver, ProviderFactory, RunStudyHomes } from "../../study/run-study-homes.js";
|
|
19
19
|
import { type BrowserScorer } from "../../study/adapter-extension.js";
|
|
@@ -23,15 +23,15 @@ import { type DesktopResourceObservation } from "../../substrates/e2b/desktop-re
|
|
|
23
23
|
import { type DetachedTimers } from "../../substrates/detached.js";
|
|
24
24
|
import type { Brain, ComputerUsePlan, ComputerUseRunner, ResidualConfig } from "../../study/plan-types.js";
|
|
25
25
|
import { type StudyCommsEmail } from "../../study/types.js";
|
|
26
|
+
import type { DesktopFile } from "../../study/desktop-files.js";
|
|
26
27
|
import { type ObserverResult } from "../../observer/render.js";
|
|
27
28
|
import { type BundleRun, type RunBundle, type RunRerunLineage, type RunScorerProvenance, type RunSubjectProvenance, type RunSubjectStateStepRecord } from "../../run/bundle.js";
|
|
28
|
-
import { type RunDesktopGeometry } from "../../run/streams.js";
|
|
29
|
+
import { type RunDesktopFile, type RunDesktopGeometry } from "../../run/streams.js";
|
|
29
30
|
import { type PreparedOutputRoot } from "../../run/contained-output.js";
|
|
30
31
|
import type { ComputerUseParticipant, SharedWorldParticipant } from "../../study/plan-participants.js";
|
|
31
32
|
import type { ResolvedParticipant } from "../../run/participant.js";
|
|
32
33
|
import { type StudyResultErrorCode, type StudyResultIdentity } from "../../run/study-result.js";
|
|
33
34
|
export declare const CUA_FANOUT_STRATEGY: "per-lane-worlds";
|
|
34
|
-
export declare const CUA_MAX_CONCURRENCY_ENV = "HUMANISH_CUA_MAX_CONCURRENCY";
|
|
35
35
|
export declare const DEFAULT_APP_URL_SESSION_TIMEOUT_MS: number;
|
|
36
36
|
export declare const MIN_DERIVED_SESSION_TIMEOUT_MS: number;
|
|
37
37
|
/**
|
|
@@ -114,6 +114,8 @@ export interface CuaParticipantPlanEntry {
|
|
|
114
114
|
startAfterMs?: number;
|
|
115
115
|
/** The participant's declared end, on the same clock. */
|
|
116
116
|
endAfterMs?: number;
|
|
117
|
+
/** The participant's session cap, from the start of its session. */
|
|
118
|
+
maxSessionMs?: number;
|
|
117
119
|
}
|
|
118
120
|
/** The pre-flight spend and participant plan (pure; printed to stderr + recorded as a bundle event before
|
|
119
121
|
* any sandbox or provider call; identical in dry-run, marked $0). */
|
|
@@ -123,8 +125,8 @@ export interface CuaParticipantPlan {
|
|
|
123
125
|
/** Effective in-flight bound (defaults to laneCount, all participants live; a declared
|
|
124
126
|
* execution.concurrency is a cap; the env override may only lower it). */
|
|
125
127
|
concurrency: number;
|
|
126
|
-
/** Present when
|
|
127
|
-
* plan never silently disagrees with the manifest. */
|
|
128
|
+
/** Present when HUMANISH_CUA_MAX_CONCURRENCY lowered the bound (the plan's
|
|
129
|
+
* envLoweredConcurrencyFrom), recorded so the plan never silently disagrees with the manifest. */
|
|
128
130
|
envLoweredConcurrencyFrom?: number;
|
|
129
131
|
/** ceil(laneCount / concurrency). */
|
|
130
132
|
waves: number;
|
|
@@ -195,7 +197,7 @@ export interface CuaParticipantSummary {
|
|
|
195
197
|
concurrency: number;
|
|
196
198
|
waves: number;
|
|
197
199
|
}
|
|
198
|
-
export type CuaActorStudyErrorCode = StudyResultErrorCode | "HUMANISH_COMPUTER_USE_FAILED" | "HUMANISH_COMPUTER_USE_KEYS_MISSING" | "HUMANISH_COMPUTER_USE_AGENT_MISSING" | "HUMANISH_COMPUTER_USE_AGENT_SIGNIN_REQUIRED" | "HUMANISH_COMPUTER_USE_SUBJECT_ENV_MISSING" | "HUMANISH_COMPUTER_USE_ACTOR_UNSUPPORTED" | "HUMANISH_COMPUTER_USE_SUBJECT_INVALID" | "HUMANISH_COMPUTER_USE_SUBJECT_UNSAFE" | "HUMANISH_COMPUTER_USE_EXECUTOR_NO_PROVIDER" | "HUMANISH_COMPUTER_USE_LOCAL_APP_NO_EXECUTOR" | "HUMANISH_COMPUTER_USE_LOCAL_DESKTOP_MISSING" | "HUMANISH_COMPUTER_USE_LOCAL_CAPACITY_EXCEEDED" | "HUMANISH_COMPUTER_USE_DESKTOP_OUT_OF_MEMORY" | "HUMANISH_COMPUTER_USE_FANOUT_INVALID" | "HUMANISH_COMPUTER_USE_RERUN_INVALID" | "HUMANISH_COMPUTER_USE_DEVICE_GEOMETRY" | "HUMANISH_COMPUTER_USE_SUBJECT_BUILD_FAILED" | "HUMANISH_COMPUTER_USE_UNPRICED_CAP" | "HUMANISH_COMPUTER_USE_COMMS_CATCH_UNREACHABLE" | "HUMANISH_COMPUTER_USE_COMMS_TOKEN_INVALID" | "HUMANISH_WATCH_ALLOW_REQUIRES_OAUTH" | "HUMANISH_WATCH_OAUTH_REQUIRES_TUNNEL" | "HUMANISH_WATCH_OPTION_CONFLICT" | "HUMANISH_WATCH_TUNNEL_REQUIRES_EXPOSE" | "HUMANISH_WATCH_EXPOSE_REQUIRES_EDGE_AUTH" | "HUMANISH_WATCH_EXPOSE_REQUIRES_LIVE_FOLLOW" | "HUMANISH_WATCH_SAFE_NOT_APPLICABLE" | "HUMANISH_SERVE_TUNNEL_NOT_FOUND" | "HUMANISH_SERVE_TUNNEL_START_FAILED";
|
|
200
|
+
export type CuaActorStudyErrorCode = StudyResultErrorCode | "HUMANISH_COMPUTER_USE_FAILED" | "HUMANISH_COMPUTER_USE_KEYS_MISSING" | "HUMANISH_COMPUTER_USE_AGENT_MISSING" | "HUMANISH_COMPUTER_USE_AGENT_SIGNIN_REQUIRED" | "HUMANISH_COMPUTER_USE_SUBJECT_ENV_MISSING" | "HUMANISH_COMPUTER_USE_ACTOR_UNSUPPORTED" | "HUMANISH_COMPUTER_USE_SUBJECT_INVALID" | "HUMANISH_COMPUTER_USE_SUBJECT_UNSAFE" | "HUMANISH_COMPUTER_USE_EXECUTOR_NO_PROVIDER" | "HUMANISH_COMPUTER_USE_LOCAL_APP_NO_EXECUTOR" | "HUMANISH_COMPUTER_USE_LOCAL_DESKTOP_MISSING" | "HUMANISH_COMPUTER_USE_LOCAL_CAPACITY_EXCEEDED" | "HUMANISH_COMPUTER_USE_DESKTOP_OUT_OF_MEMORY" | "HUMANISH_COMPUTER_USE_DESKTOP_FILES_INVALID" | "HUMANISH_COMPUTER_USE_TIME_ZONE_UNSUPPORTED" | "HUMANISH_COMPUTER_USE_FANOUT_INVALID" | "HUMANISH_COMPUTER_USE_RERUN_INVALID" | "HUMANISH_COMPUTER_USE_DEVICE_GEOMETRY" | "HUMANISH_COMPUTER_USE_SUBJECT_BUILD_FAILED" | "HUMANISH_COMPUTER_USE_UNPRICED_CAP" | "HUMANISH_COMPUTER_USE_COMMS_CATCH_UNREACHABLE" | "HUMANISH_COMPUTER_USE_COMMS_TOKEN_INVALID" | "HUMANISH_WATCH_ALLOW_REQUIRES_OAUTH" | "HUMANISH_WATCH_OAUTH_REQUIRES_TUNNEL" | "HUMANISH_WATCH_OPTION_CONFLICT" | "HUMANISH_WATCH_TUNNEL_REQUIRES_EXPOSE" | "HUMANISH_WATCH_EXPOSE_REQUIRES_EDGE_AUTH" | "HUMANISH_WATCH_EXPOSE_REQUIRES_LIVE_FOLLOW" | "HUMANISH_WATCH_SAFE_NOT_APPLICABLE" | "HUMANISH_SERVE_TUNNEL_NOT_FOUND" | "HUMANISH_SERVE_TUNNEL_START_FAILED";
|
|
199
201
|
export interface CuaActorStudyResult extends AutomaticAnalysisResult, StudyResultIdentity<"computer-use"> {
|
|
200
202
|
/** True when the Observer verified the bundle, all live participants passed credibility checks
|
|
201
203
|
* (or this is a dry-run), and no declared adapter/scorer verdict failed. */
|
|
@@ -316,6 +318,9 @@ export interface E2BDesktopDeps {
|
|
|
316
318
|
/** Local-tree route only: the once-per-run packed archive bytes, shared byte-identically
|
|
317
319
|
* across every fan-out participant's upload step. Absent on dry-run and every other route. */
|
|
318
320
|
localTreeArchiveBuffer?: ArrayBuffer;
|
|
321
|
+
/** Computer use on a hosted desktop: each participant's files (participants[].files) by id,
|
|
322
|
+
* read on this machine before any sandbox. A participant without an entry receives none. */
|
|
323
|
+
desktopFiles?: ReadonlyMap<string, readonly DesktopFile[]>;
|
|
319
324
|
env: Record<string, string | undefined>;
|
|
320
325
|
e2bApiKey: string;
|
|
321
326
|
requestTimeoutMs: number;
|
|
@@ -367,14 +372,12 @@ export interface ParticipantModelDeps {
|
|
|
367
372
|
openaiApiKey: string;
|
|
368
373
|
timeoutMs: number;
|
|
369
374
|
/**
|
|
370
|
-
*
|
|
371
|
-
* (scheduledParticipantDeps)
|
|
375
|
+
* How the participant's session ends, when it declares participants[].endAfterMs or maxSessionMs
|
|
376
|
+
* (scheduledParticipantDeps): at its scheduled end on `now`'s clock, with stop cause
|
|
377
|
+
* `scheduled_end`, or at its cap after the session starts, with stop cause `session_cap`.
|
|
372
378
|
*/
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
readonly windDownMs: number;
|
|
376
|
-
};
|
|
377
|
-
/** The run's clock. A participant with a scheduled end runs its session on it. */
|
|
379
|
+
end?: ParticipantEnd;
|
|
380
|
+
/** The run's clock. A participant with an end or a cap runs its session on it. */
|
|
378
381
|
now: () => number;
|
|
379
382
|
participantCount: number;
|
|
380
383
|
artifactRoot: PreparedOutputRoot;
|
|
@@ -449,6 +452,8 @@ export interface ParticipantRunOutcome {
|
|
|
449
452
|
/** Requested + measured desktop/browser geometry. Viewport is absent when measurement failed. */
|
|
450
453
|
desktopGeometry?: RunDesktopGeometry;
|
|
451
454
|
recording?: RunDesktopRecording;
|
|
455
|
+
/** participants[].files confirmed on the participant's desktop. */
|
|
456
|
+
desktopFiles?: RunDesktopFile[];
|
|
452
457
|
stateStepRecords: RunSubjectStateStepRecord[];
|
|
453
458
|
/** Completed subject-phase records (clone/upload/extract/install/build/ready/state groups),
|
|
454
459
|
* folded into bundle.events at build time. Empty on the in-process route (no provisioning). */
|
|
@@ -15,9 +15,6 @@ import {} from "../../run/study-result.js";
|
|
|
15
15
|
// The fan-out topology of this route: N participants = N independent E2B desktop sandboxes,
|
|
16
16
|
// each its own world (clone/serve + subject.state per participant). Shared-world has its own route.
|
|
17
17
|
export const CUA_FANOUT_STRATEGY = "per-lane-worlds";
|
|
18
|
-
// Env override that may only lower the effective concurrency (never raise concurrent paid
|
|
19
|
-
// desktops). Read names-only into a local; the value never persists.
|
|
20
|
-
export const CUA_MAX_CONCURRENCY_ENV = "HUMANISH_CUA_MAX_CONCURRENCY";
|
|
21
18
|
// The default session budget, sized so a study can finish (docs/principles/three-roles.md: a
|
|
22
19
|
// session ends because the participant is done, not because a timer fired; the time-box is a
|
|
23
20
|
// session-level cap a researcher sets generously; spend protection is the dollar caps' job).
|