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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// Assemble the scripted-browser study's run bundle from its sessions, and the review summary and
|
|
2
2
|
// markdown that ship with it.
|
|
3
|
-
import {
|
|
4
|
-
import { reviewOutcome } from "../../run/display.js";
|
|
3
|
+
import { renderReviewMarkdown } from "../../run/review-markdown.js";
|
|
5
4
|
import { redactText } from "../../evidence/redaction.js";
|
|
6
5
|
import { buildRunCostSummary, desktopSpanToMinutes, spendFreeCostSummary, } from "../../run/cost-summary.js";
|
|
7
6
|
import { REVIEW_SCHEMA, bundleArtifacts, bundleHead, } from "../../run/bundle.js";
|
|
8
7
|
import {} from "../../run/streams.js";
|
|
9
8
|
import { participantEvent, recordIdOf } from "../../run/participant-records.js";
|
|
10
9
|
import { scriptedSurfaceIds, scriptedSurfaceRecords } from "./surface-records.js";
|
|
10
|
+
import { gapsListClause } from "../../run/outcomes.js";
|
|
11
11
|
import { plural } from "../../run/text.js";
|
|
12
12
|
/**
|
|
13
13
|
* Project the scripted run into a humanish.run-bundle.v1 (no schema change: a new
|
|
@@ -146,7 +146,7 @@ function buildScriptedReview(args) {
|
|
|
146
146
|
return {
|
|
147
147
|
schema: REVIEW_SCHEMA,
|
|
148
148
|
verdict: args.verdict,
|
|
149
|
-
summary: `The
|
|
149
|
+
summary: `The run failed before the script finished: ${args.sessionError}`,
|
|
150
150
|
gaps: [],
|
|
151
151
|
};
|
|
152
152
|
}
|
|
@@ -154,41 +154,31 @@ function buildScriptedReview(args) {
|
|
|
154
154
|
return {
|
|
155
155
|
schema: REVIEW_SCHEMA,
|
|
156
156
|
verdict: args.verdict,
|
|
157
|
-
summary: `Dry run
|
|
157
|
+
summary: `Dry run: scenario ${args.journey.scenarioId} (${args.scenarioSource}, ${args.journey.steps.length} steps) would replay against ${args.appUrl} on ${plural(args.surfaces.length, "browser surface")}. No browser ran and $0 was spent.`,
|
|
158
158
|
gaps: ["Live scripted session not yet run (dry run only)."],
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
const passed = args.sessionResults.filter((result) => result.status === "passed").length;
|
|
162
|
+
const ran = args.sessionResults.length;
|
|
162
163
|
return {
|
|
163
164
|
schema: REVIEW_SCHEMA,
|
|
164
165
|
verdict: args.verdict,
|
|
165
|
-
summary: `
|
|
166
|
+
summary: `Scenario ${args.journey.scenarioId} ran on ${plural(ran, "browser surface")} against ${args.appUrl}: ${passed}/${ran} passed it${gapsListClause(ran - passed, ran)}.`,
|
|
166
167
|
gaps: args.sessionResults
|
|
167
168
|
.filter((result) => result.status !== "passed")
|
|
168
169
|
.map((result) => `${result.capture.surface.id}: ${result.reason}`),
|
|
169
170
|
};
|
|
170
171
|
}
|
|
171
|
-
export function renderScriptedReviewMarkdown(bundle) {
|
|
172
|
+
export function renderScriptedReviewMarkdown(bundle, status) {
|
|
172
173
|
const subject = bundle.events.find((event) => event.type === "scripted-lab.subject.declared");
|
|
173
174
|
const spend = bundle.events.find((event) => event.type === "scripted-lab.spend");
|
|
174
175
|
const traces = bundle.streams
|
|
175
176
|
.map((stream) => ({ stream, trace: stream.actor }))
|
|
176
177
|
.filter((entry) => entry.trace !== undefined);
|
|
177
|
-
return [
|
|
178
|
-
`# ${bundle.scenario.title}`,
|
|
179
|
-
"",
|
|
180
|
-
`- run: ${bundle.runId}`,
|
|
181
|
-
`- mode: ${bundle.mode}`,
|
|
182
|
-
`- verdict: ${verdictText(bundle.review.verdict, bundle.mode)}`,
|
|
183
|
-
`- outcome: ${reviewOutcome(bundle)}`,
|
|
184
|
-
`- summary: ${bundle.review.summary}`,
|
|
178
|
+
return renderReviewMarkdown(bundle, [
|
|
185
179
|
`- scenario: ${bundle.scenario.id} @ ${bundle.scenario.sourceDigest} (${bundle.scenario.source})`,
|
|
186
180
|
...(subject ? [`- subject: ${subject.message}`] : []),
|
|
187
181
|
...(spend ? [`- spend: ${spend.message}`] : []),
|
|
188
182
|
...traces.map(({ stream, trace }) => `- ${recordIdOf(stream)}: ${trace.provider} (${trace.lane}/${trace.protocol}) ${trace.status} (${trace.completionReason}); ${plural(trace.counts.actions ?? 0, "step action")}, ${plural(trace.counts.screenshots ?? 0, "raw screenshot")}`),
|
|
189
|
-
|
|
190
|
-
? ["", "## Gaps", ...bundle.review.gaps.map((gap) => `- ${gap}`)]
|
|
191
|
-
: []),
|
|
192
|
-
"",
|
|
193
|
-
].join("\n");
|
|
183
|
+
], { status });
|
|
194
184
|
}
|
|
@@ -5,6 +5,7 @@ import { realpath } from "node:fs/promises";
|
|
|
5
5
|
import { firstLiveRefusal, keysCheck, subjectEnvCheck } from "../../study/requirements.js";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { resolveBrowserCommand } from "../../actors/scripted-browser/browser-command.js";
|
|
8
|
+
import { publicSafeToken } from "../../actors/scripted-browser/journey.js";
|
|
8
9
|
import { prepareSelectedOutputDirectory } from "../../run/contained-output.js";
|
|
9
10
|
import { RunSecrets } from "../../run/secrets.js";
|
|
10
11
|
import { evidenceAppUrlOf } from "./plan.js";
|
|
@@ -26,7 +27,16 @@ export async function prepareScriptedRun(plan, input) {
|
|
|
26
27
|
const deps = input.deps ?? {};
|
|
27
28
|
const warnings = [];
|
|
28
29
|
const clone = plan.subject.kind === "clone" ? plan.subject : undefined;
|
|
29
|
-
const
|
|
30
|
+
const subjectEnvNames = [...(clone?.env ?? [])];
|
|
31
|
+
const env = input.env ?? process.env;
|
|
32
|
+
const e2bApiKey = env.E2B_API_KEY?.trim() ?? "";
|
|
33
|
+
const secrets = new RunSecrets([
|
|
34
|
+
e2bApiKey,
|
|
35
|
+
...(clone ? [clone.repo] : []),
|
|
36
|
+
...subjectEnvNames.map((name) => env[name] ?? ""),
|
|
37
|
+
]);
|
|
38
|
+
// An app-url run records the URL it was given, in the bundle and every summary.
|
|
39
|
+
const evidenceAppUrl = secrets.scrub(evidenceAppUrlOf(plan.subject));
|
|
30
40
|
const failed = (code, message) => refusedResult("scripted", { studyId: plan.studyId, cwd, warnings, error: { code, message } }, {
|
|
31
41
|
actor: plan.actor,
|
|
32
42
|
appUrl: evidenceAppUrl,
|
|
@@ -37,16 +47,8 @@ export async function prepareScriptedRun(plan, input) {
|
|
|
37
47
|
const urlPolicy = clone
|
|
38
48
|
? { kind: "provisioned-subject", evidenceOrigin: evidenceAppUrl }
|
|
39
49
|
: { kind: "loopback" };
|
|
40
|
-
const subjectEnvNames = [...(clone?.env ?? [])];
|
|
41
|
-
const env = input.env ?? process.env;
|
|
42
|
-
const e2bApiKey = env.E2B_API_KEY?.trim() ?? "";
|
|
43
50
|
const hasGithubToken = subjectEnvNames.includes("GITHUB_TOKEN");
|
|
44
51
|
const redactRepoLabel = plan.residual.policies?.redactRepos ?? hasGithubToken;
|
|
45
|
-
const secrets = new RunSecrets([
|
|
46
|
-
e2bApiKey,
|
|
47
|
-
...(clone ? [clone.repo] : []),
|
|
48
|
-
...subjectEnvNames.map((name) => env[name] ?? ""),
|
|
49
|
-
]);
|
|
50
52
|
// A clone's URL is replaced by its getHost URL once it is served.
|
|
51
53
|
let appUrl = plan.subject.kind === "clone" ? plan.subject.serve.url : plan.subject.appUrl;
|
|
52
54
|
const scenario = await resolveScriptedScenario(projectRoot, plan.scenarioRef);
|
|
@@ -56,7 +58,17 @@ export async function prepareScriptedRun(plan, input) {
|
|
|
56
58
|
result: failed("HUMANISH_SCRIPTED_SCENARIO_INVALID", scenario.message),
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
// The goal, title and step labels are recorded; a step's path, selector, value and expected text
|
|
62
|
+
// drive the browser and stay as written.
|
|
63
|
+
const journey = {
|
|
64
|
+
...scenario.journey,
|
|
65
|
+
scenarioId: idHoldsKnownValue(scenario.journey.scenarioId, secrets)
|
|
66
|
+
? "scenario"
|
|
67
|
+
: scenario.journey.scenarioId,
|
|
68
|
+
goal: secrets.scrub(scenario.journey.goal),
|
|
69
|
+
scenarioTitle: secrets.scrub(scenario.journey.scenarioTitle),
|
|
70
|
+
steps: recordedStepIds(scenario.journey.steps, secrets),
|
|
71
|
+
};
|
|
60
72
|
// The plan lists E2B_API_KEY and the subject env only for a live clone.
|
|
61
73
|
const { requirements } = plan;
|
|
62
74
|
const refusal = await firstLiveRefusal([
|
|
@@ -118,3 +130,34 @@ export async function prepareScriptedRun(plan, input) {
|
|
|
118
130
|
},
|
|
119
131
|
};
|
|
120
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Whether an id holds a known value as written or as the parser's id token of it, lower-cased with
|
|
135
|
+
* other characters as `-`, which the scrub would not find.
|
|
136
|
+
*/
|
|
137
|
+
function idHoldsKnownValue(id, secrets) {
|
|
138
|
+
return (secrets.scrub(id) !== id ||
|
|
139
|
+
secrets.values().some((value) => {
|
|
140
|
+
const token = publicSafeToken(value, "");
|
|
141
|
+
return token.length > 0 && id.includes(token);
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The steps with their labels scrubbed and their ids safe to record. A step id names the step's
|
|
146
|
+
* screenshot file and is recorded beside it, and the parser derives an omitted id from the label,
|
|
147
|
+
* lower-cased and cut to 80 characters, so the scrub may not find a value in it. A step whose
|
|
148
|
+
* label or id holds a known value is renamed by its position instead.
|
|
149
|
+
*/
|
|
150
|
+
function recordedStepIds(steps, secrets) {
|
|
151
|
+
const taken = new Set(steps.map((step) => step.id));
|
|
152
|
+
return steps.map((step, index) => {
|
|
153
|
+
const label = secrets.scrub(step.label);
|
|
154
|
+
if (label === step.label && !idHoldsKnownValue(step.id, secrets))
|
|
155
|
+
return { ...step, label };
|
|
156
|
+
const position = `step-${String(index + 1).padStart(2, "0")}`;
|
|
157
|
+
let id = position;
|
|
158
|
+
for (let suffix = 2; taken.has(id); suffix += 1)
|
|
159
|
+
id = `${position}-${suffix}`;
|
|
160
|
+
taken.add(id);
|
|
161
|
+
return { ...step, id, label };
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -6,7 +6,6 @@ import { type ParticipantIds } from "../../run/participant-records.js";
|
|
|
6
6
|
import type { RunStream } from "../../run/streams.js";
|
|
7
7
|
/** What each surface's records read from the bundle arguments. */
|
|
8
8
|
export interface ScriptedSurfaceContext {
|
|
9
|
-
actorId: string;
|
|
10
9
|
appUrl: string;
|
|
11
10
|
createdAt: string;
|
|
12
11
|
journey: BrowserPersonaJourney;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { judgeParticipantRecords } from "../../run/judge.js";
|
|
2
2
|
import { participantIdsOf, participantRecord, participantStream, } from "../../run/participant-records.js";
|
|
3
|
+
import { participantCaption } from "../../run/participant-caption.js";
|
|
4
|
+
import { sessionEndingInWords } from "../../run/outcomes.js";
|
|
3
5
|
/** A surface's saved ids: `scripted-<surface>` and its `-stream`. */
|
|
4
6
|
export function scriptedSurfaceIds(surfaceId) {
|
|
5
7
|
return participantIdsOf(`scripted-${surfaceId}`, `scripted-${surfaceId}-stream`);
|
|
@@ -11,14 +13,23 @@ export function scriptedSurfaceIds(surfaceId) {
|
|
|
11
13
|
export function scriptedSurfaceRecords(context, surface, index, result, screenshots) {
|
|
12
14
|
const ids = scriptedSurfaceIds(surface.id);
|
|
13
15
|
const lastScreenshot = screenshots.at(-1);
|
|
14
|
-
const status =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
const { status, reason } = judgeParticipantRecords([
|
|
17
|
+
{
|
|
18
|
+
...(result === undefined
|
|
19
|
+
? {}
|
|
20
|
+
: {
|
|
21
|
+
status: result.status,
|
|
22
|
+
completionReason: result.completionReason,
|
|
23
|
+
reason: result.reason,
|
|
24
|
+
}),
|
|
25
|
+
...(context.sessionError === undefined ? {} : { sessionError: context.sessionError }),
|
|
26
|
+
skipped: false,
|
|
27
|
+
noEngagement: false,
|
|
28
|
+
selfReportedBlocker: false,
|
|
29
|
+
},
|
|
30
|
+
], {
|
|
31
|
+
contractReason: "Dry run: the scenario was pinned without launching a browser or touching the subject app.",
|
|
32
|
+
}).participants[0];
|
|
22
33
|
const simulation = participantRecord(ids, index + 1, {
|
|
23
34
|
personaId: context.persona.id,
|
|
24
35
|
scenarioId: context.journey.scenarioId,
|
|
@@ -28,16 +39,19 @@ export function scriptedSurfaceRecords(context, surface, index, result, screensh
|
|
|
28
39
|
progress: 100,
|
|
29
40
|
currentStep: reason,
|
|
30
41
|
summary: result
|
|
31
|
-
? `
|
|
42
|
+
? `Scenario ${context.journey.scenarioId} ran in the ${surface.id} browser and ${sessionEndingInWords(result.trace)}.`
|
|
32
43
|
: context.sessionError !== undefined
|
|
33
|
-
? `The
|
|
34
|
-
: `
|
|
44
|
+
? `The run failed before the script finished: ${context.sessionError}`
|
|
45
|
+
: `Scenario ${context.journey.scenarioId} would replay in the ${surface.id} browser against ${context.appUrl}; no browser ran.`,
|
|
35
46
|
startedAt: context.createdAt,
|
|
36
47
|
updatedAt: result?.capture.capturedAt ?? context.createdAt,
|
|
37
48
|
});
|
|
38
49
|
const stream = participantStream(ids, {
|
|
39
50
|
kind: "browser",
|
|
40
|
-
label:
|
|
51
|
+
label: participantCaption({
|
|
52
|
+
personaId: context.persona.id,
|
|
53
|
+
device: { name: surface.id, preset: { isMobile: surface.viewport.isMobile } },
|
|
54
|
+
}),
|
|
41
55
|
status,
|
|
42
56
|
transport: "snapshot",
|
|
43
57
|
updatedAt: result?.capture.capturedAt ?? context.createdAt,
|
|
@@ -3,7 +3,7 @@ import type { ScriptedPlan } from "../../study/plan-types.js";
|
|
|
3
3
|
import { type PreparedRunArtifactPaths } from "../../run/paths.js";
|
|
4
4
|
import type { StudyDeps } from "../../study/study-deps.js";
|
|
5
5
|
/** One session per surface, in parallel. */
|
|
6
|
-
export declare function runScriptedSessions(session: Required<Pick<ScriptedBrowserSessionOptions, "appUrl" | "evidenceAppUrl" | "urlPolicy" | "journey" | "persona" | "timeoutMs" | "artifactRoot">>, run: {
|
|
6
|
+
export declare function runScriptedSessions(session: Required<Pick<ScriptedBrowserSessionOptions, "appUrl" | "evidenceAppUrl" | "urlPolicy" | "journey" | "persona" | "timeoutMs" | "artifactRoot" | "scrubKnownValues">>, run: {
|
|
7
7
|
surfaces: ScriptedPlan["surfaces"];
|
|
8
8
|
deps: StudyDeps;
|
|
9
9
|
browserCommand: string | undefined;
|
|
@@ -7,6 +7,7 @@ import { validateScriptedSessionResult } from "./session-result.js";
|
|
|
7
7
|
/** One session per surface, in parallel. */
|
|
8
8
|
export function runScriptedSessions(session, run) {
|
|
9
9
|
const { appUrl, evidenceAppUrl, urlPolicy, journey, persona, timeoutMs, artifactRoot } = session;
|
|
10
|
+
const { scrubKnownValues } = session;
|
|
10
11
|
const { surfaces, deps, browserCommand, runPaths, onSessionStart } = run;
|
|
11
12
|
const runSession = deps.runScriptedSession;
|
|
12
13
|
return Promise.all(surfaces.map((surface) => {
|
|
@@ -20,6 +21,7 @@ export function runScriptedSessions(session, run) {
|
|
|
20
21
|
persona,
|
|
21
22
|
timeoutMs,
|
|
22
23
|
artifactRoot,
|
|
24
|
+
scrubKnownValues,
|
|
23
25
|
...(browserCommand === undefined ? {} : { browserCommand }),
|
|
24
26
|
...(deps.launchBrowser === undefined ? {} : { launchBrowser: deps.launchBrowser }),
|
|
25
27
|
...(deps.now === undefined ? {} : { now: deps.now }),
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { RunEvent, RunSimulation } from "../../run/bundle.js";
|
|
2
|
+
import type { judgeParticipantRecords } from "../../run/judge.js";
|
|
2
3
|
import type { RunStream } from "../../run/streams.js";
|
|
3
4
|
import type { DesktopParticipantRun } from "../computer-use/types.js";
|
|
4
5
|
import type { ConcurrentBundleArgs } from "./types.js";
|
|
5
6
|
/** What every participant's records share. */
|
|
6
7
|
export interface SharedWorldRecordContext {
|
|
7
8
|
args: ConcurrentBundleArgs;
|
|
9
|
+
participants: ReturnType<typeof judgeParticipantRecords>["participants"];
|
|
8
10
|
external: boolean;
|
|
9
11
|
inProgress: boolean;
|
|
10
|
-
/** The public-safe plane label: never the raw getHost URL or public origin. */
|
|
11
|
-
appUrl: string;
|
|
12
12
|
/** Numbers events in the order they are pushed. */
|
|
13
13
|
nextEventId: (suffix: string) => string;
|
|
14
14
|
}
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
// running).
|
|
4
4
|
import { participantAssignment } from "../../study/participant-assignment.js";
|
|
5
5
|
import { participantEvent, participantRecord, participantStream, } from "../../run/participant-records.js";
|
|
6
|
-
import { judgedStatus } from "../../run/judge.js";
|
|
7
6
|
import { declaredScreenForRender } from "../../substrates/e2b/desktop-geometry.js";
|
|
8
|
-
import { participantFactsOf } from "../computer-use/participant-facts.js";
|
|
9
7
|
import { publicSafeRouteLabel } from "./provenance.js";
|
|
10
|
-
import {
|
|
8
|
+
import { participantCaption } from "../../run/participant-caption.js";
|
|
9
|
+
import { sessionEndingInWords } from "../../run/outcomes.js";
|
|
11
10
|
function participantView(ctx, spec, index) {
|
|
12
|
-
const { args, external
|
|
13
|
-
const
|
|
11
|
+
const { args, external } = ctx;
|
|
12
|
+
const name = participantCaption({ id: spec.planned.id, personaId: spec.persona.id });
|
|
14
13
|
const result = args.actorResults[index];
|
|
15
14
|
const outcome = result?.outcome;
|
|
16
15
|
const session = outcome?.session;
|
|
@@ -21,18 +20,8 @@ function participantView(ctx, spec, index) {
|
|
|
21
20
|
const route = external
|
|
22
21
|
? "[external-public-plane]"
|
|
23
22
|
: publicSafeRouteLabel(args.plan.plane.participants[index]?.entry);
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
: outcome?.sessionError !== undefined
|
|
27
|
-
? "failed"
|
|
28
|
-
: inProgress
|
|
29
|
-
? "running"
|
|
30
|
-
: "contract_proof_only";
|
|
31
|
-
const reason = session?.reason ??
|
|
32
|
-
outcome?.sessionError ??
|
|
33
|
-
(inProgress
|
|
34
|
-
? "Actor desktop is running; the attached Observer hydrates the runtime stream URL without persisting it."
|
|
35
|
-
: "Dry run: the evidence shape was written without launching a desktop or spending provider tokens.");
|
|
23
|
+
const judgment = ctx.participants[index];
|
|
24
|
+
const { status, reason } = judgment;
|
|
36
25
|
const traceScreenshotMode = session?.trace.redaction.screenshots;
|
|
37
26
|
// Include `declared` on the no-outcome fallback too (dry-run, skipped participant): otherwise an
|
|
38
27
|
// absent `declared` means either "the preset rendered faithfully" or "there was no live
|
|
@@ -53,7 +42,8 @@ function participantView(ctx, spec, index) {
|
|
|
53
42
|
? "blurred"
|
|
54
43
|
: "raw";
|
|
55
44
|
return {
|
|
56
|
-
|
|
45
|
+
name,
|
|
46
|
+
judgment,
|
|
57
47
|
outcome,
|
|
58
48
|
session,
|
|
59
49
|
screenshots,
|
|
@@ -67,7 +57,7 @@ function participantView(ctx, spec, index) {
|
|
|
67
57
|
}
|
|
68
58
|
function sharedWorldSimulation(ctx, spec, index, view) {
|
|
69
59
|
const { args, inProgress } = ctx;
|
|
70
|
-
const {
|
|
60
|
+
const { name, outcome, session } = view;
|
|
71
61
|
return participantRecord(spec, index + 1, {
|
|
72
62
|
personaId: spec.persona.id,
|
|
73
63
|
scenarioId: `concurrent-shared-world-${args.plan.studyId}`,
|
|
@@ -77,27 +67,30 @@ function sharedWorldSimulation(ctx, spec, index, view) {
|
|
|
77
67
|
progress: inProgress ? 35 : 100,
|
|
78
68
|
currentStep: view.reason,
|
|
79
69
|
summary: session
|
|
80
|
-
?
|
|
70
|
+
? `${name} used the shared app and ${sessionEndingInWords(session.trace)}.`
|
|
81
71
|
: outcome?.sessionError !== undefined
|
|
82
|
-
?
|
|
72
|
+
? `${name} did not finish a session: ${outcome.sessionError}`
|
|
83
73
|
: inProgress
|
|
84
|
-
?
|
|
85
|
-
:
|
|
74
|
+
? `${name} is using the shared app.`
|
|
75
|
+
: `${name} would use the shared app; no session ran.`,
|
|
86
76
|
startedAt: args.run.createdAt,
|
|
87
77
|
updatedAt: args.run.createdAt,
|
|
88
78
|
});
|
|
89
79
|
}
|
|
90
80
|
function sharedWorldStream(ctx, spec, index, view) {
|
|
91
81
|
const { args } = ctx;
|
|
92
|
-
const {
|
|
93
|
-
|
|
94
|
-
const judged = view.outcome === undefined ? undefined : judgedStatus(participantFactsOf(view.outcome));
|
|
82
|
+
const { name, session, screenshots, lastScreenshot, desktopGeometry, screenshotMode } = view;
|
|
83
|
+
const judged = view.judgment.judgedStatus;
|
|
95
84
|
return participantStream(spec, {
|
|
96
85
|
...(spec.evidenceAssignment === undefined
|
|
97
86
|
? {}
|
|
98
87
|
: { assignment: participantAssignment(spec.evidenceAssignment) }),
|
|
99
88
|
kind: "browser",
|
|
100
|
-
label:
|
|
89
|
+
label: participantCaption({
|
|
90
|
+
id: spec.planned.id,
|
|
91
|
+
personaId: spec.persona.id,
|
|
92
|
+
device: spec.planned.device,
|
|
93
|
+
}),
|
|
101
94
|
status: view.status,
|
|
102
95
|
...(judged === undefined ? {} : { judgedStatus: judged }),
|
|
103
96
|
transport: "snapshot",
|
|
@@ -122,7 +115,7 @@ function sharedWorldStream(ctx, spec, index, view) {
|
|
|
122
115
|
desktopGeometry,
|
|
123
116
|
ui: {
|
|
124
117
|
route: view.route,
|
|
125
|
-
intent: `Watch
|
|
118
|
+
intent: `Watch ${name} use the shared app at the same time as the others.`,
|
|
126
119
|
state: view.reason,
|
|
127
120
|
...(session ? { actorStatus: session.status } : {}),
|
|
128
121
|
...(lastScreenshot ? { screenshotUrl: lastScreenshot } : {}),
|
|
@@ -17,4 +17,4 @@ export declare function judgeSharedWorldRun(args: Omit<ConcurrentBundleArgs, "ju
|
|
|
17
17
|
export declare function actorRunPassed(result: ActorRunResult | undefined): boolean;
|
|
18
18
|
/** Project the concurrent run into a humanish.run-bundle.v1 with the concurrent shared-world block. */
|
|
19
19
|
export declare function buildConcurrentSharedWorldBundle(args: ConcurrentBundleArgs): RunBundle;
|
|
20
|
-
export declare function renderConcurrentReviewMarkdown(bundle: RunBundle): string;
|
|
20
|
+
export declare function renderConcurrentReviewMarkdown(bundle: RunBundle, status?: unknown): string;
|