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.
Files changed (77) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/dist/cli/commands/study-route-computer-use.js +3 -8
  4. package/dist/cli/commands/study-route-preview.js +4 -6
  5. package/dist/cli/commands/study-route-shared-world.js +6 -7
  6. package/dist/evidence/redaction.d.ts +2 -2
  7. package/dist/evidence/redaction.js +3 -3
  8. package/dist/routes/computer-use/bundle-parts.d.ts +1 -32
  9. package/dist/routes/computer-use/bundle-parts.js +5 -92
  10. package/dist/routes/computer-use/e2b-desktop/prepare.js +11 -4
  11. package/dist/routes/computer-use/fanout-bundle.js +3 -2
  12. package/dist/routes/computer-use/live-phase.js +3 -7
  13. package/dist/routes/computer-use/participant-deps.d.ts +46 -0
  14. package/dist/routes/computer-use/participant-deps.js +48 -0
  15. package/dist/routes/computer-use/participant-facts.d.ts +5 -4
  16. package/dist/routes/computer-use/participant-facts.js +37 -2
  17. package/dist/routes/computer-use/participant-feedback.d.ts +33 -0
  18. package/dist/routes/computer-use/participant-feedback.js +92 -0
  19. package/dist/routes/computer-use/participant-model.d.ts +11 -0
  20. package/dist/routes/computer-use/participant-model.js +15 -0
  21. package/dist/routes/computer-use/preflight.js +33 -65
  22. package/dist/routes/computer-use/result.js +13 -52
  23. package/dist/routes/computer-use/route.js +6 -7
  24. package/dist/routes/computer-use/setup.d.ts +2 -6
  25. package/dist/routes/computer-use/setup.js +47 -93
  26. package/dist/routes/computer-use/single-bundle.js +3 -2
  27. package/dist/routes/computer-use/types.d.ts +2 -2
  28. package/dist/routes/preview.js +2 -8
  29. package/dist/routes/scripted/bundle.d.ts +2 -6
  30. package/dist/routes/scripted/result.d.ts +2 -1
  31. package/dist/routes/scripted/result.js +4 -4
  32. package/dist/routes/scripted/route.js +13 -38
  33. package/dist/routes/scripted/setup.d.ts +2 -1
  34. package/dist/routes/scripted/setup.js +21 -28
  35. package/dist/routes/scripted/subject.d.ts +3 -18
  36. package/dist/routes/scripted/subject.js +22 -78
  37. package/dist/routes/scripted/types.d.ts +2 -2
  38. package/dist/routes/shared-world/bundle.js +36 -1
  39. package/dist/routes/shared-world/comms.d.ts +2 -1
  40. package/dist/routes/shared-world/comms.js +3 -7
  41. package/dist/routes/shared-world/handoff.d.ts +2 -2
  42. package/dist/routes/shared-world/handoff.js +1 -1
  43. package/dist/routes/shared-world/participant-specs.d.ts +4 -5
  44. package/dist/routes/shared-world/participant-specs.js +19 -25
  45. package/dist/routes/shared-world/plan.js +11 -7
  46. package/dist/routes/shared-world/provisioned.d.ts +2 -1
  47. package/dist/routes/shared-world/provisioned.js +37 -80
  48. package/dist/routes/shared-world/result.js +17 -35
  49. package/dist/routes/shared-world/route.js +33 -36
  50. package/dist/routes/shared-world/setup.d.ts +2 -3
  51. package/dist/routes/shared-world/setup.js +11 -29
  52. package/dist/routes/shared-world/types.d.ts +6 -15
  53. package/dist/routes/terminal/dry-run.js +14 -24
  54. package/dist/routes/terminal/live-finish.d.ts +0 -1
  55. package/dist/routes/terminal/live-finish.js +2 -2
  56. package/dist/routes/terminal/route.js +9 -15
  57. package/dist/routes/terminal/session.d.ts +2 -3
  58. package/dist/routes/terminal/session.js +28 -40
  59. package/dist/routes/terminal/trace.d.ts +1 -1
  60. package/dist/routes/terminal/types.d.ts +2 -2
  61. package/dist/run/run.d.ts +55 -1
  62. package/dist/run/run.js +38 -2
  63. package/dist/run/secrets.d.ts +22 -0
  64. package/dist/run/secrets.js +33 -0
  65. package/dist/run/study-result.d.ts +34 -0
  66. package/dist/run/study-result.js +16 -0
  67. package/dist/study/requirements.d.ts +60 -5
  68. package/dist/study/requirements.js +88 -2
  69. package/dist/study/run-study-events.d.ts +4 -3
  70. package/dist/study/run-study-events.js +9 -14
  71. package/dist/study/run-study-options.d.ts +4 -1
  72. package/dist/study/run-study-options.js +22 -32
  73. package/dist/substrates/e2b/subject-sandbox.d.ts +58 -0
  74. package/dist/substrates/e2b/subject-sandbox.js +94 -0
  75. package/docs/contracts/core.md +5 -4
  76. package/docs/contracts/feedback.md +12 -5
  77. 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 { missingKeys, missingSubjectEnv } from "../../study/requirements.js";
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
- // The plan lists OPENAI_API_KEY only for an openai brain (a signed-in local agent or the
25
- // caller's provider needs none) and E2B_API_KEY only when this run creates hosted desktops.
26
- const localAgent = brain.kind === "local-agent" ? brain.agent : undefined;
27
- const missing = missingKeys(requirements, env);
28
- if (missing.length > 0) {
29
- // The moment someone new actually hits the wall. If a signed-in coding agent is sitting
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
- message: `Live computer-use studies need ${missing.join(" and ")} in the environment (values are never persisted). ${describeMissingKeys(missing, env)}${suggestion}`,
44
- };
45
- }
46
- // A caller's createProvider makes the brain `caller`, so only the study's own local agent is checked.
47
- if (localAgent) {
48
- // Refuse here, before a sandbox exists. "codex is not installed" discovered after the
49
- // machine is paid for is the same information delivered at the worst possible moment.
50
- const refusal = await localAgentRefusal({ agent: localAgent, env, caps });
51
- if (refusal)
52
- return { code: LOCAL_AGENT_REFUSAL_CODES[refusal.kind], message: refusal.message };
53
- }
54
- const unsetSubjectEnv = missingSubjectEnv(requirements, env);
55
- if (unsetSubjectEnv.length > 0) {
56
- return {
57
- code: "HUMANISH_COMPUTER_USE_SUBJECT_ENV_MISSING",
58
- message: `subject.env declares ${unsetSubjectEnv.join(", ")} but the environment does not provide ${unsetSubjectEnv.length === 1 ? "it" : "them"} (pass via --dotenv; values are never persisted).`,
59
- };
60
- }
61
- // Fail-closed cap: a maxUsd cap needs a measurable per-turn estimate.
62
- // If the operator set caps.maxUsd but src/run/pricing.ts has no rate for the resolved
63
- // model, the loop could not enforce the cap, and silently running uncapped would break the
64
- // runaway-retry protection. Refuse at preflight (before any sandbox/spend) rather than run
65
- // uncapped: an unenforceable cap is more dangerous than none. The operator picks a priced model
66
- // or removes the cap; a source checkout can also add a rate to src/run/pricing.ts.
67
- if (caps.maxUsd !== undefined || caps.maxTotalUsd !== undefined) {
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 (externalCommsConfig && !(await externalCatchHealthy(externalCommsConfig))) {
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 { round6 } from "../../run/pricing.js";
4
- import { foldScorerFailures, judgeExecution, OUTCOME_POLICIES, participantHarnessFailed, resultOk, } from "../../run/judge.js";
5
- import { participantFactsOf, participantOutcomeOk, unreleasedSandboxFailures, } from "./participant-facts.js";
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, scrubKnownValues, bundleBase } = setup;
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(scrubKnownValues(text)),
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
- const execution = judgeExecution(computerUseExecutionFailures(outcomes, runId), policy);
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 { studyResultIdentity } from "../../run/study-result.js";
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
- ...studyResultIdentity("computer-use", config.id),
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
- warnings: [],
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
- knownSecretValues: string[];
120
- scrubKnownValues: (text: string) => string;
116
+ secrets: RunSecrets;
121
117
  publicRepo: string | undefined;
122
118
  localTreeArchive: LocalTreeArchive | undefined;
123
119
  localTreeArchiveBuffer: ArrayBuffer | undefined;