humanish 0.110.4 → 0.111.1
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 +31 -0
- package/CONTEXT.md +2 -2
- package/dist/actors/codex/restricted-participant-policy.d.ts +2 -0
- package/dist/actors/codex/restricted-participant-policy.js +30 -2
- package/dist/actors/codex/restricted-participant.js +1 -1
- package/dist/actors/computer-use/executor-diagnostic.d.ts +15 -0
- package/dist/actors/computer-use/executor-diagnostic.js +105 -0
- package/dist/actors/computer-use/executor-error.d.ts +18 -3
- package/dist/actors/computer-use/executor-error.js +30 -4
- package/dist/actors/computer-use/loop/actions.d.ts +6 -3
- package/dist/actors/computer-use/loop/actions.js +27 -7
- package/dist/actors/computer-use/loop/ending.js +9 -1
- package/dist/actors/computer-use/loop/types.d.ts +14 -0
- package/dist/actors/computer-use/loop.d.ts +1 -1
- package/dist/actors/computer-use/loop.js +1 -1
- package/dist/actors/computer-use/openai-context.d.ts +8 -2
- package/dist/actors/computer-use/openai-context.js +91 -26
- package/dist/actors/computer-use/openai-provider.js +55 -12
- package/dist/actors/computer-use/openai-requests.d.ts +2 -1
- package/dist/actors/computer-use/openai-requests.js +4 -1
- package/dist/actors/contract.d.ts +7 -1
- package/dist/actors/local-agent/claude-session.js +1 -1
- package/dist/actors/local-agent/cli.js +1 -1
- package/dist/actors/scripted-browser/actor.d.ts +2 -2
- package/dist/browser-control/client.js +5 -5
- package/dist/browser-control/protocol.d.ts +30 -1
- package/dist/browser-control/protocol.js +19 -2
- package/dist/browser-control/transport.d.ts +3 -2
- package/dist/browser-control/transport.js +12 -7
- package/dist/cli/commands/study-route-computer-use.js +2 -2
- package/dist/cli/commands/study-route-shared-world.js +2 -2
- package/dist/cli/commands/study.js +1 -4
- package/dist/cli/doctor.js +11 -0
- package/dist/evidence/encoded-text.d.ts +3 -3
- package/dist/evidence/encoded-text.js +10 -5
- package/dist/evidence/redaction.d.ts +2 -2
- package/dist/evidence/redaction.js +18 -7
- package/dist/guest/browser-tools.js +5 -1
- package/dist/guest/chromium-text.js +42 -18
- package/dist/guest/desktop-executor.js +5 -1
- package/dist/keys/key-resolution.d.ts +3 -0
- package/dist/keys/key-resolution.js +15 -2
- package/dist/routes/computer-use/bundle-parts.d.ts +4 -0
- package/dist/routes/computer-use/bundle-parts.js +7 -2
- package/dist/routes/computer-use/fanout-bundle.js +4 -2
- package/dist/routes/computer-use/local-vm.js +1 -2
- package/dist/routes/computer-use/participant-model.js +7 -2
- package/dist/routes/computer-use/plan.js +7 -10
- package/dist/routes/computer-use/preflight.js +1 -1
- package/dist/routes/computer-use/result.js +1 -1
- package/dist/routes/computer-use/route.js +2 -3
- package/dist/routes/computer-use/setup.js +2 -2
- package/dist/routes/computer-use/types.d.ts +4 -4
- package/dist/routes/preview.js +1 -0
- package/dist/routes/scripted/plan.js +4 -5
- package/dist/routes/scripted/route.js +3 -3
- package/dist/routes/scripted/scenario.d.ts +1 -1
- package/dist/routes/scripted/scenario.js +3 -3
- package/dist/routes/scripted/types.d.ts +2 -2
- package/dist/routes/shared-world/handoff.js +1 -1
- package/dist/routes/shared-world/participant-specs.js +1 -1
- package/dist/routes/shared-world/plan.js +6 -8
- package/dist/routes/shared-world/route.js +2 -2
- package/dist/routes/shared-world/setup.js +1 -0
- package/dist/routes/shared-world/types.d.ts +1 -1
- package/dist/routes/terminal/bundle.d.ts +3 -3
- package/dist/routes/terminal/dry-run.js +1 -0
- package/dist/routes/terminal/ledger.d.ts +2 -2
- package/dist/routes/terminal/lifetime.js +1 -1
- package/dist/routes/terminal/live-finish.js +25 -6
- package/dist/routes/terminal/live-sandbox.js +5 -1
- package/dist/routes/terminal/plan.js +3 -4
- package/dist/routes/terminal/recorder.d.ts +6 -0
- package/dist/routes/terminal/recorder.js +61 -6
- package/dist/routes/terminal/route.js +2 -3
- package/dist/routes/terminal/session.js +2 -1
- package/dist/routes/terminal/token-usage.d.ts +3 -0
- package/dist/routes/terminal/token-usage.js +5 -0
- package/dist/routes/terminal/types.d.ts +3 -2
- package/dist/routes/terminal/types.js +2 -1
- package/dist/run/dry-run.js +1 -0
- package/dist/run/results.d.ts +3 -1
- package/dist/run/run.d.ts +2 -0
- package/dist/run/run.js +16 -2
- package/dist/run/terminal-contract.d.ts +10 -0
- package/dist/run/terminal-contract.js +27 -9
- package/dist/run-study.d.ts +5 -3
- package/dist/run-study.js +7 -11
- package/dist/study/adapter-scorer-loader.d.ts +1 -1
- package/dist/study/adapter-scorer-loader.js +3 -3
- package/dist/study/composition-rules.js +36 -27
- package/dist/study/config.d.ts +29 -13
- package/dist/study/config.js +174 -157
- package/dist/study/discover.d.ts +1 -1
- package/dist/study/discover.js +1 -1
- package/dist/study/doctor.js +4 -5
- package/dist/study/keys.d.ts +250 -2
- package/dist/study/keys.js +32 -62
- package/dist/study/migrate/convert.js +3 -4
- package/dist/study/migrate/v2.d.ts +22 -7
- package/dist/study/migrate/v2.js +173 -23
- package/dist/study/parse/actors.d.ts +11 -32
- package/dist/study/parse/actors.js +11 -156
- package/dist/study/parse/comms.js +6 -2
- package/dist/study/parse/execution.d.ts +9 -4
- package/dist/study/parse/execution.js +4 -39
- package/dist/study/parse/{study-v3.d.ts → front.d.ts} +6 -17
- package/dist/study/parse/{study-v3.js → front.js} +24 -112
- package/dist/study/parse/subject.d.ts +2 -0
- package/dist/study/parse/subject.js +15 -18
- package/dist/study/parse/url-credentials.d.ts +3 -3
- package/dist/study/parse/url-credentials.js +155 -13
- package/dist/study/persona-resolve.js +7 -9
- package/dist/study/plan-base.d.ts +1 -1
- package/dist/study/plan-base.js +5 -7
- package/dist/study/plan-participants.d.ts +2 -2
- package/dist/study/plan-participants.js +3 -3
- package/dist/study/plan-types.d.ts +7 -5
- package/dist/study/plan.js +12 -6
- package/dist/study/preflight-probes.js +2 -2
- package/dist/study/preflight.d.ts +1 -1
- package/dist/study/preflight.js +1 -1
- package/dist/study/routing.d.ts +5 -5
- package/dist/study/routing.js +16 -16
- package/dist/study/run-study-options.d.ts +1 -1
- package/dist/study/run-study-options.js +2 -2
- package/dist/study/study-deps.d.ts +1 -1
- package/dist/study/study-fields.d.ts +4 -25
- package/dist/study/study-fields.js +15 -44
- package/dist/study/summary.js +13 -8
- package/dist/study/types.d.ts +51 -128
- package/dist/study/types.js +0 -1
- package/dist/study/url-credentials.js +3 -4
- package/dist/study/validation.d.ts +2 -10
- package/dist/study/validation.js +23 -53
- package/dist/study/warnings.d.ts +11 -3
- package/dist/study/warnings.js +56 -110
- package/dist/substrates/local/runtime-config.js +11 -13
- package/docs/architecture/browser-control.md +12 -2
- package/docs/architecture/guest-desktop.md +7 -1
- package/docs/architecture/terminal-product-route.md +3 -2
- package/docs/contracts/schemas.md +39 -9
- package/package.json +1 -1
- package/dist/study/parse/study.d.ts +0 -38
- package/dist/study/parse/study.js +0 -225
|
@@ -61,6 +61,7 @@ export async function runLiveTerminalSession(args) {
|
|
|
61
61
|
// This entry point is the live terminal route; its dry-run sibling is a separate function.
|
|
62
62
|
mode: "live",
|
|
63
63
|
study: plan.study,
|
|
64
|
+
warnings: plan.warnings,
|
|
64
65
|
renderReview: renderTerminalReviewMarkdown,
|
|
65
66
|
observer: { open: input.open === true, render: deps.renderObserver },
|
|
66
67
|
now,
|
|
@@ -77,7 +78,7 @@ export async function runLiveTerminalSession(args) {
|
|
|
77
78
|
});
|
|
78
79
|
const e2bApiKey = env.E2B_API_KEY?.trim() ?? "";
|
|
79
80
|
// The ledgers + capture buffers, mutated through the live lifecycle.
|
|
80
|
-
const recorder = createTerminalRecorder({ nowIso, sanitize, knownSecretValues });
|
|
81
|
+
const recorder = createTerminalRecorder({ nowIso, sanitize, knownSecretValues, verdictNonce });
|
|
81
82
|
const { version, model, modelSource, reasoningEffort } = plan.runtime;
|
|
82
83
|
const runtime = declaredRuntimeProvenance({
|
|
83
84
|
...(version === undefined ? {} : { version }),
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ActorTokenUsage } from "../../actors/contract.js";
|
|
2
|
+
/** The usage records in complete stdout lines, kept from output past the transcript cap so
|
|
3
|
+
* parseTerminalTokenUsage can count them with the stored ones. */
|
|
4
|
+
export declare function terminalUsageRecords(lines: string): string[];
|
|
2
5
|
/**
|
|
3
6
|
* Accumulate runtime-turn usage from a captured `codex exec --json` stream. A Codex turn may
|
|
4
7
|
* include multiple provider requests; these records cannot establish per-request pricing tiers.
|
|
@@ -19,6 +19,11 @@ const USAGE_RE = /"type"\s*:\s*"turn\.completed"\s*,\s*"usage"\s*:\s*(\{[^}\n]*\
|
|
|
19
19
|
function num(value) {
|
|
20
20
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
21
21
|
}
|
|
22
|
+
/** The usage records in complete stdout lines, kept from output past the transcript cap so
|
|
23
|
+
* parseTerminalTokenUsage can count them with the stored ones. */
|
|
24
|
+
export function terminalUsageRecords(lines) {
|
|
25
|
+
return Array.from(lines.matchAll(USAGE_RE), (match) => match[0]);
|
|
26
|
+
}
|
|
22
27
|
/**
|
|
23
28
|
* Accumulate runtime-turn usage from a captured `codex exec --json` stream. A Codex turn may
|
|
24
29
|
* include multiple provider requests; these records cannot establish per-request pricing tiers.
|
|
@@ -53,7 +53,8 @@ export interface TerminalProductScoringContext {
|
|
|
53
53
|
/**
|
|
54
54
|
* The full normalized transcript of the in-sandbox agent session, scrubbed (literal known
|
|
55
55
|
* values) then redacted (shape patterns) at the source, capped at MAX_TRANSCRIPT_BYTES, and
|
|
56
|
-
* byte-identical to the persisted terminal-transcript.txt artifact.
|
|
56
|
+
* byte-identical to the persisted terminal-transcript.txt artifact. A capped transcript ends
|
|
57
|
+
* with a `[humanish]` line giving the byte count that was not stored. The trace's transcriptTail
|
|
57
58
|
* is a ~2KB projection of this; a scorer needs the whole session so a rubric can find
|
|
58
59
|
* command-tier evidence anywhere in it.
|
|
59
60
|
*/
|
|
@@ -171,7 +172,7 @@ export interface TerminalProductStudyResult extends AutomaticAnalysisResult, Stu
|
|
|
171
172
|
observer?: ObserverResult;
|
|
172
173
|
warnings: string[];
|
|
173
174
|
error?: {
|
|
174
|
-
code: "HUMANISH_STUDY_ANALYSIS_INVALID" | "HUMANISH_STUDY_TASKS_UNSUPPORTED" | "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED" | "HUMANISH_TERMINAL_FAILED" | "HUMANISH_TERMINAL_ACTOR_UNSUPPORTED" | "HUMANISH_TERMINAL_SUBJECT_INVALID" | "HUMANISH_TERMINAL_KEYPLACEMENT_INVALID" | "HUMANISH_TERMINAL_RUNTIME_AUTH_MISSING" | "HUMANISH_TERMINAL_KEYS_MISSING" | "HUMANISH_TERMINAL_CAPS_MISSING" | "HUMANISH_TERMINAL_CAPS_INVALID" | "HUMANISH_TERMINAL_UNPRICED_CAP" | "HUMANISH_TERMINAL_CAPS_EXCEEDED" | "HUMANISH_TERMINAL_CREDENTIAL_DENIED" | "HUMANISH_TERMINAL_CLEANUP_UNPROVEN" | "HUMANISH_RUN_ID_IN_USE";
|
|
175
|
+
code: "HUMANISH_STUDY_ANALYSIS_INVALID" | "HUMANISH_STUDY_TASKS_UNSUPPORTED" | "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED" | "HUMANISH_STUDY_INVALID" | "HUMANISH_TERMINAL_FAILED" | "HUMANISH_TERMINAL_ACTOR_UNSUPPORTED" | "HUMANISH_TERMINAL_SUBJECT_INVALID" | "HUMANISH_TERMINAL_KEYPLACEMENT_INVALID" | "HUMANISH_TERMINAL_RUNTIME_AUTH_MISSING" | "HUMANISH_TERMINAL_KEYS_MISSING" | "HUMANISH_TERMINAL_CAPS_MISSING" | "HUMANISH_TERMINAL_CAPS_INVALID" | "HUMANISH_TERMINAL_UNPRICED_CAP" | "HUMANISH_TERMINAL_CAPS_EXCEEDED" | "HUMANISH_TERMINAL_CREDENTIAL_DENIED" | "HUMANISH_TERMINAL_CLEANUP_UNPROVEN" | "HUMANISH_RUN_ID_IN_USE";
|
|
175
176
|
message: string;
|
|
176
177
|
};
|
|
177
178
|
}
|
|
@@ -32,5 +32,6 @@ export const TEXT_ITEMS_BYTES = 128 * 1024;
|
|
|
32
32
|
// The longest stdout line the participant reader holds while waiting for its newline.
|
|
33
33
|
export const PENDING_LINE_CHARS = 1024 * 1024;
|
|
34
34
|
// Hard cap on the retained event-stream + transcript size, so a runaway agent cannot balloon the
|
|
35
|
-
// bundle. Redaction runs pre-truncation so a cut can never split a secret past the scrubber.
|
|
35
|
+
// bundle. Redaction runs pre-truncation so a cut can never split a secret past the scrubber. Output
|
|
36
|
+
// past the cap is still read for the verdict marker, token usage and the trace's agent text.
|
|
36
37
|
export const MAX_TRANSCRIPT_BYTES = 512 * 1024;
|
package/dist/run/dry-run.js
CHANGED
|
@@ -67,6 +67,7 @@ async function runDryRunInScope(options, scope) {
|
|
|
67
67
|
mintRunId: () => `dryrun-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`,
|
|
68
68
|
mode: options.dryRun ? "dry-run" : "live",
|
|
69
69
|
study: options.study,
|
|
70
|
+
warnings: options.warnings,
|
|
70
71
|
renderReview: renderReviewMarkdown,
|
|
71
72
|
// The run starts at the time its id and source were stamped with.
|
|
72
73
|
now: () => Date.parse(createdAt),
|
package/dist/run/results.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export declare const CLEANUP_SCHEMA = "humanish.cleanup-result.v1";
|
|
|
5
5
|
export interface RunOptions {
|
|
6
6
|
/** Which manifest produced this run. */
|
|
7
7
|
study?: RunStudyProvenance;
|
|
8
|
+
/** Warnings about the study's own fields, recorded in the bundle as warn events. */
|
|
9
|
+
warnings?: readonly string[];
|
|
8
10
|
cwd: string;
|
|
9
11
|
dryRun?: boolean;
|
|
10
12
|
runId?: string;
|
|
@@ -30,7 +32,7 @@ export interface RunResult {
|
|
|
30
32
|
observer?: ObserverResult;
|
|
31
33
|
warnings: string[];
|
|
32
34
|
error?: {
|
|
33
|
-
code: "HUMANISH_STUDY_ANALYSIS_INVALID" | "HUMANISH_STUDY_ANALYSIS_UNSUPPORTED" | "HUMANISH_STUDY_TASKS_UNSUPPORTED" | "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED" | "HUMANISH_STUDY_COMMS_UNSUPPORTED" | "HUMANISH_LIVE_RUN_UNIMPLEMENTED" | "HUMANISH_INVALID_CWD" | "HUMANISH_RUN_ID_IN_USE" | "HUMANISH_INVALID_PARTICIPANT_COUNT" | "HUMANISH_INVALID_PORT" | "HUMANISH_UNSUPPORTED_RERUN_FLAGS" | "HUMANISH_WATCH_OPTION_CONFLICT" | "HUMANISH_WATCH_SAFE_NOT_APPLICABLE" | "HUMANISH_RUN_OPTION_CONFLICT" | "HUMANISH_OBSERVE_OPTION_CONFLICT" | "HUMANISH_STUDY_SCORER_BAD_REF" | "HUMANISH_STUDY_SCORER_NOT_FOUND" | "HUMANISH_STUDY_SCORER_LOAD_FAILED" | "HUMANISH_STUDY_SCORER_NO_HOOKS" | "HUMANISH_STUDY_SCORER_UNSUPPORTED_BACKEND";
|
|
35
|
+
code: "HUMANISH_STUDY_ANALYSIS_INVALID" | "HUMANISH_STUDY_ANALYSIS_UNSUPPORTED" | "HUMANISH_STUDY_TASKS_UNSUPPORTED" | "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED" | "HUMANISH_STUDY_INVALID" | "HUMANISH_STUDY_COMMS_UNSUPPORTED" | "HUMANISH_LIVE_RUN_UNIMPLEMENTED" | "HUMANISH_INVALID_CWD" | "HUMANISH_RUN_ID_IN_USE" | "HUMANISH_INVALID_PARTICIPANT_COUNT" | "HUMANISH_INVALID_PORT" | "HUMANISH_UNSUPPORTED_RERUN_FLAGS" | "HUMANISH_WATCH_OPTION_CONFLICT" | "HUMANISH_WATCH_SAFE_NOT_APPLICABLE" | "HUMANISH_RUN_OPTION_CONFLICT" | "HUMANISH_OBSERVE_OPTION_CONFLICT" | "HUMANISH_STUDY_SCORER_BAD_REF" | "HUMANISH_STUDY_SCORER_NOT_FOUND" | "HUMANISH_STUDY_SCORER_LOAD_FAILED" | "HUMANISH_STUDY_SCORER_NO_HOOKS" | "HUMANISH_STUDY_SCORER_UNSUPPORTED_BACKEND";
|
|
34
36
|
message: string;
|
|
35
37
|
};
|
|
36
38
|
}
|
package/dist/run/run.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ interface StartRunOptions {
|
|
|
22
22
|
};
|
|
23
23
|
/** Clock for `createdAt` and the latest pointer. */
|
|
24
24
|
now?: (() => number) | undefined;
|
|
25
|
+
/** Warnings about the study's own fields. Every bundle write records each as a warn event. */
|
|
26
|
+
warnings?: readonly string[] | undefined;
|
|
25
27
|
}
|
|
26
28
|
/** How a run ended as an execution, as run.json records it: the result's ok and execution outcome. */
|
|
27
29
|
interface RecordedOutcome {
|
package/dist/run/run.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { redactText } from "../evidence/redaction.js";
|
|
6
6
|
import { buildObserverData } from "../observer/data.js";
|
|
7
7
|
import { renderObserver } from "../observer/render.js";
|
|
8
|
-
import { RUN_BUNDLE_FILE, PUBLIC_TARGET_CWD } from "./bundle.js";
|
|
8
|
+
import { RUN_BUNDLE_FILE, PUBLIC_TARGET_CWD, } from "./bundle.js";
|
|
9
9
|
import { judgeExecution, } from "./judge.js";
|
|
10
10
|
import {} from "./results.js";
|
|
11
11
|
import { createRunArtifactPaths, validatePreparedRunArtifactPaths, } from "./paths.js";
|
|
@@ -112,8 +112,20 @@ function observerTarget(options) {
|
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
function runPublisher(args) {
|
|
115
|
-
const { options, runId, paths, runStatus, now, admit, participantsRan } = args;
|
|
115
|
+
const { options, runId, createdAt, paths, runStatus, now, admit, participantsRan } = args;
|
|
116
116
|
const observer = observerTarget(options);
|
|
117
|
+
// The study's warnings join whatever events the route wrote, once, in every write.
|
|
118
|
+
const studyWarnings = (options.warnings ?? []).map((message, index) => ({
|
|
119
|
+
id: `event-study-warning-${String(index + 1).padStart(3, "0")}`,
|
|
120
|
+
at: createdAt,
|
|
121
|
+
level: "warn",
|
|
122
|
+
type: "study.warning",
|
|
123
|
+
message,
|
|
124
|
+
}));
|
|
125
|
+
const withStudyWarnings = (events) => [
|
|
126
|
+
...events.filter((event) => !studyWarnings.some((warning) => warning.id === event.id)),
|
|
127
|
+
...studyWarnings,
|
|
128
|
+
];
|
|
117
129
|
let pointerWritten = false;
|
|
118
130
|
// The bundle of the last write asked for, and the outcome every later write carries once a
|
|
119
131
|
// signal stopped the run, so no write after the interrupt can drop it.
|
|
@@ -145,6 +157,7 @@ function runPublisher(args) {
|
|
|
145
157
|
const recorded = interrupted ?? outcome;
|
|
146
158
|
const publicBundle = await withPublicSandboxIds(paths, {
|
|
147
159
|
...evidence,
|
|
160
|
+
events: withStudyWarnings(evidence.events),
|
|
148
161
|
cwd: PUBLIC_TARGET_CWD,
|
|
149
162
|
...(recorded === undefined ? {} : { outcome: recorded }),
|
|
150
163
|
});
|
|
@@ -271,6 +284,7 @@ export async function runScope(fn) {
|
|
|
271
284
|
const publisher = runPublisher({
|
|
272
285
|
options,
|
|
273
286
|
runId,
|
|
287
|
+
createdAt,
|
|
274
288
|
paths,
|
|
275
289
|
runStatus,
|
|
276
290
|
now,
|
|
@@ -22,4 +22,14 @@ type ActorVerdict = "passed" | "blocked" | "failed";
|
|
|
22
22
|
* divergent verdict logic would let the two routes disagree about what "passed" means.
|
|
23
23
|
*/
|
|
24
24
|
export declare function extractLocalActorVerdict(transcript: string, verdictNonce: string): ActorVerdict | null;
|
|
25
|
+
/**
|
|
26
|
+
* The same search over text that arrives in pieces and is not kept: the terminal route reads
|
|
27
|
+
* output past its transcript cap through this. Each piece is normalized and loses its whitespace
|
|
28
|
+
* as the whole transcript does above, and the end of the previous piece is carried so a marker
|
|
29
|
+
* split between pieces is found. The first marker wins, as above.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createLocalActorVerdictScanner(verdictNonce: string): {
|
|
32
|
+
push(text: string): void;
|
|
33
|
+
verdict(): ActorVerdict | null;
|
|
34
|
+
};
|
|
25
35
|
export {};
|
|
@@ -27,6 +27,9 @@ export function normalizeLocalActorTranscript(transcript) {
|
|
|
27
27
|
.replace(/\r/g, "\n")
|
|
28
28
|
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
|
|
29
29
|
}
|
|
30
|
+
// The nonce is mandatory: a bare HUMANISH_ACTOR_VERDICT=<status> marker echoed by an actor (or
|
|
31
|
+
// replayed from untrusted text) must never satisfy verdict extraction.
|
|
32
|
+
const verdictPattern = (verdictNonce) => new RegExp(`HUMANISH_ACTOR_VERDICT=(passed|blocked|failed)HUMANISH_ACTOR_NONCE=${escapeRegExp(verdictNonce)}`, "i");
|
|
30
33
|
/**
|
|
31
34
|
* Extract the per-run verdict from a normalized transcript: the agent must print exactly
|
|
32
35
|
* `HUMANISH_ACTOR_VERDICT=<status> HUMANISH_ACTOR_NONCE=<nonce>`, and the nonce is mandatory so a
|
|
@@ -35,13 +38,28 @@ export function normalizeLocalActorTranscript(transcript) {
|
|
|
35
38
|
* divergent verdict logic would let the two routes disagree about what "passed" means.
|
|
36
39
|
*/
|
|
37
40
|
export function extractLocalActorVerdict(transcript, verdictNonce) {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const match = verdictPattern(verdictNonce).exec(transcript.replace(/\s+/g, ""));
|
|
42
|
+
return match?.[1]?.toLowerCase() ?? null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The same search over text that arrives in pieces and is not kept: the terminal route reads
|
|
46
|
+
* output past its transcript cap through this. Each piece is normalized and loses its whitespace
|
|
47
|
+
* as the whole transcript does above, and the end of the previous piece is carried so a marker
|
|
48
|
+
* split between pieces is found. The first marker wins, as above.
|
|
49
|
+
*/
|
|
50
|
+
export function createLocalActorVerdictScanner(verdictNonce) {
|
|
51
|
+
const pattern = verdictPattern(verdictNonce);
|
|
52
|
+
const carried = `HUMANISH_ACTOR_VERDICT=blockedHUMANISH_ACTOR_NONCE=${verdictNonce}`.length - 1;
|
|
53
|
+
let tail = "";
|
|
54
|
+
let found = null;
|
|
55
|
+
return {
|
|
56
|
+
push(text) {
|
|
57
|
+
if (found)
|
|
58
|
+
return;
|
|
59
|
+
const window = tail + normalizeLocalActorTranscript(text).replace(/\s+/g, "");
|
|
60
|
+
found = pattern.exec(window)?.[1]?.toLowerCase() ?? null;
|
|
61
|
+
tail = window.slice(-carried);
|
|
62
|
+
},
|
|
63
|
+
verdict: () => found,
|
|
64
|
+
};
|
|
47
65
|
}
|
package/dist/run-study.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type LateScorer } from "./study/route-inputs.js";
|
|
|
3
3
|
import { type StudyRoute } from "./study/plan.js";
|
|
4
4
|
import type { StudyDeps } from "./study/study-deps.js";
|
|
5
5
|
import type { RunStudyDriving, RunStudyHomes } from "./study/run-study-homes.js";
|
|
6
|
-
import {
|
|
6
|
+
import type { StudyConfig } from "./study/types.js";
|
|
7
7
|
import type { ObserverResult } from "./observer/render.js";
|
|
8
8
|
import { type CuaActorStudyResult } from "./routes/computer-use/types.js";
|
|
9
9
|
import { type PreviewStudyResult } from "./routes/preview.js";
|
|
@@ -20,8 +20,10 @@ import type { RunStudyProvenance } from "./run/study-provenance.js";
|
|
|
20
20
|
export declare function runStudyWith(config: StudyConfig, options: InternalRunStudyOptions, deps?: StudyDeps): Promise<StudyOutcome>;
|
|
21
21
|
/**
|
|
22
22
|
* The package's study runner (`src/index.ts`): runStudyWith with the public options only. A
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* config that skipped parseStudy is checked first (libraryConfigRefusal): a humanish.lab.v2 config
|
|
24
|
+
* or one with a humanish 0.110 field, no `actor`, or a `route` the config does not take. That, or a
|
|
25
|
+
* field RunStudyOptions no longer has, is refused in the route's own result envelope before
|
|
26
|
+
* anything runs.
|
|
25
27
|
*/
|
|
26
28
|
export declare function runStudy(config: StudyConfig, options: RunStudyOptions): Promise<StudyOutcome>;
|
|
27
29
|
/** A study planned once: the route's refusal, or the run of its plan. */
|
package/dist/run-study.js
CHANGED
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
import { computerUseInput, scriptedInput, sharedWorldInput, terminalInput, } from "./study/route-inputs.js";
|
|
8
8
|
import { planStudy, resolveStudyDryRun, routeOf } from "./study/plan.js";
|
|
9
9
|
import { normalizeRunStudyOptions, optionRefusalOutcome, removedOptionRefusal, } from "./study/run-study-options.js";
|
|
10
|
-
import {
|
|
11
|
-
import { V2_UNSUPPORTED_MESSAGE } from "./study/config.js";
|
|
10
|
+
import { libraryConfigRefusal } from "./study/config.js";
|
|
12
11
|
import { admitComputerUsePlan, computerUseStudyRefusal } from "./routes/computer-use/route.js";
|
|
13
12
|
import {} from "./routes/computer-use/types.js";
|
|
14
13
|
import { admitPreviewPlan, previewStudyRefusal, } from "./routes/preview.js";
|
|
@@ -32,17 +31,14 @@ export async function runStudyWith(config, options, deps = {}) {
|
|
|
32
31
|
}
|
|
33
32
|
/**
|
|
34
33
|
* The package's study runner (`src/index.ts`): runStudyWith with the public options only. A
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* config that skipped parseStudy is checked first (libraryConfigRefusal): a humanish.lab.v2 config
|
|
35
|
+
* or one with a humanish 0.110 field, no `actor`, or a `route` the config does not take. That, or a
|
|
36
|
+
* field RunStudyOptions no longer has, is refused in the route's own result envelope before
|
|
37
|
+
* anything runs.
|
|
37
38
|
*/
|
|
38
39
|
export async function runStudy(config, options) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
ok: false,
|
|
42
|
-
code: "HUMANISH_STUDY_V2_UNSUPPORTED",
|
|
43
|
-
message: V2_UNSUPPORTED_MESSAGE,
|
|
44
|
-
}
|
|
45
|
-
: removedOptionRefusal(options);
|
|
40
|
+
const rejected = libraryConfigRefusal(config);
|
|
41
|
+
const refusal = rejected === undefined ? removedOptionRefusal(options) : { ok: false, ...rejected };
|
|
46
42
|
if (refusal === undefined)
|
|
47
43
|
return runStudyWith(config, options);
|
|
48
44
|
const study = localBrowserDefaults(config);
|
|
@@ -50,7 +50,7 @@ export type AdapterScorerLoadResult = {
|
|
|
50
50
|
};
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
* Resolve + load a config-declared scorer module. Resolution clones scenario
|
|
53
|
+
* Resolve + load a config-declared scorer module. Resolution clones the scripted `scenario` resolution exactly
|
|
54
54
|
* (prepareSelectedOutputDirectory → cwd-clamp → readContainedRegularFile), then `import()`s the
|
|
55
55
|
* entry file in a broad try/catch. The digest is over the readContainedRegularFile entry bytes.
|
|
56
56
|
*/
|
|
@@ -54,7 +54,7 @@ const SCORER_CAPABLE_ROUTES = new Set([
|
|
|
54
54
|
* package.json `type`. `.ts` is rejected: the compiled `dist/` CLI ships no TypeScript loader. */
|
|
55
55
|
const SCORER_EXTENSIONS = new Set([".mjs", ".js", ".cjs"]);
|
|
56
56
|
/**
|
|
57
|
-
* Resolve + load a config-declared scorer module. Resolution clones scenario
|
|
57
|
+
* Resolve + load a config-declared scorer module. Resolution clones the scripted `scenario` resolution exactly
|
|
58
58
|
* (prepareSelectedOutputDirectory → cwd-clamp → readContainedRegularFile), then `import()`s the
|
|
59
59
|
* entry file in a broad try/catch. The digest is over the readContainedRegularFile entry bytes.
|
|
60
60
|
*/
|
|
@@ -84,10 +84,10 @@ export async function loadAdapterScorer(args) {
|
|
|
84
84
|
if (!SCORER_EXTENSIONS.has(ext)) {
|
|
85
85
|
return fail("HUMANISH_STUDY_SCORER_BAD_REF", `review.scorer.ref "${trimmed}" must be a path relative to the project ending in .mjs (recommended), .js or .cjs.`);
|
|
86
86
|
}
|
|
87
|
-
// Root token: realpath(cwd) → prepareSelectedOutputDirectory(dirname, cwd). Mirrors scenario
|
|
87
|
+
// Root token: realpath(cwd) → prepareSelectedOutputDirectory(dirname, cwd). Mirrors the scripted `scenario`.
|
|
88
88
|
const physicalCwd = await realpath(path.resolve(args.cwd));
|
|
89
89
|
const root = await prepareSelectedOutputDirectory(path.dirname(physicalCwd), physicalCwd);
|
|
90
|
-
// Clamp verbatim from the scripted scenario
|
|
90
|
+
// Clamp verbatim from the scripted `scenario` branch: resolve → relative → reject escape/absolute.
|
|
91
91
|
const absolutePath = path.resolve(root.physicalPath, trimmed);
|
|
92
92
|
const relative = path.relative(root.physicalPath, absolutePath);
|
|
93
93
|
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
@@ -4,13 +4,22 @@
|
|
|
4
4
|
// a library caller's config itself, under its route's error codes. The planners call the shared
|
|
5
5
|
// checks in study/validation.ts and desktopCliProductReason below. The other subject rules here have
|
|
6
6
|
// planner counterparts whose conditions and wording differ (clone serve and repo, local-tree
|
|
7
|
-
// target, in-process fan-out, loopback targets, scripted scenario
|
|
7
|
+
// target, in-process fan-out, loopback targets, scripted `scenario`).
|
|
8
8
|
import { isLoopbackUrl } from "./parse/subject.js";
|
|
9
9
|
import { REPO_SLUG_PATTERN } from "./parse/values.js";
|
|
10
10
|
import { declaredTargets } from "./plan-participants.js";
|
|
11
11
|
import { actorResolvesToComputerUse, actorResolvesToScriptedBrowser, actorResolvesToTerminal, computerUseParticipantCount, registeredComputerUseActors, registeredScriptedBrowserActors, registeredTerminalActors, isComputerUseComposition, } from "./routing.js";
|
|
12
12
|
import { cloneTargetValidationReason, computerUseValidationReason, sharedWorldValidationReason, } from "./validation.js";
|
|
13
|
-
import {
|
|
13
|
+
import { participantList, declaredParticipantCount } from "./study-fields.js";
|
|
14
|
+
/**
|
|
15
|
+
* The count a scripted or terminal composition reads: its surfaces, or a participant count.
|
|
16
|
+
* parse/front.ts refuses `participants` on route: scripted and route: terminal, and `surfaces` off
|
|
17
|
+
* route: scripted, but a study can declare a route its composition does not take, and these rules
|
|
18
|
+
* run before parseStudy compares the two. migrate's view of a v2 file reaches them the same way.
|
|
19
|
+
*/
|
|
20
|
+
function participantOrSurfaceCount(config) {
|
|
21
|
+
return declaredParticipantCount(config) ?? config.surfaces?.length;
|
|
22
|
+
}
|
|
14
23
|
/** The first composition rule a config breaks, in the parser's order, or null. */
|
|
15
24
|
export function compositionReason(config) {
|
|
16
25
|
return (thisRepoValidationReason(config) ??
|
|
@@ -25,7 +34,7 @@ export function compositionReason(config) {
|
|
|
25
34
|
(isComputerUseComposition(config) ? computerUseValidationReason(config) : null) ??
|
|
26
35
|
// Whenever shared world is declared, not only when it routes, so a half-declared shared world
|
|
27
36
|
// fails with a precise reason instead of running as independent participants.
|
|
28
|
-
(
|
|
37
|
+
(config.route === "shared-world" ? sharedWorldValidationReason(config) : null) ??
|
|
29
38
|
desktopCliValidationReason(config) ??
|
|
30
39
|
terminalValidationReason(config) ??
|
|
31
40
|
cloneActorValidationReason(config));
|
|
@@ -39,7 +48,7 @@ function thisRepoValidationReason(config) {
|
|
|
39
48
|
if (config.execution?.target) {
|
|
40
49
|
return "`execution.target` applies only to clone/app-url/local-app subjects; this-repo studies run locally.";
|
|
41
50
|
}
|
|
42
|
-
if (
|
|
51
|
+
if (config.mode === "live") {
|
|
43
52
|
return THIS_REPO_DRY_RUN_ONLY;
|
|
44
53
|
}
|
|
45
54
|
}
|
|
@@ -54,7 +63,7 @@ function thisRepoValidationReason(config) {
|
|
|
54
63
|
// planComputerUseStudy refuses it with HUMANISH_COMPUTER_USE_LOCAL_APP_NO_EXECUTOR.
|
|
55
64
|
function localAppValidationReason(config) {
|
|
56
65
|
if (config.subject.source === "local-app") {
|
|
57
|
-
const type =
|
|
66
|
+
const type = config.actor?.type ?? "";
|
|
58
67
|
if (config.execution?.target !== undefined && config.execution.target !== "local") {
|
|
59
68
|
return "A local-app subject drives a local dev server in this process, with no E2B desktop. Set `execution.target: local` or omit it. To run on a hosted desktop, use an app-url subject with `execution.target: e2b-desktop`.";
|
|
60
69
|
}
|
|
@@ -62,7 +71,7 @@ function localAppValidationReason(config) {
|
|
|
62
71
|
return `actor.type must be a registered computer-use actor for local-app subjects (one of: ${registeredComputerUseActors().join(", ")}); the caller's custom executor runs the computer-use loop. Got "${type}".`;
|
|
63
72
|
}
|
|
64
73
|
if (computerUseParticipantCount(config) > 1) {
|
|
65
|
-
return "Fan-out to more than one participant is not supported on the in-process/local-app route: fan-out provisions one independent E2B desktop per participant, which the in-process route deliberately skips.
|
|
74
|
+
return "Fan-out to more than one participant is not supported on the in-process/local-app route: fan-out provisions one independent E2B desktop per participant, which the in-process route deliberately skips. Remove `participants`, or set `participants: 1`. For fan-out, use an app-url or clone subject on execution.target: e2b-desktop.";
|
|
66
75
|
}
|
|
67
76
|
if (participantList(config) !== undefined) {
|
|
68
77
|
return "`participants` (fan-out roster) is not supported on the in-process/local-app route: it provisions one E2B desktop per participant, which this route skips. Use an app-url or clone subject with execution.target: e2b-desktop.";
|
|
@@ -78,17 +87,17 @@ function localAppValidationReason(config) {
|
|
|
78
87
|
// app; a computer-use actor drives a hosted desktop browser. Fail closed on mis-configs.
|
|
79
88
|
function appUrlValidationReason(config) {
|
|
80
89
|
if (config.subject.source === "app-url") {
|
|
81
|
-
const type =
|
|
90
|
+
const type = config.actor?.type ?? "";
|
|
82
91
|
if (actorResolvesToScriptedBrowser(type)) {
|
|
83
92
|
// Scripted-browser route (all fail-closed, so claims match mechanism: a field that cannot act on
|
|
84
93
|
// this route is rejected, never silently ignored).
|
|
85
94
|
if (config.execution?.target !== undefined && config.execution.target !== "local") {
|
|
86
95
|
return "A scripted-browser actor on an app-url subject runs on this machine. Set `execution.target: local` or omit it. To run scripted steps on a hosted desktop, use a clone subject.";
|
|
87
96
|
}
|
|
88
|
-
if (!
|
|
97
|
+
if (!config.scenario) {
|
|
89
98
|
return "A scripted-browser study needs `scenario`: the actor runs the browser steps in that scenario file.";
|
|
90
99
|
}
|
|
91
|
-
if ((
|
|
100
|
+
if ((participantOrSurfaceCount(config) ?? 1) > 2) {
|
|
92
101
|
return "A scripted-browser study takes `surfaces` 1 (desktop) or 2 (desktop and mobile). Higher counts are not supported yet.";
|
|
93
102
|
}
|
|
94
103
|
if (config.policies?.redactScreenshots === true) {
|
|
@@ -109,9 +118,8 @@ function appUrlValidationReason(config) {
|
|
|
109
118
|
return `actor.type must be a registered computer-use actor for app-url × e2b-desktop studies (one of: ${registeredComputerUseActors().join(", ")}); for local scripted execution use a registered scripted-browser actor (${registeredScriptedBrowserActors().join(", ")}). Got "${type}".`;
|
|
110
119
|
}
|
|
111
120
|
// Multi-participant fan-out is consumed on this route (`per-lane-worlds`; the shared cua
|
|
112
|
-
// cross-validation below enforces
|
|
113
|
-
//
|
|
114
|
-
// fan-out).
|
|
121
|
+
// cross-validation below enforces the 16 cap, the per-participant target gates, and the
|
|
122
|
+
// allowPublicTargets+N>1 rejection for ambiguous one-target fan-out).
|
|
115
123
|
// Loopback by default; an owner may declare a public/preview target via policies.
|
|
116
124
|
const targets = [config.subject.appUrl ?? "", ...declaredTargets(config)];
|
|
117
125
|
const unsafeTarget = targets.find((target) => !config.policies?.allowPublicTargets && !isLoopbackUrl(target));
|
|
@@ -124,8 +132,7 @@ function appUrlValidationReason(config) {
|
|
|
124
132
|
}
|
|
125
133
|
// Scripted-browser actors on any other subject: only a provisioned clone on e2b-desktop.
|
|
126
134
|
function scriptedBrowserValidationReason(config) {
|
|
127
|
-
if (config.subject.source !== "app-url" &&
|
|
128
|
-
actorResolvesToScriptedBrowser(actorOf(config)?.type)) {
|
|
135
|
+
if (config.subject.source !== "app-url" && actorResolvesToScriptedBrowser(config.actor?.type)) {
|
|
129
136
|
if (config.subject.source !== "clone") {
|
|
130
137
|
return "scripted-browser actors require `subject.source: app-url` (a running app at a loopback URL) or `subject.source: clone` with `execution.target: e2b-desktop` (a provisioned synthetic subject).";
|
|
131
138
|
}
|
|
@@ -142,16 +149,18 @@ function scriptedBrowserValidationReason(config) {
|
|
|
142
149
|
if (!REPO_SLUG_PATTERN.test(repo)) {
|
|
143
150
|
return `subject.repos[0] must be an owner/repo slug (got "${repo}").`;
|
|
144
151
|
}
|
|
145
|
-
|
|
152
|
+
// migrate converts a v2 file's `subject.topology: shared-world` to `route: shared-world`, and
|
|
153
|
+
// refuses it here with the v2 parser's message.
|
|
154
|
+
if (config.route === "shared-world") {
|
|
146
155
|
return "A clone scripted-browser study does not support `subject.topology` yet: it runs one synthetic subject for its scripted actor. Remove `subject.topology`.";
|
|
147
156
|
}
|
|
148
157
|
if (config.subject.clone?.fanout !== undefined || config.subject.clone?.keep === true) {
|
|
149
158
|
return "A clone scripted-browser study does not support `subject.clone.fanout` or `subject.clone.keep` yet: its subject is always one sandbox, removed after the run.";
|
|
150
159
|
}
|
|
151
|
-
if (!
|
|
160
|
+
if (!config.scenario) {
|
|
152
161
|
return "A scripted-browser study needs `scenario`: the actor runs the browser steps in that scenario file.";
|
|
153
162
|
}
|
|
154
|
-
if ((
|
|
163
|
+
if ((participantOrSurfaceCount(config) ?? 1) > 2) {
|
|
155
164
|
return "A scripted-browser study takes `surfaces` 1 (desktop) or 2 (desktop and mobile). Higher counts are not supported yet.";
|
|
156
165
|
}
|
|
157
166
|
if (participantList(config) !== undefined) {
|
|
@@ -183,7 +192,7 @@ function scriptedBrowserValidationReason(config) {
|
|
|
183
192
|
function cloneComputerUseValidationReason(config) {
|
|
184
193
|
if (config.subject.source === "clone" &&
|
|
185
194
|
config.execution?.target === "e2b-desktop" &&
|
|
186
|
-
actorResolvesToComputerUse(
|
|
195
|
+
actorResolvesToComputerUse(config.actor?.type)) {
|
|
187
196
|
if (!config.subject.serve) {
|
|
188
197
|
return "A clone subject on the computer-use route needs `subject.serve` (start and url): humanish starts the app in the sandbox before the participant opens it.";
|
|
189
198
|
}
|
|
@@ -195,8 +204,8 @@ function cloneComputerUseValidationReason(config) {
|
|
|
195
204
|
return `subject.repos[0] must be an owner/repo slug (got "${repo}").`;
|
|
196
205
|
}
|
|
197
206
|
// Fan-out is consumed here: N participants each clone the same single repo into their own
|
|
198
|
-
// E2B desktop (`per-lane-worlds`). The shared cua cross-validation below enforces the
|
|
199
|
-
//
|
|
207
|
+
// E2B desktop (`per-lane-worlds`). The shared cua cross-validation below enforces the 16 cap;
|
|
208
|
+
// the single-repo rule above is unchanged.
|
|
200
209
|
}
|
|
201
210
|
return null;
|
|
202
211
|
}
|
|
@@ -210,8 +219,8 @@ function localTreeValidationReason(config) {
|
|
|
210
219
|
if (config.execution?.target !== "e2b-desktop") {
|
|
211
220
|
return "local-tree subjects require `execution.target: e2b-desktop`: the packed working tree is provisioned and served inside a hosted desktop sandbox; there is no local route for a local-tree subject.";
|
|
212
221
|
}
|
|
213
|
-
if (!actorResolvesToComputerUse(
|
|
214
|
-
return `actor.type must be a registered computer-use actor for local-tree subjects (one of: ${registeredComputerUseActors().join(", ")}); the actor drives the hosted desktop that serves the packed working tree. Got "${
|
|
222
|
+
if (!actorResolvesToComputerUse(config.actor?.type)) {
|
|
223
|
+
return `actor.type must be a registered computer-use actor for local-tree subjects (one of: ${registeredComputerUseActors().join(", ")}); the actor drives the hosted desktop that serves the packed working tree. Got "${config.actor?.type ?? ""}".`;
|
|
215
224
|
}
|
|
216
225
|
}
|
|
217
226
|
return null;
|
|
@@ -238,7 +247,7 @@ function desktopCliValidationReason(config) {
|
|
|
238
247
|
if (config.execution?.target !== undefined && config.execution.target !== "e2b-desktop") {
|
|
239
248
|
return "A desktop-cli subject runs on a hosted desktop. Set `execution.target: e2b-desktop` or omit it.";
|
|
240
249
|
}
|
|
241
|
-
if (!actorResolvesToComputerUse(
|
|
250
|
+
if (!actorResolvesToComputerUse(config.actor?.type ?? "")) {
|
|
242
251
|
return "desktop-cli subjects need a registered computer-use actor: the participant reads the screen and types, which is what makes an interactive surface studiable at all.";
|
|
243
252
|
}
|
|
244
253
|
const install = config.subject.product.install;
|
|
@@ -255,14 +264,14 @@ function desktopCliValidationReason(config) {
|
|
|
255
264
|
// fan-out lands.
|
|
256
265
|
function terminalValidationReason(config) {
|
|
257
266
|
if (config.subject.source === "terminal-product") {
|
|
258
|
-
const type =
|
|
267
|
+
const type = config.actor?.type ?? "";
|
|
259
268
|
if (config.execution?.target !== undefined && config.execution.target !== "e2b-terminal") {
|
|
260
269
|
return "A terminal-product subject runs its agent in an E2B shell. Set `execution.target: e2b-terminal` or omit it; `local` and `e2b-desktop` are not supported.";
|
|
261
270
|
}
|
|
262
271
|
if (!actorResolvesToTerminal(type)) {
|
|
263
272
|
return `actor.type must be a registered terminal actor for terminal-product subjects (one of: ${registeredTerminalActors().join(", ")}). Got "${type}".`;
|
|
264
273
|
}
|
|
265
|
-
if ((
|
|
274
|
+
if ((participantOrSurfaceCount(config) ?? 1) > 1) {
|
|
266
275
|
return "Terminal fan-out to more than one participant is not supported yet; set participants to 1.";
|
|
267
276
|
}
|
|
268
277
|
}
|
|
@@ -271,7 +280,7 @@ function terminalValidationReason(config) {
|
|
|
271
280
|
// mis-config: reject, never silently mishandle (mirrors app-url's e2b-desktop pairing rule).
|
|
272
281
|
return "`execution.target: e2b-terminal` requires `subject.source: terminal-product` with a registered terminal actor.";
|
|
273
282
|
}
|
|
274
|
-
else if (actorResolvesToTerminal(
|
|
283
|
+
else if (actorResolvesToTerminal(config.actor?.type)) {
|
|
275
284
|
// A registered terminal actor on a non-terminal-product subject: rejected, never ignored (the
|
|
276
285
|
// terminal agent only studies a declared terminal-product from public surfaces).
|
|
277
286
|
return "terminal actors require `subject.source: terminal-product` (a CLI/product the agent studies from public surfaces); other subjects are not supported on this route.";
|
|
@@ -283,7 +292,7 @@ function terminalValidationReason(config) {
|
|
|
283
292
|
// Terminal actors were already refused above with their own message.
|
|
284
293
|
function cloneActorValidationReason(config) {
|
|
285
294
|
if (config.subject.source === "clone") {
|
|
286
|
-
const type =
|
|
295
|
+
const type = config.actor?.type ?? "";
|
|
287
296
|
if (!actorResolvesToComputerUse(type) && !actorResolvesToScriptedBrowser(type)) {
|
|
288
297
|
return `clone subjects need a registered computer-use actor (one of: ${registeredComputerUseActors().join(", ")}) or scripted-browser actor (one of: ${registeredScriptedBrowserActors().join(", ")}); humanish clones and serves the app for that participant to drive. Got "${type}".`;
|
|
289
298
|
}
|
package/dist/study/config.d.ts
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
|
-
import { type StudyConfig, type StudyParseResult } from "./types.js";
|
|
2
|
-
/** The message for a humanish.lab.v2 document, which the package and the CLI no longer run. */
|
|
3
|
-
export declare const V2_UNSUPPORTED_MESSAGE = "This is a humanish.lab.v2 study, which humanish no longer reads. Convert its file with humanish migrate <path>.";
|
|
1
|
+
import { type StudyConfig, type StudyParseFailure, type StudyParseResult } from "./types.js";
|
|
4
2
|
/**
|
|
5
3
|
* The package's study parser (src/index.ts) and the one discovery uses: a humanish.study.v3
|
|
6
4
|
* document. It refuses a humanish.lab.v2 one with HUMANISH_STUDY_V2_UNSUPPORTED.
|
|
7
5
|
*/
|
|
8
6
|
export declare function parseStudy(raw: unknown): StudyParseResult;
|
|
7
|
+
interface StudyV3Options {
|
|
8
|
+
/**
|
|
9
|
+
* `report`, for `humanish migrate`: list the fields the route does not read in `inert`, in place
|
|
10
|
+
* of refusing the study for them. A shared world may then give `participants` as a count or
|
|
11
|
+
* leave it out, as a v2 file could (readStudyFront).
|
|
12
|
+
*/
|
|
13
|
+
readonly inert?: "refuse" | "report";
|
|
14
|
+
}
|
|
15
|
+
type StudyV3ParseResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
config: StudyConfig;
|
|
18
|
+
warnings: string[];
|
|
19
|
+
inert?: string[];
|
|
20
|
+
} | StudyParseFailure;
|
|
9
21
|
/**
|
|
10
|
-
* Validate a
|
|
11
|
-
*
|
|
12
|
-
* accepted but reported in `warnings` so `study show` never silently swallows a setting that does
|
|
13
|
-
* nothing. The v2 parser's tests call it directly; migrate reads v2 files through migrate/v2.ts.
|
|
22
|
+
* Validate a humanish.study.v3 document into a StudyConfig. Pure: the caller owns file IO. Fields the
|
|
23
|
+
* route does not read refuse the study; migrate's report mode lists them in `inert` instead.
|
|
14
24
|
*/
|
|
15
|
-
export declare function
|
|
25
|
+
export declare function parseStudyV3(raw: Record<string, unknown>, options?: StudyV3Options): StudyV3ParseResult;
|
|
26
|
+
/** Why runStudy refuses a config before anything runs. */
|
|
27
|
+
interface ConfigRefusal {
|
|
28
|
+
readonly code: "HUMANISH_STUDY_V2_UNSUPPORTED" | "HUMANISH_STUDY_INVALID";
|
|
29
|
+
readonly message: string;
|
|
30
|
+
}
|
|
16
31
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
32
|
+
* Why runStudy refuses a config a library caller passes, or undefined: a humanish.lab.v2 config,
|
|
33
|
+
* one that still sets a StudyConfig field of humanish 0.110, one with no `actor` object, and one
|
|
34
|
+
* whose `route` is not the route its subject and actor take, with parseStudy's message. A planner
|
|
35
|
+
* reads only the v3 fields, so a budget left in `execution.caps` would otherwise run uncapped.
|
|
21
36
|
*/
|
|
22
|
-
export declare function
|
|
37
|
+
export declare function libraryConfigRefusal(config: StudyConfig): ConfigRefusal | undefined;
|
|
38
|
+
export {};
|