humanish 0.111.4 → 0.113.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 +37 -0
- package/README.md +11 -0
- package/TELEMETRY.md +21 -0
- package/dist/actors/codex/restricted-participant-policy.d.ts +21 -1
- package/dist/actors/codex/restricted-participant-policy.js +20 -2
- package/dist/actors/codex/restricted-participant.d.ts +1 -1
- package/dist/actors/codex/restricted-participant.js +2 -1
- package/dist/actors/computer-use/loop/debrief.d.ts +12 -4
- package/dist/actors/computer-use/loop/debrief.js +57 -20
- package/dist/actors/computer-use/loop/ending.d.ts +5 -2
- package/dist/actors/computer-use/loop/impressions.d.ts +14 -0
- package/dist/actors/computer-use/loop/impressions.js +40 -0
- package/dist/actors/computer-use/loop/session.d.ts +3 -1
- package/dist/actors/computer-use/loop/session.js +2 -0
- package/dist/actors/computer-use/loop/trace.js +2 -0
- package/dist/actors/computer-use/loop/types.d.ts +8 -1
- package/dist/actors/computer-use/loop.d.ts +2 -1
- package/dist/actors/computer-use/loop.js +15 -3
- package/dist/actors/computer-use/openai-conversation.d.ts +96 -0
- package/dist/actors/computer-use/openai-conversation.js +238 -0
- package/dist/actors/computer-use/openai-provider.js +38 -48
- package/dist/actors/contract.d.ts +45 -3
- package/dist/actors/contract.js +13 -0
- package/dist/analysis/diagnostics.js +7 -12
- package/dist/analysis/evidence-sources.d.ts +3 -1
- package/dist/analysis/evidence-sources.js +10 -1
- package/dist/analysis/evidence.js +2 -2
- package/dist/analysis/execute.d.ts +1 -1
- package/dist/analysis/execute.js +37 -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 +37 -20
- package/dist/cli/commands/setup.js +95 -6
- package/dist/cli/commands/study-format.js +7 -4
- package/dist/cli/commands/study.js +1 -1
- package/dist/cli/commands/tui.js +1 -1
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.js +18 -0
- package/dist/cli/findings.d.ts +34 -3
- package/dist/cli/findings.js +136 -18
- package/dist/cli/io.d.ts +4 -1
- package/dist/cli/io.js +3 -2
- package/dist/cli/program.js +35 -1
- package/dist/cli/secret-prompt.d.ts +5 -1
- package/dist/cli/secret-prompt.js +6 -2
- package/dist/cli/update-check-worker.d.ts +1 -0
- package/dist/cli/update-check-worker.js +5 -0
- package/dist/cli/update-check.d.ts +46 -0
- package/dist/cli/update-check.js +203 -0
- package/dist/feedback/draft.js +32 -4
- package/dist/keys/key-resolution.js +2 -0
- package/dist/keys/key-status.d.ts +37 -0
- package/dist/keys/key-status.js +68 -0
- 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/local-vm.js +9 -2
- package/dist/routes/computer-use/participant-desktop.d.ts +6 -0
- package/dist/routes/computer-use/participant-execution.js +13 -12
- 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/rerun-selection.js +23 -21
- package/dist/routes/computer-use/result.js +10 -17
- package/dist/routes/computer-use/self-report.js +7 -1
- package/dist/routes/computer-use/setup.js +14 -0
- package/dist/routes/computer-use/single-bundle.d.ts +2 -0
- package/dist/routes/computer-use/single-bundle.js +81 -37
- package/dist/routes/computer-use/types.d.ts +4 -1
- package/dist/routes/scripted/bundle.d.ts +1 -1
- package/dist/routes/scripted/bundle.js +9 -19
- package/dist/routes/scripted/surface-records.d.ts +0 -1
- package/dist/routes/scripted/surface-records.js +27 -13
- package/dist/routes/shared-world/bundle-records.d.ts +2 -2
- package/dist/routes/shared-world/bundle-records.js +24 -30
- package/dist/routes/shared-world/bundle.d.ts +1 -1
- package/dist/routes/shared-world/bundle.js +71 -79
- 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/dry-run.js +1 -0
- 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 +1 -0
- package/dist/run/costs.js +9 -20
- package/dist/run/detail.d.ts +1 -1
- package/dist/run/detail.js +13 -4
- 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 +31 -0
- package/dist/run/participant-caption.js +60 -0
- package/dist/run/participant-records.d.ts +4 -2
- package/dist/run/participant-records.js +6 -0
- 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/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/run-study.js +17 -1
- package/dist/study/discover.js +6 -1
- package/dist/study/doctor.d.ts +2 -0
- package/dist/study/doctor.js +45 -7
- package/dist/study/init-templates.js +10 -5
- 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/plan.js +3 -2
- package/dist/study/preflight-result.d.ts +1 -0
- package/dist/study/preflight-result.js +1 -1
- package/dist/study/preflight.d.ts +2 -0
- package/dist/study/preflight.js +54 -14
- package/dist/study/types.d.ts +4 -1
- package/dist/study/warnings.d.ts +7 -1
- package/dist/study/warnings.js +82 -4
- package/dist/substrates/local/capacity.d.ts +75 -0
- package/dist/substrates/local/capacity.js +143 -0
- package/dist/substrates/local/firecracker-desktop.d.ts +8 -0
- package/dist/substrates/local/firecracker-desktop.js +34 -3
- package/dist/substrates/local/runtime-host.d.ts +16 -3
- package/dist/substrates/local/runtime-host.js +49 -4
- package/dist/substrates/local/runtime.d.ts +10 -0
- package/dist/substrates/local/runtime.js +44 -5
- package/dist/tui-app.js +75 -75
- package/dist/verify/liveness.d.ts +2 -2
- package/dist/verify/liveness.js +7 -4
- package/docs/architecture/actor-contract.md +20 -6
- package/docs/architecture/terminal-product-route.md +14 -0
- package/docs/contracts/feedback.md +8 -1
- package/docs/contracts/run-bundle.md +10 -0
- package/docs/contracts/schemas.md +65 -34
- package/docs/contracts/study-analysis.md +86 -22
- package/package.json +2 -1
- package/skills/humanish/SKILL.md +21 -10
- 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),
|
|
@@ -3,8 +3,9 @@ import path from "node:path";
|
|
|
3
3
|
import { inboxRecipientFor, } from "./participant-desktop.js";
|
|
4
4
|
import { createLocalFirecrackerDesktop, } from "../../substrates/local/firecracker-desktop.js";
|
|
5
5
|
import { localBrowserDefaults, localBrowserUnsupportedReason, } from "../../substrates/local/runtime-config.js";
|
|
6
|
-
import { prepareLocalRuntime } from "../../substrates/local/runtime.js";
|
|
7
|
-
import { dockerCommandLine } from "../../substrates/local/runtime-host.js";
|
|
6
|
+
import { localRuntimeCapacity, prepareLocalRuntime } from "../../substrates/local/runtime.js";
|
|
7
|
+
import { dockerCommandLine, usesLima } from "../../substrates/local/runtime-host.js";
|
|
8
|
+
import { outOfMemoryMessage } from "../../substrates/local/capacity.js";
|
|
8
9
|
import { checkRestrictedCodexAnalysisReadiness } from "../../analysis/restricted-codex.js";
|
|
9
10
|
import { createRestrictedCodexParticipant } from "../../actors/codex/restricted-participant.js";
|
|
10
11
|
import { withCloseReport } from "./participant-model.js";
|
|
@@ -110,6 +111,11 @@ function createLocalParticipantDesktop(context, run, warnings, artifactRoot) {
|
|
|
110
111
|
warnings.push("Desktop video/audio recording could not be retained. Screenshots and participant evidence remain available.");
|
|
111
112
|
}
|
|
112
113
|
}
|
|
114
|
+
// Asked before close, which removes the container; the answer comes from Docker's events.
|
|
115
|
+
if ((await session.killedForMemory?.()) === true) {
|
|
116
|
+
evidence.failureCode = "HUMANISH_COMPUTER_USE_DESKTOP_OUT_OF_MEMORY";
|
|
117
|
+
evidence.desktopFailure = outOfMemoryMessage(usesLima() ? "lima-vm" : "linux-host");
|
|
118
|
+
}
|
|
113
119
|
try {
|
|
114
120
|
evidence.released = (await session.close()).status === "released";
|
|
115
121
|
}
|
|
@@ -180,6 +186,7 @@ export function prepareLocalVmRun(options) {
|
|
|
180
186
|
options: account ? { ...runOptions, createProvider: accountProvider(state) } : runOptions,
|
|
181
187
|
localVm: {
|
|
182
188
|
desktop: (run, warnings, artifactRoot) => createLocalParticipantDesktop(context, run, warnings, artifactRoot),
|
|
189
|
+
capacity: () => localRuntimeCapacity(),
|
|
183
190
|
analysisRefusal: () => state.cleanupUnconfirmed ? "AUTOMATIC_ANALYSIS_CLEANUP_UNCONFIRMED" : undefined,
|
|
184
191
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
185
192
|
},
|
|
@@ -16,6 +16,12 @@ export type ParticipantDesktopEvidence = {
|
|
|
16
16
|
/** The substrate confirmed the desktop was released (an E2B kill or a local VM shutdown). The participant
|
|
17
17
|
* outcome records it as `killed`. */
|
|
18
18
|
released: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Why the desktop itself ended the session, in words a person reads, when the substrate knows:
|
|
21
|
+
* a local desktop Docker killed for memory. It replaces the session's error for a participant
|
|
22
|
+
* who did not pass.
|
|
23
|
+
*/
|
|
24
|
+
desktopFailure?: string;
|
|
19
25
|
} & Pick<ParticipantRunOutcome, "sandboxId" | "sandboxRelease" | "desktopDurationMs" | "desktopResources" | "streamUrlPresent" | "subjectCommit" | "desktopBrowser" | "desktopGeometry" | "stateStepRecords" | "phaseRecords" | "failureCode" | "commsArtifactPath" | "recording">;
|
|
20
26
|
/**
|
|
21
27
|
* The desktop one participant runs on, owned from preparation through final evidence collection.
|
|
@@ -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>
|
|
@@ -106,6 +107,15 @@ export async function runCuaParticipant(spec, deps) {
|
|
|
106
107
|
});
|
|
107
108
|
}
|
|
108
109
|
}
|
|
110
|
+
const { released, desktopFailure, ...desktopEvidence } = desktop.snapshot();
|
|
111
|
+
// A desktop that died under the participant explains the failure better than the transport
|
|
112
|
+
// error the session saw. A session that passed keeps its result.
|
|
113
|
+
if (desktopFailure !== undefined) {
|
|
114
|
+
if (session?.status === "passed")
|
|
115
|
+
warnings.push(desktopFailure);
|
|
116
|
+
else
|
|
117
|
+
sessionError = desktopFailure;
|
|
118
|
+
}
|
|
109
119
|
if (session)
|
|
110
120
|
await recordParticipantTrace(spec, deps, session, warnings);
|
|
111
121
|
const { noEngagement, selfReportedBlocker, reportedFriction } = judgeParticipantSession(session, deps, warnings);
|
|
@@ -115,7 +125,6 @@ export async function runCuaParticipant(spec, deps) {
|
|
|
115
125
|
providerCleanupError !== undefined ||
|
|
116
126
|
providerPolicyError !== undefined ||
|
|
117
127
|
session?.completionReason === "harness_error";
|
|
118
|
-
const { released, ...desktopEvidence } = desktop.snapshot();
|
|
119
128
|
return {
|
|
120
129
|
spec,
|
|
121
130
|
...(session ? { session } : {}),
|
|
@@ -302,16 +311,8 @@ export function toParticipantResult(spec, outcome, subject, dryRun) {
|
|
|
302
311
|
: {
|
|
303
312
|
error: {
|
|
304
313
|
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."),
|
|
314
|
+
message: judgeParticipantRecords([participantFactsOf(outcome)]).participants[0]
|
|
315
|
+
.notPassedMessage,
|
|
315
316
|
},
|
|
316
317
|
}),
|
|
317
318
|
};
|
|
@@ -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,
|
|
@@ -4,42 +4,44 @@ import {} from "../../run/bundle.js";
|
|
|
4
4
|
import { loadRunBundle } from "../../run/locate.js";
|
|
5
5
|
import {} from "../../run/streams.js";
|
|
6
6
|
import { plural } from "../../run/text.js";
|
|
7
|
+
import { access } from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { cli } from "../../cli/invocation.js";
|
|
7
10
|
export async function resolveCuaRerunSelection(args) {
|
|
8
|
-
const
|
|
11
|
+
const { studyId, sourceRunId } = args;
|
|
12
|
+
// Every refusal ends with the command that runs the whole study, which is what a user who
|
|
13
|
+
// reached for a rerun too early wants.
|
|
14
|
+
const instead = `To run every participant of ${studyId} instead, leave out --rerun-failed-from: ${cli(`watch ${studyId}`)} or ${cli(`run ${studyId}`)}.`;
|
|
15
|
+
const refuse = (what) => ({ ok: false, message: `${what} ${instead}` });
|
|
16
|
+
const source = await loadRunBundle(args.cwd, sourceRunId);
|
|
9
17
|
if (!source) {
|
|
10
|
-
|
|
18
|
+
const noRuns = sourceRunId === "latest" &&
|
|
19
|
+
!(await access(path.join(args.cwd, ".humanish", "runs", "latest.json")).then(() => true, () => false));
|
|
20
|
+
return refuse(noRuns
|
|
21
|
+
? "--rerun-failed-from latest found no run: this project has no runs yet, and a rerun repeats the failed participants of an earlier live run."
|
|
22
|
+
: `--rerun-failed-from ${sourceRunId} names no run humanish can read in this project. ${cli("runs")} lists its runs.`);
|
|
11
23
|
}
|
|
12
24
|
const bundle = source.bundle;
|
|
25
|
+
const run = `Run ${bundle.runId}${sourceRunId === bundle.runId ? "" : ` (the ${sourceRunId} run)`}`;
|
|
26
|
+
const notFanout = `${run} is not a live computer-use run with more than one participant, so it has no participants to rerun.`;
|
|
13
27
|
if (bundle.mode !== "live") {
|
|
14
|
-
return {
|
|
15
|
-
ok: false,
|
|
16
|
-
message: `source run ${bundle.runId} is ${bundle.mode}; rerun selection only applies to live CUA fan-out evidence.`,
|
|
17
|
-
};
|
|
28
|
+
return refuse(`${run} is a dry run: no participant ran in it, so none can be rerun. A rerun repeats the failed participants of a live run with more than one participant.`);
|
|
18
29
|
}
|
|
19
30
|
const fanoutEvent = bundle.events.some((event) => event.type === "cua-lab.fanout.plan");
|
|
20
|
-
if (!fanoutEvent || bundle.streams.length < 2)
|
|
21
|
-
return
|
|
22
|
-
}
|
|
31
|
+
if (!fanoutEvent || bundle.streams.length < 2)
|
|
32
|
+
return refuse(notFanout);
|
|
23
33
|
const prior = bundle.streams
|
|
24
34
|
.map(snapshotPriorParticipant)
|
|
25
35
|
.filter((entry) => entry !== null);
|
|
26
36
|
const priorById = new Map(prior.map((entry) => [entry.participantId, entry]));
|
|
27
|
-
if (priorById.size < 2)
|
|
28
|
-
return
|
|
29
|
-
ok: false,
|
|
30
|
-
message: `source run ${bundle.runId} does not expose multiple participant ids.`,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
37
|
+
if (priorById.size < 2)
|
|
38
|
+
return refuse(notFanout);
|
|
33
39
|
const explicitIds = uniqueIds(args.participantIds ?? []);
|
|
34
40
|
const selectedIds = explicitIds.length > 0
|
|
35
41
|
? explicitIds
|
|
36
42
|
: prior.filter((entry) => entry.rerunnable).map((entry) => entry.participantId);
|
|
37
|
-
if (selectedIds.length === 0)
|
|
38
|
-
return {
|
|
39
|
-
ok: false,
|
|
40
|
-
message: `source run ${bundle.runId} has no participant to rerun: none failed, was blocked, timed out or ended without engaging.`,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
+
if (selectedIds.length === 0)
|
|
44
|
+
return refuse(`${run} has no participant to rerun: none failed, was blocked, timed out or ended without engaging.`);
|
|
43
45
|
const missingPrior = selectedIds.filter((id) => !priorById.has(id));
|
|
44
46
|
if (missingPrior.length > 0) {
|
|
45
47
|
return {
|
|
@@ -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,24 +90,17 @@ 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));
|
|
109
|
-
|
|
110
|
-
const
|
|
101
|
+
// A participant whose desktop failed for a known cause gives the run that cause's code.
|
|
102
|
+
const codedFailure = (outcomes ?? []).find((outcome) => outcome.failureCode !== undefined);
|
|
103
|
+
const code = codedFailure?.failureCode ?? "HUMANISH_COMPUTER_USE_FAILED";
|
|
111
104
|
return {
|
|
112
105
|
code,
|
|
113
106
|
message: observer.ok
|
|
@@ -200,8 +200,14 @@ export function resolveSelfReportedFriction(session) {
|
|
|
200
200
|
// reached the goal and described what was hard on the way has reported friction.
|
|
201
201
|
if (session.trace.declaredOutcome === "blocked")
|
|
202
202
|
return session.reason;
|
|
203
|
+
// Impressions are opinions about the product, not reports of what went wrong in the session.
|
|
204
|
+
const impressions = new Set(session.trace.impressions?.status === "collected"
|
|
205
|
+
? session.trace.impressions.items.map((item) => item.messageId)
|
|
206
|
+
: []);
|
|
203
207
|
const messages = session.trace.items
|
|
204
|
-
.filter((item) => item.kind === "message" &&
|
|
208
|
+
.filter((item) => item.kind === "message" &&
|
|
209
|
+
item.id !== session.trace.debrief?.messageId &&
|
|
210
|
+
!impressions.has(item.id))
|
|
205
211
|
.map((item) => item.text?.trim() ?? "")
|
|
206
212
|
.filter((text) => text.length > 0);
|
|
207
213
|
// A custom session may keep its closing report only in reason even when earlier messages exist.
|
|
@@ -22,6 +22,7 @@ import {} from "./bundle.js";
|
|
|
22
22
|
import { packRunLocalTree } from "./local-tree-pack.js";
|
|
23
23
|
import { projectParticipantSubjects, subjectProvenanceArg } from "./subject-projection.js";
|
|
24
24
|
import { participantSubjectEnv, } from "./types.js";
|
|
25
|
+
import { capacityShortfall } from "../../substrates/local/capacity.js";
|
|
25
26
|
import { studyPersonaIds } from "../../study/persona-resolve.js";
|
|
26
27
|
import { refusedResult } from "../../run/study-result.js";
|
|
27
28
|
/** The physical project, bound before any caller hook runs. */
|
|
@@ -145,6 +146,11 @@ export async function admitCuaRun(plan, input, config) {
|
|
|
145
146
|
});
|
|
146
147
|
if (rejection)
|
|
147
148
|
return refuse(rejection.code, rejection.message, descriptor.id);
|
|
149
|
+
const shortfall = await localCapacityShortfall(input.localVm, participantPlan.concurrency);
|
|
150
|
+
if (shortfall !== undefined && "refusal" in shortfall)
|
|
151
|
+
return refuse("HUMANISH_COMPUTER_USE_LOCAL_CAPACITY_EXCEEDED", shortfall.refusal, descriptor.id);
|
|
152
|
+
if (shortfall !== undefined)
|
|
153
|
+
process.stderr.write(`warning: ${shortfall.warning}\n`);
|
|
148
154
|
}
|
|
149
155
|
// Pack the working tree once per run, on the host, before any sandbox or provider call: every
|
|
150
156
|
// fan-out participant below uploads this same archive, so one archiveSha256 describes every
|
|
@@ -192,6 +198,14 @@ export async function admitCuaRun(plan, input, config) {
|
|
|
192
198
|
},
|
|
193
199
|
};
|
|
194
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Whether a local study's desktops at once fit the local runtime. Read before any desktop: a
|
|
203
|
+
* desktop past the VM's memory is killed mid-session, after its participant has spent.
|
|
204
|
+
*/
|
|
205
|
+
async function localCapacityShortfall(localVm, desktops) {
|
|
206
|
+
const capacity = await localVm?.capacity?.();
|
|
207
|
+
return capacity === undefined ? undefined : capacityShortfall(capacity, desktops);
|
|
208
|
+
}
|
|
195
209
|
/** Starts the run and builds what the participants and the finish read: the deps and the bundle base. */
|
|
196
210
|
export async function startCuaRun(plan, input, admitted, scope) {
|
|
197
211
|
const { cwd, descriptor, participantRuns, participantPlan, publicRepo } = admitted;
|
|
@@ -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. */
|