humanish 0.111.2 → 0.111.3
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/ARCHITECTURE.md +1 -1
- package/CHANGELOG.md +17 -0
- package/dist/cli/commands/study-route-computer-use.js +3 -8
- package/dist/cli/commands/study-route-preview.js +4 -6
- package/dist/cli/commands/study-route-shared-world.js +6 -7
- package/dist/evidence/redaction.d.ts +2 -2
- package/dist/evidence/redaction.js +3 -3
- package/dist/routes/computer-use/bundle-parts.d.ts +1 -32
- package/dist/routes/computer-use/bundle-parts.js +5 -92
- package/dist/routes/computer-use/e2b-desktop/prepare.js +11 -4
- package/dist/routes/computer-use/fanout-bundle.js +3 -2
- package/dist/routes/computer-use/live-phase.js +3 -7
- package/dist/routes/computer-use/participant-deps.d.ts +46 -0
- package/dist/routes/computer-use/participant-deps.js +48 -0
- package/dist/routes/computer-use/participant-facts.d.ts +5 -4
- package/dist/routes/computer-use/participant-facts.js +37 -2
- package/dist/routes/computer-use/participant-feedback.d.ts +33 -0
- package/dist/routes/computer-use/participant-feedback.js +92 -0
- package/dist/routes/computer-use/participant-model.d.ts +11 -0
- package/dist/routes/computer-use/participant-model.js +15 -0
- package/dist/routes/computer-use/preflight.js +33 -65
- package/dist/routes/computer-use/result.js +13 -52
- package/dist/routes/computer-use/route.js +6 -7
- package/dist/routes/computer-use/setup.d.ts +2 -6
- package/dist/routes/computer-use/setup.js +47 -93
- package/dist/routes/computer-use/single-bundle.js +3 -2
- package/dist/routes/computer-use/types.d.ts +2 -2
- package/dist/routes/preview.js +2 -8
- package/dist/routes/scripted/bundle.d.ts +2 -6
- package/dist/routes/scripted/result.d.ts +2 -1
- package/dist/routes/scripted/result.js +4 -4
- package/dist/routes/scripted/route.js +13 -38
- package/dist/routes/scripted/setup.d.ts +2 -1
- package/dist/routes/scripted/setup.js +21 -28
- package/dist/routes/scripted/subject.d.ts +3 -18
- package/dist/routes/scripted/subject.js +22 -78
- package/dist/routes/scripted/types.d.ts +2 -2
- package/dist/routes/shared-world/bundle.js +36 -1
- package/dist/routes/shared-world/comms.d.ts +2 -1
- package/dist/routes/shared-world/comms.js +3 -7
- package/dist/routes/shared-world/handoff.d.ts +2 -2
- package/dist/routes/shared-world/handoff.js +1 -1
- package/dist/routes/shared-world/participant-specs.d.ts +4 -5
- package/dist/routes/shared-world/participant-specs.js +19 -25
- package/dist/routes/shared-world/plan.js +11 -7
- package/dist/routes/shared-world/provisioned.d.ts +2 -1
- package/dist/routes/shared-world/provisioned.js +37 -80
- package/dist/routes/shared-world/result.js +17 -35
- package/dist/routes/shared-world/route.js +33 -36
- package/dist/routes/shared-world/setup.d.ts +2 -3
- package/dist/routes/shared-world/setup.js +11 -29
- package/dist/routes/shared-world/types.d.ts +6 -15
- package/dist/routes/terminal/dry-run.js +14 -24
- package/dist/routes/terminal/live-finish.d.ts +0 -1
- package/dist/routes/terminal/live-finish.js +2 -2
- package/dist/routes/terminal/route.js +9 -15
- package/dist/routes/terminal/session.d.ts +2 -3
- package/dist/routes/terminal/session.js +28 -40
- package/dist/routes/terminal/trace.d.ts +1 -1
- package/dist/routes/terminal/types.d.ts +2 -2
- package/dist/run/run.d.ts +55 -1
- package/dist/run/run.js +38 -2
- package/dist/run/secrets.d.ts +22 -0
- package/dist/run/secrets.js +33 -0
- package/dist/run/study-result.d.ts +34 -0
- package/dist/run/study-result.js +16 -0
- package/dist/study/requirements.d.ts +60 -5
- package/dist/study/requirements.js +88 -2
- package/dist/study/run-study-events.d.ts +4 -3
- package/dist/study/run-study-events.js +9 -14
- package/dist/study/run-study-options.d.ts +4 -1
- package/dist/study/run-study-options.js +22 -32
- package/dist/substrates/e2b/subject-sandbox.d.ts +58 -0
- package/dist/substrates/e2b/subject-sandbox.js +94 -0
- package/docs/contracts/core.md +5 -4
- package/docs/contracts/feedback.md +12 -5
- package/package.json +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Feedback candidates from what live participants reported. The computer-use bundles (single and
|
|
2
|
+
// fan-out) and the shared-world bundle all build them here, so a participant's report becomes the
|
|
3
|
+
// same candidate whichever route ran it.
|
|
4
|
+
import { redactText } from "../../evidence/redaction.js";
|
|
5
|
+
import { feedbackProofCommands } from "../../feedback/proof.js";
|
|
6
|
+
import { resolveSelfReportedFriction } from "./self-report.js";
|
|
7
|
+
/**
|
|
8
|
+
* Feedback candidates derived from what live participants actually reported.
|
|
9
|
+
*
|
|
10
|
+
* A live run's feedback draft comes from these candidates, never from a dry-run template. The
|
|
11
|
+
* candidate worth filing is the one the study produced: a participant
|
|
12
|
+
* who reported friction on the way (the most valuable thing a run captures), or one who stopped
|
|
13
|
+
* trying. A clean pass files nothing here: feedback exists to carry findings, and a run without
|
|
14
|
+
* any falls back to a live summary in the draft layer instead of a template.
|
|
15
|
+
*
|
|
16
|
+
* Everything quoted is already scrub+redacted (participant messages and `session.reason` pass
|
|
17
|
+
* through redactNarration in the loop) and passes redactText again here as defense-in-depth.
|
|
18
|
+
*/
|
|
19
|
+
export function participantFeedbackCandidates(args) {
|
|
20
|
+
const candidates = [];
|
|
21
|
+
for (const participant of args.participants) {
|
|
22
|
+
const session = participant.session;
|
|
23
|
+
if (session === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
const friction = resolveSelfReportedFriction(session);
|
|
26
|
+
const abandoned = session.status === "abandoned";
|
|
27
|
+
if (friction === undefined && !abandoned)
|
|
28
|
+
continue;
|
|
29
|
+
const summary = friction !== undefined
|
|
30
|
+
? `Participant ${participant.personaId} (${participant.participantId}) reported friction on the way through the study goal`
|
|
31
|
+
: `Participant ${participant.personaId} (${participant.participantId}) stopped before completing the study goal`;
|
|
32
|
+
const lastScreenshot = participant.screenshots[participant.screenshots.length - 1];
|
|
33
|
+
candidates.push({
|
|
34
|
+
schema: "humanish.feedback-candidate.v1",
|
|
35
|
+
id: `participant-report-${participant.participantId}`,
|
|
36
|
+
run_id: args.runId,
|
|
37
|
+
stream_id: participant.streamId,
|
|
38
|
+
adapter_id: args.adapterId,
|
|
39
|
+
scenario_id: args.scenarioId,
|
|
40
|
+
persona_id: participant.personaId,
|
|
41
|
+
actor: "computer-use",
|
|
42
|
+
substrate: args.substrate,
|
|
43
|
+
// The participant is reporting on the product: friction and abandonment are target-app
|
|
44
|
+
// findings by the three-roles rule. A harness failure never reaches this builder; it is
|
|
45
|
+
// not a participant report.
|
|
46
|
+
failure_owner: "target-app",
|
|
47
|
+
summary,
|
|
48
|
+
expected: participant.goal,
|
|
49
|
+
actual: redactText(friction ?? session.reason),
|
|
50
|
+
evidence: [
|
|
51
|
+
...(participant.traceArtifactPath === undefined
|
|
52
|
+
? []
|
|
53
|
+
: [
|
|
54
|
+
{
|
|
55
|
+
path: participant.traceArtifactPath,
|
|
56
|
+
kind: "trace",
|
|
57
|
+
note: "Full actor trace: turns, actions, and the participant's own report.",
|
|
58
|
+
},
|
|
59
|
+
]),
|
|
60
|
+
...(lastScreenshot === undefined
|
|
61
|
+
? []
|
|
62
|
+
: [
|
|
63
|
+
{
|
|
64
|
+
path: lastScreenshot,
|
|
65
|
+
kind: "screenshot",
|
|
66
|
+
note: "Final screenshot at the moment the session ended.",
|
|
67
|
+
},
|
|
68
|
+
]),
|
|
69
|
+
...(participant.commsArtifactPath === undefined
|
|
70
|
+
? []
|
|
71
|
+
: [
|
|
72
|
+
{
|
|
73
|
+
path: participant.commsArtifactPath,
|
|
74
|
+
kind: "log",
|
|
75
|
+
note: "Digest-only comms thread captured in-sandbox.",
|
|
76
|
+
},
|
|
77
|
+
]),
|
|
78
|
+
],
|
|
79
|
+
redaction: {
|
|
80
|
+
status: "passed",
|
|
81
|
+
notes: "Quoted participant text passed the loop's known-value scrub and pattern redaction before persisting, and redactText again here.",
|
|
82
|
+
},
|
|
83
|
+
idempotency_key: `humanish:${args.runId}:${participant.participantId}:participant-report`,
|
|
84
|
+
proposed_next_state: "study-quality-review",
|
|
85
|
+
acceptance_proof: [
|
|
86
|
+
feedbackProofCommands(args.runId).verify,
|
|
87
|
+
feedbackProofCommands(args.runId).observe,
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return candidates;
|
|
92
|
+
}
|
|
@@ -41,4 +41,15 @@ export declare function judgeParticipantSession(session: CuaLoopResult | undefin
|
|
|
41
41
|
selfReportedBlocker: boolean;
|
|
42
42
|
reportedFriction: boolean;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* caps.maxUsd is enforced inside each participant's loop independently, so N participants can
|
|
46
|
+
* spend up to N × maxUsd before any of them stops, while the run cost summary reports the larger
|
|
47
|
+
* aggregate. The warning names that ceiling, unless the study declared a shared maxTotalUsd budget.
|
|
48
|
+
* `capped` names what the cap counts: shared world says model spend, because the external-public
|
|
49
|
+
* plane's lobby-code reads are not counted against it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function participantCapWarning(caps: {
|
|
52
|
+
readonly maxUsd?: number;
|
|
53
|
+
readonly maxTotalUsd?: number;
|
|
54
|
+
}, participantCount: number, capped?: string): string | undefined;
|
|
44
55
|
export declare function makeCuaRunBudget(maxTotalUsd: number): CuaRunBudget;
|
|
@@ -257,6 +257,21 @@ export function judgeParticipantSession(session, deps, warnings) {
|
|
|
257
257
|
}
|
|
258
258
|
return { noEngagement, selfReportedBlocker, reportedFriction };
|
|
259
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* caps.maxUsd is enforced inside each participant's loop independently, so N participants can
|
|
262
|
+
* spend up to N × maxUsd before any of them stops, while the run cost summary reports the larger
|
|
263
|
+
* aggregate. The warning names that ceiling, unless the study declared a shared maxTotalUsd budget.
|
|
264
|
+
* `capped` names what the cap counts: shared world says model spend, because the external-public
|
|
265
|
+
* plane's lobby-code reads are not counted against it.
|
|
266
|
+
*/
|
|
267
|
+
export function participantCapWarning(caps, participantCount, capped = "each participant") {
|
|
268
|
+
const capUsd = caps.maxUsd;
|
|
269
|
+
if (capUsd === undefined || participantCount <= 1)
|
|
270
|
+
return undefined;
|
|
271
|
+
if (caps.maxTotalUsd !== undefined)
|
|
272
|
+
return undefined;
|
|
273
|
+
return `caps.maxUsd ($${capUsd}) caps ${capped}, so ${participantCount} participants may spend up to ${participantCount} × $${capUsd} (about $${round6(capUsd * participantCount)}) before any of them stops. Set caps.maxTotalUsd for one budget across the study.`;
|
|
274
|
+
}
|
|
260
275
|
export function makeCuaRunBudget(maxTotalUsd) {
|
|
261
276
|
const participantEstimates = new Map();
|
|
262
277
|
return {
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { pricedModel } from "../../study/plan-base.js";
|
|
2
|
-
import {
|
|
3
|
-
import { detectLocalAgents } from "../../actors/local-agent/cli.js";
|
|
4
|
-
import { localAgentRefusal } from "../../actors/local-agent/readiness.js";
|
|
5
|
-
import { describeMissingKeys } from "../../keys/key-resolution.js";
|
|
2
|
+
import { firstLiveRefusal, keysCheck, localAgentCheck, subjectEnvCheck, unpricedCapCheck, } from "../../study/requirements.js";
|
|
6
3
|
import { catchTokenOf, catchTokenRefusal } from "../../comms/external-evidence.js";
|
|
7
4
|
import { externalCatchHealthy } from "../../comms/sandbox-catch.js";
|
|
8
|
-
import { MODEL_RATES, unpricedCapMessage } from "../../run/pricing.js";
|
|
9
5
|
/** The computer-use code for each local-agent refusal; shared-world keeps the same kinds. */
|
|
10
6
|
const LOCAL_AGENT_REFUSAL_CODES = {
|
|
11
7
|
"agent-missing": "HUMANISH_COMPUTER_USE_AGENT_MISSING",
|
|
@@ -21,69 +17,41 @@ const LOCAL_AGENT_REFUSAL_CODES = {
|
|
|
21
17
|
*/
|
|
22
18
|
export async function liveCuaRejection(args) {
|
|
23
19
|
const { caps, brain, env, requirements, externalCommsConfig } = args;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// right there, say so here rather than making them go and find an API key; that detour is
|
|
31
|
-
// where most people trying humanish stop.
|
|
32
|
-
const suggestion = missing.includes("OPENAI_API_KEY")
|
|
33
|
-
? await (async () => {
|
|
34
|
-
const ready = (await detectLocalAgents({ env })).filter((agent) => agent.authStatus === "authenticated");
|
|
35
|
-
return ready.length === 0
|
|
36
|
-
? ""
|
|
37
|
-
: ` ${ready.map((agent) => agent.label).join(" and ")} reports authenticated on this machine` +
|
|
38
|
-
`. Set actor.type: local-agent to use ${ready.length === 1 ? "it" : "one"} instead of a key.`;
|
|
39
|
-
})()
|
|
40
|
-
: "";
|
|
41
|
-
return {
|
|
20
|
+
return firstLiveRefusal([
|
|
21
|
+
// The plan lists OPENAI_API_KEY only for an openai brain (a signed-in local agent or the
|
|
22
|
+
// caller's provider needs none) and E2B_API_KEY only when this run creates hosted desktops.
|
|
23
|
+
() => keysCheck({
|
|
24
|
+
requirements,
|
|
25
|
+
env,
|
|
42
26
|
code: "HUMANISH_COMPUTER_USE_KEYS_MISSING",
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const model = pricedModel(brain);
|
|
69
|
-
const capModelId = model.trim().toLowerCase();
|
|
70
|
-
if (!MODEL_RATES[capModelId]) {
|
|
71
|
-
return {
|
|
72
|
-
code: "HUMANISH_COMPUTER_USE_UNPRICED_CAP",
|
|
73
|
-
message: unpricedCapMessage(model),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// Adopter-hosted comms catch: fail closed before any sandbox is created, because a comms study
|
|
78
|
-
// whose catch is unreachable collects nothing while every participant still spends. The probe asserts
|
|
79
|
-
// humanish's own service marker in /health, so an adopter's proxy answering 200 for everything cannot
|
|
80
|
-
// pass for a catch.
|
|
81
|
-
const tokenRefusal = externalCommsConfig === undefined
|
|
82
|
-
? undefined
|
|
83
|
-
: catchTokenRefusal(catchTokenOf(externalCommsConfig, env));
|
|
27
|
+
need: (names) => `Live computer-use studies need ${names} in the environment (values are never persisted).`,
|
|
28
|
+
suggestLocalAgent: true,
|
|
29
|
+
}),
|
|
30
|
+
// A caller's createProvider makes the brain `caller`, so only the study's own local agent is
|
|
31
|
+
// checked.
|
|
32
|
+
() => localAgentCheck({ brain, env, caps, codes: LOCAL_AGENT_REFUSAL_CODES }),
|
|
33
|
+
() => subjectEnvCheck({ requirements, env, code: "HUMANISH_COMPUTER_USE_SUBJECT_ENV_MISSING" }),
|
|
34
|
+
() => unpricedCapCheck({
|
|
35
|
+
caps,
|
|
36
|
+
model: pricedModel(brain),
|
|
37
|
+
code: "HUMANISH_COMPUTER_USE_UNPRICED_CAP",
|
|
38
|
+
}),
|
|
39
|
+
() => externalCatchCheck(externalCommsConfig, env),
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Adopter-hosted comms catch: refused before any sandbox is created, because a comms study whose
|
|
44
|
+
* catch is unreachable collects nothing while every participant still spends. The probe asserts
|
|
45
|
+
* humanish's own service marker in /health, so an adopter's proxy answering 200 for everything
|
|
46
|
+
* cannot pass for a catch.
|
|
47
|
+
*/
|
|
48
|
+
async function externalCatchCheck(externalCommsConfig, env) {
|
|
49
|
+
if (externalCommsConfig === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
const tokenRefusal = catchTokenRefusal(catchTokenOf(externalCommsConfig, env));
|
|
84
52
|
if (tokenRefusal !== undefined)
|
|
85
53
|
return { code: "HUMANISH_COMPUTER_USE_COMMS_TOKEN_INVALID", message: tokenRefusal };
|
|
86
|
-
if (
|
|
54
|
+
if (!(await externalCatchHealthy(externalCommsConfig))) {
|
|
87
55
|
return {
|
|
88
56
|
code: "HUMANISH_COMPUTER_USE_COMMS_CATCH_UNREACHABLE",
|
|
89
57
|
message: "The external comms catch or inbox is unreachable or incompatible (GET /health must identify humanish-comms-catch and advertise recipient-inbox-v1). Update humanish on the catch host and restart it with `humanish comms catch` on that host, or drop comms.email to run without the inbox funnel.",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { adapterScoreFailureMessage, applyBrowserScorer } from "../../study/adapter-extension.js";
|
|
2
2
|
import { redactText } from "../../evidence/redaction.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { foldScorerFailures, judgeExecution, OUTCOME_POLICIES, resultOk, } from "../../run/judge.js";
|
|
4
|
+
import { validatePreparedRunArtifactPaths } from "../../run/paths.js";
|
|
5
|
+
import { participantExecutionFailures, participantOutcomeOk } from "./participant-facts.js";
|
|
6
|
+
import { participantCapWarning } from "./participant-model.js";
|
|
6
7
|
import { summarizeCuaDiagnostics } from "./diagnostics.js";
|
|
7
8
|
import { toParticipantResult } from "./participant-execution.js";
|
|
8
9
|
import { buildCuaRunBundle, judgeComputerUseRun } from "./bundle.js";
|
|
@@ -159,56 +160,10 @@ function cuaStudyResult(args) {
|
|
|
159
160
|
...(errorResult === undefined ? {} : { error: errorResult }),
|
|
160
161
|
};
|
|
161
162
|
}
|
|
162
|
-
/**
|
|
163
|
-
* The run's execution failures before the Observer renders: each participant whose session failed
|
|
164
|
-
* in the harness, each provider whose cleanup is unconfirmed or that reported a disallowed item
|
|
165
|
-
* after its last request, and each sandbox whose release is unconfirmed. FinishedRun.renderObserver
|
|
166
|
-
* adds an Observer that did not render.
|
|
167
|
-
*/
|
|
168
|
-
function computerUseExecutionFailures(outcomes, runId) {
|
|
169
|
-
return [
|
|
170
|
-
...(outcomes ?? [])
|
|
171
|
-
.filter((outcome) => participantHarnessFailed(participantFactsOf(outcome)))
|
|
172
|
-
.map((outcome) => ({
|
|
173
|
-
kind: "harness",
|
|
174
|
-
message: `${outcome.spec.planned.id}: ${outcome.sessionError ?? outcome.session?.reason ?? "harness error"}`,
|
|
175
|
-
})),
|
|
176
|
-
...(outcomes ?? []).flatMap((outcome) => outcome.providerCleanupError === undefined
|
|
177
|
-
? []
|
|
178
|
-
: [
|
|
179
|
-
{
|
|
180
|
-
kind: "provider-cleanup",
|
|
181
|
-
message: `${outcome.spec.planned.id}: ${outcome.providerCleanupError}`,
|
|
182
|
-
},
|
|
183
|
-
]),
|
|
184
|
-
...(outcomes ?? []).flatMap((outcome) => outcome.providerPolicyError === undefined
|
|
185
|
-
? []
|
|
186
|
-
: [
|
|
187
|
-
{
|
|
188
|
-
kind: "provider-policy",
|
|
189
|
-
message: `${outcome.spec.planned.id}: ${outcome.providerPolicyError}`,
|
|
190
|
-
},
|
|
191
|
-
]),
|
|
192
|
-
...unreleasedSandboxFailures(outcomes, runId),
|
|
193
|
-
];
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* caps.maxUsd is enforced inside each participant's loop independently, so an
|
|
197
|
-
* N-participant fan-out can spend up to N × maxUsd before any participant aborts, while the run cost summary reports the larger
|
|
198
|
-
* aggregate. The warning names that ceiling, unless the study declared a shared maxTotalUsd budget.
|
|
199
|
-
*/
|
|
200
|
-
function participantCapWarning(caps, participantCount) {
|
|
201
|
-
const capUsd = caps.maxUsd;
|
|
202
|
-
if (capUsd === undefined || participantCount <= 1)
|
|
203
|
-
return undefined;
|
|
204
|
-
if (caps.maxTotalUsd !== undefined)
|
|
205
|
-
return undefined;
|
|
206
|
-
return `caps.maxUsd ($${capUsd}) caps each participant, so ${participantCount} participants may spend up to ${participantCount} × $${capUsd} (about $${round6(capUsd * participantCount)}) before any of them stops. Set caps.maxTotalUsd for one budget across the study.`;
|
|
207
|
-
}
|
|
208
163
|
/** Builds and publishes the final bundle, runs the adapter hooks, renders the Observer and returns the result. */
|
|
209
164
|
export async function finishCuaRun(setup, finish, ran) {
|
|
210
165
|
const { plan, input, cwd, streams, descriptor, run } = setup;
|
|
211
|
-
const { participantRuns, participantPlan,
|
|
166
|
+
const { participantRuns, participantPlan, bundleBase } = setup;
|
|
212
167
|
const { rerunLineage, publicRepo, subjectArgs } = finish;
|
|
213
168
|
const { dryRun } = plan;
|
|
214
169
|
const appUrl = plannedAppUrl(plan.runner.subject);
|
|
@@ -252,7 +207,7 @@ export async function finishCuaRun(setup, finish, ran) {
|
|
|
252
207
|
dryRun,
|
|
253
208
|
participantCount,
|
|
254
209
|
},
|
|
255
|
-
sanitize: (text) => redactText(
|
|
210
|
+
sanitize: (text) => redactText(run.secrets.scrub(text)),
|
|
256
211
|
warnings: adapterWarnings,
|
|
257
212
|
...(input.scorerProvenance === undefined ? {} : { scorerProvenance: input.scorerProvenance }),
|
|
258
213
|
});
|
|
@@ -261,13 +216,19 @@ export async function finishCuaRun(setup, finish, ran) {
|
|
|
261
216
|
if (receiving)
|
|
262
217
|
bundle.commsReceiving = receiving.snapshot();
|
|
263
218
|
const policy = OUTCOME_POLICIES["computer-use"];
|
|
264
|
-
|
|
219
|
+
// FinishedRun.renderObserver adds an Observer that did not render.
|
|
220
|
+
const execution = judgeExecution(participantExecutionFailures(outcomes, runId), policy);
|
|
265
221
|
const finished = await run.finish(bundle, {
|
|
266
222
|
ok: resultOk({ judgment, execution, scorerFailures: scorerResult.failures, policy }),
|
|
267
223
|
execution,
|
|
268
224
|
policy,
|
|
269
225
|
});
|
|
270
226
|
const observer = await finished.renderObserver();
|
|
227
|
+
// A caller's renderer runs with this process's file access, so after one that reports success
|
|
228
|
+
// the run directory is checked again before the result points anyone at it. A failed render is
|
|
229
|
+
// already an evidence failure, and its error code stays the run's diagnosis.
|
|
230
|
+
if (observer.ok)
|
|
231
|
+
await validatePreparedRunArtifactPaths(finished.paths);
|
|
271
232
|
streams.attachFinal(observer);
|
|
272
233
|
return cuaStudyResult({
|
|
273
234
|
studyId: plan.studyId,
|
|
@@ -31,7 +31,7 @@ import { finishCuaRun } from "./result.js";
|
|
|
31
31
|
import { runStudyParticipants } from "./live-phase.js";
|
|
32
32
|
import { admitCuaRun, refuseCuaStudy, startCuaRun } from "./setup.js";
|
|
33
33
|
import {} from "./types.js";
|
|
34
|
-
import {
|
|
34
|
+
import { refusedResult } from "../../run/study-result.js";
|
|
35
35
|
/**
|
|
36
36
|
* A refused computer-use study's result, at the refusal's stage: a before-scope refusal has its own
|
|
37
37
|
* envelope and no analysis record; the others come after the cwd checks, and the participant cap after
|
|
@@ -40,18 +40,17 @@ import { studyResultIdentity } from "../../run/study-result.js";
|
|
|
40
40
|
export async function computerUseStudyRefusal(options, refusal) {
|
|
41
41
|
const { config, dryRun } = options;
|
|
42
42
|
if (refusal.stage === "before-scope")
|
|
43
|
-
return {
|
|
44
|
-
|
|
45
|
-
ok: false,
|
|
43
|
+
return refusedResult("computer-use", {
|
|
44
|
+
studyId: config.id,
|
|
46
45
|
cwd: path.resolve(options.cwd),
|
|
46
|
+
error: { code: refusal.code, message: refusal.message },
|
|
47
|
+
}, {
|
|
47
48
|
actor: config.actor?.type ?? "",
|
|
48
49
|
dryRun,
|
|
49
50
|
runId: options.runId ?? "not-created",
|
|
50
51
|
appUrl: config.subject.appUrl ?? config.subject.serve?.url ?? "",
|
|
51
52
|
lanes: [],
|
|
52
|
-
|
|
53
|
-
error: { code: refusal.code, message: refusal.message },
|
|
54
|
-
};
|
|
53
|
+
});
|
|
55
54
|
// The other refusals come after the cwd checks, and the participant cap after the personas are read.
|
|
56
55
|
return completeRefusalAnalysis(await refuseCuaStudy(options, refusal), config, options);
|
|
57
56
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RunScope } from "../../run/run.js";
|
|
2
2
|
import type { RefusedStudy } from "../../run/route-shell.js";
|
|
3
|
+
import { RunSecrets } from "../../run/secrets.js";
|
|
3
4
|
import type { CuaActorDescriptor } from "../../actors/registry.js";
|
|
4
5
|
import type { StudyCommsEmail, StudyCommsExternal, StudyConfig } from "../../study/types.js";
|
|
5
6
|
import { type RunRerunLineage } from "../../run/bundle.js";
|
|
@@ -32,15 +33,11 @@ export interface CuaRunSetup {
|
|
|
32
33
|
streams: ReturnType<typeof trackRuntimeStreams>;
|
|
33
34
|
participantRuns: DesktopParticipantRun[];
|
|
34
35
|
participantPlan: CuaParticipantPlan;
|
|
35
|
-
scrubKnownValues: (text: string) => string;
|
|
36
36
|
bundleBase: CuaRunBundleBase;
|
|
37
37
|
}
|
|
38
38
|
/** What only runStudyParticipants reads. */
|
|
39
39
|
export interface CuaParticipantsSetup {
|
|
40
40
|
env: Record<string, string | undefined>;
|
|
41
|
-
/** The array scrubKnownValues reads at each call. Email receiving appends the values it
|
|
42
|
-
* provisions before the participants run. */
|
|
43
|
-
knownSecretValues: string[];
|
|
44
41
|
deps: Omit<CuaParticipantDeps, "signalProvisioned">;
|
|
45
42
|
/** Filled by runStudyParticipants on a live run; deps.onTrace reads it. */
|
|
46
43
|
liveTrace: {
|
|
@@ -116,8 +113,7 @@ export declare function admitCuaRun(plan: ComputerUsePlan, input: ComputerUseRun
|
|
|
116
113
|
participantCount: number;
|
|
117
114
|
openaiApiKey: string;
|
|
118
115
|
e2bApiKey: string;
|
|
119
|
-
|
|
120
|
-
scrubKnownValues: (text: string) => string;
|
|
116
|
+
secrets: RunSecrets;
|
|
121
117
|
publicRepo: string | undefined;
|
|
122
118
|
localTreeArchive: LocalTreeArchive | undefined;
|
|
123
119
|
localTreeArchiveBuffer: ArrayBuffer | undefined;
|