humanish 0.114.0 → 0.116.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 +41 -0
- package/CONTEXT.md +1 -1
- package/README.md +7 -6
- package/TELEMETRY.md +6 -0
- package/dist/actors/closing-report-limits.d.ts +11 -0
- package/dist/actors/closing-report-limits.js +13 -0
- package/dist/actors/closing-report.d.ts +68 -0
- package/dist/actors/closing-report.js +55 -0
- package/dist/actors/codex/app-server-ui.js +11 -4
- package/dist/actors/codex/restricted-participant-policy.d.ts +4 -47
- package/dist/actors/codex/restricted-participant-policy.js +12 -40
- package/dist/actors/computer-use/loop/debrief.d.ts +1 -10
- package/dist/actors/computer-use/loop/debrief.js +68 -70
- package/dist/actors/computer-use/loop/impressions.d.ts +2 -2
- package/dist/actors/computer-use/loop/impressions.js +6 -1
- package/dist/actors/computer-use/loop.d.ts +0 -1
- package/dist/actors/computer-use/loop.js +3 -3
- package/dist/actors/computer-use/openai-conversation.d.ts +1 -1
- package/dist/actors/computer-use/openai-conversation.js +3 -29
- package/dist/actors/computer-use/openai-provider.js +11 -5
- package/dist/actors/contract.d.ts +2 -20
- package/dist/actors/contract.js +0 -13
- package/dist/actors/scripted-browser/steps.js +6 -5
- package/dist/analysis/admission.d.ts +14 -4
- package/dist/analysis/admission.js +14 -9
- package/dist/analysis/analysis-limits.d.ts +35 -0
- package/dist/analysis/analysis-limits.js +41 -0
- package/dist/analysis/execute.d.ts +5 -0
- package/dist/analysis/execute.js +3 -0
- package/dist/analysis/finding-lead.d.ts +27 -0
- package/dist/analysis/finding-lead.js +23 -0
- package/dist/analysis/service.js +5 -3
- package/dist/analysis/validation.js +14 -21
- package/dist/cli/commands/analyze.js +2 -1
- package/dist/cli/commands/notes.js +5 -3
- package/dist/cli/commands/run-signals.js +5 -1
- package/dist/cli/commands/runs.js +2 -1
- package/dist/cli/commands/setup.js +3 -3
- package/dist/cli/commands/study-format.d.ts +5 -2
- package/dist/cli/commands/study-format.js +29 -9
- package/dist/cli/commands/study-route-computer-use.js +2 -2
- package/dist/cli/commands/study-route-scripted.js +1 -1
- package/dist/cli/commands/tui.js +59 -17
- package/dist/cli/doctor.js +9 -2
- package/dist/cli/findings-text.d.ts +5 -0
- package/dist/cli/findings-text.js +169 -0
- package/dist/cli/findings.d.ts +0 -4
- package/dist/cli/findings.js +11 -195
- package/dist/cli/io.js +10 -8
- package/dist/cli/telemetry.js +10 -22
- package/dist/cli/update-check.js +10 -19
- package/dist/cli/user-config.d.ts +11 -0
- package/dist/cli/user-config.js +25 -0
- package/dist/comms/connections.js +4 -3
- package/dist/comms/email-catch.js +7 -31
- package/dist/comms/setup.js +18 -12
- package/dist/evidence/encoded-text.d.ts +12 -11
- package/dist/evidence/encoded-text.js +81 -70
- package/dist/evidence/redaction.d.ts +11 -0
- package/dist/evidence/redaction.js +30 -11
- package/dist/evidence/secret-scrub.d.ts +26 -3
- package/dist/evidence/secret-scrub.js +172 -38
- package/dist/feedback/draft.d.ts +1 -1
- package/dist/feedback/draft.js +13 -16
- package/dist/feedback/export-bundle.js +21 -17
- package/dist/feedback/export.js +32 -46
- package/dist/feedback/feedback.js +31 -26
- package/dist/guest/desktop-keys.js +59 -5
- package/dist/keys/key-resolution.d.ts +9 -0
- package/dist/keys/key-resolution.js +62 -55
- package/dist/keys/key-status.d.ts +2 -0
- package/dist/keys/key-status.js +9 -16
- package/dist/observer/artifact.d.ts +1 -1
- package/dist/observer/http.d.ts +7 -0
- package/dist/observer/http.js +19 -1
- package/dist/observer/notes-route.d.ts +0 -1
- package/dist/observer/notes-route.js +24 -44
- package/dist/observer/pinned-files.js +4 -19
- package/dist/observer/render.js +6 -4
- package/dist/observer/run-routes.js +13 -28
- package/dist/observer/serve.js +2 -1
- package/dist/observer-app.html +7 -7
- package/dist/routes/computer-use/bundle-parts.d.ts +4 -4
- package/dist/routes/computer-use/bundle-parts.js +2 -2
- package/dist/routes/computer-use/bundle.d.ts +4 -5
- package/dist/routes/computer-use/bundle.js +1 -3
- package/dist/routes/computer-use/fanout-records.js +10 -11
- package/dist/routes/computer-use/live-phase.d.ts +1 -1
- package/dist/routes/computer-use/live-phase.js +0 -1
- package/dist/routes/computer-use/participant-execution.d.ts +3 -2
- package/dist/routes/computer-use/participant-runs.d.ts +6 -2
- package/dist/routes/computer-use/participant-runs.js +9 -5
- package/dist/routes/computer-use/plan.d.ts +3 -0
- package/dist/routes/computer-use/plan.js +14 -11
- package/dist/routes/computer-use/rerun-selection.js +2 -2
- package/dist/routes/computer-use/result.js +3 -6
- package/dist/routes/computer-use/setup.d.ts +4 -6
- package/dist/routes/computer-use/setup.js +2 -6
- package/dist/routes/computer-use/single-bundle.d.ts +4 -5
- package/dist/routes/computer-use/single-bundle.js +22 -25
- package/dist/routes/computer-use/subject-projection.d.ts +4 -8
- package/dist/routes/computer-use/subject-projection.js +9 -31
- package/dist/routes/computer-use/types.d.ts +4 -41
- package/dist/routes/scripted/scenario.js +28 -12
- package/dist/routes/scripted/session-result.js +6 -3
- package/dist/routes/shared-world/bundle-records.js +2 -8
- package/dist/routes/shared-world/participant-specs.d.ts +0 -2
- package/dist/routes/shared-world/participant-specs.js +1 -20
- package/dist/routes/shared-world/plan.d.ts +3 -0
- package/dist/routes/shared-world/plan.js +23 -3
- package/dist/routes/shared-world/setup.js +2 -2
- package/dist/routes/terminal/ledger.d.ts +9 -9
- package/dist/routes/terminal/ledger.js +63 -40
- package/dist/routes/terminal/live-finish.js +7 -10
- package/dist/routes/terminal/plan.d.ts +3 -0
- package/dist/routes/terminal/plan.js +8 -5
- package/dist/routes/terminal/token-usage.d.ts +1 -2
- package/dist/routes/terminal/token-usage.js +4 -6
- package/dist/routes/terminal/types.d.ts +9 -1
- package/dist/run/bundle-shape.js +6 -1
- package/dist/run/bundle.d.ts +5 -3
- package/dist/run/contained-output.d.ts +59 -10
- package/dist/run/contained-output.js +160 -68
- package/dist/run/detail.d.ts +2 -1
- package/dist/run/detail.js +12 -7
- package/dist/run/escape-sequences.d.ts +11 -0
- package/dist/run/escape-sequences.js +93 -0
- package/dist/run/evidence-files.d.ts +8 -1
- package/dist/run/evidence-files.js +24 -18
- package/dist/run/locate.d.ts +32 -3
- package/dist/run/locate.js +45 -40
- package/dist/run/note-files.d.ts +26 -45
- package/dist/run/note-files.js +86 -58
- package/dist/run/note-shape.d.ts +35 -0
- package/dist/run/note-shape.js +20 -0
- package/dist/run/notes.d.ts +3 -14
- package/dist/run/notes.js +23 -75
- package/dist/run/outcomes.d.ts +24 -0
- package/dist/run/outcomes.js +19 -0
- package/dist/run/participant-records.d.ts +2 -0
- package/dist/run/participant-records.js +17 -2
- package/dist/run/paths.d.ts +2 -0
- package/dist/run/paths.js +5 -1
- package/dist/run/preflight-receipts.js +13 -8
- package/dist/run/project.js +7 -4
- package/dist/run/reclaim-outcomes.d.ts +8 -6
- package/dist/run/reclaim-outcomes.js +10 -6
- package/dist/run/reclaim.d.ts +5 -0
- package/dist/run/reclaim.js +43 -16
- package/dist/run/run-clock.d.ts +50 -0
- package/dist/run/run-clock.js +66 -0
- package/dist/run/run.js +5 -2
- package/dist/run/sandbox-ids.d.ts +7 -2
- package/dist/run/sandbox-ids.js +27 -11
- package/dist/run/secrets.d.ts +2 -1
- package/dist/run/secrets.js +18 -22
- package/dist/run/stored-runs.js +14 -7
- package/dist/run/transient-comms-secrets.d.ts +6 -5
- package/dist/run/transient-comms-secrets.js +23 -39
- package/dist/run-study.js +22 -13
- package/dist/study/adapter-scorer-loader.js +6 -4
- package/dist/study/discover.js +9 -6
- package/dist/study/init-paths.js +7 -5
- package/dist/study/persona-resolve.d.ts +2 -2
- package/dist/study/persona-resolve.js +9 -5
- package/dist/study/plan-types.d.ts +2 -2
- package/dist/study/plan.d.ts +5 -3
- package/dist/study/plan.js +18 -9
- package/dist/study/preflight-probes.js +11 -3
- package/dist/study/preflight.js +9 -5
- package/dist/study/summary.d.ts +12 -7
- package/dist/study/summary.js +24 -24
- package/dist/study/warnings.d.ts +19 -12
- package/dist/study/warnings.js +39 -17
- package/dist/substrates/e2b/connection.d.ts +12 -0
- package/dist/substrates/e2b/connection.js +35 -0
- package/dist/substrates/e2b/desktop-executor.d.ts +1 -3
- package/dist/substrates/e2b/desktop-executor.js +42 -9
- package/dist/substrates/e2b/lifetime.d.ts +24 -4
- package/dist/substrates/e2b/lifetime.js +41 -4
- package/dist/substrates/e2b/sandbox.d.ts +4 -3
- package/dist/substrates/e2b/sandbox.js +13 -18
- package/dist/substrates/e2b/sdk.d.ts +20 -9
- package/dist/substrates/local/capacity.d.ts +18 -2
- package/dist/substrates/local/capacity.js +21 -2
- package/dist/substrates/local/runtime-host.js +21 -14
- package/dist/substrates/local/runtime.js +3 -11
- package/dist/tui/contract.d.ts +30 -5
- package/dist/tui/contract.js +4 -1
- package/dist/tui-app.js +149 -147
- package/dist/verify/artifacts.d.ts +9 -2
- package/dist/verify/artifacts.js +37 -13
- package/dist/verify/liveness.js +19 -5
- package/dist/verify/verify.js +54 -27
- package/docs/architecture/actor-contract.md +3 -3
- package/docs/architecture/guest-desktop.md +7 -2
- package/docs/architecture/restricted-codex-analysis.md +1 -1
- package/docs/architecture/state-driven-executor.md +5 -3
- package/docs/contracts/run-bundle.md +25 -13
- package/docs/contracts/schemas.md +38 -29
- package/docs/contracts/study-analysis.md +22 -5
- package/docs/product/automatic-analysis.md +7 -1
- package/package.json +8 -7
- package/skills/humanish/SKILL.md +1 -2
package/dist/run/stored-runs.js
CHANGED
|
@@ -2,12 +2,12 @@ import { lstat, readdir } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { redactText } from "../evidence/redaction.js";
|
|
4
4
|
import { bindExistingRunArtifactPaths, RUNS_RELATIVE_ROOT, isSafeRunIdSegment, resolveRunsRoot, } from "./paths.js";
|
|
5
|
-
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory, } from "./contained-output.js";
|
|
5
|
+
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory, refusalText, } from "./contained-output.js";
|
|
6
6
|
import { RUN_BUNDLE_FILE } from "./bundle.js";
|
|
7
7
|
import { isReviewSummary, isRunOutcome } from "./bundle-shape.js";
|
|
8
8
|
import { bundleDisplayFacts, displayedReview, runDisplay, } from "./display.js";
|
|
9
9
|
import { RUN_STATUS_FILE } from "./status.js";
|
|
10
|
-
import { readLatest, readRunJsonIfExists, resolveRunPath } from "./locate.js";
|
|
10
|
+
import { readLatest, readRunJsonIfExists, resolveRunPath, runJsonValue } from "./locate.js";
|
|
11
11
|
import { withCuaReviewProvenance } from "./outcomes.js";
|
|
12
12
|
import { isNodeError, isRecord } from "./type-guards.js";
|
|
13
13
|
import { invalidRunStorageVerifyResult, verifyResolvedRun, } from "../verify/verify.js";
|
|
@@ -70,7 +70,8 @@ export async function listRuns(cwdInput) {
|
|
|
70
70
|
if (runsRoot && entryRunPaths.physicalRunsRoot !== runsRoot.physicalPath) {
|
|
71
71
|
return runsUnavailableResult(cwd, new Error("humanish runs root changed physical destination."));
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
// A listing shows what it can read: a run.json it refuses lists like a missing one.
|
|
74
|
+
const bundle = runJsonValue(await readRunJsonIfExists(entryRunPaths, RUN_BUNDLE_FILE));
|
|
74
75
|
runs.push({
|
|
75
76
|
runId: entryName,
|
|
76
77
|
createdAt: isRecord(bundle) && typeof bundle.createdAt === "string" ? bundle.createdAt : null,
|
|
@@ -105,7 +106,7 @@ async function listedRunDisplay(runPaths, bundle) {
|
|
|
105
106
|
if (displayed === undefined)
|
|
106
107
|
return {};
|
|
107
108
|
const status = displayed.outcome === undefined
|
|
108
|
-
? await readRunJsonIfExists(runPaths, RUN_STATUS_FILE)
|
|
109
|
+
? runJsonValue(await readRunJsonIfExists(runPaths, RUN_STATUS_FILE))
|
|
109
110
|
: undefined;
|
|
110
111
|
return { display: runDisplay(bundleDisplayFacts(displayed, status)) };
|
|
111
112
|
}
|
|
@@ -155,18 +156,24 @@ export async function readReview(cwdInput, runInput) {
|
|
|
155
156
|
if (!verified.ok || !verified.bundlePath) {
|
|
156
157
|
return verified;
|
|
157
158
|
}
|
|
158
|
-
const
|
|
159
|
+
const reviewRead = runPaths ? await readRunJsonIfExists(runPaths, "review.json") : undefined;
|
|
160
|
+
const review = reviewRead === undefined ? null : runJsonValue(reviewRead);
|
|
159
161
|
if (!isReviewSummary(review)) {
|
|
160
162
|
return {
|
|
161
163
|
...verified,
|
|
162
164
|
ok: false,
|
|
163
165
|
error: {
|
|
164
166
|
code: "HUMANISH_INVALID_RUN_BUNDLE",
|
|
165
|
-
message:
|
|
167
|
+
message: reviewRead?.status === "refused"
|
|
168
|
+
? `${refusalText("review.json", reviewRead)}.`
|
|
169
|
+
: "review.json is missing or invalid.",
|
|
166
170
|
},
|
|
167
171
|
};
|
|
168
172
|
}
|
|
169
|
-
|
|
173
|
+
// The display reads what it can: a run.json refused here shows as a missing one does.
|
|
174
|
+
const bundle = runPaths
|
|
175
|
+
? runJsonValue(await readRunJsonIfExists(runPaths, RUN_BUNDLE_FILE))
|
|
176
|
+
: null;
|
|
170
177
|
const projected = isRecord(bundle) && Array.isArray(bundle.streams)
|
|
171
178
|
? withCuaReviewProvenance(review, bundle.streams.filter(isRecord))
|
|
172
179
|
: review;
|
|
@@ -6,10 +6,11 @@ export declare function scrubTransientCommsText(text: string): string;
|
|
|
6
6
|
/**
|
|
7
7
|
* A scrub for text a model writes: each scope value as written and in its encoded forms
|
|
8
8
|
* (percent-encoded, JSON-escaped, base64, base64url, hex), and where escapes split one. Text that
|
|
9
|
-
* holds
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* holds a value is returned decoded with each value replaced. Text without one keeps its original
|
|
10
|
+
* spelling, so an exact quote that holds an escape still matches its evidence. The literal scrub
|
|
11
|
+
* runs first, before decoding can rewrite a value that itself holds an escape, and last. The
|
|
12
|
+
* result is checked last of all: if it still holds a value as written or decoded, as a value
|
|
13
|
+
* holding part of a marker can after the literal scrub, the whole text is replaced. Outside a
|
|
14
|
+
* scope it changes nothing.
|
|
14
15
|
*/
|
|
15
16
|
export declare function transientCommsKnownValueScrub(): (text: string) => string;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import {
|
|
3
|
-
import { scrubSecretValues } from "../evidence/secret-scrub.js";
|
|
4
|
-
import { escapeRegExp } from "./text.js";
|
|
2
|
+
import { REDACTION_MARKERS } from "../evidence/redaction.js";
|
|
3
|
+
import { holdsSecretValue, scrubSecretValues, scrubValuesAsWritten, } from "../evidence/secret-scrub.js";
|
|
5
4
|
const scopes = new AsyncLocalStorage();
|
|
6
5
|
const MAX_VALUES = 8192;
|
|
7
6
|
const MAX_BYTES = 1024 * 1024;
|
|
@@ -15,7 +14,7 @@ function usable(scope) {
|
|
|
15
14
|
function fail(scope) {
|
|
16
15
|
scope.failed = true;
|
|
17
16
|
scope.values.clear();
|
|
18
|
-
delete scope.
|
|
17
|
+
delete scope.literal;
|
|
19
18
|
throw new Error("TRANSIENT_NARRATION_SECRET_LIMIT");
|
|
20
19
|
}
|
|
21
20
|
/** Enclose both participant execution and its automatic analysis in one scope. Nested runs isolate too. */
|
|
@@ -30,7 +29,7 @@ export async function withTransientCommsSecrets(work) {
|
|
|
30
29
|
scope.closed = true;
|
|
31
30
|
scope.values.clear();
|
|
32
31
|
scope.bytes = 0;
|
|
33
|
-
delete scope.
|
|
32
|
+
delete scope.literal;
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
export function registerTransientCommsSecrets(values) {
|
|
@@ -50,7 +49,7 @@ export function registerTransientCommsSecrets(values) {
|
|
|
50
49
|
fail(scope);
|
|
51
50
|
scope.values.add(value);
|
|
52
51
|
scope.bytes += bytes;
|
|
53
|
-
delete scope.
|
|
52
|
+
delete scope.literal;
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
/** Literal, longest-first replacement in one pass; replacement text is never matched recursively. */
|
|
@@ -59,46 +58,31 @@ export function scrubTransientCommsText(text) {
|
|
|
59
58
|
if (!scope)
|
|
60
59
|
return text;
|
|
61
60
|
usable(scope);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
scope.pattern ??= new RegExp([...scope.values]
|
|
66
|
-
.sort((a, b) => b.length - a.length)
|
|
67
|
-
.map(escapeRegExp)
|
|
68
|
-
.join("|"), "g");
|
|
69
|
-
return text.replace(scope.pattern, "[REDACTED_SECRET]");
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return fail(scope);
|
|
73
|
-
}
|
|
61
|
+
scope.literal ??= scrubValuesAsWritten([...scope.values]);
|
|
62
|
+
return scope.literal(text);
|
|
74
63
|
}
|
|
75
|
-
|
|
76
|
-
* The scope's values as a scrubSecretValues scrub, which also finds them percent-encoded, escaped or
|
|
77
|
-
* base64-encoded and returns decoded text. Outside a scope it changes nothing.
|
|
78
|
-
*/
|
|
79
|
-
function transientCommsEncodedScrub() {
|
|
80
|
-
const scope = scopes.getStore();
|
|
81
|
-
if (!scope)
|
|
82
|
-
return (text) => text;
|
|
83
|
-
usable(scope);
|
|
84
|
-
return scrubSecretValues([...scope.values]);
|
|
85
|
-
}
|
|
86
|
-
const REDACTED = "[REDACTED_SECRET]";
|
|
87
|
-
const markers = (text) => text.split(REDACTED).length - 1;
|
|
64
|
+
const REDACTED = REDACTION_MARKERS.secret;
|
|
88
65
|
/**
|
|
89
66
|
* A scrub for text a model writes: each scope value as written and in its encoded forms
|
|
90
67
|
* (percent-encoded, JSON-escaped, base64, base64url, hex), and where escapes split one. Text that
|
|
91
|
-
* holds
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
68
|
+
* holds a value is returned decoded with each value replaced. Text without one keeps its original
|
|
69
|
+
* spelling, so an exact quote that holds an escape still matches its evidence. The literal scrub
|
|
70
|
+
* runs first, before decoding can rewrite a value that itself holds an escape, and last. The
|
|
71
|
+
* result is checked last of all: if it still holds a value as written or decoded, as a value
|
|
72
|
+
* holding part of a marker can after the literal scrub, the whole text is replaced. Outside a
|
|
73
|
+
* scope it changes nothing.
|
|
96
74
|
*/
|
|
97
75
|
export function transientCommsKnownValueScrub() {
|
|
98
|
-
const
|
|
76
|
+
const scope = scopes.getStore();
|
|
77
|
+
if (!scope)
|
|
78
|
+
return scrubTransientCommsText;
|
|
79
|
+
usable(scope);
|
|
80
|
+
const values = [...scope.values];
|
|
81
|
+
const encoded = scrubSecretValues(values);
|
|
82
|
+
const holds = holdsSecretValue(values);
|
|
99
83
|
return (text) => {
|
|
100
84
|
const literal = scrubTransientCommsText(text);
|
|
101
|
-
const
|
|
102
|
-
return
|
|
85
|
+
const result = scrubTransientCommsText(holds(literal) ? encoded(literal) : literal);
|
|
86
|
+
return holds(result) ? REDACTED : result;
|
|
103
87
|
};
|
|
104
88
|
}
|
package/dist/run-study.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
// which runs the route's local checks that need no scorer (keys, subject env, the local agent) and
|
|
5
5
|
// returns the route's run. prepareStudy is the same path in two steps, so the CLI can present either
|
|
6
6
|
// refusal before it loads a declared review scorer.
|
|
7
|
-
import { participantList } from "./study/study-fields.js";
|
|
8
7
|
import { personaBackgroundWarnings } from "./study/warnings.js";
|
|
9
8
|
import { resolveCommittedPersonasForCwd, studyPersonaIds } from "./study/persona-resolve.js";
|
|
10
9
|
import { computerUseInput, scriptedInput, sharedWorldInput, terminalInput, } from "./study/route-inputs.js";
|
|
@@ -73,8 +72,12 @@ export async function prepareStudy(config, options, deps = {}) {
|
|
|
73
72
|
outcome.result.warnings.push(...normalized.warnings);
|
|
74
73
|
return { ok: false, outcome };
|
|
75
74
|
}
|
|
76
|
-
|
|
77
|
-
const
|
|
75
|
+
// The run records the background warnings in its bundle with the plan's own.
|
|
76
|
+
const backgroundWarnings = await planBackgroundWarnings(planned.planned.plan, study, options.cwd);
|
|
77
|
+
const plan = {
|
|
78
|
+
...planned.planned.plan,
|
|
79
|
+
warnings: [...(planned.planned.plan.warnings ?? []), ...backgroundWarnings],
|
|
80
|
+
};
|
|
78
81
|
const admitted = await admitPlan(study, planning, plan, deps, normalized.emit);
|
|
79
82
|
if (!admitted.ok) {
|
|
80
83
|
await vm?.close();
|
|
@@ -97,16 +100,7 @@ export async function prepareStudy(config, options, deps = {}) {
|
|
|
97
100
|
return outcome;
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
|
-
//
|
|
101
|
-
const personas = route === "computer-use" || route === "shared-world"
|
|
102
|
-
? (await resolveCommittedPersonasForCwd(options.cwd, studyPersonaIds(study))).personas
|
|
103
|
-
: new Map();
|
|
104
|
-
const backgroundWarnings = personaBackgroundWarnings(route === "computer-use" &&
|
|
105
|
-
options.count !== undefined &&
|
|
106
|
-
participantList(study) === undefined
|
|
107
|
-
? { ...study, participants: options.count }
|
|
108
|
-
: study, personas);
|
|
109
|
-
runWarnings.splice(0, runWarnings.length, ...(planned.planned.plan.warnings ?? []), ...backgroundWarnings);
|
|
103
|
+
// A run's result lists the background warnings; a refusal before the run does not.
|
|
110
104
|
normalized.warnings.push(...backgroundWarnings);
|
|
111
105
|
// The route layers the scorer over the inputs it admitted, and the local study is not
|
|
112
106
|
// rebuilt for it. The admitted runSession, provider and participant desktop belong to this
|
|
@@ -125,6 +119,21 @@ export async function prepareStudy(config, options, deps = {}) {
|
|
|
125
119
|
},
|
|
126
120
|
};
|
|
127
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* A warning for each of the plan's participants whose persona has no background. Only
|
|
124
|
+
* computer-use and shared-world plans have participants; the committed personas are read for them.
|
|
125
|
+
*/
|
|
126
|
+
async function planBackgroundWarnings(plan, study, cwd) {
|
|
127
|
+
const participants = plan.route === "computer-use"
|
|
128
|
+
? plan.runner.participants
|
|
129
|
+
: plan.route === "shared-world"
|
|
130
|
+
? plan.plane.participants
|
|
131
|
+
: [];
|
|
132
|
+
if (participants.length === 0)
|
|
133
|
+
return [];
|
|
134
|
+
const { personas } = await resolveCommittedPersonasForCwd(cwd, studyPersonaIds(study));
|
|
135
|
+
return personaBackgroundWarnings(plan.studyId, participants, personas);
|
|
136
|
+
}
|
|
128
137
|
/** The route's admit function for a plan, with the route's input from the run's options. */
|
|
129
138
|
async function admitPlan(config, options, plan, deps, emit) {
|
|
130
139
|
switch (plan.route) {
|
|
@@ -18,7 +18,7 @@ import { realpath } from "node:fs/promises";
|
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import { pathToFileURL } from "node:url";
|
|
20
20
|
import { digestText, redactText } from "../evidence/redaction.js";
|
|
21
|
-
import { prepareSelectedOutputDirectory, readContainedRegularFile, } from "../run/contained-output.js";
|
|
21
|
+
import { prepareSelectedOutputDirectory, readContainedRegularFile, refusalText, PROJECT_FILE_MAX_BYTES, } from "../run/contained-output.js";
|
|
22
22
|
/**
|
|
23
23
|
* A scorer written for computer use and shared world, as `RunStudyOptions.scorer` takes it. Those
|
|
24
24
|
* routes pass the browser context; another route that calls it passes a context it was not written
|
|
@@ -96,11 +96,13 @@ export async function loadAdapterScorer(args) {
|
|
|
96
96
|
const relPosix = relative.split(path.sep).join("/");
|
|
97
97
|
// Fail-closed containment gate on the entry file only: rejects symlink, nlink>1, realpath-escape,
|
|
98
98
|
// TOCTOU. Its returned bytes are the digest input.
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
99
|
+
const read = await readContainedRegularFile(root, relPosix, PROJECT_FILE_MAX_BYTES);
|
|
100
|
+
if (read.status === "refused" && read.reason === "too-large")
|
|
101
|
+
return fail("HUMANISH_STUDY_SCORER_NOT_FOUND", `review.scorer.ref "${trimmed}" could not be read: ${refusalText(relPosix, read)}.`);
|
|
102
|
+
if (read.status !== "read") {
|
|
101
103
|
return fail("HUMANISH_STUDY_SCORER_NOT_FOUND", `review.scorer.ref "${trimmed}" could not be read as a regular file inside the project (${relPosix}). Use a plain file there: not a symlink or a hard link, and not a path that resolves outside the project.`);
|
|
102
104
|
}
|
|
103
|
-
const digest = digestText(bytes.toString("utf8"));
|
|
105
|
+
const digest = digestText(read.bytes.toString("utf8"));
|
|
104
106
|
// Import the entry module in a broad try/catch (ERR_MODULE_NOT_FOUND / SyntaxError / ERR_REQUIRE_ESM
|
|
105
107
|
// / top-level throw). import() executes the transitive graph, and the cwd clamp guards only the entry
|
|
106
108
|
// file; out-of-tree/transitive code is acceptable only because of the trust boundary above.
|
package/dist/study/discover.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { personaBackgroundWarnings } from "./warnings.js";
|
|
1
|
+
import { personaBackgroundWarnings, plannedParticipants } from "./warnings.js";
|
|
2
2
|
import { studyPersonaIds, resolveCommittedPersonasForCwd } from "./persona-resolve.js";
|
|
3
3
|
import { personaBrief, PersonaConfigError } from "./persona.js";
|
|
4
4
|
import { constants } from "node:fs";
|
|
@@ -7,7 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { parse } from "yaml";
|
|
8
8
|
import { parseStudy } from "./config.js";
|
|
9
9
|
import { V2_SCHEMA } from "./migrate/v2.js";
|
|
10
|
-
import { assertPreparedSelectedOutputDirectory, assertSafeOutputPathSegment, prepareSelectedOutputDirectory, readContainedRegularFile, } from "../run/contained-output.js";
|
|
10
|
+
import { assertPreparedSelectedOutputDirectory, assertSafeOutputPathSegment, prepareSelectedOutputDirectory, readContainedRegularFile, refusalText, PROJECT_FILE_MAX_BYTES, } from "../run/contained-output.js";
|
|
11
11
|
import { isNodeError } from "../run/type-guards.js";
|
|
12
12
|
import { studyFileCandidates, STUDY_DIRECTORIES } from "./files.js";
|
|
13
13
|
const STUDY_LIST_SCHEMA = "humanish.study-list.v1";
|
|
@@ -197,7 +197,7 @@ export async function inspectStudyManifest(cwd, study) {
|
|
|
197
197
|
warnings: [
|
|
198
198
|
...resolved.warnings,
|
|
199
199
|
...personaResolution.warnings,
|
|
200
|
-
...personaBackgroundWarnings(resolved.config, personaResolution.personas),
|
|
200
|
+
...personaBackgroundWarnings(resolved.config.id, plannedParticipants(resolved.config), personaResolution.personas),
|
|
201
201
|
],
|
|
202
202
|
};
|
|
203
203
|
}
|
|
@@ -279,14 +279,17 @@ async function readManagedManifest(projectRoot, relativePath) {
|
|
|
279
279
|
if (inspected.status !== "ok") {
|
|
280
280
|
return inspected;
|
|
281
281
|
}
|
|
282
|
-
const contents = await readContainedRegularFile(projectRoot, relativePath.replace(/\\/g, "/"));
|
|
283
|
-
if (
|
|
282
|
+
const contents = await readContainedRegularFile(projectRoot, relativePath.replace(/\\/g, "/"), PROJECT_FILE_MAX_BYTES);
|
|
283
|
+
if (contents.status === "refused" && contents.reason === "too-large")
|
|
284
|
+
return { status: "unsafe", message: `${refusalText("The study file", contents)}.` };
|
|
285
|
+
// Gone or swapped since it was inspected above.
|
|
286
|
+
if (contents.status !== "read") {
|
|
284
287
|
return {
|
|
285
288
|
status: "unsafe",
|
|
286
289
|
message: "The study file changed or failed containment validation.",
|
|
287
290
|
};
|
|
288
291
|
}
|
|
289
|
-
return { status: "ok", contents: contents.toString("utf8") };
|
|
292
|
+
return { status: "ok", contents: contents.bytes.toString("utf8") };
|
|
290
293
|
}
|
|
291
294
|
async function readExplicitManifest(projectRoot, requestedPath) {
|
|
292
295
|
try {
|
package/dist/study/init-paths.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { lstat } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { runtimeDirectories, starterFiles } from "./init-templates.js";
|
|
7
|
-
import { assertPreparedSelectedOutputDirectory, readContainedRegularFile, } from "../run/contained-output.js";
|
|
7
|
+
import { assertPreparedSelectedOutputDirectory, ContainedReadRefusedError, readContainedRegularFile, PROJECT_FILE_MAX_BYTES, } from "../run/contained-output.js";
|
|
8
8
|
import { isPathInside } from "../run/paths.js";
|
|
9
9
|
import { isNodeError } from "../run/type-guards.js";
|
|
10
10
|
export async function validateInitProjectPaths(cwd) {
|
|
@@ -54,10 +54,12 @@ function unsafeProjectPath(relativePath) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
export async function readTextIfExists(projectRoot, relativePath) {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
return bytes.toString("utf8");
|
|
60
|
-
|
|
57
|
+
const read = await readContainedRegularFile(projectRoot, relativePath, PROJECT_FILE_MAX_BYTES);
|
|
58
|
+
if (read.status === "read")
|
|
59
|
+
return read.bytes.toString("utf8");
|
|
60
|
+
// A file over the limit is a size problem, not an unsafe path.
|
|
61
|
+
if (read.status === "refused" && read.reason === "too-large")
|
|
62
|
+
throw new ContainedReadRefusedError(relativePath, read);
|
|
61
63
|
const target = path.join(projectRoot.physicalPath, relativePath);
|
|
62
64
|
try {
|
|
63
65
|
await lstat(target);
|
|
@@ -5,8 +5,8 @@ import type { StudyConfig } from "./types.js";
|
|
|
5
5
|
export declare function personaTitleFromId(personaId: string): string;
|
|
6
6
|
/**
|
|
7
7
|
* Resolve one persona from `humanish/personas/`, then the ignored `.humanish/local/personas/`.
|
|
8
|
-
* Returns `null` with a warning when the id is unsafe or no file exists. Invalid rich backgrounds
|
|
9
|
-
* reject the study before execution.
|
|
8
|
+
* Returns `null` with a warning when the id is unsafe or no file exists. Invalid rich backgrounds,
|
|
9
|
+
* and a persona file that is there and cannot be read, reject the study before execution.
|
|
10
10
|
*/
|
|
11
11
|
export declare function resolveCommittedPersona(projectRoot: PreparedSelectedOutputDirectory, personaId: string): Promise<{
|
|
12
12
|
persona: ResolvedPersona | null;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { parse as parseYaml } from "yaml";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parseResolvedPersona, PersonaConfigError } from "./persona.js";
|
|
5
|
-
import { prepareSelectedOutputDirectory, readContainedRegularFile, } from "../run/contained-output.js";
|
|
5
|
+
import { prepareSelectedOutputDirectory, readContainedRegularFile, PROJECT_FILE_MAX_BYTES, refusalText, } from "../run/contained-output.js";
|
|
6
6
|
import { digestText, redactText } from "../evidence/redaction.js";
|
|
7
7
|
import { realpath } from "node:fs/promises";
|
|
8
8
|
import { participantList } from "./study-fields.js";
|
|
@@ -23,8 +23,8 @@ const PERSONA_DIRECTORIES = [
|
|
|
23
23
|
];
|
|
24
24
|
/**
|
|
25
25
|
* Resolve one persona from `humanish/personas/`, then the ignored `.humanish/local/personas/`.
|
|
26
|
-
* Returns `null` with a warning when the id is unsafe or no file exists. Invalid rich backgrounds
|
|
27
|
-
* reject the study before execution.
|
|
26
|
+
* Returns `null` with a warning when the id is unsafe or no file exists. Invalid rich backgrounds,
|
|
27
|
+
* and a persona file that is there and cannot be read, reject the study before execution.
|
|
28
28
|
*/
|
|
29
29
|
export async function resolveCommittedPersona(projectRoot, personaId) {
|
|
30
30
|
if (!PERSONA_ID_PATTERN.test(personaId)) {
|
|
@@ -37,9 +37,13 @@ export async function resolveCommittedPersona(projectRoot, personaId) {
|
|
|
37
37
|
path.posix.join(directory, `${personaId}.yaml`),
|
|
38
38
|
path.posix.join(directory, `${personaId}.yml`),
|
|
39
39
|
])) {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
40
|
+
const read = await readContainedRegularFile(projectRoot, candidate, PROJECT_FILE_MAX_BYTES);
|
|
41
|
+
if (read.status === "missing")
|
|
42
42
|
continue;
|
|
43
|
+
// A candidate that is there and refused stops resolution: the next file is another persona.
|
|
44
|
+
if (read.status === "refused")
|
|
45
|
+
throw new PersonaConfigError(`Persona ${redactText(personaId)}: ${refusalText(candidate, read)}. Fix or remove that file; a lower-priority persona file is not used in its place.`);
|
|
46
|
+
const bytes = read.bytes;
|
|
43
47
|
let raw;
|
|
44
48
|
try {
|
|
45
49
|
raw = parseYaml(bytes.toString("utf8"));
|
|
@@ -196,8 +196,8 @@ export interface SharedWorldPlan extends PlanBase {
|
|
|
196
196
|
readonly plane: SharedWorldPlane;
|
|
197
197
|
/** At least 2. */
|
|
198
198
|
readonly concurrency: number;
|
|
199
|
-
/**
|
|
200
|
-
readonly sessionTimeoutMs
|
|
199
|
+
/** Each participant's session budget: execution.timeoutMs, else the plane's derived default. */
|
|
200
|
+
readonly sessionTimeoutMs: number;
|
|
201
201
|
readonly brain: Extract<Brain, {
|
|
202
202
|
kind: "openai" | "local-agent";
|
|
203
203
|
}>;
|
package/dist/study/plan.d.ts
CHANGED
|
@@ -6,8 +6,10 @@ export { routeOf, type StudyRoute } from "./routing.js";
|
|
|
6
6
|
/** Resolve dry-run: explicit override wins, else the scenario mode, else the given fallback. */
|
|
7
7
|
export declare function resolveStudyDryRun(config: StudyConfig, override: boolean | undefined, fallback: boolean | undefined): boolean | undefined;
|
|
8
8
|
/**
|
|
9
|
-
* The plan a study runs under, built without reading files
|
|
10
|
-
*
|
|
11
|
-
* route's
|
|
9
|
+
* The plan a study runs under, built without reading files or the network. From the environment
|
|
10
|
+
* (options.env, else process.env, as the routes read it) it reads one value, the E2B sandbox
|
|
11
|
+
* ceiling, which no study file can know. Each route's planner makes every refusal that route
|
|
12
|
+
* makes, in the route's order and with its codes and messages; the route's exported runner calls
|
|
13
|
+
* the same planner.
|
|
12
14
|
*/
|
|
13
15
|
export declare function planStudy(config: StudyConfig, options: InternalRunStudyOptions, deps?: StudyDeps): PlanResult;
|
package/dist/study/plan.js
CHANGED
|
@@ -6,13 +6,14 @@ import { callerDrivingOf, planComputerUseStudy } from "../routes/computer-use/pl
|
|
|
6
6
|
import { injectedBrowser, planScriptedStudy } from "../routes/scripted/plan.js";
|
|
7
7
|
import { planSharedWorldStudy } from "../routes/shared-world/plan.js";
|
|
8
8
|
import { planTerminalStudy } from "../routes/terminal/plan.js";
|
|
9
|
+
import { sandboxCeiling } from "../substrates/e2b/lifetime.js";
|
|
9
10
|
import { localBrowserDefaults } from "../substrates/local/runtime-config.js";
|
|
10
11
|
import { THIS_REPO_DRY_RUN_ONLY } from "./composition-rules.js";
|
|
11
12
|
import { planBase } from "./plan-base.js";
|
|
12
13
|
import { routeOf } from "./routing.js";
|
|
13
14
|
import { automaticAnalysisRouteReason, taskProtocolValidationReason } from "./validation.js";
|
|
14
15
|
import { declaredParticipantCount } from "./study-fields.js";
|
|
15
|
-
import {
|
|
16
|
+
import { recordedStudyWarnings } from "./warnings.js";
|
|
16
17
|
export { routeOf } from "./routing.js";
|
|
17
18
|
/** Resolve dry-run: explicit override wins, else the scenario mode, else the given fallback. */
|
|
18
19
|
export function resolveStudyDryRun(config, override, fallback) {
|
|
@@ -63,20 +64,22 @@ function planPreview(config, options, input) {
|
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
66
|
/**
|
|
66
|
-
* The plan a study runs under, built without reading files
|
|
67
|
-
*
|
|
68
|
-
* route's
|
|
67
|
+
* The plan a study runs under, built without reading files or the network. From the environment
|
|
68
|
+
* (options.env, else process.env, as the routes read it) it reads one value, the E2B sandbox
|
|
69
|
+
* ceiling, which no study file can know. Each route's planner makes every refusal that route
|
|
70
|
+
* makes, in the route's order and with its codes and messages; the route's exported runner calls
|
|
71
|
+
* the same planner.
|
|
69
72
|
*/
|
|
70
73
|
export function planStudy(config, options, deps = {}) {
|
|
71
74
|
const study = localBrowserDefaults(config);
|
|
72
75
|
const input = { dryRun: resolveStudyDryRun(study, options.dryRun, true) ?? true };
|
|
73
|
-
const
|
|
76
|
+
const ceiling = sandboxCeiling(options.env ?? process.env);
|
|
77
|
+
const result = planRoute(routeOf(config), study, options, input, deps, ceiling);
|
|
74
78
|
if (!result.ok)
|
|
75
79
|
return result;
|
|
76
80
|
// The manifest the CLI resolved enters the plan here and nowhere else; the routes read
|
|
77
81
|
// plan.study for the run's status record and bundle, and plan.warnings for its bundle events.
|
|
78
|
-
const
|
|
79
|
-
const warnings = [...(warning === undefined ? [] : [warning]), ...scriptedMissionWarnings(study)];
|
|
82
|
+
const warnings = recordedStudyWarnings(study);
|
|
80
83
|
const plan = {
|
|
81
84
|
...result.plan,
|
|
82
85
|
...(options.study === undefined ? {} : { study: options.study }),
|
|
@@ -84,7 +87,7 @@ export function planStudy(config, options, deps = {}) {
|
|
|
84
87
|
};
|
|
85
88
|
return { ok: true, planned: { plan } };
|
|
86
89
|
}
|
|
87
|
-
function planRoute(route, study, options, input, deps) {
|
|
90
|
+
function planRoute(route, study, options, input, deps, sandboxCeiling) {
|
|
88
91
|
switch (route) {
|
|
89
92
|
case "preview":
|
|
90
93
|
return planPreview(study, options, input);
|
|
@@ -95,14 +98,20 @@ function planRoute(route, study, options, input, deps) {
|
|
|
95
98
|
driving: callerDrivingOf(options),
|
|
96
99
|
...(options.count === undefined ? {} : { countOverride: options.count }),
|
|
97
100
|
...(options.rerun === undefined ? {} : { rerun: options.rerun }),
|
|
101
|
+
sandboxCeiling,
|
|
98
102
|
});
|
|
99
103
|
case "shared-world":
|
|
100
104
|
return planSharedWorldStudy(study, {
|
|
101
105
|
...input,
|
|
102
106
|
hasRunSession: deps.runSession !== undefined,
|
|
107
|
+
sandboxCeiling,
|
|
103
108
|
});
|
|
104
109
|
case "terminal":
|
|
105
|
-
return planTerminalStudy(study, {
|
|
110
|
+
return planTerminalStudy(study, {
|
|
111
|
+
...input,
|
|
112
|
+
hasCostProbe: deps.costProbe !== undefined,
|
|
113
|
+
sandboxCeiling,
|
|
114
|
+
});
|
|
106
115
|
case "scripted":
|
|
107
116
|
return planScriptedStudy(study, { ...input, injectedBrowser: injectedBrowser(deps) });
|
|
108
117
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// the study and dispatches here.
|
|
5
5
|
import { cloneProvisioningBudgetMs, provisionCloneSubject } from "../subject/clone.js";
|
|
6
6
|
import { CUA_ACTOR_STUDY_PROVIDER_METADATA } from "../routes/computer-use/e2b-desktop/prepare.js";
|
|
7
|
-
import {
|
|
7
|
+
import { sandboxCeiling } from "../substrates/e2b/lifetime.js";
|
|
8
8
|
import { abandonPreflightJournal, discardPreflightJournal, openPreflightJournal, } from "../run/preflight-receipts.js";
|
|
9
9
|
import { probeUrl } from "../substrates/detached.js";
|
|
10
10
|
import { loadE2BDesktopModule } from "../substrates/e2b/sdk.js";
|
|
@@ -147,8 +147,16 @@ async function withPreflightSandbox(ctx, args, callback) {
|
|
|
147
147
|
let allocation;
|
|
148
148
|
let failureMessage;
|
|
149
149
|
// The lease is sized to the probe's work, never longer than a declared sandbox timeout (the run
|
|
150
|
-
// gets no more than that either) or
|
|
151
|
-
const
|
|
150
|
+
// gets no more than that either) or the sandbox ceiling.
|
|
151
|
+
const ceiling = sandboxCeiling(ctx.env);
|
|
152
|
+
if (!ceiling.ok)
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
result: fail(ctx, "HUMANISH_STUDY_PREFLIGHT_PROVISION_FAILED", ceiling.message, [
|
|
156
|
+
{ name: "sandbox preflight", ok: false, message: ceiling.message },
|
|
157
|
+
]),
|
|
158
|
+
};
|
|
159
|
+
const timeoutMs = Math.min(args.leaseMs, ctx.config.execution?.desktop?.sandboxTimeoutMs ?? ceiling.ms);
|
|
152
160
|
// The receipt goes to a journal under .humanish/preflight, so `humanish reclaim --preflight`
|
|
153
161
|
// can kill the probe if this process dies before the finally block does.
|
|
154
162
|
let journal;
|
package/dist/study/preflight.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { personaBackgroundWarnings } from "./warnings.js";
|
|
1
|
+
import { personaBackgroundWarnings, plannedParticipants } from "./warnings.js";
|
|
2
2
|
import { resolveCommittedPersonasForCwd, studyPersonaIds } from "./persona-resolve.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { isLoopbackUrl } from "./parse/subject.js";
|
|
@@ -37,9 +37,10 @@ export async function runStudyPreflight(options) {
|
|
|
37
37
|
error: resolved.error,
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const participants = plannedParticipants(resolved.config);
|
|
41
|
+
const personas = participants.length === 0
|
|
42
|
+
? new Map()
|
|
43
|
+
: (await resolveCommittedPersonasForCwd(cwd, studyPersonaIds(resolved.config))).personas;
|
|
43
44
|
const route = routeOf(resolved.config);
|
|
44
45
|
const ctx = {
|
|
45
46
|
cwd,
|
|
@@ -59,7 +60,10 @@ export async function runStudyPreflight(options) {
|
|
|
59
60
|
],
|
|
60
61
|
targets: collectTargets(resolved.config),
|
|
61
62
|
sandbox: { created: false },
|
|
62
|
-
warnings: [
|
|
63
|
+
warnings: [
|
|
64
|
+
...resolved.warnings,
|
|
65
|
+
...personaBackgroundWarnings(resolved.config.id, participants, personas),
|
|
66
|
+
],
|
|
63
67
|
};
|
|
64
68
|
const machine = machineCheck(ctx.config);
|
|
65
69
|
// A local study's app and desktops run on this machine, so the hosted probes do not apply.
|
package/dist/study/summary.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type LocalRuntimeStatus } from "../substrates/local/runtime.js";
|
|
2
|
+
import { type AutomaticAnalysisBudget } from "../analysis/automatic-config.js";
|
|
2
3
|
import { type KeyResolutionDeps } from "../keys/key-resolution.js";
|
|
3
4
|
export declare const STUDY_SUMMARY_SCHEMA = "humanish.study-summary.v1";
|
|
4
5
|
export interface StudyCaps {
|
|
@@ -14,11 +15,9 @@ export interface StudySummary {
|
|
|
14
15
|
message: string;
|
|
15
16
|
};
|
|
16
17
|
communications?: string;
|
|
17
|
-
analysis?: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
model: string;
|
|
21
|
-
maxCostUsd: number | null;
|
|
18
|
+
analysis?: AutomaticAnalysisBudget & {
|
|
19
|
+
/** The budget line `run` and `study check` print, from formatAutomaticAnalysisBudget. */
|
|
20
|
+
line: string;
|
|
22
21
|
};
|
|
23
22
|
schema: typeof STUDY_SUMMARY_SCHEMA;
|
|
24
23
|
/** The study's `id`. */
|
|
@@ -39,9 +38,15 @@ export interface StudySummary {
|
|
|
39
38
|
* default. A study variable you cannot see is one nobody chose.
|
|
40
39
|
*/
|
|
41
40
|
reasoningEffort?: string;
|
|
42
|
-
caps
|
|
41
|
+
/** Absent on routes whose dollar caps the summary does not show; `{}` when none is declared. */
|
|
42
|
+
caps?: StudyCaps;
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* What `humanish run <study>` runs this file as: its `mode`, a dry run when it sets none. The TUI
|
|
45
|
+
* cannot start a live run of a dry-run file, because no `run` flag overrides the file to live.
|
|
46
|
+
*/
|
|
47
|
+
mode: "live" | "dry-run";
|
|
48
|
+
/**
|
|
49
|
+
* Whether the keys a live run of this study needs resolve, whatever its mode. This checks key
|
|
45
50
|
* presence, not local CLI authentication or provider validity. Undefined when not checked.
|
|
46
51
|
*/
|
|
47
52
|
keysReady?: boolean;
|