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
|
@@ -9,13 +9,14 @@ import { commandDigestOf } from "../../subject/state.js";
|
|
|
9
9
|
import { buildRunCostSummary, desktopSpanToMinutes } from "../../run/cost-summary.js";
|
|
10
10
|
import { participantFactsOf } from "../computer-use/participant-facts.js";
|
|
11
11
|
import { participantFeedbackCandidates } from "../computer-use/participant-feedback.js";
|
|
12
|
-
import { judgeSharedWorld, participantPassed, sharedWorldShortfall,
|
|
13
|
-
import {
|
|
12
|
+
import { judgeSharedWorld, judgeParticipantRecords, participantPassed, sharedWorldShortfall, } from "../../run/judge.js";
|
|
13
|
+
import { renderReviewMarkdown } from "../../run/review-markdown.js";
|
|
14
14
|
import { combineCheckpointDigest } from "./checkpoints.js";
|
|
15
15
|
import { hostOriginDigest } from "./provenance.js";
|
|
16
16
|
import { participantEvent, participantIds } from "../../run/participant-records.js";
|
|
17
17
|
import { sharedWorldParticipantRecords } from "./bundle-records.js";
|
|
18
18
|
import { CONCURRENT_ATTRIBUTION_LIMITS, EXTERNAL_PUBLIC_ATTRIBUTION_LIMITS, } from "./types.js";
|
|
19
|
+
import { gapsListClause } from "../../run/outcomes.js";
|
|
19
20
|
import { plural } from "../../run/text.js";
|
|
20
21
|
/** Max windows live at the same instant (sweep over start/end points). The simultaneity
|
|
21
22
|
* count: the participant count says how many existed; this says how many ever ran at once. */
|
|
@@ -63,33 +64,39 @@ export function actorRunPassed(result) {
|
|
|
63
64
|
return result !== undefined && participantPassed(participantFactsOf(result.outcome));
|
|
64
65
|
}
|
|
65
66
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* None of those is adopter-scored proof that the mission text was completed. In particular, a
|
|
73
|
-
* productive `budget_reached` session can coexist with lobby convergence without becoming a
|
|
74
|
-
* `goal_satisfied` result.
|
|
67
|
+
* The run's summary in plain sentences: who took part, how many reached the goal, and what the
|
|
68
|
+
* world showed. A participant reached the goal when its session passed the judge's checks and
|
|
69
|
+
* ended at the goal, so a productive `budget_reached` session does not count even when the lobby
|
|
70
|
+
* converged. Each participant's ending stays in `sharedWorld.outcomes`, and the plane, topology
|
|
71
|
+
* and attribution limits stay in the bundle's events and `sharedWorld` block.
|
|
75
72
|
*/
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
73
|
+
function sharedWorldSummary(args, facts) {
|
|
74
|
+
const count = args.actorSpecs.length;
|
|
75
|
+
const people = plural(count, "participant");
|
|
76
|
+
const app = facts.external ? "one public app" : "one shared app";
|
|
77
|
+
if (args.dryRun) {
|
|
78
|
+
const served = facts.external ? ", as it is deployed" : ", served from a sandbox";
|
|
79
|
+
return `Dry run: ${people} would use ${app} at the same time${served}. No sandboxes were launched and $0 was spent.`;
|
|
83
80
|
}
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
if (facts.inProgress) {
|
|
82
|
+
return `${people} ${count === 1 ? "is" : "are"} using ${app} at the same time. The run is still going, so nothing here is final.`;
|
|
86
83
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
const passed = facts.outcomes.filter((outcome) => outcome.ok);
|
|
85
|
+
const reached = passed.filter((outcome) => outcome.completionReason === "goal_satisfied").length;
|
|
86
|
+
const together = facts.overlaps ? " at the same time" : ", but never at the same time";
|
|
87
|
+
// A participant who did not pass has a gap line; one who passed but stopped short has none.
|
|
88
|
+
const rest = [
|
|
89
|
+
passed.length > reached ? `; ${passed.length - reached} ended without reaching it` : "",
|
|
90
|
+
gapsListClause(count - passed.length, count),
|
|
91
|
+
].join("");
|
|
92
|
+
const world = facts.external
|
|
93
|
+
? args.lobbyConvergenceDigest
|
|
94
|
+
? `All ${count} ended up in the same lobby.`
|
|
95
|
+
: "humanish did not see them all end up in the same lobby."
|
|
96
|
+
: facts.deltas === 0
|
|
97
|
+
? "The app's shared state did not change during the run."
|
|
98
|
+
: `The app's shared state changed ${plural(facts.deltas, "time")} during the run.`;
|
|
99
|
+
return `${people} used ${app}${together}. ${reached} of ${count} reached the goal${rest}. ${world}`;
|
|
93
100
|
}
|
|
94
101
|
/** The run's first two events: its creation and the shared plane's provenance. */
|
|
95
102
|
function planeEvents(args, external) {
|
|
@@ -234,19 +241,13 @@ function sharedWorldEvidence(args, external, inProgress, planeCommit) {
|
|
|
234
241
|
* A finished run's gaps: each participant that did not pass, or, when every participant passed, the
|
|
235
242
|
* world shortfall that failed the run.
|
|
236
243
|
*/
|
|
237
|
-
function finishedGaps(
|
|
238
|
-
const participantGaps =
|
|
239
|
-
.filter((result) => result.outcome.sessionError !== undefined ||
|
|
240
|
-
result.outcome.noEngagement ||
|
|
241
|
-
result.outcome.selfReportedBlocker ||
|
|
242
|
-
result.outcome.session === undefined ||
|
|
243
|
-
result.outcome.session.status !== "passed")
|
|
244
|
-
.map((result) => `${result.spec.planned.id}: ${result.outcome.sessionError ?? result.outcome.session?.reason ?? "did not pass"}`);
|
|
244
|
+
function finishedGaps(participants, shortfall) {
|
|
245
|
+
const participantGaps = participants.flatMap((participant) => participant.gapLine === undefined ? [] : [participant.gapLine]);
|
|
245
246
|
return participantGaps.length === 0 && shortfall !== undefined ? [shortfall] : participantGaps;
|
|
246
247
|
}
|
|
247
248
|
/** Records the concurrency event and returns the review. */
|
|
248
|
-
function concurrencyReview(args, external, inProgress, evidence, events, nextEventId) {
|
|
249
|
-
const { plan,
|
|
249
|
+
function concurrencyReview(args, external, inProgress, evidence, events, nextEventId, participants) {
|
|
250
|
+
const { plan, run: { createdAt }, dryRun, } = args;
|
|
250
251
|
const { sharedWorld, windows, stateSeries, outcomes } = evidence;
|
|
251
252
|
const overlaps = args.judgment.world.overlap;
|
|
252
253
|
const deltas = (stateSeries ?? []).filter((snapshot, i) => i > 0 && snapshot.digest !== (stateSeries ?? [])[i - 1].digest).length;
|
|
@@ -268,25 +269,12 @@ function concurrencyReview(args, external, inProgress, evidence, events, nextEve
|
|
|
268
269
|
message: `Concurrency: ${plural(windows.length, "participant")}${dryRun ? " (dry run; $0)" : `, up to ${maxLive} live at once (cap ${capForReport}), overlap ${overlaps ? "shown" : "not observed"}`}; ${stateSeriesLabel}${convergenceLabel}. Attribution ceiling: ${sharedWorld.attributionLimits.join(", ")}. ${dryRun ? "This dry run proves nothing about live concurrency, scale, or adoption." : "This run reports only its own observed overlap and state changes; it does not prove scale, repeatability, or adopter-harness replacement."}`,
|
|
269
270
|
});
|
|
270
271
|
// The judge's verdict (judgeSharedWorld): every participant produced a terminal, engaged, passed
|
|
271
|
-
// session
|
|
272
|
-
// is not renamed into mission success.
|
|
273
|
-
const verdict = args.judgment.verdict;
|
|
274
|
-
const actorOutcomeSummary = formatSharedWorldActorOutcomes(outcomes, actorSpecs.length);
|
|
272
|
+
// session and the world showed the concurrency verify requires.
|
|
275
273
|
const review = {
|
|
276
274
|
schema: REVIEW_SCHEMA,
|
|
277
|
-
verdict,
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
// report "state delta(s) under load" (live). It reports lobby convergence instead.
|
|
281
|
-
summary: dryRun
|
|
282
|
-
? external
|
|
283
|
-
? `Concurrent shared-world dry run: ${plural(actorSpecs.length, "persona")} declared against one public deployment, used as it is (no sandbox, clone or seed); no sandboxes launched, $0 spent.`
|
|
284
|
-
: `Concurrent shared-world dry run: ${plural(actorSpecs.length, "persona")} declared against one app served at a public sandbox URL (${descriptor.id}); no sandboxes launched, $0 spent.`
|
|
285
|
-
: inProgress
|
|
286
|
-
? `In-progress concurrent shared-world Observer snapshot: ${plural(actorSpecs.length, "persona")} running against one shared app; final verification is pending.`
|
|
287
|
-
: external
|
|
288
|
-
? `Concurrent shared-world (one public deployment, ${actorSpecs.length} personas at once): swarm ${verdict === "pass" ? "ran coherently" : "did not run coherently"}; ${actorOutcomeSummary}; overlap ${overlaps ? "proven" : "not observed"}; ${args.lobbyConvergenceDigest ? `${actorSpecs.length} participants converged on one lobby` : "lobby convergence not observed"}.`
|
|
289
|
-
: `Concurrent shared-world (one shared app, ${actorSpecs.length} personas at once): swarm ${verdict === "pass" ? "ran coherently" : "did not run coherently"}; ${actorOutcomeSummary}; overlap ${overlaps ? "proven" : "not observed"}; ${plural(deltas, "state delta")} under load.`,
|
|
275
|
+
verdict: args.judgment.verdict,
|
|
276
|
+
// The external-public app has no state series, so its summary reports the lobby instead.
|
|
277
|
+
summary: sharedWorldSummary(args, { external, inProgress, overlaps, deltas, outcomes }),
|
|
290
278
|
gaps: dryRun
|
|
291
279
|
? [
|
|
292
280
|
"This dry run launched no concurrent shared-world session; it checks the evidence shape only, not live behavior, scale, or adopter-harness replacement.",
|
|
@@ -295,7 +283,7 @@ function concurrencyReview(args, external, inProgress, evidence, events, nextEve
|
|
|
295
283
|
? [
|
|
296
284
|
"Final actor traces, screenshots, state deltas, and verification are pending; this Observer is for live watch only.",
|
|
297
285
|
]
|
|
298
|
-
: finishedGaps(
|
|
286
|
+
: finishedGaps(participants, sharedWorldShortfall(args.judgment.world)),
|
|
299
287
|
};
|
|
300
288
|
return review;
|
|
301
289
|
}
|
|
@@ -306,15 +294,26 @@ export function buildConcurrentSharedWorldBundle(args) {
|
|
|
306
294
|
const external = (args.planeClass ?? "provisioned-getHost") === "external-public";
|
|
307
295
|
const simulations = [];
|
|
308
296
|
const streams = [];
|
|
309
|
-
// Public-safe label only: neither the raw getHost URL (provisioned) nor the raw public origin
|
|
310
|
-
// (external-public) lands in the bundle. The plane identity is a digest (plane.hostDigest on
|
|
311
|
-
// getHost; plane.publicOriginDigest on external-public).
|
|
312
|
-
const appUrl = external ? "[external-public-plane]" : "[provisioned-subject]";
|
|
313
297
|
const planeCommit = external ? undefined : dryRun ? undefined : args.subjectCommit;
|
|
314
298
|
const events = planeEvents(args, external);
|
|
315
299
|
let eventSeq = 2;
|
|
316
300
|
const nextEventId = (suffix) => `event-${String(eventSeq++).padStart(3, "0")}-${suffix}`;
|
|
317
|
-
const
|
|
301
|
+
const judgment = judgeParticipantRecords(actorSpecs.map((spec, index) => ({
|
|
302
|
+
...participantFactsOf(actorResults[index]?.outcome),
|
|
303
|
+
id: spec.planned.id,
|
|
304
|
+
inProgress: inProgress && actorResults[index] === undefined,
|
|
305
|
+
})), {
|
|
306
|
+
runningReason: "Actor desktop is running; the attached Observer hydrates the runtime stream URL without persisting it.",
|
|
307
|
+
sessionLabel: "Participant",
|
|
308
|
+
missingSessionMessage: "Actor did not produce a terminal session.",
|
|
309
|
+
});
|
|
310
|
+
const recordContext = {
|
|
311
|
+
args,
|
|
312
|
+
external,
|
|
313
|
+
inProgress,
|
|
314
|
+
nextEventId,
|
|
315
|
+
participants: judgment.participants,
|
|
316
|
+
};
|
|
318
317
|
actorSpecs.forEach((spec, index) => {
|
|
319
318
|
const records = sharedWorldParticipantRecords(recordContext, spec, index);
|
|
320
319
|
simulations.push(records.simulation);
|
|
@@ -323,7 +322,7 @@ export function buildConcurrentSharedWorldBundle(args) {
|
|
|
323
322
|
});
|
|
324
323
|
const evidence = sharedWorldEvidence(args, external, inProgress, planeCommit);
|
|
325
324
|
const { sharedWorld } = evidence;
|
|
326
|
-
const review = concurrencyReview(args, external, inProgress, evidence, events, nextEventId);
|
|
325
|
+
const review = concurrencyReview(args, external, inProgress, evidence, events, nextEventId, judgment.participants.filter((_, index) => actorResults[index] !== undefined));
|
|
327
326
|
const anyRaw = actorResults.some((result) => result.outcome.session?.trace.redaction.screenshots === "raw");
|
|
328
327
|
const ranLive = actorResults.some((result) => result.outcome.session !== undefined || result.outcome.sessionError !== undefined);
|
|
329
328
|
const cost = concurrentCostSummary(args, inProgress);
|
|
@@ -335,7 +334,7 @@ export function buildConcurrentSharedWorldBundle(args) {
|
|
|
335
334
|
}),
|
|
336
335
|
persona: {
|
|
337
336
|
id: actorSpecs[0]?.persona.id ?? "concurrent-persona",
|
|
338
|
-
name:
|
|
337
|
+
name: `${plural(actorSpecs.length, "participant")} in one ${external ? "public" : "shared"} app`,
|
|
339
338
|
source: `study:${plan.studyId}`,
|
|
340
339
|
sourceDigest: actorSpecs[0]?.persona.promptDigest ?? args.seedDigest,
|
|
341
340
|
},
|
|
@@ -456,27 +455,20 @@ function declaredStateDigest(state) {
|
|
|
456
455
|
const probes = state?.checkpoint ?? [];
|
|
457
456
|
return combineCheckpointDigest(probes.map((probe) => `${probe.name}=${commandDigestOf(probe.command)}`));
|
|
458
457
|
}
|
|
459
|
-
export function renderConcurrentReviewMarkdown(bundle) {
|
|
458
|
+
export function renderConcurrentReviewMarkdown(bundle, status) {
|
|
460
459
|
const plane = bundle.events.find((event) => event.type === "concurrent-shared-world.plane.provenance");
|
|
461
460
|
const concurrency = bundle.events.find((event) => event.type === "concurrent-shared-world.concurrency");
|
|
462
461
|
const sw = bundle.sharedWorld;
|
|
463
|
-
return [
|
|
464
|
-
`# ${bundle.scenario.title}`,
|
|
465
|
-
"",
|
|
466
|
-
`- run: ${bundle.runId}`,
|
|
467
|
-
`- mode: ${bundle.mode}`,
|
|
468
|
-
`- attribution class: ${bundle.attributionClass ?? "isolated"}`,
|
|
469
|
-
`- topology: ${sw?.topology ?? "(none)"} / ${sw?.topologyMode ?? "(none)"}`,
|
|
470
|
-
`- personas: ${sw?.roleCount ?? 0}`,
|
|
471
|
-
`- verdict: ${verdictText(bundle.review.verdict, bundle.mode)}`,
|
|
472
|
-
`- outcome: ${reviewOutcome(bundle)}`,
|
|
473
|
-
`- summary: ${bundle.review.summary}`,
|
|
462
|
+
return renderReviewMarkdown(bundle, [
|
|
474
463
|
...(plane ? [`- plane: ${plane.message}`] : []),
|
|
475
464
|
...(concurrency ? [`- concurrency: ${concurrency.message}`] : []),
|
|
476
465
|
...(sw ? [`- attribution limits: ${sw.attributionLimits.join(", ")}`] : []),
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
466
|
+
], {
|
|
467
|
+
status,
|
|
468
|
+
beforeVerdict: [
|
|
469
|
+
`- attribution class: ${bundle.attributionClass ?? "isolated"}`,
|
|
470
|
+
`- topology: ${sw?.topology ?? "(none)"} / ${sw?.topologyMode ?? "(none)"}`,
|
|
471
|
+
`- personas: ${sw?.roleCount ?? 0}`,
|
|
472
|
+
],
|
|
473
|
+
});
|
|
482
474
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SharedWorldPlan } from "../../study/plan-types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SharedWorldParticipant } from "../../study/plan-participants.js";
|
|
3
3
|
import type { RunBundle } from "../../run/bundle.js";
|
|
4
4
|
import type { DesktopParticipantRun, ParticipantRunOutcome } from "../computer-use/types.js";
|
|
5
5
|
import { type DesktopParticipantDeps } from "../computer-use/participant-deps.js";
|
|
@@ -8,7 +8,6 @@ export declare function defaultSessionTimeoutMs(plan: SharedWorldPlan): number;
|
|
|
8
8
|
/** Resolve an actor's participant URL against the harness-minted getHost base (entry is a
|
|
9
9
|
* same-origin relative path, validated at parse against serve.url). */
|
|
10
10
|
export declare function resolveActorEntryUrl(baseUrl: string, entry: string | undefined): string;
|
|
11
|
-
export declare function participantTaxonomyLabel(labels: Participant["labels"]): string;
|
|
12
11
|
/** Thread the host-yielded lobby code into a follower's mission at runtime (external-public route).
|
|
13
12
|
* The code flows into the follower's join instruction; it is persisted only as the composed prompt
|
|
14
13
|
* the model reads (never a raw bundle field), and humanish scrubs the code from all narration. The
|
|
@@ -45,14 +45,6 @@ export function resolveActorEntryUrl(baseUrl, entry) {
|
|
|
45
45
|
return baseUrl;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
export function participantTaxonomyLabel(labels) {
|
|
49
|
-
const parts = [
|
|
50
|
-
labels.actorType ? `type:${labels.actorType}` : undefined,
|
|
51
|
-
labels.surface ? `surface:${labels.surface}` : undefined,
|
|
52
|
-
labels.caseGroup ? `case:${labels.caseGroup}` : undefined,
|
|
53
|
-
].filter((part) => part !== undefined);
|
|
54
|
-
return parts.length > 0 ? ` (${parts.join(" / ")})` : "";
|
|
55
|
-
}
|
|
56
48
|
/** Build one participant's DesktopParticipantRun from its plan (each participant has its own
|
|
57
49
|
* desktop, so its own device). */
|
|
58
50
|
function buildActorSpec(participant, personas) {
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import { redactText } from "../../evidence/redaction.js";
|
|
4
4
|
import { adapterScoreFailureMessage, applyBrowserScorer } from "../../study/adapter-extension.js";
|
|
5
5
|
import { attachObserverRuntimeStreamUrls } from "../../observer/render.js";
|
|
6
|
-
import { foldScorerFailures, judgeExecution, sandboxCleanupFailure, OUTCOME_POLICIES, resultOk, sharedWorldShortfall, } from "../../run/judge.js";
|
|
6
|
+
import { foldScorerFailures, judgeParticipantRecords, judgeExecution, sandboxCleanupFailure, OUTCOME_POLICIES, resultOk, sharedWorldShortfall, } from "../../run/judge.js";
|
|
7
7
|
import { validatePreparedRunArtifactPaths } from "../../run/paths.js";
|
|
8
|
-
import { participantExecutionFailures } from "../computer-use/participant-facts.js";
|
|
8
|
+
import { participantExecutionFailures, participantFactsOf, } from "../computer-use/participant-facts.js";
|
|
9
9
|
import { participantCapWarning } from "../computer-use/participant-model.js";
|
|
10
10
|
import { resolveSubjectState } from "../computer-use/subject-projection.js";
|
|
11
11
|
import { actorRunPassed, buildConcurrentSharedWorldBundle, judgeSharedWorldRun, maxSimultaneousWindows, } from "./bundle.js";
|
|
@@ -70,14 +70,10 @@ function concurrentParticipantResults(actorSpecs, actorResults, dryRun) {
|
|
|
70
70
|
: {
|
|
71
71
|
error: {
|
|
72
72
|
code: "HUMANISH_SHARED_WORLD_FAILED",
|
|
73
|
-
message: result.outcome
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
? "Actor reported goal_satisfied while its final message described a blocker or asked for missing instructions; not a credible pass."
|
|
78
|
-
: session?.completionReason === "harness_error"
|
|
79
|
-
? `Participant ended with a harness error: ${session.reason}`
|
|
80
|
-
: "Actor did not produce a terminal session."),
|
|
73
|
+
message: judgeParticipantRecords([participantFactsOf(result.outcome)], {
|
|
74
|
+
sessionLabel: "Participant",
|
|
75
|
+
missingSessionMessage: "Actor did not produce a terminal session.",
|
|
76
|
+
}).participants[0].notPassedMessage,
|
|
81
77
|
},
|
|
82
78
|
}),
|
|
83
79
|
};
|
|
@@ -126,7 +122,7 @@ function concurrentStudyError(args) {
|
|
|
126
122
|
if (passed === participantCount && args.shortfall !== undefined) {
|
|
127
123
|
return {
|
|
128
124
|
code: "HUMANISH_SHARED_WORLD_FAILED",
|
|
129
|
-
message: `
|
|
125
|
+
message: `The shared-world run failed although every participant passed. ${args.shortfall}`,
|
|
130
126
|
};
|
|
131
127
|
}
|
|
132
128
|
if (passed === participantCount && args.executionFailure !== undefined) {
|
|
@@ -137,7 +133,7 @@ function concurrentStudyError(args) {
|
|
|
137
133
|
}
|
|
138
134
|
return {
|
|
139
135
|
code: "HUMANISH_SHARED_WORLD_FAILED",
|
|
140
|
-
message: `
|
|
136
|
+
message: `The shared-world run failed: ${passed} of ${plural(participantCount, "participant")} passed. The run's review.md lists what happened to the others.`,
|
|
141
137
|
};
|
|
142
138
|
}
|
|
143
139
|
/**
|
|
@@ -71,4 +71,4 @@ export declare function buildLiveTerminalProductBundle(args: {
|
|
|
71
71
|
* the review names, beside the agent's own verdict. */
|
|
72
72
|
capFailure?: string;
|
|
73
73
|
}): RunBundle;
|
|
74
|
-
export declare function renderTerminalReviewMarkdown(bundle: RunBundle): string;
|
|
74
|
+
export declare function renderTerminalReviewMarkdown(bundle: RunBundle, status?: unknown): string;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { redactText } from "../../evidence/redaction.js";
|
|
2
2
|
import { participantAssignment } from "../../study/participant-assignment.js";
|
|
3
3
|
import { REVIEW_SCHEMA, bundleArtifacts, bundleHead, } from "../../run/bundle.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import {} from "../../run/judge.js";
|
|
5
|
+
import { renderReviewMarkdown } from "../../run/review-markdown.js";
|
|
6
6
|
import {} from "../../run/streams.js";
|
|
7
7
|
import { participantEvent, participantIds, participantRecord, participantStream, } from "../../run/participant-records.js";
|
|
8
8
|
import { TERMINAL_EVENTS_ARTIFACT, TERMINAL_LEDGERS_ARTIFACT, TERMINAL_TRANSCRIPT_ARTIFACT, } from "../../run/terminal-contract.js";
|
|
9
9
|
import { DEFAULT_RUNTIME_AUTH } from "./runtime-auth.js";
|
|
10
|
+
import { participantCaption } from "../../run/participant-caption.js";
|
|
11
|
+
import { formatParticipantOutcomes, tallyParticipantOutcomes } from "../../run/outcomes.js";
|
|
10
12
|
import { describeMeasuredSpend, noSpendLineMeasured, noSpendNotEstablished } from "./ledger.js";
|
|
11
13
|
/**
|
|
12
14
|
* Project the terminal-product run into a humanish.run-bundle.v1 (no schema change: a new
|
|
@@ -80,7 +82,7 @@ export function buildTerminalProductBundle(args) {
|
|
|
80
82
|
const review = {
|
|
81
83
|
schema: REVIEW_SCHEMA,
|
|
82
84
|
verdict: args.verdict,
|
|
83
|
-
summary:
|
|
85
|
+
summary: `Dry run: 1 participant would try ${args.productName} from a terminal. No sandbox was created, no key was used and $0 was spent.`,
|
|
84
86
|
gaps: [
|
|
85
87
|
"This dry run did not execute the live in-sandbox agent route; it checks the evidence shape only, not live behavior, scale, or adoption.",
|
|
86
88
|
"No exec-stream, transcript, substrate, cost, or cleanup artifacts were produced because no live session ran; live verification requires those artifacts.",
|
|
@@ -164,7 +166,7 @@ export function buildLiveTerminalProductBundle(args) {
|
|
|
164
166
|
const review = {
|
|
165
167
|
schema: REVIEW_SCHEMA,
|
|
166
168
|
verdict: args.verdict,
|
|
167
|
-
summary: args.sessionReason
|
|
169
|
+
summary: `1 participant took part: ${formatParticipantOutcomes(tallyParticipantOutcomes([args.trace.status]))}. How it ended: ${args.sessionReason}`,
|
|
168
170
|
gaps: [
|
|
169
171
|
...(args.trace.status === "passed"
|
|
170
172
|
? []
|
|
@@ -212,7 +214,7 @@ function terminalParticipant(args, session) {
|
|
|
212
214
|
const stream = participantStream(TERMINAL_IDS, {
|
|
213
215
|
assignment: participantAssignment({ mission: args.mission }),
|
|
214
216
|
kind: "terminal",
|
|
215
|
-
label:
|
|
217
|
+
label: participantCaption({ personaId: args.persona.id }),
|
|
216
218
|
status: session.status,
|
|
217
219
|
transport: "snapshot",
|
|
218
220
|
updatedAt: session.updatedAt,
|
|
@@ -275,25 +277,14 @@ function describeCaps(caps) {
|
|
|
275
277
|
parts.push(`maxMinutes=${caps.maxMinutes}`);
|
|
276
278
|
return parts.length > 0 ? parts.join(", ") : "empty";
|
|
277
279
|
}
|
|
278
|
-
export function renderTerminalReviewMarkdown(bundle) {
|
|
280
|
+
export function renderTerminalReviewMarkdown(bundle, status) {
|
|
279
281
|
const subject = bundle.events.find((event) => event.type === "terminal-lab.subject.declared");
|
|
280
282
|
const credentials = bundle.events.find((event) => event.type === "terminal-lab.credentials.declared");
|
|
281
283
|
const caps = bundle.events.find((event) => event.type === "terminal-lab.caps.declared");
|
|
282
|
-
return [
|
|
283
|
-
`# ${bundle.scenario.title}`,
|
|
284
|
-
"",
|
|
285
|
-
`- run: ${bundle.runId}`,
|
|
286
|
-
`- mode: ${bundle.mode}`,
|
|
287
|
-
`- verdict: ${verdictText(bundle.review.verdict, bundle.mode)}`,
|
|
288
|
-
`- outcome: ${reviewOutcome(bundle)}`,
|
|
289
|
-
`- summary: ${bundle.review.summary}`,
|
|
284
|
+
return renderReviewMarkdown(bundle, [
|
|
290
285
|
`- mission: ${bundle.scenario.goal}`,
|
|
291
286
|
...(subject ? [`- subject: ${subject.message}`] : []),
|
|
292
287
|
...(credentials ? [`- credentials: ${credentials.message}`] : []),
|
|
293
288
|
...(caps ? [`- caps: ${caps.message}`] : []),
|
|
294
|
-
|
|
295
|
-
? ["", "## Gaps", ...bundle.review.gaps.map((gap) => `- ${gap}`)]
|
|
296
|
-
: []),
|
|
297
|
-
"",
|
|
298
|
-
].join("\n");
|
|
289
|
+
], { status });
|
|
299
290
|
}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { DEFAULT_RUNTIME_AUTH, E2B_SYSTEM_CA_BUNDLE, OPENAI_EGRESS_PLACEHOLDER, } from "./runtime-auth.js";
|
|
2
2
|
import { TERMINAL_PRODUCT_STUDY_PROVIDER_METADATA } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Where the study's runtime auth puts the key, as a sentence for the missing-key refusal. Typed
|
|
5
|
+
* `unknown` because a config that skipped the planner may hold a mode the type does not list.
|
|
6
|
+
*/
|
|
7
|
+
function keyPlacement(runtimeAuth) {
|
|
8
|
+
if (runtimeAuth === undefined)
|
|
9
|
+
return ` \`execution.runtimeAuth\` is unset, so the default ${DEFAULT_RUNTIME_AUTH} keeps the key outside the sandbox in an E2B header transform.`;
|
|
10
|
+
if (runtimeAuth === "openai-egress")
|
|
11
|
+
return " `execution.runtimeAuth: openai-egress` keeps the key outside the sandbox in an E2B header transform.";
|
|
12
|
+
if (runtimeAuth === "openai-env")
|
|
13
|
+
return " `execution.runtimeAuth: openai-env` passes the key to the codex command only.";
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
3
16
|
/**
|
|
4
17
|
* Resolve the runtime key on the host. openai-egress, the default, returns an inert command env
|
|
5
18
|
* while retaining the actual value for the external transform and literal redaction; openai-env
|
|
@@ -38,7 +51,7 @@ export function buildRuntimeAuth(args) {
|
|
|
38
51
|
return {
|
|
39
52
|
ok: false,
|
|
40
53
|
code: "HUMANISH_TERMINAL_RUNTIME_AUTH_MISSING",
|
|
41
|
-
message: `Live terminal-product studies
|
|
54
|
+
message: `Live terminal-product studies need ${ALLOWED_RUNTIME_KEY_NAMES.join(" or ")} in the environment, and neither is set.${keyPlacement(args.runtimeAuth)} Pass one with --dotenv; the value is never persisted.`,
|
|
42
55
|
};
|
|
43
56
|
}
|
|
44
57
|
const keyValue = args.env[keyName];
|
|
@@ -124,6 +124,7 @@ function runtimeDeclaredEvent(runtime, createdAt) {
|
|
|
124
124
|
level: "info",
|
|
125
125
|
type: "terminal-lab.runtime.declared",
|
|
126
126
|
message: redactText(JSON.stringify(declaredRuntimeProvenance({
|
|
127
|
+
doNotTrack: runtime.doNotTrack,
|
|
127
128
|
...(version === undefined ? {} : { version }),
|
|
128
129
|
model,
|
|
129
130
|
modelSource,
|
|
@@ -19,14 +19,17 @@ function buildLiveTrace(inputs) {
|
|
|
19
19
|
const { session } = inputs;
|
|
20
20
|
const { createdAt } = inputs.run;
|
|
21
21
|
const { terminalEvents, commandLog, discardedPrefixes } = inputs.recorder;
|
|
22
|
-
// Prefix reconciliation may cut through a known key. Scrub
|
|
23
|
-
// chunks before any transcript/trace/event artifact is
|
|
22
|
+
// Prefix reconciliation may cut through a known key. Scrub the known values, as written and in
|
|
23
|
+
// each encoded form, across the retained chunks before any transcript/trace/event artifact is
|
|
24
|
+
// persisted. Check both each stream and
|
|
24
25
|
// the combined event order that the transcript uses; either view can assemble a split value.
|
|
25
|
-
scrubSplitKnownValues(terminalEvents, inputs.run.secrets.
|
|
26
|
+
scrubSplitKnownValues(terminalEvents, (text) => inputs.run.secrets.spans(text), discardedPrefixes);
|
|
26
27
|
// Build the actor trace first (the cost ledger reads its tokenUsage). A session past the cap
|
|
27
28
|
// ends its transcript with what was not stored, and its trace says the same.
|
|
28
29
|
const cut = inputs.recorder.transcriptCut();
|
|
29
|
-
|
|
30
|
+
// Normalizing removes terminal escape sequences, which rejoins a value one of them split, so the
|
|
31
|
+
// transcript is sanitized again after it.
|
|
32
|
+
const normalizedTranscript = `${sanitize(normalizeLocalActorTranscript(terminalEvents.map((e) => e.chunk).join("")))}${cut ? `\n[humanish] ${cut.notice}` : ""}`;
|
|
30
33
|
// Parsed from the full stream: usage records arrive once per turn, and the tail
|
|
31
34
|
// would drop all but the last. Records past the cap were kept as they arrived.
|
|
32
35
|
const terminalTokenUsage = parseTerminalTokenUsage([normalizedTranscript, ...(cut?.usage ?? [])].join("\n"));
|
|
@@ -261,7 +261,10 @@ export class LiveTerminalSandbox {
|
|
|
261
261
|
// The install step may run the product itself (release:dogfood's does: `humanish init
|
|
262
262
|
// --yes`); on the 0.67.0 dogfood that one command arrived unmarked while the participant's
|
|
263
263
|
// nine others carried the marker.
|
|
264
|
-
envs: {
|
|
264
|
+
envs: {
|
|
265
|
+
HUMANISH_STUDY_PARTICIPANT: "1",
|
|
266
|
+
...(plan.runtime.doNotTrack ? { DO_NOT_TRACK: "1" } : {}),
|
|
267
|
+
},
|
|
265
268
|
requestTimeoutMs,
|
|
266
269
|
timeoutMs: PRODUCT_SETUP_TIMEOUT_MS,
|
|
267
270
|
});
|
|
@@ -314,7 +317,11 @@ export class LiveTerminalSandbox {
|
|
|
314
317
|
// The selected command env (raw key or inert placeholder). The participant
|
|
315
318
|
// marker rides the same command: humanish telemetry from inside a study reads as a new
|
|
316
319
|
// adopter otherwise.
|
|
317
|
-
envs: {
|
|
320
|
+
envs: {
|
|
321
|
+
...runtimeEnv.envs,
|
|
322
|
+
HUMANISH_STUDY_PARTICIPANT: "1",
|
|
323
|
+
...(plan.runtime.doNotTrack ? { DO_NOT_TRACK: "1" } : {}),
|
|
324
|
+
},
|
|
318
325
|
requestTimeoutMs,
|
|
319
326
|
timeoutMs: wallClockMs,
|
|
320
327
|
onStdout: (data) => recordStreamedTerminalChunk("stdout", data),
|
|
@@ -69,6 +69,7 @@ export function planTerminalStudy(config, input) {
|
|
|
69
69
|
...(actor?.persona === undefined ? {} : { personaId: actor.persona }),
|
|
70
70
|
...(actor?.mission === undefined ? {} : { mission: actor.mission }),
|
|
71
71
|
runtime: {
|
|
72
|
+
doNotTrack: config.execution?.terminal?.doNotTrack !== false,
|
|
72
73
|
...(runtimeVersion === undefined ? {} : { version: runtimeVersion }),
|
|
73
74
|
// Codex's own default can change with any release and its JSON stream does not name it, so
|
|
74
75
|
// the route always passes a model: the declared one, else humanish's participant default.
|
|
@@ -8,6 +8,7 @@ export declare function parseTerminalRuntimeVersion(stdout: string): string | un
|
|
|
8
8
|
export declare function buildRuntimeVersionCommand(requestedVersion?: string): string;
|
|
9
9
|
export declare function buildRuntimeExecPrefix(version: string, model: string, reasoningEffort?: ReasoningEffort): string;
|
|
10
10
|
export declare function declaredRuntimeProvenance(args: {
|
|
11
|
+
doNotTrack: boolean;
|
|
11
12
|
version?: string;
|
|
12
13
|
model: string;
|
|
13
14
|
modelSource: "declared" | "humanish_default";
|
|
@@ -35,6 +35,7 @@ export function declaredRuntimeProvenance(args) {
|
|
|
35
35
|
return {
|
|
36
36
|
schema: "humanish.actor-runtime.v1",
|
|
37
37
|
package: TERMINAL_RUNTIME_PACKAGE,
|
|
38
|
+
doNotTrack: args.doNotTrack,
|
|
38
39
|
requestedVersion: args.version ?? "latest",
|
|
39
40
|
versionStatus: "unobserved",
|
|
40
41
|
requestedModel: args.model,
|
|
@@ -70,11 +70,12 @@ export async function runLiveTerminalSession(args) {
|
|
|
70
70
|
const recorder = createTerminalRecorder({
|
|
71
71
|
nowIso,
|
|
72
72
|
sanitize,
|
|
73
|
-
knownSecretValues: run.secrets.
|
|
73
|
+
knownSecretValues: run.secrets.forms(),
|
|
74
74
|
verdictNonce,
|
|
75
75
|
});
|
|
76
76
|
const { version, model, modelSource, reasoningEffort } = plan.runtime;
|
|
77
77
|
const runtime = declaredRuntimeProvenance({
|
|
78
|
+
doNotTrack: plan.runtime.doNotTrack,
|
|
78
79
|
...(version === undefined ? {} : { version }),
|
|
79
80
|
model: sanitize(model),
|
|
80
81
|
modelSource,
|
|
@@ -5,9 +5,11 @@ import { type CommandLogRecord, type TerminalEventRecord } from "./types.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Per-chunk sanitization cannot recognize a value split across deliveries. Redact those complete
|
|
7
7
|
* known values before persistence without collapsing events or changing stdout/stderr ordering.
|
|
8
|
-
*
|
|
8
|
+
* `spansOf` finds them in the joined text: RunSecrets.spans, which also finds a value in an
|
|
9
|
+
* encoded form or split by a terminal escape sequence. Work backwards through the spans so edits
|
|
10
|
+
* to later text leave earlier offsets valid.
|
|
9
11
|
*/
|
|
10
|
-
export declare function scrubSplitKnownValues(events: TerminalEventRecord[],
|
|
12
|
+
export declare function scrubSplitKnownValues(events: TerminalEventRecord[], spansOf: (text: string) => ReadonlyArray<readonly [number, number]>, discardedPrefixes: Record<"stdout" | "stderr" | "combined", string>): void;
|
|
11
13
|
/** Redacted, ellipsis-prefixed tail of a captured stream/log for a message field. */
|
|
12
14
|
export declare function tailOf(text: string): string;
|
|
13
15
|
/**
|