humanish 0.116.0 → 0.118.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 +40 -0
- package/dist/actors/codex/restricted-participant-policy.d.ts +0 -2
- package/dist/actors/codex/restricted-participant-policy.js +4 -32
- package/dist/actors/codex/restricted-participant.d.ts +5 -0
- package/dist/actors/codex/restricted-participant.js +3 -2
- package/dist/actors/computer-use/actor.d.ts +4 -0
- package/dist/actors/computer-use/actor.js +2 -0
- package/dist/actors/computer-use/loop/actions.d.ts +4 -4
- package/dist/actors/computer-use/loop/actions.js +25 -18
- package/dist/actors/computer-use/loop/session.d.ts +3 -0
- package/dist/actors/computer-use/loop/session.js +9 -1
- package/dist/actors/computer-use/loop/trace.js +1 -0
- package/dist/actors/computer-use/loop/types.d.ts +16 -14
- package/dist/actors/computer-use/loop.d.ts +1 -1
- package/dist/actors/computer-use/loop.js +1 -1
- package/dist/actors/computer-use/wait.d.ts +74 -0
- package/dist/actors/computer-use/wait.js +100 -0
- package/dist/actors/contract.d.ts +14 -0
- package/dist/analysis/admission.d.ts +7 -2
- package/dist/analysis/admission.js +12 -3
- package/dist/analysis/automatic-config.d.ts +6 -1
- package/dist/analysis/automatic-config.js +12 -11
- package/dist/analysis/execute.d.ts +19 -7
- package/dist/analysis/execute.js +55 -17
- package/dist/browser-control/protocol.d.ts +86 -6
- package/dist/browser-control/protocol.js +13 -6
- package/dist/cli/commands/analyze.js +2 -1
- package/dist/cli/commands/study-format.js +8 -11
- package/dist/cli/commands/study-route-computer-use.js +2 -2
- package/dist/cli/commands/study-route-scripted.js +2 -2
- package/dist/cli/commands/study-route-shared-world.js +5 -3
- package/dist/cli/commands/study-route-terminal.js +2 -2
- package/dist/cli/commands/study-run.js +3 -3
- package/dist/cli/commands/study.js +2 -2
- package/dist/cli/doctor.js +11 -1
- package/dist/cli/io.js +3 -2
- package/dist/comms/lease-store.d.ts +1 -1
- package/dist/comms/lease-store.js +3 -2
- package/dist/comms/receiving-types.d.ts +2 -0
- package/dist/comms/receiving-types.js +2 -0
- package/dist/guest/desktop-executor.js +3 -1
- package/dist/observer-app.html +7 -7
- package/dist/routes/computer-use/e2b-desktop/prepare.js +40 -31
- package/dist/routes/computer-use/fanout-bundle.js +2 -1
- package/dist/routes/computer-use/fanout-records.js +2 -1
- package/dist/routes/computer-use/local-vm.js +5 -2
- package/dist/routes/computer-use/participant-model.js +9 -0
- package/dist/routes/computer-use/participant-runs.js +3 -9
- package/dist/routes/computer-use/plan.d.ts +3 -1
- package/dist/routes/computer-use/plan.js +29 -9
- package/dist/routes/computer-use/single-bundle.js +7 -4
- package/dist/routes/computer-use/subject-projection.js +2 -0
- package/dist/routes/computer-use/types.d.ts +1 -1
- package/dist/routes/scripted/plan.d.ts +4 -1
- package/dist/routes/scripted/plan.js +25 -2
- package/dist/routes/scripted/setup.js +1 -4
- package/dist/routes/scripted/types.d.ts +1 -1
- package/dist/routes/shared-world/participant-specs.js +2 -2
- package/dist/routes/shared-world/plan.d.ts +3 -1
- package/dist/routes/shared-world/plan.js +39 -7
- package/dist/routes/shared-world/route.js +2 -2
- package/dist/routes/terminal/live-sandbox.d.ts +1 -1
- package/dist/routes/terminal/live-sandbox.js +14 -11
- package/dist/routes/terminal/sandbox.d.ts +4 -3
- package/dist/routes/terminal/sandbox.js +5 -5
- package/dist/run/bundle-shape.js +2 -1
- package/dist/run/bundle.d.ts +5 -3
- package/dist/run/projection.js +7 -2
- package/dist/run/route-shell.js +10 -5
- package/dist/run/subject-name.d.ts +11 -0
- package/dist/run/subject-name.js +11 -0
- package/dist/study/automatic-analysis-plan.d.ts +28 -0
- package/dist/study/automatic-analysis-plan.js +57 -0
- package/dist/study/composition-rules.js +1 -1
- package/dist/study/config.js +10 -21
- package/dist/study/doctor.js +17 -3
- package/dist/study/keys.d.ts +2 -0
- package/dist/study/keys.js +2 -0
- package/dist/study/migrate/v2.js +2 -1
- package/dist/study/parse/actors.d.ts +1 -1
- package/dist/study/parse/actors.js +9 -1
- package/dist/study/parse/front.d.ts +1 -1
- package/dist/study/parse/front.js +7 -1
- package/dist/study/plan-base.d.ts +2 -0
- package/dist/study/plan-base.js +3 -0
- package/dist/study/plan-participants.d.ts +4 -0
- package/dist/study/plan-participants.js +6 -5
- package/dist/study/plan-types.d.ts +8 -3
- package/dist/study/plan.d.ts +4 -4
- package/dist/study/plan.js +21 -12
- package/dist/study/preflight-result.js +2 -2
- package/dist/study/preflight.d.ts +2 -1
- package/dist/study/preflight.js +12 -3
- package/dist/study/routing.d.ts +5 -4
- package/dist/study/routing.js +16 -7
- package/dist/study/summary.d.ts +3 -3
- package/dist/study/summary.js +3 -5
- package/dist/study/types.d.ts +14 -0
- package/dist/study/validation.d.ts +7 -2
- package/dist/study/validation.js +30 -14
- package/dist/study/warnings.js +12 -0
- package/dist/subject/serve.d.ts +3 -0
- package/dist/subject/serve.js +4 -1
- package/dist/substrates/detached.js +14 -7
- package/dist/substrates/e2b/desktop-executor.d.ts +0 -2
- package/dist/substrates/e2b/desktop-executor.js +3 -3
- package/dist/substrates/e2b/lifetime.d.ts +75 -4
- package/dist/substrates/e2b/lifetime.js +134 -10
- package/dist/substrates/e2b/subject-sandbox.js +2 -6
- package/dist/tui-app.js +119 -118
- package/dist/verify/artifacts.d.ts +19 -4
- package/dist/verify/artifacts.js +39 -8
- package/dist/verify/verify.js +6 -15
- package/docs/architecture/browser-control.md +10 -4
- package/docs/contracts/run-bundle.md +7 -6
- package/docs/contracts/schemas.md +33 -19
- package/docs/contracts/study-analysis.md +2 -2
- package/docs/principles/invariants-and-defaults.md +1 -1
- package/docs/product/automatic-analysis.md +4 -3
- package/package.json +2 -2
|
@@ -62,8 +62,8 @@ export function sharedWorldStudyRefusal(options, refusal) {
|
|
|
62
62
|
studyId: config.id,
|
|
63
63
|
actor: config.actor?.type ?? "",
|
|
64
64
|
participantCount: declared.length,
|
|
65
|
-
//
|
|
66
|
-
//
|
|
65
|
+
// A refused study never planned its concurrency, so its result reports the declared cap, else
|
|
66
|
+
// every declared participant.
|
|
67
67
|
concurrency: config.execution?.concurrency ?? Math.max(1, declared.length),
|
|
68
68
|
dryRun,
|
|
69
69
|
runId: options.runId,
|
|
@@ -11,6 +11,7 @@ import { extractLocalActorVerdict, normalizeLocalActorTranscript, } from "../../
|
|
|
11
11
|
import { NODE_BOOTSTRAP_COMMAND, NODE_BOOTSTRAP_TIMEOUT_MS } from "../../subject/node-bootstrap.js";
|
|
12
12
|
import { observeDesktopResources, } from "../../substrates/e2b/desktop-resources.js";
|
|
13
13
|
import { acquireE2BShellSandbox } from "../../substrates/e2b/sandbox.js";
|
|
14
|
+
import { e2bAccount } from "../../substrates/e2b/connection.js";
|
|
14
15
|
import { E2BDesktopStartupError, loadE2BDesktopModule, } from "../../substrates/e2b/sdk.js";
|
|
15
16
|
import { shellQuote } from "../../substrates/shell.js";
|
|
16
17
|
import { buildOpenAiEgressNetwork } from "./runtime-auth.js";
|
|
@@ -36,7 +37,7 @@ export class LiveTerminalSandbox {
|
|
|
36
37
|
wallClockMs;
|
|
37
38
|
sandboxTimeoutMs;
|
|
38
39
|
sandbox;
|
|
39
|
-
|
|
40
|
+
account;
|
|
40
41
|
allocation;
|
|
41
42
|
startupCleanup;
|
|
42
43
|
startupCleanupDetail;
|
|
@@ -60,7 +61,6 @@ export class LiveTerminalSandbox {
|
|
|
60
61
|
// Declared egress allowlist, or undefined for the historical unrestricted default.
|
|
61
62
|
const egressAllow = plan.egressAllow;
|
|
62
63
|
const sandboxModule = await (deps.desktopModule ?? loadE2BDesktopModule)();
|
|
63
|
-
this.module = sandboxModule;
|
|
64
64
|
await validatePreparedRunArtifactPaths(runPaths);
|
|
65
65
|
// No sandbox-global env in either mode. In openai-egress, only this host-side SDK request
|
|
66
66
|
// carries the real runtime key; participant commands receive an inert placeholder. The proxy
|
|
@@ -71,16 +71,19 @@ export class LiveTerminalSandbox {
|
|
|
71
71
|
const network = runtimeEnv.mode === "openai-egress"
|
|
72
72
|
? buildOpenAiEgressNetwork(runtimeEnv.keyValue, routing)
|
|
73
73
|
: routing;
|
|
74
|
+
const options = {
|
|
75
|
+
apiKey: e2bApiKey,
|
|
76
|
+
requestTimeoutMs,
|
|
77
|
+
timeoutMs: sandboxTimeoutMs,
|
|
78
|
+
metadata,
|
|
79
|
+
...(network === undefined ? {} : { network }),
|
|
80
|
+
lifecycle: { onTimeout: "kill" },
|
|
81
|
+
};
|
|
82
|
+
// Teardown's check by id reaches the account the create used, whatever process.env names.
|
|
83
|
+
this.account = e2bAccount(sandboxModule, options);
|
|
74
84
|
const acquired = await acquireE2BShellSandbox({
|
|
75
85
|
module: sandboxModule,
|
|
76
|
-
options
|
|
77
|
-
apiKey: e2bApiKey,
|
|
78
|
-
requestTimeoutMs,
|
|
79
|
-
timeoutMs: sandboxTimeoutMs,
|
|
80
|
-
metadata,
|
|
81
|
-
...(network === undefined ? {} : { network }),
|
|
82
|
-
lifecycle: { onTimeout: "kill" },
|
|
83
|
-
},
|
|
86
|
+
options,
|
|
84
87
|
retry: {
|
|
85
88
|
// A failed first attempt may have allocated a sandbox whose id never reached this run;
|
|
86
89
|
// its own kill-on-timeout reclaims it.
|
|
@@ -418,7 +421,7 @@ export class LiveTerminalSandbox {
|
|
|
418
421
|
// --- Proven cleanup, by exact id, never Sandbox.list. ---
|
|
419
422
|
this.cleanup = await teardownSandbox({
|
|
420
423
|
allocation: this.allocation,
|
|
421
|
-
|
|
424
|
+
account: this.account,
|
|
422
425
|
...(this.startupCleanup === undefined ? {} : { startupCleanup: this.startupCleanup }),
|
|
423
426
|
startupCleanupDetail: this.startupCleanupDetail,
|
|
424
427
|
requestTimeoutMs,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OwnedDesktopAllocation } from "../../substrates/desktop-session.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { E2BAccount } from "../../substrates/e2b/connection.js";
|
|
3
|
+
import { E2BDesktopStartupError } from "../../substrates/e2b/sdk.js";
|
|
3
4
|
import type { TerminalLedgers } from "./types.js";
|
|
4
5
|
/**
|
|
5
6
|
* Tear the sandbox down and prove it by exact id, never by Sandbox.list (humanish must never
|
|
@@ -14,8 +15,8 @@ import type { TerminalLedgers } from "./types.js";
|
|
|
14
15
|
export declare function teardownSandbox(args: {
|
|
15
16
|
/** The allocation from acquisition; its release kills the exact id it captured. */
|
|
16
17
|
allocation: OwnedDesktopAllocation | undefined;
|
|
17
|
-
/**
|
|
18
|
-
|
|
18
|
+
/** The account the create used, for the Sandbox.getInfo(id) re-check only. */
|
|
19
|
+
account: E2BAccount | undefined;
|
|
19
20
|
startupCleanup?: E2BDesktopStartupError["cleanup"];
|
|
20
21
|
/** Why the startup guard's cleanup is unconfirmed, when it knows. */
|
|
21
22
|
startupCleanupDetail?: string | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toErrorMessage } from "../../evidence/redaction.js";
|
|
2
|
-
import { E2BDesktopStartupError, isSandboxNotFoundError
|
|
2
|
+
import { E2BDesktopStartupError, isSandboxNotFoundError } from "../../substrates/e2b/sdk.js";
|
|
3
3
|
import { releaseUnavailableDetail } from "../../substrates/e2b/sandbox.js";
|
|
4
4
|
/**
|
|
5
5
|
* Tear the sandbox down and prove it by exact id, never by Sandbox.list (humanish must never
|
|
@@ -12,8 +12,8 @@ import { releaseUnavailableDetail } from "../../substrates/e2b/sandbox.js";
|
|
|
12
12
|
* fails closed on an unproven teardown.
|
|
13
13
|
*/
|
|
14
14
|
export async function teardownSandbox(args) {
|
|
15
|
-
const { allocation,
|
|
16
|
-
if (allocation === undefined ||
|
|
15
|
+
const { allocation, account, startupCleanup, startupCleanupDetail, requestTimeoutMs, sanitize, recordLifecycle, warnings, } = args;
|
|
16
|
+
if (allocation === undefined || account === undefined) {
|
|
17
17
|
// create() can reject after its constructor acquired a handle. The default loader retains
|
|
18
18
|
// that authority and reclaims it before rejecting; the route itself never receives its ID.
|
|
19
19
|
if (startupCleanup === "killed" || startupCleanup === "already_gone") {
|
|
@@ -58,7 +58,7 @@ export async function teardownSandbox(args) {
|
|
|
58
58
|
const killNote = released.reason === "terminated"
|
|
59
59
|
? "kill(id) returned true (found and killed)"
|
|
60
60
|
: "kill(id) found the exact sandbox already gone (404)";
|
|
61
|
-
if (
|
|
61
|
+
if (account.getInfo === undefined) {
|
|
62
62
|
recordLifecycle("terminal-lab.cleanup.killed", `Sandbox ${sandboxId} reclaimed: ${killNote}; the installed SDK has no getInfo(id) to re-verify, so kill(id)'s own result is the proof.`);
|
|
63
63
|
return {
|
|
64
64
|
killed: true,
|
|
@@ -67,7 +67,7 @@ export async function teardownSandbox(args) {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
try {
|
|
70
|
-
const info = await
|
|
70
|
+
const info = await account.getInfo(sandboxId, { requestTimeoutMs });
|
|
71
71
|
const state = info.state ?? "unknown";
|
|
72
72
|
recordLifecycle("terminal-lab.cleanup.unconfirmed", `Sandbox ${sandboxId} ${killNote}, but getInfo(id) still reports state=${state} (not confirmed reclaimed by id).`);
|
|
73
73
|
return {
|
package/dist/run/bundle-shape.js
CHANGED
|
@@ -216,7 +216,8 @@ function isRunSubjectProvenance(value) {
|
|
|
216
216
|
if (value.source !== "clone" &&
|
|
217
217
|
value.source !== "app-url" &&
|
|
218
218
|
value.source !== "local-tree" &&
|
|
219
|
-
value.source !== "desktop-cli"
|
|
219
|
+
value.source !== "desktop-cli" &&
|
|
220
|
+
value.source !== "local-app")
|
|
220
221
|
return false;
|
|
221
222
|
if (value.repo !== undefined && typeof value.repo !== "string")
|
|
222
223
|
return false;
|
package/dist/run/bundle.d.ts
CHANGED
|
@@ -158,7 +158,8 @@ export interface RunSubjectStateStepRecord {
|
|
|
158
158
|
* pre-existing bundles).
|
|
159
159
|
*/
|
|
160
160
|
export interface RunSubjectProvenance {
|
|
161
|
-
|
|
161
|
+
/** A run saved before `desktop-cli` or `local-app` was recorded says `app-url` for either. */
|
|
162
|
+
source: "clone" | "app-url" | "local-tree" | "desktop-cli" | "local-app";
|
|
162
163
|
/** Clone-route only. Honors policies.redactRepos exactly as the provenance event does. */
|
|
163
164
|
repo?: string;
|
|
164
165
|
/** Desktop-cli only: the product the study declares in `subject.product.name`. */
|
|
@@ -186,8 +187,9 @@ export interface RunSubjectProvenance {
|
|
|
186
187
|
* an external DB is still unpinned overall).
|
|
187
188
|
* declared-not-run: steps declared but not (all) executed ok: dry-run contract bundles
|
|
188
189
|
* and failed live provisioning.
|
|
189
|
-
* undeclared: no subject.state block (stateless apps, app-url and desktop-cli
|
|
190
|
-
* the explicit "absence declared" marker that provenance requires when there
|
|
190
|
+
* undeclared: no subject.state block (stateless apps, app-url, local-app and desktop-cli
|
|
191
|
+
* subjects), the explicit "absence declared" marker that provenance requires when there
|
|
192
|
+
* is none.
|
|
191
193
|
* external-public: an operator-declared, operator-owned public deployment used
|
|
192
194
|
* directly as the shared plane; humanish neither provisioned nor seeded it (no getHost, no
|
|
193
195
|
* clone, no in-sandbox filesystem). It is not "seeded" (nothing was seeded) and not "unpinned" (this is
|
package/dist/run/projection.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// - an unknown cost is `null` (declared absent), never 0;
|
|
8
8
|
// - truncation is reported, never silent;
|
|
9
9
|
// - a participant's reported thinking is quoted, never paraphrased or summarized.
|
|
10
|
+
import { indexedRunCost } from "./run-cost.js";
|
|
10
11
|
/**
|
|
11
12
|
* Group runs by study, newest-first within each study and by recency between studies. Runs with no study
|
|
12
13
|
* attribution are collected under `unattributed` rather than invented into a study. That is the
|
|
@@ -74,9 +75,13 @@ export function expectationFor(entries, mode) {
|
|
|
74
75
|
.map((entry) => entry.durationMs)
|
|
75
76
|
.filter((value) => typeof value === "number" && Number.isFinite(value) && value >= 0)
|
|
76
77
|
.sort((a, b) => a - b);
|
|
78
|
+
// Each run's cost is the one its row and card show (src/run/run-cost.ts): participants and
|
|
79
|
+
// desktops plus its analyses. A run whose participants and desktops have no figure stays
|
|
80
|
+
// unpriced below, so an analysis alone never stands in for what a run cost.
|
|
77
81
|
const costs = finished
|
|
78
|
-
.
|
|
79
|
-
.
|
|
82
|
+
.filter((entry) => typeof entry.estimatedCostUsd === "number" && Number.isFinite(entry.estimatedCostUsd))
|
|
83
|
+
.map((entry) => indexedRunCost(entry).total?.usd)
|
|
84
|
+
.filter((value) => value !== undefined)
|
|
80
85
|
.sort((a, b) => a - b);
|
|
81
86
|
// A cost is unknown whether it was declared absent (`null`) or never recorded at all
|
|
82
87
|
// (`undefined`: every fail-closed exit finalized with no outcome). Counting only the first
|
package/dist/run/route-shell.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// either is made here once. The preview route records no run and has no shell.
|
|
5
5
|
import { completeAutomaticAnalysis } from "../analysis/automatic-completion.js";
|
|
6
6
|
import { resolveAutomaticAnalysis } from "../analysis/automatic-config.js";
|
|
7
|
+
import { withParticipantLimitSkip } from "../study/automatic-analysis-plan.js";
|
|
7
8
|
import { runScope } from "./run.js";
|
|
8
9
|
import { withTransientCommsSecrets } from "./transient-comms-secrets.js";
|
|
9
10
|
/**
|
|
@@ -12,11 +13,15 @@ import { withTransientCommsSecrets } from "./transient-comms-secrets.js";
|
|
|
12
13
|
* it takes closes the run it started, and only a run that published its final bundle is analyzed.
|
|
13
14
|
*/
|
|
14
15
|
export async function admitRoute(shell) {
|
|
15
|
-
const complete = (input, result, finished) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const complete = (input, result, finished) =>
|
|
17
|
+
// A live run of more participants than one analysis reads records the skip and starts nothing.
|
|
18
|
+
shell.analysis?.skip !== undefined && !result.dryRun
|
|
19
|
+
? Promise.resolve(withParticipantLimitSkip(result, shell.analysis.trigger))
|
|
20
|
+
: completeAutomaticAnalysis(result, finished, shell.analysis?.config, input, {
|
|
21
|
+
...(shell.analysis === undefined ? {} : { trigger: shell.analysis.trigger }),
|
|
22
|
+
preferLargerOutput: shell.analysis?.preferLargerOutput === true,
|
|
23
|
+
...(shell.analysisRefusal === undefined ? {} : { refusal: shell.analysisRefusal }),
|
|
24
|
+
});
|
|
20
25
|
const admission = await shell.admit();
|
|
21
26
|
if (!admission.ok)
|
|
22
27
|
return {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A subject as a run records it: its source and, for a product used at a desktop, the product. */
|
|
2
|
+
interface NamedSubject {
|
|
3
|
+
readonly source: string;
|
|
4
|
+
readonly product?: string | undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The URL a participant opens names the subject. A product used at a desktop has no URL, so it is
|
|
8
|
+
* named with its source, as in `humanish (desktop-cli)`. Empty when there is neither.
|
|
9
|
+
*/
|
|
10
|
+
export declare function subjectName(url: string, subject: NamedSubject): string;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// The words a reader sees for a run's subject. The `subject:` line of `humanish run`, the run's
|
|
2
|
+
// summary and each participant's sentence name it the same way.
|
|
3
|
+
/**
|
|
4
|
+
* The URL a participant opens names the subject. A product used at a desktop has no URL, so it is
|
|
5
|
+
* named with its source, as in `humanish (desktop-cli)`. Empty when there is neither.
|
|
6
|
+
*/
|
|
7
|
+
export function subjectName(url, subject) {
|
|
8
|
+
if (url !== "" || subject.product === undefined)
|
|
9
|
+
return url;
|
|
10
|
+
return `${subject.product} (${subject.source})`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AutomaticAnalysisResult } from "../analysis/automatic-completion.js";
|
|
2
|
+
import { type AutomaticAnalysisBudget } from "../analysis/automatic-config.js";
|
|
3
|
+
import type { StudyRoute } from "./routing.js";
|
|
4
|
+
/** The reason a run records for an analysis its study has too many participants for. */
|
|
5
|
+
export declare const PARTICIPANT_LIMIT_SKIP = "AUTOMATIC_ANALYSIS_PARTICIPANT_LIMIT";
|
|
6
|
+
/** The CLI's words for that reason, after "analysis: ". */
|
|
7
|
+
export declare const PARTICIPANT_LIMIT_SKIP_TEXT: string;
|
|
8
|
+
/** The skip a study of `participants` gets, or undefined when its analysis runs. */
|
|
9
|
+
export declare function analysisSkipFor(participants: number): typeof PARTICIPANT_LIMIT_SKIP | undefined;
|
|
10
|
+
/** The analysis budget the plan line shows, and the skip when the analysis will not run. */
|
|
11
|
+
export type StudyAnalysisBudget = AutomaticAnalysisBudget & {
|
|
12
|
+
readonly skip?: typeof PARTICIPANT_LIMIT_SKIP;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The budget of a study's automatic analysis after a live run of `participants`, or undefined
|
|
16
|
+
* when none is planned. A skipped analysis carries no expected cost, since it spends nothing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function studyAnalysisBudget(raw: unknown, route: StudyRoute, participants: number): StudyAnalysisBudget | undefined;
|
|
19
|
+
/** The line `run` and `study check` print about the analysis after a live run. */
|
|
20
|
+
export declare function formatStudyAnalysisBudget(budget: StudyAnalysisBudget): string;
|
|
21
|
+
/** A run's result with its analysis recorded as skipped for its participant count. */
|
|
22
|
+
export declare function withParticipantLimitSkip<T>(result: T, trigger: "default" | "explicit"): T & AutomaticAnalysisResult;
|
|
23
|
+
/**
|
|
24
|
+
* Whether a run's automatic analysis lets the command exit 0. A participant-count skip of the
|
|
25
|
+
* analysis a study did not ask for passes, like the default analysis's missing-key skip; a skip of
|
|
26
|
+
* an explicit request fails, like that request's missing key.
|
|
27
|
+
*/
|
|
28
|
+
export declare function studyAnalysisSucceeded(result: AutomaticAnalysisResult): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// What automatic analysis does for a study of N participants. One analysis reads at most
|
|
2
|
+
// EVIDENCE_LIMITS.participants of them (the analysis module owns that number). A study with more
|
|
3
|
+
// runs every participant and records its analysis as skipped for that reason, where the analysis
|
|
4
|
+
// would otherwise cover the first participants and drop the rest. The plan line, the run's
|
|
5
|
+
// analysis record and the CLI's exit code all read the skip from here. Once the analysis covers
|
|
6
|
+
// every participant a study may have, the skip never applies and this module can go.
|
|
7
|
+
import { automaticAnalysisSucceeded, } from "../analysis/automatic-completion.js";
|
|
8
|
+
import { automaticAnalysisBudget, formatAutomaticAnalysisBudget, } from "../analysis/automatic-config.js";
|
|
9
|
+
import { EVIDENCE_LIMITS } from "../analysis/evidence.js";
|
|
10
|
+
/** The reason a run records for an analysis its study has too many participants for. */
|
|
11
|
+
export const PARTICIPANT_LIMIT_SKIP = "AUTOMATIC_ANALYSIS_PARTICIPANT_LIMIT";
|
|
12
|
+
/** The CLI's words for that reason, after "analysis: ". */
|
|
13
|
+
export const PARTICIPANT_LIMIT_SKIP_TEXT = `not run, because the study has more than ${EVIDENCE_LIMITS.participants} participants and automatic analysis reads at most ${EVIDENCE_LIMITS.participants}`;
|
|
14
|
+
/** The skip a study of `participants` gets, or undefined when its analysis runs. */
|
|
15
|
+
export function analysisSkipFor(participants) {
|
|
16
|
+
return participants > EVIDENCE_LIMITS.participants ? PARTICIPANT_LIMIT_SKIP : undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The budget of a study's automatic analysis after a live run of `participants`, or undefined
|
|
20
|
+
* when none is planned. A skipped analysis carries no expected cost, since it spends nothing.
|
|
21
|
+
*/
|
|
22
|
+
export function studyAnalysisBudget(raw, route, participants) {
|
|
23
|
+
const skip = analysisSkipFor(participants);
|
|
24
|
+
if (skip === undefined)
|
|
25
|
+
return automaticAnalysisBudget(raw, route, participants);
|
|
26
|
+
const budget = automaticAnalysisBudget(raw, route);
|
|
27
|
+
return budget && { ...budget, participants, skip };
|
|
28
|
+
}
|
|
29
|
+
/** The line `run` and `study check` print about the analysis after a live run. */
|
|
30
|
+
export function formatStudyAnalysisBudget(budget) {
|
|
31
|
+
if (budget.skip === undefined)
|
|
32
|
+
return formatAutomaticAnalysisBudget(budget);
|
|
33
|
+
const next = budget.trigger === "default"
|
|
34
|
+
? "Set review.analysis: false to leave it out of this plan."
|
|
35
|
+
: "The analysis this study asks for will be recorded as skipped and the run will exit 2; set review.analysis: false or run fewer participants.";
|
|
36
|
+
return `After live runs: automatic analysis will not run. This study has ${budget.participants ?? "more"} participants, and automatic analysis reads at most ${EVIDENCE_LIMITS.participants}. ${next}`;
|
|
37
|
+
}
|
|
38
|
+
/** A run's result with its analysis recorded as skipped for its participant count. */
|
|
39
|
+
export function withParticipantLimitSkip(result, trigger) {
|
|
40
|
+
return {
|
|
41
|
+
...result,
|
|
42
|
+
...(trigger === "default" ? { automaticAnalysisTrigger: trigger } : {}),
|
|
43
|
+
automaticAnalysis: { state: "skipped", reason: PARTICIPANT_LIMIT_SKIP },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether a run's automatic analysis lets the command exit 0. A participant-count skip of the
|
|
48
|
+
* analysis a study did not ask for passes, like the default analysis's missing-key skip; a skip of
|
|
49
|
+
* an explicit request fails, like that request's missing key.
|
|
50
|
+
*/
|
|
51
|
+
export function studyAnalysisSucceeded(result) {
|
|
52
|
+
const value = result.automaticAnalysis;
|
|
53
|
+
return (automaticAnalysisSucceeded(result) ||
|
|
54
|
+
(result.automaticAnalysisTrigger === "default" &&
|
|
55
|
+
value?.state === "skipped" &&
|
|
56
|
+
value.reason === PARTICIPANT_LIMIT_SKIP));
|
|
57
|
+
}
|
|
@@ -118,7 +118,7 @@ function appUrlValidationReason(config) {
|
|
|
118
118
|
return `actor.type must be a registered computer-use actor for app-url × e2b-desktop studies (one of: ${registeredComputerUseActors().join(", ")}); for local scripted execution use a registered scripted-browser actor (${registeredScriptedBrowserActors().join(", ")}). Got "${type}".`;
|
|
119
119
|
}
|
|
120
120
|
// Multi-participant fan-out is consumed on this route (`per-lane-worlds`; the shared cua
|
|
121
|
-
// cross-validation below enforces the
|
|
121
|
+
// cross-validation below enforces the participant bound, the per-participant target gates, and the
|
|
122
122
|
// allowPublicTargets+N>1 rejection for ambiguous one-target fan-out).
|
|
123
123
|
// Loopback by default; an owner may declare a public/preview target via policies.
|
|
124
124
|
const targets = [config.subject.appUrl ?? "", ...declaredTargets(config)];
|
package/dist/study/config.js
CHANGED
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
// The terminal route consumes subject.product, `caps` and execution.{terminal,runtimeAuth}.
|
|
19
19
|
//
|
|
20
20
|
// NOTE on `participants`: a count (preview: the simulated count; computer use: N identical
|
|
21
|
-
// participants, each its own desktop,
|
|
21
|
+
// participants, each its own desktop, at most 100), `{ count, instruction }` (identical
|
|
22
22
|
// participants with one steer), or a list of `{ id?, persona?, device?, instruction?, target? ... }`
|
|
23
23
|
// entries, each its own desktop. A list entry with a `count` is a group that expands into
|
|
24
24
|
// `<id>-01` to `<id>-NN`. A shared world takes a list of at least two. `participants[].device`
|
|
25
25
|
// excludes a raw `execution.desktop.resolution`, and `participants[].target` is app-url computer
|
|
26
26
|
// use only: an absolute browser URL this participant opens in place of `subject.appUrl`.
|
|
27
|
-
// `execution.concurrency` bounds in-flight participants (default: all at once
|
|
28
|
-
// HUMANISH_CUA_MAX_CONCURRENCY may only lower it).
|
|
27
|
+
// `execution.concurrency` bounds in-flight participants (default: all at once, up to the E2B
|
|
28
|
+
// plan's concurrent sandboxes; env HUMANISH_CUA_MAX_CONCURRENCY may only lower it).
|
|
29
29
|
import { isLocalBrowserStudy, localBrowserDefaults, localBrowserUnsupportedReason, } from "../substrates/local/runtime-config.js";
|
|
30
30
|
import { isRecord } from "../run/type-guards.js";
|
|
31
31
|
import { compositionReason } from "./composition-rules.js";
|
|
@@ -36,11 +36,10 @@ import { parseCaps, parseDefaults, parseExecution, parsePolicies, parseReview, }
|
|
|
36
36
|
import { ACTOR_REQUIRED, readStudyFront, routeNameReason, } from "./parse/front.js";
|
|
37
37
|
import { parseSubject } from "./parse/subject.js";
|
|
38
38
|
import { invalid, optionalStr, str } from "./parse/values.js";
|
|
39
|
-
import { isComputerUseComposition,
|
|
39
|
+
import { isComputerUseComposition, routeOf } from "./routing.js";
|
|
40
40
|
import { declaredParticipantIds } from "./plan-participants.js";
|
|
41
|
-
import { declaredParticipantCount, participantList } from "./study-fields.js";
|
|
42
41
|
import { ID_PATTERN, STUDY_SCHEMA, } from "./types.js";
|
|
43
|
-
import { automaticAnalysisRouteReason, desktopMediaValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, smtpValidationReason, taskProtocolValidationReason, } from "./validation.js";
|
|
42
|
+
import { automaticAnalysisRouteReason, desktopMediaValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, smtpValidationReason, taskProtocolValidationReason, waitLimitValidationReason, } from "./validation.js";
|
|
44
43
|
import { forwardDeclaredWarnings, inertFieldLabels, inertFieldPaths } from "./warnings.js";
|
|
45
44
|
import { plural } from "../run/text.js";
|
|
46
45
|
/**
|
|
@@ -216,7 +215,8 @@ function parseParticipants(raw, read) {
|
|
|
216
215
|
}
|
|
217
216
|
/**
|
|
218
217
|
* The checks that read more than one section of a parsed config, and the defaults they fill:
|
|
219
|
-
*
|
|
218
|
+
* `comms.email.recipients` and the local browser's defaults. An omitted `execution.concurrency`
|
|
219
|
+
* stays unset: the planner resolves it from the participants and the E2B plan's limit.
|
|
220
220
|
*/
|
|
221
221
|
function checkStudyConfig(config) {
|
|
222
222
|
const smtpReason = smtpValidationReason(config);
|
|
@@ -228,20 +228,9 @@ function checkStudyConfig(config) {
|
|
|
228
228
|
const outputLimitReason = outputTokenLimitValidationReason(config);
|
|
229
229
|
if (outputLimitReason)
|
|
230
230
|
return invalid(outputLimitReason);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// when they plan, after any --count override, so the parser leaves it unset for them. A shared
|
|
235
|
-
// world's roster is fixed, so its default is filled here for the envelopes and warnings that read
|
|
236
|
-
// the parsed config.
|
|
237
|
-
{
|
|
238
|
-
const participantCount = participantList(config)?.length ?? declaredParticipantCount(config) ?? 1;
|
|
239
|
-
if (participantCount > 1 &&
|
|
240
|
-
config.execution?.concurrency === undefined &&
|
|
241
|
-
isSharedWorldComposition(config)) {
|
|
242
|
-
config.execution = { ...config.execution, concurrency: participantCount };
|
|
243
|
-
}
|
|
244
|
-
}
|
|
231
|
+
const waitLimitReason = waitLimitValidationReason(config);
|
|
232
|
+
if (waitLimitReason)
|
|
233
|
+
return invalid(waitLimitReason);
|
|
245
234
|
// Email that just works: the funnel's only handoff to an actor is the per-participant
|
|
246
235
|
// inbox instruction, gated on recipients[]. Guessed participant ids broke a field run:
|
|
247
236
|
// recipients copied from a single-participant example matched nothing, so every actor was left inbox-blind with zero
|
package/dist/study/doctor.js
CHANGED
|
@@ -5,7 +5,8 @@ import { capacityShortfall, describeCapacity, } from "../substrates/local/capaci
|
|
|
5
5
|
import { keyNamesOf, requiredKeys, requiredSubjectEnv } from "./requirements.js";
|
|
6
6
|
import { localAgentReady, localAgentVersionRefusal, } from "../actors/local-agent/cli.js";
|
|
7
7
|
import { protocolAdditionsWarning, protocolIncompatibilityMessage, } from "../actors/codex/protocol-compat.js";
|
|
8
|
-
import {
|
|
8
|
+
import { admissionRule } from "../analysis/admission.js";
|
|
9
|
+
import { studyAnalysisBudget } from "./automatic-analysis-plan.js";
|
|
9
10
|
import { externalCatchHealthy } from "../comms/sandbox-catch.js";
|
|
10
11
|
import { receivingRequiredKey } from "../comms/setup.js";
|
|
11
12
|
import { classifyCodexInstallation, codexInstallAdvice, codexVersionRecovery, } from "../actors/codex/codex-admission.js";
|
|
@@ -190,7 +191,11 @@ export async function studySetupChecks(args) {
|
|
|
190
191
|
if (route === "scripted")
|
|
191
192
|
checks.push(await scriptedBrowserCheck());
|
|
192
193
|
checks.push(...subjectEnvChecks(requiredSubjectEnv(requirements), args));
|
|
193
|
-
const analysis =
|
|
194
|
+
const analysis = studyAnalysisBudget(config.review?.analysis, route, plan.route === "computer-use"
|
|
195
|
+
? plan.runner.participants.length
|
|
196
|
+
: plan.route === "shared-world"
|
|
197
|
+
? plan.plane.participants.length
|
|
198
|
+
: 1);
|
|
194
199
|
if (analysis)
|
|
195
200
|
checks.push(await analysisCheck(analysis, args, checkAccount));
|
|
196
201
|
checks.push(checkScope(analysis));
|
|
@@ -347,6 +352,15 @@ function subjectEnvChecks(names, args) {
|
|
|
347
352
|
}
|
|
348
353
|
/** The post-run analysis: the Codex account, or the OpenAI key and the analysis cost limit. */
|
|
349
354
|
async function analysisCheck(analysis, args, checkAccount) {
|
|
355
|
+
if (analysis.skip !== undefined) {
|
|
356
|
+
const explicit = analysis.trigger === "explicit";
|
|
357
|
+
return {
|
|
358
|
+
name: "post-run analysis",
|
|
359
|
+
ok: !explicit,
|
|
360
|
+
...(explicit ? {} : { status: "note" }),
|
|
361
|
+
message: `Will not run: this study has ${analysis.participants ?? "more"} participants, more than automatic analysis reads. The participants still run.${explicit ? " review.analysis asks for it, so the run will exit 2; set review.analysis: false or run fewer participants." : ""}`,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
350
364
|
if (analysis.provider === "codex") {
|
|
351
365
|
const readiness = await checkAccount();
|
|
352
366
|
const recovery = readiness.errorCode === "codex_unsupported_platform"
|
|
@@ -368,7 +382,7 @@ async function analysisCheck(analysis, args, checkAccount) {
|
|
|
368
382
|
ok: true,
|
|
369
383
|
...(keyed ? {} : { status: "note" }),
|
|
370
384
|
message: keyed
|
|
371
|
-
? `OPENAI_API_KEY is present for the separate automatic analysis request; model access and quota are not tested. The analysis is refused before it starts if
|
|
385
|
+
? `OPENAI_API_KEY is present for the separate automatic analysis request; model access and quota are not tested. The analysis is refused before it starts if ${admissionRule(`$${analysis.maxCostUsd}`)}; this is not a billing cap. Participant readiness is independent.`
|
|
372
386
|
: "Will be skipped: OPENAI_API_KEY is missing. The participant may run, but there will be no automatic findings report. Add an OpenAI API key or set review.analysis: false deliberately.",
|
|
373
387
|
};
|
|
374
388
|
}
|
package/dist/study/keys.d.ts
CHANGED
package/dist/study/keys.js
CHANGED
package/dist/study/migrate/v2.js
CHANGED
|
@@ -95,7 +95,8 @@ const V2_KEYS = {
|
|
|
95
95
|
lanes: PARTICIPANT_ENTRY,
|
|
96
96
|
// Roster groups are participants with a count; the parser expanded them into `lanes[]`.
|
|
97
97
|
roster: { ...PARTICIPANT_ENTRY, count: true },
|
|
98
|
-
|
|
98
|
+
// maxWaitMs and idleWaitMs arrived after v2, so a v2 file cannot carry them.
|
|
99
|
+
...without(ACTOR, "type", "maxWaitMs", "idleWaitMs"),
|
|
99
100
|
laneFocus: { id: true, label: true, instruction: true },
|
|
100
101
|
},
|
|
101
102
|
execution: withAfter(EXECUTION, "egressAllow", { caps: CAPS }),
|
|
@@ -28,7 +28,7 @@ export interface ParticipantPaths {
|
|
|
28
28
|
* instruction?, target?, entry?, host?, reasoningEffort?, stopWhen?, dwell? }`.
|
|
29
29
|
* Participant ids (when declared) must be public-safe path tokens and unique; grouping metadata
|
|
30
30
|
* must be public-safe tokens; a device must be a known preset name. The
|
|
31
|
-
* route-scoped cross-validation (device XOR raw resolution, targets,
|
|
31
|
+
* route-scoped cross-validation (device XOR raw resolution, targets, the participant bound) runs in parseStudy
|
|
32
32
|
* where the route is known.
|
|
33
33
|
*/
|
|
34
34
|
export declare function parseParticipantEntries(raw: unknown, paths: ParticipantPaths): {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DEVICE_PRESET_NAMES, isDevicePresetName } from "../device-presets.js";
|
|
2
2
|
import { isReasoningEffort, reasoningEffortNames } from "../../actors/reasoning-effort.js";
|
|
3
3
|
import { isMaxOutputTokens } from "../../actors/output-token-limit.js";
|
|
4
|
+
import { waitFieldsReason } from "../../actors/computer-use/wait.js";
|
|
4
5
|
import { isHttpUrl } from "./subject.js";
|
|
5
6
|
import { entryCredentialReason, urlCredentialReason } from "./url-credentials.js";
|
|
6
7
|
import { registeredComputerUseActors, registeredScriptedBrowserActors, registeredTerminalActors, } from "../routing.js";
|
|
@@ -54,6 +55,13 @@ export function parseActorFields(entry, path) {
|
|
|
54
55
|
return invalid(`${path}.maxOutputTokens must be a positive safe integer.`);
|
|
55
56
|
actor.maxOutputTokens = entry.maxOutputTokens;
|
|
56
57
|
}
|
|
58
|
+
const waitReason = waitFieldsReason(entry, `${path}.`);
|
|
59
|
+
if (waitReason)
|
|
60
|
+
return invalid(waitReason);
|
|
61
|
+
if (typeof entry.maxWaitMs === "number")
|
|
62
|
+
actor.maxWaitMs = entry.maxWaitMs;
|
|
63
|
+
if (typeof entry.idleWaitMs === "number")
|
|
64
|
+
actor.idleWaitMs = entry.idleWaitMs;
|
|
57
65
|
const localAgent = str(entry.localAgent);
|
|
58
66
|
if (entry.localAgent !== undefined) {
|
|
59
67
|
if (localAgent !== "codex" && localAgent !== "claude") {
|
|
@@ -91,7 +99,7 @@ export function parseActorFields(entry, path) {
|
|
|
91
99
|
* instruction?, target?, entry?, host?, reasoningEffort?, stopWhen?, dwell? }`.
|
|
92
100
|
* Participant ids (when declared) must be public-safe path tokens and unique; grouping metadata
|
|
93
101
|
* must be public-safe tokens; a device must be a known preset name. The
|
|
94
|
-
* route-scoped cross-validation (device XOR raw resolution, targets,
|
|
102
|
+
* route-scoped cross-validation (device XOR raw resolution, targets, the participant bound) runs in parseStudy
|
|
95
103
|
* where the route is known.
|
|
96
104
|
*/
|
|
97
105
|
export function parseParticipantEntries(raw, paths) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type StudyRoute } from "../routing.js";
|
|
2
2
|
import type { StudyParseFailure, StudySurfaces } from "../types.js";
|
|
3
3
|
/** A document's `participants`, read before the participant entries parse. */
|
|
4
4
|
export interface Participants {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { isRecord } from "../../run/type-guards.js";
|
|
5
5
|
import { findUnknownStudyKey } from "../keys.js";
|
|
6
6
|
import { movedV2KeyReason } from "../migrate/v2.js";
|
|
7
|
+
import { participantCountReason } from "../routing.js";
|
|
7
8
|
import { PARTICIPANT_ID_MAX_CHARS, PARTICIPANT_ID_PATTERN } from "./actors.js";
|
|
8
9
|
import { invalid, posInt } from "./values.js";
|
|
9
10
|
// The values `route:` takes: every route routeOf can return.
|
|
@@ -131,8 +132,13 @@ function homogeneousOf(raw) {
|
|
|
131
132
|
return { ok: true, value: participants };
|
|
132
133
|
}
|
|
133
134
|
// A list entry with `count: n` is a group: n participants `<id>-01` to `<id>-NN`, even when n is 1.
|
|
134
|
-
// config.ts then checks every entry and the expanded ids for collisions.
|
|
135
|
+
// config.ts then checks every entry and the expanded ids for collisions. The roster's size is
|
|
136
|
+
// checked before any group expands, so a large count is refused without building it.
|
|
135
137
|
function entriesOf(raw) {
|
|
138
|
+
const size = raw.reduce((sum, entry) => sum + (isRecord(entry) && typeof entry.count === "number" ? (posInt(entry.count) ?? 1) : 1), 0);
|
|
139
|
+
const sizeReason = participantCountReason(size);
|
|
140
|
+
if (sizeReason)
|
|
141
|
+
return invalid(sizeReason);
|
|
136
142
|
const entries = [];
|
|
137
143
|
const source = [];
|
|
138
144
|
for (const [index, entry] of raw.entries()) {
|
|
@@ -25,4 +25,6 @@ export declare function planBase(config: StudyConfig, input: {
|
|
|
25
25
|
readonly config?: PlannedAnalysis["config"] | undefined;
|
|
26
26
|
readonly preferLargerOutput?: boolean | undefined;
|
|
27
27
|
};
|
|
28
|
+
/** How many participants the run has, which decides whether its analysis can run. */
|
|
29
|
+
readonly participants?: number;
|
|
28
30
|
}): Base;
|
package/dist/study/plan-base.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 { analysisSkipFor } from "./automatic-analysis-plan.js";
|
|
4
5
|
// A YAML alias can make a parsed record contain itself (an inline persona that names its own
|
|
5
6
|
// anchor), and structuredClone keeps the cycle, so each object is frozen once.
|
|
6
7
|
function deepFreeze(value, frozen = new WeakSet()) {
|
|
@@ -108,6 +109,7 @@ export function pricedModel(brain) {
|
|
|
108
109
|
/** The fields every plan shares, from a config whose analysis already resolved. */
|
|
109
110
|
export function planBase(config, input) {
|
|
110
111
|
const analysis = input.analysis?.config;
|
|
112
|
+
const skip = analysisSkipFor(input.participants ?? 1);
|
|
111
113
|
return {
|
|
112
114
|
studyId: config.id,
|
|
113
115
|
...(config.title === undefined ? {} : { title: config.title }),
|
|
@@ -120,6 +122,7 @@ export function planBase(config, input) {
|
|
|
120
122
|
config: analysis,
|
|
121
123
|
trigger: config.review?.analysis === undefined ? "default" : "explicit",
|
|
122
124
|
preferLargerOutput: input.analysis?.preferLargerOutput === true,
|
|
125
|
+
...(skip === undefined ? {} : { skip }),
|
|
123
126
|
},
|
|
124
127
|
}),
|
|
125
128
|
requirements: [],
|
|
@@ -36,6 +36,10 @@ interface DesktopParticipant extends Participant {
|
|
|
36
36
|
readonly dwell?: DwellWindow;
|
|
37
37
|
readonly reasoningEffort?: ReasoningEffort;
|
|
38
38
|
readonly maxOutputTokens?: number;
|
|
39
|
+
/** actor.maxWaitMs: the longest one wait action lasts. */
|
|
40
|
+
readonly maxWaitMs?: number;
|
|
41
|
+
/** actor.idleWaitMs: how long a wait with no duration lasts while the participant only waits. */
|
|
42
|
+
readonly idleWaitMs?: number;
|
|
39
43
|
};
|
|
40
44
|
}
|
|
41
45
|
/** Only independent computer-use participants consume a task protocol and their own target. */
|