humanish 0.110.2 → 0.110.3

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.
Files changed (52) hide show
  1. package/ARCHITECTURE.md +10 -15
  2. package/CHANGELOG.md +13 -0
  3. package/CONTEXT.md +1 -1
  4. package/dist/actors/computer-use/actor.js +2 -2
  5. package/dist/actors/computer-use/loop/ending.js +1 -1
  6. package/dist/actors/registry.js +1 -1
  7. package/dist/analysis/automatic-completion.js +8 -0
  8. package/dist/cli/commands/observe.js +3 -2
  9. package/dist/cli/commands/study-run.js +9 -19
  10. package/dist/cli/findings.d.ts +5 -0
  11. package/dist/cli/findings.js +21 -0
  12. package/dist/cli/io.d.ts +2 -1
  13. package/dist/cli/io.js +3 -2
  14. package/dist/cli/observer-follow.d.ts +6 -2
  15. package/dist/cli/observer-follow.js +15 -5
  16. package/dist/evidence/redaction.js +15 -4
  17. package/dist/routes/computer-use/participant-runs.d.ts +6 -9
  18. package/dist/routes/computer-use/participant-runs.js +16 -36
  19. package/dist/routes/computer-use/plan.d.ts +2 -2
  20. package/dist/routes/computer-use/plan.js +1 -1
  21. package/dist/routes/computer-use/route.d.ts +5 -18
  22. package/dist/routes/computer-use/route.js +17 -78
  23. package/dist/routes/computer-use/setup.d.ts +3 -2
  24. package/dist/routes/computer-use/types.d.ts +6 -10
  25. package/dist/routes/scripted/route.d.ts +4 -17
  26. package/dist/routes/scripted/route.js +10 -42
  27. package/dist/routes/scripted/types.d.ts +1 -6
  28. package/dist/routes/shared-world/route.d.ts +5 -11
  29. package/dist/routes/shared-world/route.js +24 -63
  30. package/dist/routes/shared-world/types.d.ts +2 -6
  31. package/dist/routes/terminal/route.d.ts +3 -13
  32. package/dist/routes/terminal/route.js +19 -59
  33. package/dist/routes/terminal/session.d.ts +2 -3
  34. package/dist/routes/terminal/session.js +2 -3
  35. package/dist/routes/terminal/types.d.ts +2 -7
  36. package/dist/run/route-shell.d.ts +60 -0
  37. package/dist/run/route-shell.js +51 -0
  38. package/dist/run/run.d.ts +6 -1
  39. package/dist/run/run.js +11 -2
  40. package/dist/run/status.d.ts +2 -0
  41. package/dist/run/status.js +5 -0
  42. package/dist/study/composition-rules.js +1 -1
  43. package/dist/study/init-templates.d.ts +1 -1
  44. package/dist/study/init-templates.js +43 -34
  45. package/dist/study/init.js +1 -1
  46. package/dist/study/parse/subject.js +1 -1
  47. package/docs/architecture/actor-contract.md +1 -1
  48. package/docs/architecture/external-public-shared-world.md +1 -1
  49. package/docs/architecture/terminal-product-route.md +13 -13
  50. package/docs/contracts/study-analysis.md +5 -2
  51. package/docs/product/automatic-analysis.md +1 -1
  52. package/package.json +1 -1
@@ -21,39 +21,17 @@
21
21
  // harness errors are redacted at this boundary; the bundle's `stream.actor` carries the
22
22
  // conformant humanish.actor-trace.v1 projection, whose `redaction.screenshots` records the
23
23
  // run's actual mode ("raw" | "blurred" | "n/a"), and every label downstream derives from it.
24
- import { withTransientCommsSecrets } from "../../run/transient-comms-secrets.js";
25
24
  import path from "node:path";
26
- import { completeAutomaticAnalysis } from "../../analysis/automatic-completion.js";
27
- import { runScope } from "../../run/run.js";
28
- import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
25
+ import {} from "../../run/run.js";
26
+ import { admitRoute, completeRefusalAnalysis } from "../../run/route-shell.js";
29
27
  import { browserRouteScorer } from "../../study/adapter-scorer-loader.js";
30
28
  import { withLateScorer } from "../../study/route-inputs.js";
31
- import { callerDrivingOf, planComputerUseStudy } from "./plan.js";
29
+ import {} from "./plan.js";
32
30
  import { finishCuaRun } from "./result.js";
33
31
  import { runStudyParticipants } from "./live-phase.js";
34
32
  import { admitCuaRun, refuseCuaStudy, startCuaRun } from "./setup.js";
35
33
  import {} from "./types.js";
36
34
  import { studyResultIdentity } from "../../run/study-result.js";
37
- /**
38
- * Plans and runs a computer-use study in one call. It is not exported from src/index.ts; tests call
39
- * it. It plans the config with planComputerUseStudy and runs
40
- * the plan with runComputerUsePlan, whose run scope and withTransientCommsSecrets wrapper cover the
41
- * run and its analysis.
42
- */
43
- export async function runCuaActorStudy(options) {
44
- const { config, dryRun, ...input } = options;
45
- // planComputerUseStudy makes every configuration refusal, in the order this route always has.
46
- const planned = planComputerUseStudy(config, {
47
- dryRun,
48
- hasRunSession: input.deps?.runSession !== undefined,
49
- driving: callerDrivingOf(input),
50
- ...(input.countOverride === undefined ? {} : { countOverride: input.countOverride }),
51
- ...(input.rerun === undefined ? {} : { rerun: input.rerun }),
52
- });
53
- if (planned.ok)
54
- return runComputerUsePlan(planned.plan, input, config);
55
- return computerUseStudyRefusal(options, planned.refusal);
56
- }
57
35
  /**
58
36
  * A refused computer-use study's result, at the refusal's stage: a before-scope refusal has its own
59
37
  * envelope and no analysis record; the others come after the cwd checks, and the participant cap after
@@ -75,66 +53,27 @@ export async function computerUseStudyRefusal(options, refusal) {
75
53
  error: { code: refusal.code, message: refusal.message },
76
54
  };
77
55
  // The other refusals come after the cwd checks, and the participant cap after the personas are read.
78
- const analysis = resolveAutomaticAnalysis(config.review?.analysis);
79
- return completeAutomaticAnalysis(await refuseCuaStudy(options, refusal), undefined, analysis.ok ? analysis.config : undefined, options, { trigger: config.review?.analysis === undefined ? "default" : "explicit" });
56
+ return completeRefusalAnalysis(await refuseCuaStudy(options, refusal), config, options);
80
57
  }
81
58
  /**
82
59
  * runStudyWith's step for a computer-use plan. It runs the plan's local checks (admitCuaRun: the
83
60
  * participants, the preflight plan, the keys, the local agent, subject env and caps, and the
84
61
  * local-tree archive) before any run scope opens, so the CLI can present their refusal before it
85
- * loads a declared scorer, and returns the run that continues from them with that scorer.
86
- */
87
- export async function admitComputerUsePlan(plan, input, config) {
88
- const admission = await admitCuaRun(plan, input, config);
89
- if (!admission.ok)
90
- return {
91
- ok: false,
92
- outcome: cuaOutcome(await completeCuaAnalysis(plan, input, admission.result, undefined)),
93
- };
94
- return {
95
- ok: true,
96
- run: async (scorer) => {
97
- // The scorer joins the input only; the runSession, provider and desktop the checks admitted
98
- // stay the ones the run uses.
99
- const running = withLateScorer(input, scorer, browserRouteScorer);
100
- return cuaOutcome(await runAdmittedCuaRun(plan, running, admission.admitted));
101
- },
102
- };
103
- }
104
- function cuaOutcome(result) {
105
- return { route: "computer-use", result };
106
- }
107
- /**
108
- * Run a computer-use plan: its local checks, then the run. The run scope in runAdmittedCuaRun
109
- * finalizes any status record the run opened, on every exit, so a test or library caller does not
110
- * leave the 5 s status cadence writing into a directory something else is deleting (an unrelated
111
- * ENOTEMPTY). Participants, the brain, the subject, the caps and the residual config come from the
112
- * plan. `config` is what the caller's createProvider and inProcess executor receive.
113
- */
114
- export async function runComputerUsePlan(plan, input, config) {
115
- const admission = await admitCuaRun(plan, input, config);
116
- if (!admission.ok)
117
- return completeCuaAnalysis(plan, input, admission.result, undefined);
118
- return runAdmittedCuaRun(plan, input, admission.admitted);
119
- }
120
- /**
121
- * Runs an admitted plan in its own run scope, then its automatic analysis. The
122
- * withTransientCommsSecrets wrapper scopes any email secret the run registers to this run and its
123
- * analysis.
62
+ * loads a declared scorer, and returns the run that continues from them with that scorer. The
63
+ * run and its analysis share one scope for the email secrets it registers.
124
64
  */
125
- function runAdmittedCuaRun(plan, input, admitted) {
126
- return withTransientCommsSecrets(async () => {
127
- const { result, finished } = await runScope((scope) => runPlanInScope(plan, input, admitted, scope));
128
- return completeCuaAnalysis(plan, input, result, finished);
129
- });
130
- }
131
- function completeCuaAnalysis(plan, input, result, finished) {
65
+ export function admitComputerUsePlan(plan, input, config) {
132
66
  // A local VM study whose cleanup is unconfirmed records a skip instead of analyzing.
133
- const refusal = input.localVm?.analysisRefusal;
134
- return completeAutomaticAnalysis(result, finished, plan.analysis?.config, input, {
135
- ...(plan.analysis === undefined ? {} : { trigger: plan.analysis.trigger }),
136
- preferLargerOutput: plan.analysis?.preferLargerOutput === true,
137
- ...(refusal === undefined ? {} : { refusal }),
67
+ const analysisRefusal = input.localVm?.analysisRefusal;
68
+ return admitRoute({
69
+ route: "computer-use",
70
+ analysis: plan.analysis,
71
+ input,
72
+ admit: () => admitCuaRun(plan, input, config),
73
+ withScorer: (base, scorer) => withLateScorer(base, scorer, browserRouteScorer),
74
+ runInScope: (admitted, running, scope) => runPlanInScope(plan, running, admitted, scope),
75
+ ...(analysisRefusal === undefined ? {} : { analysisRefusal }),
76
+ commsSecrets: true,
138
77
  });
139
78
  }
140
79
  async function runPlanInScope(plan, input, admitted, scope) {
@@ -1,4 +1,5 @@
1
1
  import { type RunScope } from "../../run/run.js";
2
+ import type { RefusedStudy } from "../../run/route-shell.js";
2
3
  import type { CuaActorDescriptor } from "../../actors/registry.js";
3
4
  import type { StudyCommsEmail, StudyCommsExternal, StudyConfig } from "../../study/types.js";
4
5
  import { type RunRerunLineage } from "../../run/bundle.js";
@@ -8,12 +9,12 @@ import type { ComputerUsePlan } from "../../study/plan-types.js";
8
9
  import { trackRuntimeStreams, type LiveTraceFlush } from "./live-flush.js";
9
10
  import { type CuaRunBundleBase } from "./bundle.js";
10
11
  import { projectParticipantSubjects } from "./subject-projection.js";
11
- import { type CuaActorStudyErrorCode, type CuaActorStudyResult, type CuaParticipantDeps, type CuaParticipantPlan, type DesktopParticipantRun, type CuaSubjectProjection, type CuaSubjectProvenanceArg, type ComputerUseRunInput, type RunCuaActorStudyOptions } from "./types.js";
12
+ import { type CuaActorStudyErrorCode, type CuaActorStudyResult, type CuaParticipantDeps, type CuaParticipantPlan, type DesktopParticipantRun, type CuaSubjectProjection, type CuaSubjectProvenanceArg, type ComputerUseRunInput } from "./types.js";
12
13
  /**
13
14
  * The envelope of a refusal the plan left for after the cwd checks. The participant cap and
14
15
  * in-process fan-out refusals come after the committed personas are read, so a persona-file error still wins.
15
16
  */
16
- export declare function refuseCuaStudy(options: RunCuaActorStudyOptions, refusal: ComputerUseRefusal): Promise<CuaActorStudyResult>;
17
+ export declare function refuseCuaStudy(options: ComputerUseRunInput & RefusedStudy, refusal: ComputerUseRefusal): Promise<CuaActorStudyResult>;
17
18
  export type AdmittedCuaRun = Extract<Awaited<ReturnType<typeof admitCuaRun>>, {
18
19
  ok: true;
19
20
  }>["admitted"];
@@ -19,7 +19,7 @@ import { type E2BDesktopModule } from "../../substrates/e2b/sdk.js";
19
19
  import { type DesktopResourceObservation } from "../../substrates/e2b/desktop-resources.js";
20
20
  import { type DetachedTimers } from "../../substrates/detached.js";
21
21
  import type { Brain, ComputerUsePlan, ComputerUseRunner, ResidualConfig } from "../../study/plan-types.js";
22
- import { type StudyCommsEmail, type StudyConfig } from "../../study/types.js";
22
+ import { type StudyCommsEmail } from "../../study/types.js";
23
23
  import { type ObserverResult } from "../../observer/render.js";
24
24
  import { type BundleRun, type RunBundle, type RunRerunLineage, type RunScorerProvenance, type RunSubjectProvenance, type RunSubjectStateStepRecord } from "../../run/bundle.js";
25
25
  import { type RunDesktopGeometry } from "../../run/streams.js";
@@ -31,11 +31,6 @@ export declare const CUA_FANOUT_STRATEGY: "per-lane-worlds";
31
31
  export declare const CUA_MAX_CONCURRENCY_ENV = "HUMANISH_CUA_MAX_CONCURRENCY";
32
32
  export declare const DEFAULT_APP_URL_SESSION_TIMEOUT_MS: number;
33
33
  export declare const MIN_DERIVED_SESSION_TIMEOUT_MS: number;
34
- /**
35
- * What a computer-use run takes besides its plan and config. The count override and rerun go to
36
- * participant building with the config.
37
- */
38
- export type ComputerUseRunInput = Omit<RunCuaActorStudyOptions, "config" | "dryRun">;
39
34
  /**
40
35
  * What runStudyWith's local VM study gives a computer-use run: the desktop each participant runs on, the
41
36
  * reason automatic analysis must not run (the study's cleanup is unconfirmed), and the signal its
@@ -47,13 +42,14 @@ export interface LocalVmInput {
47
42
  readonly analysisRefusal: () => AutomaticAnalysisRefusal | undefined;
48
43
  readonly signal?: AbortSignal;
49
44
  }
50
- export interface RunCuaActorStudyOptions {
45
+ /**
46
+ * What a computer-use run takes besides its plan and config. The count override and rerun go to
47
+ * participant building with the config.
48
+ */
49
+ export interface ComputerUseRunInput {
51
50
  /** Cancels post-run analysis only. */
52
51
  analysisSignal?: AbortSignal;
53
52
  cwd: string;
54
- config: StudyConfig;
55
- /** Resolved upstream (scenario.mode + CLI override); defaults safe (dry-run). */
56
- dryRun: boolean;
57
53
  open?: boolean;
58
54
  runId?: string;
59
55
  /** CLI `--count` override for the homogeneous fan-out participant count (ignored when a `lanes`
@@ -1,28 +1,15 @@
1
1
  import type { ScriptedPlan } from "../../study/plan-types.js";
2
2
  import type { AdmittedPlan } from "../../run-study.js";
3
+ import { type RefusedStudy } from "../../run/route-shell.js";
3
4
  import { type ScriptedRefusal } from "./plan.js";
4
- import { type RunScriptedBrowserStudyOptions, type ScriptedBrowserStudyResult, type ScriptedRunInput } from "./types.js";
5
- /**
6
- * The config-taking entry point. It plans, returns a refusal with the envelope the route has always
7
- * returned at that refusal's stage, and otherwise runs the plan.
8
- */
9
- export declare function runScriptedBrowserStudy(options: RunScriptedBrowserStudyOptions): Promise<ScriptedBrowserStudyResult>;
5
+ import { type ScriptedBrowserStudyResult, type ScriptedRunInput } from "./types.js";
10
6
  /**
11
7
  * A refused scripted study's result, at the refusal's stage: a before-scope refusal has its own field
12
8
  * order and no analysis record; the others come after the output directory checks.
13
9
  */
14
- export declare function scriptedStudyRefusal(options: RunScriptedBrowserStudyOptions, refusal: ScriptedRefusal): Promise<ScriptedBrowserStudyResult>;
10
+ export declare function scriptedStudyRefusal(options: ScriptedRunInput & RefusedStudy, refusal: ScriptedRefusal): Promise<ScriptedBrowserStudyResult>;
15
11
  /**
16
12
  * runStudyWith's step for a scripted plan. It takes no scorer, and its local checks (keys, a browser)
17
13
  * run inside the run, so it returns the run.
18
14
  */
19
- export declare function admitScriptedPlan(plan: ScriptedPlan, input: ScriptedRunInput): AdmittedPlan<"scripted">;
20
- /**
21
- * Run a scripted plan. The run scope gives a direct library caller the same status-record lifetime
22
- * the CLI gets: returning from this function finalizes any record the run opened, whichever of its
23
- * fail-closed exits it took. Each route opens its own scope; `runStudyWith` opens none. Without this a
24
- * test or an adopter calling the route directly leaves
25
- * the 5s cadence ticking into a directory something else is deleting, which surfaces as an
26
- * unrelated ENOTEMPTY.
27
- */
28
- export declare function runScriptedPlan(plan: ScriptedPlan, input: ScriptedRunInput): Promise<ScriptedBrowserStudyResult>;
15
+ export declare function admitScriptedPlan(plan: ScriptedPlan, input: ScriptedRunInput): Promise<AdmittedPlan<"scripted">>;
@@ -17,14 +17,13 @@
17
17
  import { randomBytes } from "node:crypto";
18
18
  import { realpath } from "node:fs/promises";
19
19
  import path from "node:path";
20
- import { completeAutomaticAnalysis } from "../../analysis/automatic-completion.js";
21
- import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
22
20
  import { redactText, toErrorMessage } from "../../evidence/redaction.js";
23
21
  import { buildRunSource } from "../../run/bundle.js";
24
22
  import { prepareSelectedOutputDirectory } from "../../run/contained-output.js";
25
- import { runScope } from "../../run/run.js";
23
+ import {} from "../../run/run.js";
24
+ import { admitRoute, completeRefusalAnalysis } from "../../run/route-shell.js";
26
25
  import { renderScriptedReviewMarkdown } from "./bundle.js";
27
- import { injectedBrowser, planScriptedStudy } from "./plan.js";
26
+ import {} from "./plan.js";
28
27
  import { finishScriptedRun } from "./result.js";
29
28
  import { UnsafeScriptedSessionResultError } from "./session-result.js";
30
29
  import { prepareScriptedRun } from "./setup.js";
@@ -32,21 +31,6 @@ import { ScriptedSubject } from "./subject.js";
32
31
  import { runScriptedSessions, writeSurfaceTraces } from "./surface-sessions.js";
33
32
  import {} from "./types.js";
34
33
  import { studyResultIdentity } from "../../run/study-result.js";
35
- /**
36
- * The config-taking entry point. It plans, returns a refusal with the envelope the route has always
37
- * returned at that refusal's stage, and otherwise runs the plan.
38
- */
39
- export async function runScriptedBrowserStudy(options) {
40
- const { config, dryRun, ...input } = options;
41
- // planScriptedStudy makes every configuration refusal, in the order this route always has.
42
- const planned = planScriptedStudy(config, {
43
- dryRun,
44
- injectedBrowser: injectedBrowser(input.deps),
45
- });
46
- if (planned.ok)
47
- return runScriptedPlan(planned.plan, input);
48
- return scriptedStudyRefusal(options, planned.refusal);
49
- }
50
34
  /**
51
35
  * A refused scripted study's result, at the refusal's stage: a before-scope refusal has its own field
52
36
  * order and no analysis record; the others come after the output directory checks.
@@ -83,35 +67,19 @@ export async function scriptedStudyRefusal(options, refusal) {
83
67
  warnings: [],
84
68
  error: { code: refusal.code, message: refusal.message },
85
69
  };
86
- const analysis = resolveAutomaticAnalysis(config.review?.analysis);
87
- return completeAutomaticAnalysis(refused, undefined, analysis.ok ? analysis.config : undefined, options, { trigger: config.review?.analysis === undefined ? "default" : "explicit" });
70
+ return completeRefusalAnalysis(refused, config, options);
88
71
  }
89
72
  /**
90
73
  * runStudyWith's step for a scripted plan. It takes no scorer, and its local checks (keys, a browser)
91
74
  * run inside the run, so it returns the run.
92
75
  */
93
76
  export function admitScriptedPlan(plan, input) {
94
- return {
95
- ok: true,
96
- run: async () => ({
97
- route: "scripted",
98
- result: await runScriptedPlan(plan, input),
99
- }),
100
- };
101
- }
102
- /**
103
- * Run a scripted plan. The run scope gives a direct library caller the same status-record lifetime
104
- * the CLI gets: returning from this function finalizes any record the run opened, whichever of its
105
- * fail-closed exits it took. Each route opens its own scope; `runStudyWith` opens none. Without this a
106
- * test or an adopter calling the route directly leaves
107
- * the 5s cadence ticking into a directory something else is deleting, which surfaces as an
108
- * unrelated ENOTEMPTY.
109
- */
110
- export async function runScriptedPlan(plan, input) {
111
- const { result, finished } = await runScope((scope) => runScriptedPlanInScope(plan, input, scope));
112
- return completeAutomaticAnalysis(result, finished, plan.analysis?.config, input, {
113
- ...(plan.analysis === undefined ? {} : { trigger: plan.analysis.trigger }),
114
- preferLargerOutput: plan.analysis?.preferLargerOutput === true,
77
+ return admitRoute({
78
+ route: "scripted",
79
+ analysis: plan.analysis,
80
+ input,
81
+ admit: () => ({ ok: true, admitted: plan }),
82
+ runInScope: runScriptedPlanInScope,
115
83
  });
116
84
  }
117
85
  async function runScriptedPlanInScope(plan, input, scope) {
@@ -1,23 +1,18 @@
1
1
  import type { ActorCompletionReason, ActorStatus } from "../../actors/contract.js";
2
2
  import type { StudyEvent } from "../../study/run-study-events.js";
3
3
  import type { AutomaticAnalysisResult } from "../../analysis/automatic-completion.js";
4
- import type { StudyConfig } from "../../study/types.js";
5
4
  import type { ObserverResult } from "../../observer/render.js";
6
5
  import type { RunSubjectProvenance } from "../../run/bundle.js";
7
6
  import type { StudyDeps } from "../../study/study-deps.js";
8
7
  import type { RunStudyHomes } from "../../study/run-study-homes.js";
9
8
  import { type StudyResultIdentity } from "../../run/study-result.js";
10
9
  /** What a scripted run takes besides its plan. The plan carries the config, dry run and study. */
11
- export type ScriptedRunInput = Omit<RunScriptedBrowserStudyOptions, "config" | "dryRun">;
12
- export interface RunScriptedBrowserStudyOptions {
10
+ export interface ScriptedRunInput {
13
11
  /** Cancels post-run analysis only. */
14
12
  analysisSignal?: AbortSignal;
15
13
  /** Reports the analysis window to onEvent; built by normalizeRunStudyOptions. */
16
14
  emit?: (event: StudyEvent) => void;
17
15
  cwd: string;
18
- config: StudyConfig;
19
- /** Resolved upstream (scenario.mode + CLI override); defaults safe (dry-run). */
20
- dryRun: boolean;
21
16
  open?: boolean;
22
17
  runId?: string;
23
18
  /** Keys and subject env for the run. Defaults to process.env. Values are scrubbed; names persist. */
@@ -1,21 +1,15 @@
1
+ import { type RefusedStudy } from "../../run/route-shell.js";
1
2
  import { type SharedWorldRefusal } from "./plan.js";
2
3
  import type { SharedWorldPlan } from "../../study/plan-types.js";
3
4
  import type { AdmittedPlan } from "../../run-study.js";
4
5
  import type { StudyConfig } from "../../study/types.js";
5
- import { type ConcurrentSharedWorldStudyResult, type SharedWorldRunInput, type RunConcurrentSharedWorldStudyOptions } from "./types.js";
6
- /**
7
- * The library entry for a shared-world study. It plans the config with planSharedWorldStudy and runs
8
- * the plan with runSharedWorldPlan, whose run scope and withTransientCommsSecrets wrapper cover the
9
- * run and its analysis.
10
- */
11
- export declare function runConcurrentSharedWorld(options: RunConcurrentSharedWorldStudyOptions): Promise<ConcurrentSharedWorldStudyResult>;
6
+ import { type ConcurrentSharedWorldStudyResult, type SharedWorldRunInput } from "./types.js";
12
7
  /** A refused shared-world study's result: the route's envelope, and a refusal's analysis record. */
13
- export declare function sharedWorldStudyRefusal(options: RunConcurrentSharedWorldStudyOptions, refusal: SharedWorldRefusal): Promise<ConcurrentSharedWorldStudyResult>;
8
+ export declare function sharedWorldStudyRefusal(options: SharedWorldRunInput & RefusedStudy, refusal: SharedWorldRefusal): Promise<ConcurrentSharedWorldStudyResult>;
14
9
  /**
15
10
  * runStudyWith's step for a shared-world plan. It runs a live plan's local checks (the keys, a local
16
11
  * agent's sign-in and the subject env) before any run scope opens, so the CLI can present their refusal before it loads a
17
- * declared scorer, and returns the run that continues from them with that scorer.
12
+ * declared scorer, and returns the run that continues from them with that scorer. The run and its
13
+ * analysis share one scope for the email secrets it registers.
18
14
  */
19
15
  export declare function admitSharedWorldPlan(plan: SharedWorldPlan, input: SharedWorldRunInput, config: StudyConfig): Promise<AdmittedPlan<"shared-world">>;
20
- /** Run a shared-world plan: its local checks, then the run. */
21
- export declare function runSharedWorldPlan(plan: SharedWorldPlan, input: SharedWorldRunInput, config: StudyConfig): Promise<ConcurrentSharedWorldStudyResult>;
@@ -34,15 +34,13 @@
34
34
  // gate; humanish cannot tell synthetic from real data, so it makes no claim about real data.
35
35
  import path from "node:path";
36
36
  import { missingKeys, missingSubjectEnv } from "../../study/requirements.js";
37
- import { completeAutomaticAnalysis } from "../../analysis/automatic-completion.js";
38
- import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
39
37
  import { scrubLiterals } from "../../evidence/redaction.js";
40
38
  import { describeMissingKeys } from "../../keys/key-resolution.js";
41
- import { withTransientCommsSecrets } from "../../run/transient-comms-secrets.js";
42
- import { runScope } from "../../run/run.js";
39
+ import {} from "../../run/run.js";
40
+ import { admitRoute, completeRefusalAnalysis, } from "../../run/route-shell.js";
43
41
  import { makeCuaRunBudget } from "../computer-use/participant-model.js";
44
42
  import { runExternalPublicPlane } from "./external-public.js";
45
- import { planSharedWorldStudy, sharedWorldDescriptorOf } from "./plan.js";
43
+ import { sharedWorldDescriptorOf } from "./plan.js";
46
44
  import { localAgentRefusal } from "../../actors/local-agent/readiness.js";
47
45
  import { runProvisionedPlane } from "./provisioned.js";
48
46
  import { concurrentStudyFailure, finishConcurrentRun } from "./result.js";
@@ -58,22 +56,6 @@ const LOCAL_AGENT_REFUSAL_CODES = {
58
56
  unsupported: "HUMANISH_SHARED_WORLD_ACTOR_UNSUPPORTED",
59
57
  "unpriced-cap": "HUMANISH_SHARED_WORLD_UNPRICED_CAP",
60
58
  };
61
- /**
62
- * The library entry for a shared-world study. It plans the config with planSharedWorldStudy and runs
63
- * the plan with runSharedWorldPlan, whose run scope and withTransientCommsSecrets wrapper cover the
64
- * run and its analysis.
65
- */
66
- export async function runConcurrentSharedWorld(options) {
67
- const { config, dryRun, ...input } = options;
68
- // planSharedWorldStudy makes every configuration refusal, in the order this route always has.
69
- const planned = planSharedWorldStudy(config, {
70
- dryRun,
71
- hasRunSession: input.deps?.runSession !== undefined,
72
- });
73
- if (planned.ok)
74
- return runSharedWorldPlan(planned.plan, input, config);
75
- return sharedWorldStudyRefusal(options, planned.refusal);
76
- }
77
59
  /** A refused shared-world study's result: the route's envelope, and a refusal's analysis record. */
78
60
  export function sharedWorldStudyRefusal(options, refusal) {
79
61
  const { config, dryRun } = options;
@@ -89,42 +71,38 @@ export function sharedWorldStudyRefusal(options, refusal) {
89
71
  dryRun,
90
72
  runId: options.runId,
91
73
  });
92
- // A refusal starts no run, so a declared or default analysis is recorded as skipped.
93
- const analysis = resolveAutomaticAnalysis(config.review?.analysis);
94
- return completeAutomaticAnalysis(fail(refusal.code, refusal.message, refusal.actor), undefined, analysis.ok ? analysis.config : undefined, options, { trigger: config.review?.analysis === undefined ? "default" : "explicit" });
74
+ return completeRefusalAnalysis(fail(refusal.code, refusal.message, refusal.actor), config, options);
95
75
  }
96
76
  /**
97
77
  * runStudyWith's step for a shared-world plan. It runs a live plan's local checks (the keys, a local
98
78
  * agent's sign-in and the subject env) before any run scope opens, so the CLI can present their refusal before it loads a
99
- * declared scorer, and returns the run that continues from them with that scorer.
79
+ * declared scorer, and returns the run that continues from them with that scorer. The run and its
80
+ * analysis share one scope for the email secrets it registers.
100
81
  */
101
- export async function admitSharedWorldPlan(plan, input, config) {
102
- const refused = await admitSharedWorldRun(plan, input);
103
- if (refused)
104
- return { ok: false, outcome: sharedWorldOutcome(refused) };
105
- return {
106
- ok: true,
107
- run: async (scorer) => sharedWorldOutcome(await runAdmittedSharedWorldRun(plan, withLateScorer(input, scorer, browserRouteScorer), config)),
108
- };
109
- }
110
- function sharedWorldOutcome(result) {
111
- return { route: "shared-world", result };
112
- }
113
- /** Run a shared-world plan: its local checks, then the run. */
114
- export async function runSharedWorldPlan(plan, input, config) {
115
- const refused = await admitSharedWorldRun(plan, input);
116
- return refused ?? runAdmittedSharedWorldRun(plan, input, config);
82
+ export function admitSharedWorldPlan(plan, input, config) {
83
+ return admitRoute({
84
+ route: "shared-world",
85
+ analysis: plan.analysis,
86
+ input,
87
+ admit: () => admitSharedWorldRun(plan, input),
88
+ withScorer: (base, scorer) => withLateScorer(base, scorer, browserRouteScorer),
89
+ runInScope: (_admitted, running, scope) => runPlanInScope(plan, running, config, scope),
90
+ commsSecrets: true,
91
+ });
117
92
  }
118
93
  /**
119
94
  * A live plan's local checks, made outside any run scope: the keys, a local agent's sign-in, then
120
- * the subject env. A refusal is the result runSharedWorldPlan returns for it, with the analysis
121
- * record of a run that never started.
95
+ * the subject env.
122
96
  */
123
97
  async function admitSharedWorldRun(plan, input) {
98
+ const admitted = { ok: true, admitted: undefined };
124
99
  if (plan.dryRun)
125
- return undefined;
100
+ return admitted;
126
101
  const env = input.env ?? process.env;
127
- const fail = (code, message) => completeSharedWorldAnalysis(plan, input, sharedWorldFailure(plan, input)(code, message, sharedWorldDescriptorOf(plan.actor).id), undefined);
102
+ const fail = (code, message) => ({
103
+ ok: false,
104
+ result: sharedWorldFailure(plan, input)(code, message, sharedWorldDescriptorOf(plan.actor).id),
105
+ });
128
106
  // The plan lists OPENAI_API_KEY for an openai brain's participants and the external-public
129
107
  // plane's lobby-code reader; a local-agent brain's participants run on the operator's signed-in
130
108
  // agent instead.
@@ -143,24 +121,7 @@ async function admitSharedWorldRun(plan, input) {
143
121
  if (unsetSubjectEnv.length > 0) {
144
122
  return fail("HUMANISH_SHARED_WORLD_SUBJECT_ENV_MISSING", `subject.env declares ${unsetSubjectEnv.join(", ")} but the environment does not provide ${unsetSubjectEnv.length === 1 ? "it" : "them"} (pass via --dotenv; values are never persisted).`);
145
123
  }
146
- return undefined;
147
- }
148
- /**
149
- * Runs an admitted plan in its own run scope, then its automatic analysis. The
150
- * withTransientCommsSecrets wrapper scopes any email secret the run registers to this run and its
151
- * analysis.
152
- */
153
- function runAdmittedSharedWorldRun(plan, input, config) {
154
- return withTransientCommsSecrets(async () => {
155
- const { result, finished } = await runScope((scope) => runPlanInScope(plan, input, config, scope));
156
- return completeSharedWorldAnalysis(plan, input, result, finished);
157
- });
158
- }
159
- function completeSharedWorldAnalysis(plan, input, result, finished) {
160
- return completeAutomaticAnalysis(result, finished, plan.analysis?.config, input, {
161
- ...(plan.analysis === undefined ? {} : { trigger: plan.analysis.trigger }),
162
- preferLargerOutput: plan.analysis?.preferLargerOutput === true,
163
- });
124
+ return admitted;
164
125
  }
165
126
  /** The route's envelope for a run that stops before its bundle. */
166
127
  function sharedWorldFailure(plan, input) {
@@ -24,13 +24,11 @@ export declare const CONCURRENT_SHARED_WORLD_PROVIDER_METADATA: {
24
24
  readonly tool: "humanish";
25
25
  };
26
26
  export declare const CONCURRENT_ATTRIBUTION_LIMITS: readonly ["concurrent", "best-effort-causal-attribution", "non-deterministic-shared-state", "window-and-snapshot-granularity", "contention-observed-not-proven-safe", "state-change-not-isolated-to-actors"];
27
- export interface RunConcurrentSharedWorldStudyOptions {
27
+ /** What a shared-world run takes besides its plan. The plan carries the config, dry run and study. */
28
+ export interface SharedWorldRunInput {
28
29
  /** Cancels post-run analysis only. */
29
30
  analysisSignal?: AbortSignal;
30
31
  cwd: string;
31
- config: StudyConfig;
32
- /** Resolved upstream (scenario.mode + CLI override); defaults safe (dry-run). */
33
- dryRun: boolean;
34
32
  open?: boolean;
35
33
  runId?: string;
36
34
  onObserverReady?: (observer: ObserverResult & {
@@ -56,8 +54,6 @@ export interface RunConcurrentSharedWorldStudyOptions {
56
54
  * core-stamped onto the bundle as evidence. Absent for library callers. */
57
55
  scorerProvenance?: RunScorerProvenance;
58
56
  }
59
- /** What a shared-world run takes besides its plan. The plan carries the config, dry run and study. */
60
- export type SharedWorldRunInput = Omit<RunConcurrentSharedWorldStudyOptions, "config" | "dryRun">;
61
57
  export type ConcurrentSharedWorldStudyErrorCode = "HUMANISH_STUDY_ANALYSIS_INVALID" | "HUMANISH_STUDY_TASKS_UNSUPPORTED" | "HUMANISH_STUDY_OPTION_UNSUPPORTED" | "HUMANISH_STUDY_V2_UNSUPPORTED" | "HUMANISH_SHARED_WORLD_FAILED" | "HUMANISH_SHARED_WORLD_ACTOR_UNSUPPORTED" | "HUMANISH_SHARED_WORLD_INVALID" | "HUMANISH_SHARED_WORLD_KEYS_MISSING"
62
58
  /** A local-agent brain's CLI is not on `PATH`. */
63
59
  | "HUMANISH_SHARED_WORLD_AGENT_MISSING"
@@ -1,20 +1,10 @@
1
+ import { type RefusedStudy } from "../../run/route-shell.js";
1
2
  import { type TerminalRefusal } from "./plan.js";
2
3
  import type { TerminalPlan } from "../../study/plan-types.js";
3
4
  import type { AdmittedPlan } from "../../run-study.js";
4
- import { type RunTerminalProductStudyOptions, type TerminalProductStudyResult, type TerminalRunInput } from "./types.js";
5
- /**
6
- * The config-taking entry point. It plans, returns a refusal with the same envelope and analysis
7
- * record the route has always returned, and otherwise runs the plan.
8
- */
9
- export declare function runTerminalProductStudy(options: RunTerminalProductStudyOptions): Promise<TerminalProductStudyResult>;
5
+ import { type TerminalProductStudyResult, type TerminalRunInput } from "./types.js";
10
6
  /** A refused terminal study's result: the route's envelope, and the analysis record a refusal gets. */
11
- export declare function terminalStudyRefusal(options: RunTerminalProductStudyOptions, refusal: TerminalRefusal): Promise<TerminalProductStudyResult>;
12
- /**
13
- * Run a terminal plan. The run scope gives a direct library caller the same run lifetime the CLI
14
- * gets: whichever of its fail-closed exits the run takes, the run it started is closed, and only a
15
- * run that published its final bundle reaches automatic analysis.
16
- */
17
- export declare function runTerminalPlan(plan: TerminalPlan, input: TerminalRunInput): Promise<TerminalProductStudyResult>;
7
+ export declare function terminalStudyRefusal(options: TerminalRunInput & RefusedStudy, refusal: TerminalRefusal): Promise<TerminalProductStudyResult>;
18
8
  /**
19
9
  * runStudyWith's step for a terminal plan. It runs a live plan's local checks (checkLiveTerminalMachine)
20
10
  * before any run scope opens, so the CLI can present their refusal before it loads a declared