humanish 0.111.3 → 0.112.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 +33 -0
- package/dist/actors/computer-use/openai-conversation.d.ts +91 -0
- package/dist/actors/computer-use/openai-conversation.js +209 -0
- package/dist/actors/computer-use/openai-provider.js +25 -41
- package/dist/actors/contract.d.ts +2 -0
- package/dist/actors/scripted-browser/actor.d.ts +5 -0
- package/dist/actors/scripted-browser/actor.js +40 -19
- package/dist/actors/scripted-browser/journey.d.ts +2 -0
- package/dist/actors/scripted-browser/journey.js +2 -1
- package/dist/actors/scripted-browser/steps.d.ts +2 -0
- package/dist/actors/scripted-browser/steps.js +17 -0
- package/dist/analysis/diagnostics.js +7 -12
- package/dist/analysis/execute.d.ts +1 -1
- package/dist/analysis/execute.js +35 -5
- package/dist/analysis/service.js +7 -1
- package/dist/analysis/types.d.ts +23 -0
- package/dist/analysis/validation.d.ts +80 -35
- package/dist/analysis/validation.js +58 -11
- package/dist/cli/commands/runs.js +4 -10
- package/dist/cli/findings.d.ts +49 -4
- package/dist/cli/findings.js +179 -23
- package/dist/cli/io.d.ts +2 -1
- package/dist/cli/io.js +3 -2
- package/dist/evidence/secret-scrub.d.ts +7 -0
- package/dist/evidence/secret-scrub.js +21 -12
- package/dist/feedback/draft.js +14 -4
- package/dist/observer/run-routes.js +8 -4
- package/dist/observer-app.html +4 -4
- package/dist/routes/computer-use/bundle.d.ts +1 -1
- package/dist/routes/computer-use/bundle.js +6 -16
- package/dist/routes/computer-use/fanout-bundle.js +40 -37
- package/dist/routes/computer-use/fanout-records.d.ts +2 -0
- package/dist/routes/computer-use/fanout-records.js +18 -26
- package/dist/routes/computer-use/participant-execution.js +4 -11
- package/dist/routes/computer-use/participant-facts.d.ts +2 -2
- package/dist/routes/computer-use/participant-facts.js +10 -1
- package/dist/routes/computer-use/result.js +7 -15
- package/dist/routes/computer-use/single-bundle.d.ts +2 -0
- package/dist/routes/computer-use/single-bundle.js +81 -37
- package/dist/routes/scripted/bundle.d.ts +1 -1
- package/dist/routes/scripted/bundle.js +9 -19
- package/dist/routes/scripted/route.js +1 -0
- package/dist/routes/scripted/setup.js +53 -10
- package/dist/routes/scripted/surface-records.d.ts +0 -1
- package/dist/routes/scripted/surface-records.js +27 -13
- package/dist/routes/scripted/surface-sessions.d.ts +1 -1
- package/dist/routes/scripted/surface-sessions.js +2 -0
- package/dist/routes/shared-world/bundle-records.d.ts +2 -2
- package/dist/routes/shared-world/bundle-records.js +21 -28
- package/dist/routes/shared-world/bundle.d.ts +1 -1
- package/dist/routes/shared-world/bundle.js +70 -78
- package/dist/routes/shared-world/participant-specs.d.ts +1 -2
- package/dist/routes/shared-world/participant-specs.js +0 -8
- package/dist/routes/shared-world/result.js +8 -12
- package/dist/routes/terminal/bundle.d.ts +1 -1
- package/dist/routes/terminal/bundle.js +10 -19
- package/dist/routes/terminal/credentials.js +14 -1
- package/dist/routes/terminal/dry-run.js +1 -0
- package/dist/routes/terminal/live-finish.js +7 -4
- package/dist/routes/terminal/live-sandbox.js +9 -2
- package/dist/routes/terminal/plan.js +1 -0
- package/dist/routes/terminal/runtime.d.ts +1 -0
- package/dist/routes/terminal/runtime.js +1 -0
- package/dist/routes/terminal/session.js +2 -1
- package/dist/routes/terminal/trace.d.ts +4 -2
- package/dist/routes/terminal/trace.js +30 -35
- package/dist/run/costs.js +9 -20
- package/dist/run/detail.d.ts +1 -1
- package/dist/run/detail.js +10 -1
- package/dist/run/display.d.ts +2 -2
- package/dist/run/display.js +15 -18
- package/dist/run/dry-run.js +10 -25
- package/dist/run/judge.d.ts +24 -0
- package/dist/run/judge.js +54 -0
- package/dist/run/liveness.d.ts +18 -0
- package/dist/run/liveness.js +24 -0
- package/dist/run/outcomes.d.ts +8 -1
- package/dist/run/outcomes.js +58 -12
- package/dist/run/participant-caption.d.ts +30 -0
- package/dist/run/participant-caption.js +50 -0
- package/dist/run/participant-records.d.ts +1 -1
- package/dist/run/review-markdown.d.ts +11 -0
- package/dist/run/review-markdown.js +22 -0
- package/dist/run/run-cost.d.ts +7 -0
- package/dist/run/run-cost.js +25 -5
- package/dist/run/run-index.d.ts +4 -50
- package/dist/run/run-index.js +57 -87
- package/dist/run/run.d.ts +2 -2
- package/dist/run/run.js +5 -3
- package/dist/run/secrets.d.ts +15 -2
- package/dist/run/secrets.js +134 -9
- package/dist/run/stats.js +2 -12
- package/dist/run/transient-comms-secrets.d.ts +8 -3
- package/dist/run/transient-comms-secrets.js +21 -1
- package/dist/study/keys.d.ts +1 -0
- package/dist/study/keys.js +1 -1
- package/dist/study/parse/execution.js +7 -1
- package/dist/study/plan-types.d.ts +1 -0
- package/dist/study/requirements.js +2 -1
- package/dist/study/types.d.ts +4 -1
- package/dist/study/warnings.d.ts +2 -1
- package/dist/study/warnings.js +3 -2
- package/dist/tui-app.js +73 -73
- package/dist/verify/liveness.d.ts +2 -2
- package/dist/verify/liveness.js +7 -4
- package/docs/architecture/terminal-product-route.md +14 -0
- package/docs/contracts/run-bundle.md +10 -0
- package/docs/contracts/schemas.md +9 -1
- package/docs/contracts/study-analysis.md +76 -23
- package/package.json +2 -1
- package/dist/actors/computer-use/openai-requests.d.ts +0 -62
- package/dist/actors/computer-use/openai-requests.js +0 -144
|
@@ -45,5 +45,5 @@ interface CuaRunBundleState {
|
|
|
45
45
|
*/
|
|
46
46
|
export declare function buildCuaRunBundle(base: CuaRunBundleBase, state: CuaRunBundleState): RunBundle;
|
|
47
47
|
/** The run's review.md: title, run, mode, gate, summary, subject, actor evidence and gaps. */
|
|
48
|
-
export declare function renderCuaReviewMarkdown(bundle: RunBundle): string;
|
|
48
|
+
export declare function renderCuaReviewMarkdown(bundle: RunBundle, status?: unknown): string;
|
|
49
49
|
export {};
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// and buildCuaRunBundle sends it to the single-participant or the fan-out builder with that
|
|
3
3
|
// shape's arguments. renderCuaReviewMarkdown writes review.md. shared-world/bundle.ts holds the
|
|
4
4
|
// same for that route.
|
|
5
|
-
import { judgeOneParticipant, judgeParticipants
|
|
6
|
-
import {
|
|
5
|
+
import { judgeOneParticipant, judgeParticipants } from "../../run/judge.js";
|
|
6
|
+
import { renderReviewMarkdown } from "../../run/review-markdown.js";
|
|
7
7
|
import { desktopSpanToMinutes } from "../../run/cost-summary.js";
|
|
8
8
|
import { e2bDesktopTemplate } from "../../substrates/e2b/sandbox.js";
|
|
9
9
|
import { providerResourcesForOutcome } from "./bundle-parts.js";
|
|
@@ -92,6 +92,7 @@ function singleParticipantArgs(base, state) {
|
|
|
92
92
|
...(spec.evidenceAssignment === undefined ? {} : { assignment: spec.evidenceAssignment }),
|
|
93
93
|
persona: spec.persona,
|
|
94
94
|
resolution: spec.planned.device.resolution,
|
|
95
|
+
deviceName: spec.planned.device.name,
|
|
95
96
|
desktopRoute: base.plan.runner.desktop !== "in-process",
|
|
96
97
|
substrate: base.plan.runner.desktop === "in-process"
|
|
97
98
|
? "local-filesystem"
|
|
@@ -151,17 +152,10 @@ function singleParticipantArgs(base, state) {
|
|
|
151
152
|
};
|
|
152
153
|
}
|
|
153
154
|
/** The run's review.md: title, run, mode, gate, summary, subject, actor evidence and gaps. */
|
|
154
|
-
export function renderCuaReviewMarkdown(bundle) {
|
|
155
|
+
export function renderCuaReviewMarkdown(bundle, status) {
|
|
155
156
|
const trace = bundle.streams[0]?.actor;
|
|
156
157
|
const provenance = bundle.events.find((event) => event.type === "cua-lab.subject.provenance");
|
|
157
|
-
return [
|
|
158
|
-
`# ${bundle.scenario.title}`,
|
|
159
|
-
"",
|
|
160
|
-
`- run: ${bundle.runId}`,
|
|
161
|
-
`- mode: ${bundle.mode}`,
|
|
162
|
-
`- verdict: ${verdictText(bundle.review.verdict, bundle.mode)}`,
|
|
163
|
-
`- outcome: ${reviewOutcome(bundle)}`,
|
|
164
|
-
`- summary: ${bundle.review.summary}`,
|
|
158
|
+
return renderReviewMarkdown(bundle, [
|
|
165
159
|
...(provenance ? [`- subject: ${provenance.message}`] : []),
|
|
166
160
|
...(trace
|
|
167
161
|
? [
|
|
@@ -173,9 +167,5 @@ export function renderCuaReviewMarkdown(bundle) {
|
|
|
173
167
|
: "screenshot")}`,
|
|
174
168
|
]
|
|
175
169
|
: []),
|
|
176
|
-
|
|
177
|
-
? ["", "## Gaps", ...bundle.review.gaps.map((gap) => `- ${gap}`)]
|
|
178
|
-
: []),
|
|
179
|
-
"",
|
|
180
|
-
].join("\n");
|
|
170
|
+
], { status });
|
|
181
171
|
}
|
|
@@ -3,11 +3,11 @@ import { receivingPublication } from "../../comms/receiving-runtime.js";
|
|
|
3
3
|
import { actorEnding } from "../../actors/stop-cause.js";
|
|
4
4
|
import { redactText } from "../../evidence/redaction.js";
|
|
5
5
|
import { REVIEW_SCHEMA, bundleArtifacts, bundleHead, } from "../../run/bundle.js";
|
|
6
|
-
import { aggregateTaskFunnels, formatParticipantOutcomes, formatRunTaskFunnel,
|
|
6
|
+
import { aggregateTaskFunnels, formatParticipantOutcomes, formatRunTaskFunnel, gapsListClause, withCuaReviewProvenance, } from "../../run/outcomes.js";
|
|
7
7
|
import { providerResourcesForOutcome, publicSafeAppUrlLabel } from "./bundle-parts.js";
|
|
8
8
|
import { participantFeedbackCandidates } from "./participant-feedback.js";
|
|
9
9
|
import { participantFactsOf } from "./participant-facts.js";
|
|
10
|
-
import {
|
|
10
|
+
import { judgeParticipantRecords } from "../../run/judge.js";
|
|
11
11
|
import { buildRunCostSummary, desktopSpanToMinutes } from "../../run/cost-summary.js";
|
|
12
12
|
import { formatParticipantPlanEntry } from "./participant-runs.js";
|
|
13
13
|
import { fanoutParticipantRecords } from "./fanout-records.js";
|
|
@@ -32,29 +32,32 @@ function fanoutPlanEvents(args) {
|
|
|
32
32
|
});
|
|
33
33
|
return events;
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
/**
|
|
36
|
+
* The run's summary in plain sentences: who took part, what happened to them with the denominator
|
|
37
|
+
* attached, and that the gaps list the participants who did not pass.
|
|
38
|
+
*/
|
|
39
|
+
function fanoutSummary(args, facts) {
|
|
40
|
+
const count = args.specs.length;
|
|
41
|
+
const people = plural(count, "participant");
|
|
42
|
+
const rerun = args.rerun === undefined ? "" : `Rerun from ${args.rerun.sourceRunId}: `;
|
|
43
|
+
if (args.inProgress === true) {
|
|
44
|
+
return `${people} ${count === 1 ? "is" : "are"} each using their own copy of the app. The run is still going, so nothing here is final.`;
|
|
45
|
+
}
|
|
46
|
+
if (args.dryRun) {
|
|
47
|
+
return `${rerun}Dry run: ${people} would each use their own copy of ${publicSafeAppUrlLabel(args.appUrl)}. No desktops were launched and $0 was spent.`;
|
|
48
|
+
}
|
|
49
|
+
const tasks = facts.tasks === undefined ? "" : ` Tasks: ${facts.tasks}.`;
|
|
50
|
+
return `${rerun}${people} took part, each in their own copy of the app: ${facts.outcomes}${gapsListClause(count - facts.passed, count)}.${tasks}`;
|
|
51
|
+
}
|
|
52
|
+
function fanoutReview(args, streams, judgment) {
|
|
53
|
+
const { outcomes } = args;
|
|
37
54
|
// The judge's verdict: live fan-out must prove every participant (judgeParticipants).
|
|
38
55
|
const verdict = args.verdict;
|
|
39
|
-
const passedParticipants =
|
|
56
|
+
const passedParticipants = judgment.participants.filter((participant) => participant.judgedStatus === "passed").length;
|
|
40
57
|
// What happened to the participants, with the denominator attached. The verdict above has to
|
|
41
58
|
// collapse the run to one word; this does not (docs/principles/three-roles.md).
|
|
42
59
|
const terminalOutcomes = (outcomes ?? []).filter((outcome) => outcome?.session?.status !== undefined);
|
|
43
|
-
const participants =
|
|
44
|
-
? tallyParticipantOutcomes(
|
|
45
|
-
// A no-engagement participant is not one who reached the goal. It said "done" having
|
|
46
|
-
// taken zero actions and said nothing, and `passedParticipants` above already refuses to
|
|
47
|
-
// count it, but `reachedGoal` was reading the trace status directly, so one run could be both
|
|
48
|
-
// "not a passed participant" and "1/1 reached the goal". The headline number a researcher reads
|
|
49
|
-
// first was the dishonest one. Found by a provider bug that ended a study on turn one.
|
|
50
|
-
terminalOutcomes.map((outcome) => participantStatus(outcome.session.status, {
|
|
51
|
-
noEngagement: outcome.noEngagement === true,
|
|
52
|
-
selfReportedBlocker: outcome.selfReportedBlocker === true,
|
|
53
|
-
})),
|
|
54
|
-
// A participant who reached the goal and told you the road there was broken is the most
|
|
55
|
-
// useful result a study produces; reporting only the outcome would bury it.
|
|
56
|
-
terminalOutcomes.map((outcome) => outcome.reportedFriction === true))
|
|
57
|
-
: undefined;
|
|
60
|
+
const participants = judgment.tally.total > 0 ? judgment.tally : undefined;
|
|
58
61
|
// The study funnel: per-task completion rates across every session that measured one. This is
|
|
59
62
|
// "where did people get stuck" as data, next to who got stuck (participants) above.
|
|
60
63
|
const participantFunnels = (outcomes ?? [])
|
|
@@ -75,25 +78,18 @@ function fanoutReview(args, streams) {
|
|
|
75
78
|
...(runTasks === undefined ? {} : { tasks: runTasks }),
|
|
76
79
|
// The app URL is named as the participant records name it: one on an E2B host names a
|
|
77
80
|
// sandbox, so the summary carries its digest, which verify accepts in a shared run.
|
|
78
|
-
summary: args
|
|
79
|
-
|
|
80
|
-
:
|
|
81
|
-
?
|
|
82
|
-
:
|
|
81
|
+
summary: fanoutSummary(args, {
|
|
82
|
+
passed: passedParticipants,
|
|
83
|
+
outcomes: participants === undefined
|
|
84
|
+
? `0/${args.specs.length} finished a session`
|
|
85
|
+
: formatParticipantOutcomes(participants, participantEndings),
|
|
86
|
+
...(runTasks === undefined ? {} : { tasks: formatRunTaskFunnel(runTasks) }),
|
|
87
|
+
}),
|
|
83
88
|
gaps: args.inProgress === true
|
|
84
89
|
? ["Live fan-out session is still running."]
|
|
85
90
|
: args.dryRun
|
|
86
91
|
? ["Live fan-out session not yet run (dry run only)."]
|
|
87
|
-
:
|
|
88
|
-
.map((spec, index) => ({ spec, outcome: outcomes?.[index] }))
|
|
89
|
-
.filter(({ outcome }) => outcome === undefined ||
|
|
90
|
-
outcome.skippedReason !== undefined ||
|
|
91
|
-
outcome.sessionError !== undefined ||
|
|
92
|
-
outcome.noEngagement ||
|
|
93
|
-
outcome.selfReportedBlocker ||
|
|
94
|
-
outcome.session === undefined ||
|
|
95
|
-
outcome.session.status !== "passed")
|
|
96
|
-
.map(({ spec, outcome }) => `${spec.planned.id}: ${outcome?.skippedReason ?? outcome?.sessionError ?? outcome?.session?.reason ?? "did not pass"}`),
|
|
92
|
+
: judgment.participants.flatMap((participant) => participant.gapLine === undefined ? [] : [participant.gapLine]),
|
|
97
93
|
}, streams);
|
|
98
94
|
return review;
|
|
99
95
|
}
|
|
@@ -175,6 +171,13 @@ export function buildCuaFanoutBundle(args) {
|
|
|
175
171
|
const simulations = [];
|
|
176
172
|
const streams = [];
|
|
177
173
|
const events = fanoutPlanEvents(args);
|
|
174
|
+
const judgment = judgeParticipantRecords(specs.map((spec, index) => ({
|
|
175
|
+
...participantFactsOf(outcomes?.[index]),
|
|
176
|
+
id: spec.planned.id,
|
|
177
|
+
inProgress: args.inProgress === true && outcomes?.[index] === undefined,
|
|
178
|
+
})), {
|
|
179
|
+
runningReason: "Live computer-use participant is running; stream auth URL is available only through the attached Observer server.",
|
|
180
|
+
});
|
|
178
181
|
let eventSeq = 2;
|
|
179
182
|
const nextEventId = (suffix) => `event-${String(eventSeq++).padStart(3, "0")}-${suffix}`;
|
|
180
183
|
if (args.rerun) {
|
|
@@ -187,7 +190,7 @@ export function buildCuaFanoutBundle(args) {
|
|
|
187
190
|
});
|
|
188
191
|
}
|
|
189
192
|
specs.forEach((spec, index) => {
|
|
190
|
-
const records = fanoutParticipantRecords({ args, nextEventId }, spec, index);
|
|
193
|
+
const records = fanoutParticipantRecords({ args, nextEventId, participants: judgment.participants }, spec, index);
|
|
191
194
|
simulations.push(records.simulation);
|
|
192
195
|
streams.push(records.stream);
|
|
193
196
|
events.push(...records.events);
|
|
@@ -201,7 +204,7 @@ export function buildCuaFanoutBundle(args) {
|
|
|
201
204
|
message: `Fan-out fail-fast: ${args.failFastReason}. In-flight participants finished; queued participants were skipped (blocked); completed evidence is retained.`,
|
|
202
205
|
});
|
|
203
206
|
}
|
|
204
|
-
const review = fanoutReview(args, streams);
|
|
207
|
+
const review = fanoutReview(args, streams, judgment);
|
|
205
208
|
const anyRaw = (outcomes ?? []).some((outcome) => outcome.session?.trace.redaction.screenshots === "raw");
|
|
206
209
|
const ranLive = (outcomes ?? []).some((outcome) => outcome.session !== undefined || outcome.sessionError !== undefined);
|
|
207
210
|
const desktopTemplate = e2bDesktopTemplate(plan.residual);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { RunEvent, RunSimulation } from "../../run/bundle.js";
|
|
2
2
|
import type { RunStream } from "../../run/streams.js";
|
|
3
|
+
import type { judgeParticipantRecords } from "../../run/judge.js";
|
|
3
4
|
import type { CuaFanoutBundleArgs, DesktopParticipantRun } from "./types.js";
|
|
4
5
|
/** What every participant's records share. */
|
|
5
6
|
export interface FanoutParticipantContext {
|
|
6
7
|
args: CuaFanoutBundleArgs;
|
|
8
|
+
participants: ReturnType<typeof judgeParticipantRecords>["participants"];
|
|
7
9
|
/** Numbers events in the order they are pushed. */
|
|
8
10
|
nextEventId: (suffix: string) => string;
|
|
9
11
|
}
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// subject provenance event and the events of its outcome (session, geometry warnings, phase trail).
|
|
3
3
|
import { participantAssignment } from "../../study/participant-assignment.js";
|
|
4
4
|
import { participantEvent, participantRecord, participantStream, } from "../../run/participant-records.js";
|
|
5
|
+
import { participantCaption } from "../../run/participant-caption.js";
|
|
6
|
+
import { sessionEndingInWords } from "../../run/outcomes.js";
|
|
5
7
|
import { declaredScreenForRender } from "../../substrates/e2b/desktop-geometry.js";
|
|
6
8
|
import { describeSubjectState, phaseEventIdSuffix, publicSafeAppUrlLabel } from "./bundle-parts.js";
|
|
7
|
-
import { judgedStatus } from "../../run/judge.js";
|
|
8
|
-
import { participantFactsOf } from "./participant-facts.js";
|
|
9
9
|
import { participantSubjectEnv } from "./types.js";
|
|
10
10
|
const describeEnvNames = (names) => names.length > 0 ? names.join(", ") : "none";
|
|
11
|
-
function fanoutParticipantView(args, spec, index) {
|
|
11
|
+
function fanoutParticipantView(args, spec, index, participant) {
|
|
12
12
|
const { outcomes, plan } = args;
|
|
13
13
|
const outcome = outcomes?.[index];
|
|
14
14
|
const targetUrl = spec.planned.targetUrl ?? args.appUrl;
|
|
@@ -27,21 +27,7 @@ function fanoutParticipantView(args, spec, index) {
|
|
|
27
27
|
};
|
|
28
28
|
const screenshots = outcome?.screenshots ?? [];
|
|
29
29
|
const lastScreenshot = screenshots[screenshots.length - 1];
|
|
30
|
-
const status
|
|
31
|
-
? "running"
|
|
32
|
-
: outcome?.skippedReason !== undefined
|
|
33
|
-
? "blocked"
|
|
34
|
-
: session
|
|
35
|
-
? session.status
|
|
36
|
-
: outcome?.sessionError !== undefined
|
|
37
|
-
? "failed"
|
|
38
|
-
: "contract_proof_only";
|
|
39
|
-
const reason = args.inProgress === true && outcome === undefined
|
|
40
|
-
? "Live computer-use participant is running; stream auth URL is available only through the attached Observer server."
|
|
41
|
-
: (outcome?.skippedReason ??
|
|
42
|
-
session?.reason ??
|
|
43
|
-
outcome?.sessionError ??
|
|
44
|
-
"Dry run: the evidence shape was written without launching a desktop or spending provider tokens.");
|
|
30
|
+
const { status, reason } = participant;
|
|
45
31
|
const traceScreenshotMode = session?.trace.redaction.screenshots;
|
|
46
32
|
const screenshotMode = traceScreenshotMode === "raw" || traceScreenshotMode === "blurred"
|
|
47
33
|
? traceScreenshotMode
|
|
@@ -50,6 +36,7 @@ function fanoutParticipantView(args, spec, index) {
|
|
|
50
36
|
: "raw";
|
|
51
37
|
return {
|
|
52
38
|
outcome,
|
|
39
|
+
participant,
|
|
53
40
|
publicTargetUrl,
|
|
54
41
|
subject,
|
|
55
42
|
session,
|
|
@@ -64,6 +51,7 @@ function fanoutParticipantView(args, spec, index) {
|
|
|
64
51
|
function fanoutParticipantRecord(args, spec, index, view) {
|
|
65
52
|
const { plan } = args;
|
|
66
53
|
const { outcome, publicTargetUrl, session, status, reason } = view;
|
|
54
|
+
const name = participantCaption({ id: spec.planned.id, personaId: spec.persona.id });
|
|
67
55
|
return participantRecord(spec, index + 1, {
|
|
68
56
|
personaId: spec.persona.id,
|
|
69
57
|
scenarioId: `cua-${plan.studyId}`,
|
|
@@ -73,14 +61,14 @@ function fanoutParticipantRecord(args, spec, index, view) {
|
|
|
73
61
|
progress: args.inProgress === true && outcome === undefined ? 20 : 100,
|
|
74
62
|
currentStep: reason,
|
|
75
63
|
summary: session
|
|
76
|
-
?
|
|
64
|
+
? `${name} used the app and ${sessionEndingInWords(session.trace)}.`
|
|
77
65
|
: args.inProgress === true && outcome === undefined
|
|
78
|
-
?
|
|
66
|
+
? `${name} is using the app.`
|
|
79
67
|
: outcome?.skippedReason !== undefined
|
|
80
|
-
?
|
|
68
|
+
? `${name} was ${outcome.skippedReason}.`
|
|
81
69
|
: outcome?.sessionError !== undefined
|
|
82
|
-
?
|
|
83
|
-
:
|
|
70
|
+
? `${name} did not finish a session: ${outcome.sessionError}`
|
|
71
|
+
: `${name} would use ${publicTargetUrl}; no session ran.`,
|
|
84
72
|
startedAt: args.run.createdAt,
|
|
85
73
|
updatedAt: args.run.createdAt,
|
|
86
74
|
});
|
|
@@ -88,7 +76,7 @@ function fanoutParticipantRecord(args, spec, index, view) {
|
|
|
88
76
|
function fanoutParticipantStream(args, spec, view) {
|
|
89
77
|
const { outcome, publicTargetUrl, session, desktopGeometry, screenshots } = view;
|
|
90
78
|
const { lastScreenshot, status, reason, screenshotMode } = view;
|
|
91
|
-
const judged =
|
|
79
|
+
const judged = view.participant.judgedStatus;
|
|
92
80
|
return participantStream(spec, {
|
|
93
81
|
...(spec.evidenceAssignment === undefined
|
|
94
82
|
? {}
|
|
@@ -103,7 +91,11 @@ function fanoutParticipantStream(args, spec, view) {
|
|
|
103
91
|
? {}
|
|
104
92
|
: { caseGroup: spec.planned.labels.caseGroup }),
|
|
105
93
|
kind: "browser",
|
|
106
|
-
label:
|
|
94
|
+
label: participantCaption({
|
|
95
|
+
id: spec.planned.id,
|
|
96
|
+
personaId: spec.persona.id,
|
|
97
|
+
device: spec.planned.device,
|
|
98
|
+
}),
|
|
107
99
|
status,
|
|
108
100
|
...(judged === undefined ? {} : { judgedStatus: judged }),
|
|
109
101
|
transport: "snapshot",
|
|
@@ -298,7 +290,7 @@ function fanoutOutcomeEvents(ctx, spec, view) {
|
|
|
298
290
|
}
|
|
299
291
|
/** One participant's simulation, stream and events, in the order the bundle records them. */
|
|
300
292
|
export function fanoutParticipantRecords(ctx, spec, index) {
|
|
301
|
-
const view = fanoutParticipantView(ctx.args, spec, index);
|
|
293
|
+
const view = fanoutParticipantView(ctx.args, spec, index, ctx.participants[index]);
|
|
302
294
|
return {
|
|
303
295
|
simulation: fanoutParticipantRecord(ctx.args, spec, index, view),
|
|
304
296
|
stream: fanoutParticipantStream(ctx.args, spec, view),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { judgeParticipantRecords } from "../../run/judge.js";
|
|
1
2
|
import { createE2BParticipantDesktop } from "./e2b-desktop/desktop.js";
|
|
2
3
|
import { createInProcessDesktop } from "./in-process-desktop.js";
|
|
3
4
|
import path from "node:path";
|
|
@@ -6,7 +7,7 @@ import { mapWithConcurrency } from "../../run/concurrency.js";
|
|
|
6
7
|
import { assertScreenshotEvidence, stripPngMetadataChunks } from "../../evidence/image.js";
|
|
7
8
|
import { redactText, toErrorMessage } from "../../evidence/redaction.js";
|
|
8
9
|
import { assertSafeOutputPathSegment, writeContainedOutputFile, } from "../../run/contained-output.js";
|
|
9
|
-
import { participantOutcomeOk } from "./participant-facts.js";
|
|
10
|
+
import { participantOutcomeOk, participantFactsOf } from "./participant-facts.js";
|
|
10
11
|
import { closeParticipantModel, judgeParticipantSession, participantSessionOptions, recordParticipantTrace, startParticipantModel, } from "./participant-model.js";
|
|
11
12
|
/** Build a participant's writeScreenshot closure: writes under screenshots/<screenshotDir>/ and records
|
|
12
13
|
* the relative path the trace references (screenshots/<name> at N=1; screenshots/<laneId>/<name>
|
|
@@ -302,16 +303,8 @@ export function toParticipantResult(spec, outcome, subject, dryRun) {
|
|
|
302
303
|
: {
|
|
303
304
|
error: {
|
|
304
305
|
code: outcome.failureCode ?? "HUMANISH_COMPUTER_USE_FAILED",
|
|
305
|
-
message: outcome.
|
|
306
|
-
|
|
307
|
-
? "Actor took no actions and produced no message (likely a blank/still-loading screen); not a credible goal_satisfied."
|
|
308
|
-
: outcome.selfReportedBlocker
|
|
309
|
-
? "Actor reported goal_satisfied while its final message described a blocker or asked for missing instructions; not a credible pass."
|
|
310
|
-
: session?.completionReason === "harness_error"
|
|
311
|
-
? `Computer-use session ended with a harness error: ${session.reason}`
|
|
312
|
-
: session?.status !== "passed"
|
|
313
|
-
? `Computer-use session ended with ${session?.status ?? "unknown"}: ${session?.reason ?? "no terminal reason"}`
|
|
314
|
-
: "The computer-use run did not produce a terminal session."),
|
|
306
|
+
message: judgeParticipantRecords([participantFactsOf(outcome)]).participants[0]
|
|
307
|
+
.notPassedMessage,
|
|
315
308
|
},
|
|
316
309
|
}),
|
|
317
310
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type ExecutionFailure, type
|
|
1
|
+
import { type ExecutionFailure, type ParticipantRecordFacts } from "../../run/judge.js";
|
|
2
2
|
import type { ParticipantRunOutcome } from "./types.js";
|
|
3
3
|
/** A participant outcome's facts for the judge. */
|
|
4
|
-
export declare function participantFactsOf(outcome: ParticipantRunOutcome):
|
|
4
|
+
export declare function participantFactsOf(outcome: ParticipantRunOutcome | undefined): ParticipantRecordFacts;
|
|
5
5
|
/** Whether a participant passed. A dry-run participant passes as a contract. */
|
|
6
6
|
export declare function participantOutcomeOk(outcome: ParticipantRunOutcome | undefined, dryRun: boolean): boolean;
|
|
7
7
|
/**
|
|
@@ -4,10 +4,19 @@
|
|
|
4
4
|
import { participantHarnessFailed, participantPassed, sandboxCleanupFailure, } from "../../run/judge.js";
|
|
5
5
|
/** A participant outcome's facts for the judge. */
|
|
6
6
|
export function participantFactsOf(outcome) {
|
|
7
|
+
if (outcome === undefined)
|
|
8
|
+
return { skipped: false, noEngagement: false, selfReportedBlocker: false };
|
|
7
9
|
return {
|
|
10
|
+
id: outcome.spec.planned.id,
|
|
11
|
+
...(outcome.skippedReason === undefined ? {} : { skippedReason: outcome.skippedReason }),
|
|
12
|
+
reportedFriction: outcome.reportedFriction === true,
|
|
8
13
|
...(outcome.session === undefined
|
|
9
14
|
? {}
|
|
10
|
-
: {
|
|
15
|
+
: {
|
|
16
|
+
status: outcome.session.status,
|
|
17
|
+
completionReason: outcome.session.completionReason,
|
|
18
|
+
reason: outcome.session.reason,
|
|
19
|
+
}),
|
|
11
20
|
...(outcome.sessionError === undefined ? {} : { sessionError: outcome.sessionError }),
|
|
12
21
|
skipped: outcome.skippedReason !== undefined,
|
|
13
22
|
noEngagement: outcome.noEngagement === true,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { adapterScoreFailureMessage, applyBrowserScorer } from "../../study/adapter-extension.js";
|
|
2
2
|
import { redactText } from "../../evidence/redaction.js";
|
|
3
|
-
import { foldScorerFailures, judgeExecution, OUTCOME_POLICIES, resultOk, } from "../../run/judge.js";
|
|
3
|
+
import { foldScorerFailures, judgeExecution, judgeParticipantRecords, OUTCOME_POLICIES, resultOk, } from "../../run/judge.js";
|
|
4
4
|
import { validatePreparedRunArtifactPaths } from "../../run/paths.js";
|
|
5
|
-
import { participantExecutionFailures, participantOutcomeOk } from "./participant-facts.js";
|
|
5
|
+
import { participantExecutionFailures, participantOutcomeOk, participantFactsOf, } from "./participant-facts.js";
|
|
6
6
|
import { participantCapWarning } from "./participant-model.js";
|
|
7
7
|
import { summarizeCuaDiagnostics } from "./diagnostics.js";
|
|
8
8
|
import { toParticipantResult } from "./participant-execution.js";
|
|
@@ -90,19 +90,11 @@ function cuaStudyResult(args) {
|
|
|
90
90
|
message: outcome?.sessionError ??
|
|
91
91
|
outcome?.providerCleanupError ??
|
|
92
92
|
outcome?.providerPolicyError ??
|
|
93
|
-
(outcome?.noEngagement
|
|
94
|
-
?
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
? "Actor reported goal_satisfied while its final message described a blocker or asked for missing instructions; not a credible pass."
|
|
99
|
-
: observer.ok
|
|
100
|
-
? outcome?.session?.completionReason === "harness_error"
|
|
101
|
-
? `Computer-use session ended with a harness error: ${outcome.session.reason}`
|
|
102
|
-
: outcome?.session?.status !== "passed"
|
|
103
|
-
? `Computer-use session ended with ${outcome?.session?.status ?? "unknown"}: ${outcome?.session?.reason ?? "no terminal reason"}`
|
|
104
|
-
: "The computer-use run did not produce a terminal session."
|
|
105
|
-
: (observer.error?.message ?? "Observer failed for the computer-use run.")),
|
|
93
|
+
(outcome?.noEngagement || outcome?.selfReportedBlocker || observer.ok
|
|
94
|
+
? (participantResults[0]?.error?.message ??
|
|
95
|
+
judgeParticipantRecords([participantFactsOf(outcome)]).participants[0]
|
|
96
|
+
.notPassedMessage)
|
|
97
|
+
: (observer.error?.message ?? "Observer failed for the computer-use run.")),
|
|
106
98
|
};
|
|
107
99
|
}
|
|
108
100
|
const failing = (outcomes ?? []).find((outcome) => !participantOk(outcome));
|
|
@@ -32,6 +32,8 @@ export declare function buildSingleParticipantBundle(args: {
|
|
|
32
32
|
assignment?: RunStream["assignment"];
|
|
33
33
|
persona: ActorPersonaRef;
|
|
34
34
|
resolution: [number, number];
|
|
35
|
+
/** The device preset's name; the caption names a tablet. */
|
|
36
|
+
deviceName?: string;
|
|
35
37
|
/** False only for the custom in-process route, which has no hosted screen/window to claim. */
|
|
36
38
|
desktopRoute?: boolean;
|
|
37
39
|
/** The participant's runner; see runnerSubstrate for the default. */
|
|
@@ -2,10 +2,12 @@ import { participantAssignment } from "../../study/participant-assignment.js";
|
|
|
2
2
|
import { redactText } from "../../evidence/redaction.js";
|
|
3
3
|
import { REVIEW_SCHEMA, bundleArtifacts, bundleHead, } from "../../run/bundle.js";
|
|
4
4
|
import {} from "../../run/streams.js";
|
|
5
|
-
import { aggregateTaskFunnels,
|
|
5
|
+
import { aggregateTaskFunnels, formatParticipantOutcomes, formatRunTaskFunnel, sessionEndingInWords, withCuaReviewProvenance, } from "../../run/outcomes.js";
|
|
6
|
+
import { participantCaption } from "../../run/participant-caption.js";
|
|
7
|
+
import { actorEnding } from "../../actors/stop-cause.js";
|
|
6
8
|
import { describeSubjectState, phaseEventIdSuffix, publicSafeAppUrlLabel, subjectProvenanceMessage, } from "./bundle-parts.js";
|
|
7
9
|
import { participantFeedbackCandidates } from "./participant-feedback.js";
|
|
8
|
-
import {
|
|
10
|
+
import { judgeParticipantRecords } from "../../run/judge.js";
|
|
9
11
|
import { buildRunCostSummary } from "../../run/cost-summary.js";
|
|
10
12
|
import { participantEvent, participantIds, participantRecord, participantStream, } from "../../run/participant-records.js";
|
|
11
13
|
import { plural } from "../../run/text.js";
|
|
@@ -43,18 +45,25 @@ function runCost(args) {
|
|
|
43
45
|
}
|
|
44
46
|
/** The participant's status, reason, last frame, geometry and screenshot mode, shared by its records. */
|
|
45
47
|
function participantView(args, publicAppUrl) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
args.
|
|
57
|
-
|
|
48
|
+
const judgment = judgeParticipantRecords([
|
|
49
|
+
{
|
|
50
|
+
...(args.session === undefined
|
|
51
|
+
? {}
|
|
52
|
+
: {
|
|
53
|
+
status: args.session.status,
|
|
54
|
+
completionReason: args.session.completionReason,
|
|
55
|
+
reason: args.session.reason,
|
|
56
|
+
}),
|
|
57
|
+
...(args.sessionError === undefined ? {} : { sessionError: args.sessionError }),
|
|
58
|
+
inProgress: args.inProgress === true,
|
|
59
|
+
skipped: false,
|
|
60
|
+
noEngagement: args.credibility?.noEngagement === true,
|
|
61
|
+
selfReportedBlocker: args.credibility?.selfReportedBlocker === true,
|
|
62
|
+
reportedFriction: args.credibility?.reportedFriction === true,
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
const participant = judgment.participants[0];
|
|
66
|
+
const { status, reason } = participant;
|
|
58
67
|
const lastScreenshot = args.screenshots[args.screenshots.length - 1];
|
|
59
68
|
const desktopGeometry = args.desktopRoute === false
|
|
60
69
|
? undefined
|
|
@@ -70,6 +79,8 @@ function participantView(args, publicAppUrl) {
|
|
|
70
79
|
: (args.captureRedaction ?? "raw");
|
|
71
80
|
return {
|
|
72
81
|
publicAppUrl,
|
|
82
|
+
participant,
|
|
83
|
+
tally: judgment.tally,
|
|
73
84
|
status,
|
|
74
85
|
reason,
|
|
75
86
|
lastScreenshot,
|
|
@@ -80,6 +91,10 @@ function participantView(args, publicAppUrl) {
|
|
|
80
91
|
}
|
|
81
92
|
function singleSimulation(args, view) {
|
|
82
93
|
const { publicAppUrl, status, reason } = view;
|
|
94
|
+
const name = participantCaption({
|
|
95
|
+
id: args.participantId ?? "lane-01",
|
|
96
|
+
personaId: args.persona.id,
|
|
97
|
+
});
|
|
83
98
|
return participantRecord(SINGLE, 1, {
|
|
84
99
|
personaId: args.persona.id,
|
|
85
100
|
scenarioId: `cua-${args.studyId}`,
|
|
@@ -89,23 +104,19 @@ function singleSimulation(args, view) {
|
|
|
89
104
|
progress: args.inProgress === true ? 20 : 100,
|
|
90
105
|
currentStep: reason,
|
|
91
106
|
summary: args.session
|
|
92
|
-
?
|
|
107
|
+
? `${name} used the app ${browserPlace(args)}, and ${sessionEndingInWords(args.session.trace)}.`
|
|
93
108
|
: args.inProgress === true
|
|
94
|
-
?
|
|
109
|
+
? `${name} is using the app ${browserPlace(args)}.`
|
|
95
110
|
: args.sessionError !== undefined
|
|
96
|
-
?
|
|
97
|
-
:
|
|
111
|
+
? `${name} did not finish a session: ${args.sessionError}`
|
|
112
|
+
: `${name} would use ${publicAppUrl}; no session ran.`,
|
|
98
113
|
startedAt: args.run.createdAt,
|
|
99
114
|
updatedAt: args.run.createdAt,
|
|
100
115
|
});
|
|
101
116
|
}
|
|
102
117
|
function singleStream(args, view) {
|
|
103
118
|
const { publicAppUrl, status, reason, lastScreenshot, desktopGeometry, screenshotMode } = view;
|
|
104
|
-
|
|
105
|
-
// the participant the way the verdict judged it.
|
|
106
|
-
const judged = args.session === undefined || args.inProgress === true
|
|
107
|
-
? undefined
|
|
108
|
-
: participantStatusFor(args.session.status, args.credibility);
|
|
119
|
+
const judged = args.session === undefined ? undefined : view.participant.judgedStatus;
|
|
109
120
|
return participantStream(SINGLE, {
|
|
110
121
|
...(args.assignment === undefined
|
|
111
122
|
? {}
|
|
@@ -114,7 +125,13 @@ function singleStream(args, view) {
|
|
|
114
125
|
...(args.surface === undefined ? {} : { surface: args.surface }),
|
|
115
126
|
...(args.caseGroup === undefined ? {} : { caseGroup: args.caseGroup }),
|
|
116
127
|
kind: "browser",
|
|
117
|
-
label:
|
|
128
|
+
label: participantCaption({
|
|
129
|
+
id: args.participantId ?? "lane-01",
|
|
130
|
+
personaId: args.persona.id,
|
|
131
|
+
...(args.isMobile === undefined
|
|
132
|
+
? {}
|
|
133
|
+
: { device: { name: args.deviceName ?? "", preset: { isMobile: args.isMobile } } }),
|
|
134
|
+
}),
|
|
118
135
|
status,
|
|
119
136
|
...(judged === undefined ? {} : { judgedStatus: judged }),
|
|
120
137
|
transport: "snapshot",
|
|
@@ -257,37 +274,64 @@ function singleEvents(args, view) {
|
|
|
257
274
|
}
|
|
258
275
|
return events;
|
|
259
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* The run's summary in plain sentences. A finished session gives the outcome with its denominator
|
|
279
|
+
* of one, any reason the harness did not count a pass, and how the session ended. The single
|
|
280
|
+
* participant has no gap line, so the summary carries the reason.
|
|
281
|
+
*/
|
|
282
|
+
function singleSummary(args, view, facts) {
|
|
283
|
+
if (args.inProgress === true) {
|
|
284
|
+
return "1 participant is using the app. The run is still going, so nothing here is final.";
|
|
285
|
+
}
|
|
286
|
+
if (args.dryRun) {
|
|
287
|
+
return `Dry run: 1 participant would use ${view.publicAppUrl}. Nothing was launched and $0 was spent.`;
|
|
288
|
+
}
|
|
289
|
+
if (args.session === undefined) {
|
|
290
|
+
return args.sessionError === undefined
|
|
291
|
+
? `1 participant was planned against ${view.publicAppUrl}, but no session ran.`
|
|
292
|
+
: `1 participant was planned, but the run failed before their session finished: ${view.reason}`;
|
|
293
|
+
}
|
|
294
|
+
const ending = actorEnding(args.session.trace);
|
|
295
|
+
const outcomes = formatParticipantOutcomes(view.tally, [
|
|
296
|
+
{ status: args.session.status, ...(ending === undefined ? {} : { label: ending.label }) },
|
|
297
|
+
]);
|
|
298
|
+
return [
|
|
299
|
+
`1 participant took part: ${outcomes}.`,
|
|
300
|
+
facts.tasks === undefined ? undefined : `Tasks: ${formatRunTaskFunnel(facts.tasks)}.`,
|
|
301
|
+
facts.credibilityNote,
|
|
302
|
+
`How it ended: ${view.reason}`,
|
|
303
|
+
]
|
|
304
|
+
.filter((sentence) => sentence !== undefined)
|
|
305
|
+
.join(" ");
|
|
306
|
+
}
|
|
260
307
|
function singleReview(args, view, stream) {
|
|
261
|
-
const { reason } = view;
|
|
262
308
|
// A funnel with a denominator of one is still the funnel, and its absence stays recorded: no
|
|
263
309
|
// declared protocol (or a dry run) means no `tasks` field, never an empty one.
|
|
264
310
|
const singleRunTasks = args.inProgress !== true && args.session?.trace.taskFunnel !== undefined
|
|
265
311
|
? aggregateTaskFunnels([args.session.trace.taskFunnel])
|
|
266
312
|
: undefined;
|
|
267
|
-
|
|
268
|
-
// applies (participantStatus in src/run/judge.ts). Reading the actor's own status instead would write up
|
|
269
|
-
// a run the route refused as "not a credible pass" as verdict pass, 1/1 reached the goal, and
|
|
270
|
-
// every projection of the bundle (Observer tally, `runs`, the status index) would repeat it.
|
|
271
|
-
const participantStatus = args.session === undefined
|
|
272
|
-
? undefined
|
|
273
|
-
: participantStatusFor(args.session.status, args.credibility);
|
|
274
|
-
const credibilityNote = args.session === undefined || participantStatus === args.session.status
|
|
313
|
+
const credibilityNote = args.inProgress === true || args.session?.status !== "passed"
|
|
275
314
|
? undefined
|
|
276
315
|
: args.credibility?.noEngagement === true
|
|
277
316
|
? "Not counted as a pass: the participant took no actions and said nothing."
|
|
278
|
-
:
|
|
317
|
+
: args.credibility?.selfReportedBlocker === true
|
|
318
|
+
? "Not counted as a pass: the participant's final message described a blocker."
|
|
319
|
+
: undefined;
|
|
279
320
|
const review = withCuaReviewProvenance({
|
|
280
321
|
schema: REVIEW_SCHEMA,
|
|
281
322
|
verdict: args.verdict,
|
|
282
323
|
// One participant is still a study with a denominator of one, and saying so keeps a
|
|
283
324
|
// single-participant result from being read as though it generalized.
|
|
284
|
-
...(
|
|
325
|
+
...(args.session !== undefined && args.inProgress !== true
|
|
285
326
|
? {
|
|
286
|
-
participants:
|
|
327
|
+
participants: view.tally,
|
|
287
328
|
}
|
|
288
329
|
: {}),
|
|
289
330
|
...(singleRunTasks === undefined ? {} : { tasks: singleRunTasks }),
|
|
290
|
-
summary:
|
|
331
|
+
summary: singleSummary(args, view, {
|
|
332
|
+
...(singleRunTasks === undefined ? {} : { tasks: singleRunTasks }),
|
|
333
|
+
...(credibilityNote === undefined ? {} : { credibilityNote }),
|
|
334
|
+
}),
|
|
291
335
|
gaps: args.session || args.sessionError !== undefined
|
|
292
336
|
? []
|
|
293
337
|
: args.inProgress === true
|
|
@@ -36,5 +36,5 @@ interface ScriptedBundleArgs {
|
|
|
36
36
|
* the bundle-builder tests.
|
|
37
37
|
*/
|
|
38
38
|
export declare function buildScriptedStudyBundle(args: ScriptedBundleArgs): RunBundle;
|
|
39
|
-
export declare function renderScriptedReviewMarkdown(bundle: RunBundle): string;
|
|
39
|
+
export declare function renderScriptedReviewMarkdown(bundle: RunBundle, status?: unknown): string;
|
|
40
40
|
export {};
|